This commit is contained in:
natib21
2026-07-10 11:25:59 +00:00
parent 2696ca7498
commit e6e44e773b
1146 changed files with 200266 additions and 90527 deletions

View File

@@ -0,0 +1,389 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { EditIcon, TrashIcon, PlusIcon } from "lucide-react";
import { Button } from "@/shared/common/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/shared/common/ui/alert-dialog";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/shared/common/ui/dialog";
import { useAuth } from "@/shared/context/AuthContext";
import {
useServiceCategoryList,
useServiceCategoryMutations,
} from "@/performance-management/hooks/useServiceCategory";
import { ServiceCategory } from "@/performance-management/services/api/serviceCategoryService";
import { ServiceCategoryForm } from "@/performance-management/components/ServiceCategory/ServiceCategoryForm";
import { useLocalizedName } from "@/shared/common/localizedName";
import { AdvancedTable } from "@/shared/common/ui/table/AdvancedTable";
import { ColumnDef } from "@tanstack/react-table";
import { useToast } from "@/shared/common/ui/use-toast";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/shared/common/ui/tooltip";
import { Skeleton } from "@/shared/common/ui/skeleton";
export const ServiceCategoryPage = () => {
const { t } = useTranslation();
const localizedName = useLocalizedName();
const { user } = useAuth();
const { toast } = useToast();
const { delete: deleteCategory } = useServiceCategoryMutations();
const [pageIndex, setPageIndex] = useState(0);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [editingCategory, setEditingCategory] =
useState<ServiceCategory | null>(null);
const [selectedCategory, setSelectedCategory] =
useState<ServiceCategory | null>(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const organizationId = user?.employee?.[0]?.organizationId;
const {
data: serviceCategoryList,
isLoading,
isError,
refetch,
} = useServiceCategoryList(organizationId ?? "");
const categories = (serviceCategoryList?.data?.items || []).sort(
(a: ServiceCategory, b: ServiceCategory) => {
const dateA = new Date(a.createdAt).getTime();
const dateB = new Date(b.createdAt).getTime();
return dateB - dateA; // Descending order (newest first)
}
);
const handleEdit = (category: ServiceCategory) => {
setEditingCategory(category);
};
const handleDelete = async () => {
if (!selectedCategory) return;
try {
await deleteCategory.mutateAsync(selectedCategory.id);
toast({
title: t("serviceCategory.deleteSuccess", "Deleted"),
description: t(
"serviceCategory.deleteSuccessMessage",
"Service category deleted successfully"
),
});
setIsDeleteDialogOpen(false);
setSelectedCategory(null);
refetch();
} catch {
toast({
title: t("common.error", "Error"),
description: t(
"serviceCategory.deleteError",
"Failed to delete service category"
),
variant: "destructive",
});
}
};
const handleFormSuccess = () => {
setIsCreateDialogOpen(false);
setEditingCategory(null);
refetch();
};
const handleFormCancel = () => {
setIsCreateDialogOpen(false);
setEditingCategory(null);
};
const columns: ColumnDef<ServiceCategory>[] = [
{
accessorKey: "slug",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-auto p-0 font-semibold"
>
{t("serviceCategory.slug", "Slug")}
{column.getIsSorted() === "asc"
? " ↑"
: column.getIsSorted() === "desc"
? " ↓"
: ""}
</Button>
);
},
cell: ({ row }) => <div>{row.original.slug}</div>,
size: 150,
enableSorting: true,
},
{
accessorKey: "name",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-auto p-0 font-semibold"
>
{t("serviceCategory.name", "Name")}
{column.getIsSorted() === "asc"
? " ↑"
: column.getIsSorted() === "desc"
? " ↓"
: ""}
</Button>
);
},
cell: ({ row }) => <div>{localizedName(row.original.name)}</div>,
size: 200,
enableSorting: true,
sortingFn: (rowA, rowB) => {
const nameA = localizedName(rowA.original.name).toLowerCase();
const nameB = localizedName(rowB.original.name).toLowerCase();
return nameA.localeCompare(nameB);
},
},
{
accessorKey: "description",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-auto p-0 font-semibold"
>
{t("serviceCategory.description", "Description")}
{column.getIsSorted() === "asc"
? " ↑"
: column.getIsSorted() === "desc"
? " ↓"
: ""}
</Button>
);
},
cell: ({ row }) => (
<div
className="max-w-[300px] truncate"
title={localizedName(row.original.description)}
>
{localizedName(row.original.description)}
</div>
),
enableSorting: true,
sortingFn: (rowA, rowB) => {
const descA = localizedName(rowA.original.description).toLowerCase();
const descB = localizedName(rowB.original.description).toLowerCase();
return descA.localeCompare(descB);
},
},
{
accessorKey: "createdAt",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-auto p-0 font-semibold"
>
{t("serviceCategory.createdAt", "Created At")}
{column.getIsSorted() === "asc"
? " ↑"
: column.getIsSorted() === "desc"
? " ↓"
: " ↓"}
</Button>
);
},
cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),
size: 150,
enableSorting: true,
sortingFn: (rowA, rowB) => {
const dateA = new Date(rowA.original.createdAt).getTime();
const dateB = new Date(rowB.original.createdAt).getTime();
return dateA - dateB;
},
},
{
id: "actions",
header: t("common.actions", "Actions"),
cell: ({ row }) => (
<div className="flex gap-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={() => handleEdit(row.original)}
>
<EditIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.edit", "Edit")}</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedCategory(row.original);
setIsDeleteDialogOpen(true);
}}
disabled={deleteCategory.isPending}
>
<TrashIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.delete", "Delete")}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
),
size: 100,
},
];
const extraToolbar = (
<div className="flex gap-2">
<Button
onClick={() => setIsCreateDialogOpen(true)}
className="bg-gradient-to-r from-purple-500 to-violet-600 text-white"
>
<PlusIcon className="h-4 w-4 mr-2" />
{t("serviceCategory.createNew", "Create Category")}
</Button>
</div>
);
// Loading UI
if (isLoading) {
return (
<div className="p-6 space-y-6">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
);
}
// Error UI
if (isError) {
return (
<div className="p-6 space-y-4">
<p className="text-destructive font-semibold">
{t("serviceCategory.loadError", "Failed to load service categories")}
</p>
<Button onClick={() => refetch()}>{t("common.retry", "Retry")}</Button>
</div>
);
}
return (
<div className="p-6">
<AdvancedTable<ServiceCategory, unknown>
columns={columns}
data={categories}
tableName={t("serviceCategory.title", "Service Categories")}
extraToolbar={extraToolbar}
toolBarPosition="right"
itemCount={categories.length}
pageIndex={pageIndex}
onPageChange={setPageIndex}
nextFunction={() => setPageIndex((p) => p + 1)}
prevFunction={() => setPageIndex((p) => Math.max(0, p - 1))}
refresh={refetch}
/>
{/* Create Dialog */}
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader></DialogHeader>
<ServiceCategoryForm
onSuccess={handleFormSuccess}
onCancel={handleFormCancel}
/>
</DialogContent>
</Dialog>
{/* Edit Dialog */}
<Dialog
open={!!editingCategory}
onOpenChange={() => setEditingCategory(null)}
>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{t("serviceCategory.editTitle", "Edit Service Category")}
</DialogTitle>
</DialogHeader>
{editingCategory && (
<ServiceCategoryForm
category={editingCategory}
onSuccess={handleFormSuccess}
onCancel={handleFormCancel}
/>
)}
</DialogContent>
</Dialog>
{/* Delete Dialog */}
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("serviceCategory.deleteTitle", "Delete Service Category")}
</AlertDialogTitle>
<AlertDialogDescription>
{t(
"serviceCategory.deleteConfirmation",
'Are you sure you want to delete "{{name}}"? This action cannot be undone.',
{ name: localizedName(selectedCategory?.name) }
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("common.cancel", "Cancel")}
</AlertDialogCancel>
<AlertDialogAction
className="bg-red-600 hover:bg-red-700"
disabled={deleteCategory.isPending}
onClick={handleDelete}
>
{deleteCategory.isPending
? t("common.deleting", "Deleting...")
: t("common.delete", "Delete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};