Admin page UI revamp

This commit is contained in:
Lucas Oliveira 2023-08-31 14:35:12 -03:00
commit 6990d4865d
14 changed files with 318 additions and 305 deletions

View file

@ -12,17 +12,15 @@ import { Button } from "../ui/button";
export default function PaginatorComponent({ export default function PaginatorComponent({
pageSize = 10, pageSize = 10,
pageIndex = 0, pageIndex = 1,
rowsCount = [10, 20, 50, 100], rowsCount = [10, 20, 50, 100],
totalRowsCount = 0, totalRowsCount = 0,
paginate, paginate,
}: PaginatorComponentType) { }: PaginatorComponentType) {
const [size, setPageSize] = useState(pageSize); const [size, setPageSize] = useState(pageSize);
const [index, setPageIndex] = useState(pageIndex);
const [maxIndex, setMaxPageIndex] = useState( const [maxIndex, setMaxPageIndex] = useState(
Math.ceil(totalRowsCount / pageSize) Math.ceil(totalRowsCount / pageSize)
); );
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => { useEffect(() => {
setMaxPageIndex(Math.ceil(totalRowsCount / size)); setMaxPageIndex(Math.ceil(totalRowsCount / size));
@ -39,8 +37,9 @@ export default function PaginatorComponent({
onValueChange={(pageSize: string) => { onValueChange={(pageSize: string) => {
setPageSize(Number(pageSize)); setPageSize(Number(pageSize));
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize))); setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
paginate(Number(pageSize), 0); paginate(Number(pageSize), 1);
}} }}
value={pageSize.toString()}
> >
<SelectTrigger className="w-[100px]"> <SelectTrigger className="w-[100px]">
<SelectValue placeholder="10" /> <SelectValue placeholder="10" />
@ -55,30 +54,25 @@ export default function PaginatorComponent({
</Select> </Select>
</div> </div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium"> <div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {currentPage} of {maxIndex} Page {pageIndex} of {maxIndex}
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button
disabled={index <= 0} disabled={pageIndex <= 1}
variant="outline" variant="outline"
className="hidden h-8 w-8 p-0 lg:flex" className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => { onClick={() => {
setPageIndex(0); paginate(size, 1);
setCurrentPage(1);
paginate(size, 0);
}} }}
> >
<span className="sr-only">Go to first page</span> <span className="sr-only">Go to first page</span>
<IconComponent name="ChevronsLeft" className="h-4 w-4" /> <IconComponent name="ChevronsLeft" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={index <= 0} disabled={pageIndex <= 1}
onClick={() => { onClick={() => {
if (index > 0) { if (pageIndex > 0) {
const pgIndex = size - index; paginate(size, pageIndex - 1);
setCurrentPage(currentPage - 1);
setPageIndex(pgIndex);
paginate(size, pgIndex);
} }
}} }}
variant="outline" variant="outline"
@ -88,12 +82,9 @@ export default function PaginatorComponent({
<IconComponent name="ChevronLeft" className="h-4 w-4" /> <IconComponent name="ChevronLeft" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={currentPage === maxIndex} disabled={pageIndex === maxIndex}
onClick={() => { onClick={() => {
const pgIndex = size + index; paginate(size, pageIndex + 1);
setPageIndex(pgIndex);
setCurrentPage(currentPage + 1);
paginate(size, pgIndex);
}} }}
variant="outline" variant="outline"
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
@ -102,13 +93,11 @@ export default function PaginatorComponent({
<IconComponent name="ChevronRight" className="h-4 w-4" /> <IconComponent name="ChevronRight" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={currentPage === maxIndex} disabled={pageIndex === maxIndex}
variant="outline" variant="outline"
className="hidden h-8 w-8 p-0 lg:flex" className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => { onClick={() => {
setPageIndex(maxIndex - 1); paginate(size, maxIndex);
setCurrentPage(maxIndex);
paginate(size, size);
}} }}
> >
<span className="sr-only">Go to last page</span> <span className="sr-only">Go to last page</span>

View file

@ -4,10 +4,10 @@ export default function LoadingComponent({
remSize, remSize,
}: LoadingComponentProps): JSX.Element { }: LoadingComponentProps): JSX.Element {
return ( return (
<div role="status" className="m-auto w-min"> <div role="status" className="flex flex-col items-center justify-center">
<svg <svg
aria-hidden="true" aria-hidden="true"
className={`w-${remSize} h-${remSize} mr-2 animate-spin fill-almost-medium-blue text-muted`} className={`w-${remSize} h-${remSize} animate-spin fill-almost-medium-blue text-muted`}
viewBox="0 0 100 101" viewBox="0 0 100 101"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -499,6 +499,21 @@ export const NOUNS: string[] = [
*/ */
export const USER_PROJECTS_HEADER = "My Collection"; export const USER_PROJECTS_HEADER = "My Collection";
/**
* Header text for admin page
* @constant
*
*/
export const ADMIN_HEADER_TITLE = "Welcome back!";
/**
* Header description for admin page
* @constant
*
*/
export const ADMIN_HEADER_DESCRIPTION =
"Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.";
/** /**
* URLs excluded from error retries. * URLs excluded from error retries.
* @constant * @constant

View file

@ -400,7 +400,7 @@ export async function renewAccessToken(token: string) {
} }
} }
export async function getLoggedUser(): Promise<Users> { export async function getLoggedUser(): Promise<Array<Users>> {
try { try {
const res = await api.get(`${BASE_URL_API}user`); const res = await api.get(`${BASE_URL_API}user`);
@ -411,9 +411,10 @@ export async function getLoggedUser(): Promise<Users> {
console.log("Error:", error); console.log("Error:", error);
throw error; throw error;
} }
return [];
} }
export async function addUser(user: UserInputType): Promise<Users> { export async function addUser(user: UserInputType): Promise<Array<Users>> {
try { try {
const res = await api.post(`${BASE_URL_API}user`, user); const res = await api.post(`${BASE_URL_API}user`, user);
if (res.status === 200) { if (res.status === 200) {
@ -423,12 +424,13 @@ export async function addUser(user: UserInputType): Promise<Users> {
console.log("Error:", error); console.log("Error:", error);
throw error; throw error;
} }
return [];
} }
export async function getUsersPage( export async function getUsersPage(
skip: number, skip: number,
limit: number limit: number
): Promise<[Users]> { ): Promise<Array<Users>> {
try { try {
const res = await api.get( const res = await api.get(
`${BASE_URL_API}users?skip=${skip}&limit=${limit}` `${BASE_URL_API}users?skip=${skip}&limit=${limit}`
@ -440,6 +442,7 @@ export async function getUsersPage(
console.log("Error:", error); console.log("Error:", error);
throw error; throw error;
} }
return [];
} }
export async function deleteUser(user_id: string) { export async function deleteUser(user_id: string) {

View file

@ -201,8 +201,8 @@ const ApiModal = forwardRef(
} }
return ( return (
<BaseModal open={open} setOpen={setOpen} disable={disable}> <BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger disable={disable}>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}> <BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">Code</span> <span className="pr-2">Code</span>
<IconComponent <IconComponent

View file

@ -23,6 +23,7 @@ export default function UserManagementModal({
data, data,
index, index,
onConfirm, onConfirm,
asChild,
}: UserManagementType) { }: UserManagementType) {
const Icon: any = nodeIconsLucide[icon]; const Icon: any = nodeIconsLucide[icon];
const [pwdVisible, setPwdVisible] = useState(false); const [pwdVisible, setPwdVisible] = useState(false);
@ -60,7 +61,7 @@ export default function UserManagementModal({
return ( return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}> <BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger asChild={asChild}>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}> <BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span> <span className="pr-2">{title}</span>
<Icon <Icon

View file

@ -14,13 +14,25 @@ import { modalHeaderType } from "../../types/components";
type ContentProps = { children: ReactNode }; type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string }; type HeaderProps = { children: ReactNode; description: string };
type FooterProps = { children: ReactNode }; type FooterProps = { children: ReactNode };
type TriggerProps = { children: ReactNode }; type TriggerProps = {
children: ReactNode;
asChild?: boolean;
disable?: boolean;
};
const Content: React.FC<ContentProps> = ({ children }) => { const Content: React.FC<ContentProps> = ({ children }) => {
return <div className="h-full w-full">{children}</div>; return <div className="h-full w-full">{children}</div>;
}; };
const Trigger: React.FC<ContentProps> = ({ children }) => { const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
return <>{children}</>; return (
<DialogTrigger
className={"w-full " + (disable ? "button-disable" : "")}
hidden={children ? false : true}
asChild={asChild}
>
{children}
</DialogTrigger>
);
}; };
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({ const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
@ -47,7 +59,6 @@ interface BaseModalProps {
]; ];
open?: boolean; open?: boolean;
setOpen?: (open: boolean) => void; setOpen?: (open: boolean) => void;
disable?: boolean;
size?: size?:
| "x-small" | "x-small"
| "smaller" | "smaller"
@ -61,7 +72,6 @@ interface BaseModalProps {
function BaseModal({ function BaseModal({
open, open,
setOpen, setOpen,
disable = false,
children, children,
size = "large", size = "large",
}: BaseModalProps) { }: BaseModalProps) {
@ -120,17 +130,12 @@ function BaseModal({
//UPDATE COLORS AND STYLE CLASSSES //UPDATE COLORS AND STYLE CLASSSES
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger {triggerChild}
className={"w-full " + (disable ? "button-disable" : "")}
hidden={triggerChild ? false : true}
>
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}> <DialogContent className={minWidth}>
<div className="truncate-doubleline word-break-break-word"> <div className="truncate-doubleline word-break-break-word">
{headerChild} {headerChild}
</div> </div>
<div className={`mt-2 flex flex-col ${height} w-full `}> <div className={`mt-2 flex flex-col ${height!} w-full `}>
{ContentChild} {ContentChild}
</div> </div>
{ContentFooter && ( {ContentFooter && (

View file

@ -28,8 +28,7 @@ export default function CodeAreaModal({
const { dark } = useContext(darkContext); const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const [height, setHeight] = useState<string | null>(null); const [height, setHeight] = useState<string | null>(null);
const { setErrorData, setSuccessData, isTweakPage } = const { setErrorData, setSuccessData } = useContext(alertContext);
useContext(alertContext);
const [error, setError] = useState<{ const [error, setError] = useState<{
detail: { error: string | undefined; traceback: string | undefined }; detail: { error: string | undefined; traceback: string | undefined };
} | null>(null); } | null>(null);

View file

@ -10,15 +10,15 @@ import BaseModal from "../baseModal";
const ExportModal = forwardRef( const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => { (props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext); const { flows, tabId, downloadFlow } = useContext(TabsContext);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const flow = flows.find((f) => f.id === tabId); const flow = flows.find((f) => f.id === tabId);
useEffect(() => { useEffect(() => {
setName(flow.name); setName(flow!.name);
setDescription(flow.description); setDescription(flow!.description);
}, [flow.name, flow.description]); }, [flow!.name, flow!.description]);
const [name, setName] = useState(flow.name); const [name, setName] = useState(flow!.name);
const [description, setDescription] = useState(flow.description); const [description, setDescription] = useState(flow!.description);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
@ -40,7 +40,6 @@ const ExportModal = forwardRef(
tabId={tabId} tabId={tabId}
setName={setName} setName={setName}
setDescription={setDescription} setDescription={setDescription}
updateFlow={updateFlow}
/> />
<div className="mt-3 flex items-center space-x-2"> <div className="mt-3 flex items-center space-x-2">
<Checkbox <Checkbox

View file

@ -1,10 +1,10 @@
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { X } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent"; import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent"; import Header from "../../components/headerComponent";
import LoadingComponent from "../../components/loadingComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox"; import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -16,6 +16,10 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "../../components/ui/table"; } from "../../components/ui/table";
import {
ADMIN_HEADER_DESCRIPTION,
ADMIN_HEADER_TITLE,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { import {
@ -33,7 +37,7 @@ export default function AdminPage() {
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [size, setPageSize] = useState(10); const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(0); const [index, setPageIndex] = useState(1);
const [loadingUsers, setLoadingUsers] = useState(true); const [loadingUsers, setLoadingUsers] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext); const { setErrorData, setSuccessData } = useContext(alertContext);
const { userData } = useContext(AuthContext); const { userData } = useContext(AuthContext);
@ -65,7 +69,9 @@ export default function AdminPage() {
function handleChangePagination(pageIndex: number, pageSize: number) { function handleChangePagination(pageIndex: number, pageSize: number) {
setLoadingUsers(true); setLoadingUsers(true);
getUsersPage(pageIndex, pageSize) setPageSize(pageSize);
setPageIndex(pageIndex);
getUsersPage(pageSize * (pageIndex - 1), pageSize)
.then((users) => { .then((users) => {
setTotalRowsCount(users["total_count"]); setTotalRowsCount(users["total_count"]);
userList.current = users["users"]; userList.current = users["users"];
@ -78,7 +84,7 @@ export default function AdminPage() {
} }
function resetFilter() { function resetFilter() {
setPageIndex(0); setPageIndex(1);
setPageSize(10); setPageSize(10);
getUsers(); getUsers();
} }
@ -183,255 +189,238 @@ export default function AdminPage() {
return ( return (
<> <>
<div className="flex flex-col"> <Header />
<Header /> {userData && (
{userData && ( <div className="admin-page-panel flex h-full flex-col pb-8">
<div className="main-page-panel"> <div className="main-page-nav-arrangement">
<div className="m-auto flex h-full flex-row justify-center"> <span className="main-page-nav-title">
<div className="basis-5/6"> <IconComponent name="Shield" className="w-6" />
<div className="m-auto flex h-full flex-col space-y-8 p-8 "> {ADMIN_HEADER_TITLE}
<div className="flex items-center justify-between space-y-2"> </span>
<div> </div>
<h2 className="text-2xl font-bold tracking-tight"> <span className="admin-page-description-text">
Welcome back! {ADMIN_HEADER_DESCRIPTION}
</h2> </span>
<p className="text-muted-foreground"> <div className="flex w-full justify-between px-4">
Navigate through this section to efficiently oversee all <div className="flex w-96 items-center gap-4">
application users. From here, you can seamlessly manage <Input
user accounts. placeholder="Search Examples"
</p> value={inputValue}
</div> onChange={(e) => handleFilterUsers(e.target.value)}
<div className="flex items-center space-x-2"></div> />
</div> {inputValue.length > 0 ? (
<div
{userList.current.length === 0 && !loadingUsers && ( className="cursor-pointer"
<> onClick={() => {
<div className="flex items-center justify-between"> setInputValue("");
<h2>There's no users registered :)</h2> setFilterUserList(userList.current);
</div> }}
</> >
)} <IconComponent name="X" className="w-6 text-foreground" />
<>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
setInputValue("");
setFilterUserList(userList.current);
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
</div>
</div>
{loadingUsers && (
<div>
<strong>Loading...</strong>
</div>
)}
<div
className={
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
(loadingUsers ? " border-0" : "")
}
>
<Table className={"table-fixed bg-muted outline-1"}>
<TableHeader
className={
loadingUsers
? "hidden"
: "table-fixed bg-muted outline-1"
}
>
<TableRow>
<TableHead className="h-10">Id</TableHead>
<TableHead className="h-10">Username</TableHead>
<TableHead className="h-10">Active</TableHead>
<TableHead className="h-10">Superuser</TableHead>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody>
{filterUserList.map(
(user: UserInputType, index) => (
<TableRow key={index}>
<TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}>
<span className="cursor-default">
{user.id}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate py-2">
<ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip
content="Delete"
side="top"
>
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
)
)}
</TableBody>
)}
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={totalRowsCount}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
</div> </div>
</div> ) : (
<div>
<IconComponent
name="Search"
className="w-6 text-foreground"
/>
</div>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
asChild
>
<Button variant="primary">New User</Button>
</UserManagementModal>
</div> </div>
</div> </div>
)} {loadingUsers ? (
</div> <div className="flex h-full w-full items-center justify-center">
<LoadingComponent remSize={12} />
</div>
) : userList.current.length === 0 ? (
<>
<div className="m-4 flex items-center justify-between text-sm">
No users registered.
</div>
</>
) : (
<>
<div
className={
"m-4 h-full overflow-x-hidden overflow-y-scroll rounded-md border-2 bg-background custom-scroll" +
(loadingUsers ? " border-0" : "")
}
>
<Table className={"table-fixed outline-1 "}>
<TableHeader
className={
loadingUsers ? "hidden" : "table-fixed bg-muted outline-1"
}
>
<TableRow>
<TableHead className="h-10">Id</TableHead>
<TableHead className="h-10">Username</TableHead>
<TableHead className="h-10">Active</TableHead>
<TableHead className="h-10">Superuser</TableHead>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody>
{filterUserList.map((user: UserInputType, index) => (
<TableRow key={index}>
<TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}>
<span className="cursor-default">{user.id}</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate py-2">
<ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={totalRowsCount}
paginate={(pageSize, pageIndex) => {
handleChangePagination(pageIndex, pageSize);
}}
></PaginatorComponent>
</>
)}
</div>
)}
</> </>
); );
} }

View file

@ -212,6 +212,10 @@
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16; @apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
} }
.admin-page-panel {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
}
.main-page-nav-arrangement { .main-page-nav-arrangement {
@apply flex-max-width justify-between px-6 py-12 pb-2; @apply flex-max-width justify-between px-6 py-12 pb-2;
} }
@ -228,6 +232,10 @@
@apply flex w-[60%] px-6 pb-14 text-muted-foreground; @apply flex w-[60%] px-6 pb-14 text-muted-foreground;
} }
.admin-page-description-text {
@apply flex w-[80%] px-6 pb-8 text-muted-foreground;
}
.main-page-flows-display { .main-page-flows-display {
@apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4; @apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
} }

View file

@ -253,6 +253,7 @@ export type UserManagementType = {
icon: string; icon: string;
data?: any; data?: any;
index?: number; index?: number;
asChild?: boolean;
onConfirm: (index, data) => void; onConfirm: (index, data) => void;
}; };
@ -548,5 +549,5 @@ export type fetchErrorComponentType = {
export type dropdownButtonPropsType = { export type dropdownButtonPropsType = {
firstButtonName: string; firstButtonName: string;
onFirstBtnClick: () => void; onFirstBtnClick: () => void;
options: Array<{ name: string; onBtnClick: () => void; }>; options: Array<{ name: string; onBtnClick: () => void }>;
}; };

View file

@ -255,7 +255,9 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
} }
export function handleKeyDown( export function handleKeyDown(
e: React.KeyboardEvent<HTMLInputElement>, e:
| React.KeyboardEvent<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>,
inputValue: string | string[] | null, inputValue: string | string[] | null,
block: string block: string
) { ) {

View file

@ -58,6 +58,7 @@ import {
Scissors, Scissors,
Search, Search,
Settings2, Settings2,
Shield,
Sparkles, Sparkles,
SunIcon, SunIcon,
TerminalSquare, TerminalSquare,
@ -260,6 +261,7 @@ export const nodeIconsLucide: iconsType = {
Bell, Bell,
ChevronLeft, ChevronLeft,
ChevronDown, ChevronDown,
Shield,
Plus, Plus,
Redo, Redo,
Settings2, Settings2,