Files
pangolin/src/components/RolesTable.tsx
Fred KISSIE efb2e78d9d 🐛 fix imports
2026-03-23 21:34:58 +01:00

260 lines
9.1 KiB
TypeScript

"use client";
import CreateRoleForm from "@app/components/CreateRoleForm";
import DeleteRoleForm from "@app/components/DeleteRoleForm";
import { Button } from "@app/components/ui/button";
import { ExtendedColumnDef } from "@app/components/ui/data-table";
import { useNavigationContext } from "@app/hooks/useNavigationContext";
import { toast } from "@app/hooks/useToast";
import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { Role } from "@server/db";
import type { PaginationState } from "@tanstack/react-table";
import {
ArrowDown01Icon,
ArrowUp10Icon,
ChevronsUpDownIcon,
MoreHorizontal
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState, useTransition } from "react";
import { useDebouncedCallback } from "use-debounce";
import EditRoleForm from "./EditRoleForm";
import { ControlledDataTable } from "./ui/controlled-data-table";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "./ui/dropdown-menu";
export type RoleRow = Role;
type RolesTableProps = {
roles: RoleRow[];
pagination: PaginationState;
rowCount: number;
};
export default function UsersTable({
roles,
pagination,
rowCount
}: RolesTableProps) {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [editingRole, setEditingRole] = useState<RoleRow | null>(null);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const router = useRouter();
const [isRefreshing, startTransition] = useTransition();
const {
navigate: filter,
isNavigating: isFiltering,
searchParams
} = useNavigationContext();
const [roleToRemove, setRoleToRemove] = useState<RoleRow | null>(null);
const t = useTranslations();
const refreshData = async () => {
console.log("Data refreshed");
try {
router.refresh();
} catch (error) {
toast({
title: t("error"),
description: t("refreshError"),
variant: "destructive"
});
}
};
const columns: ExtendedColumnDef<RoleRow>[] = [
{
accessorKey: "name",
enableHiding: false,
friendlyName: t("name"),
header: ({ column }) => {
const nameOrder = getSortDirection("name", searchParams);
const Icon =
nameOrder === "asc"
? ArrowDown01Icon
: nameOrder === "desc"
? ArrowUp10Icon
: ChevronsUpDownIcon;
return (
<Button variant="ghost" onClick={() => toggleSort("name")}>
{t("name")}
<Icon className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: "description",
friendlyName: t("description"),
header: () => <span className="p-3">{t("description")}</span>
},
// {
// id: "actions",
// enableHiding: false,
// header: () => <span className="p-3"></span>,
// cell: ({ row }) => {
// const roleRow = row.original;
// return (
// <div className="flex items-center gap-2 justify-end">
// <Button
// variant={"outline"}
// disabled={roleRow.isAdmin || false}
// onClick={() => {
// setIsDeleteModalOpen(true);
// setUserToRemove(roleRow);
// }}
// >
// {t("accessRoleDelete")}
// </Button>
// </div>
// );
// }
// },
{
id: "actions",
enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => {
const roleRow = row.original;
const isAdmin = roleRow.isAdmin;
return (
<div className="flex items-center gap-2 justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
disabled={isAdmin || false}
>
<span className="sr-only">
{t("openMenu")}
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
disabled={isAdmin || false}
onClick={() => {
setRoleToRemove(roleRow);
setIsDeleteModalOpen(true);
}}
>
<span className="text-red-500">
{t("delete")}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant={"outline"}
onClick={() => {
setEditingRole(roleRow);
setIsEditDialogOpen(true);
}}
>
{t("edit")}
</Button>
</div>
);
}
}
];
function toggleSort(column: string) {
const newSearch = getNextSortOrder(column, searchParams);
filter({
searchParams: newSearch
});
}
const handlePaginationChange = (newPage: PaginationState) => {
searchParams.set("page", (newPage.pageIndex + 1).toString());
searchParams.set("pageSize", newPage.pageSize.toString());
filter({
searchParams
});
};
const handleSearchChange = useDebouncedCallback((query: string) => {
searchParams.set("query", query);
searchParams.delete("page");
filter({
searchParams
});
}, 300);
return (
<>
{editingRole && (
<EditRoleForm
role={editingRole}
open={isEditDialogOpen}
key={editingRole.roleId}
setOpen={setIsEditDialogOpen}
onSuccess={() => {
// Delay refresh to allow modal to close smoothly
setTimeout(() => {
startTransition(async () => {
await refreshData().then(() =>
setEditingRole(null)
);
});
}, 150);
}}
/>
)}
<CreateRoleForm
open={isCreateModalOpen}
setOpen={setIsCreateModalOpen}
afterCreate={() => {
startTransition(refreshData);
}}
/>
{roleToRemove && (
<DeleteRoleForm
open={isDeleteModalOpen}
setOpen={setIsDeleteModalOpen}
roleToDelete={roleToRemove}
afterDelete={() => {
startTransition(async () => {
await refreshData().then(() =>
setRoleToRemove(null)
);
});
}}
/>
)}
<ControlledDataTable
columns={columns}
rows={roles}
tableId="roles-table"
searchQuery={searchParams.get("query")?.toString()}
onSearch={handleSearchChange}
onPaginationChange={handlePaginationChange}
searchPlaceholder={t("accessRolesSearch")}
addButtonText={t("accessRolesAdd")}
rowCount={rowCount}
pagination={pagination}
onAdd={() => {
setIsCreateModalOpen(true);
}}
onRefresh={() => startTransition(refreshData)}
isRefreshing={isRefreshing}
/>
</>
);
}