Files
edr-platform/apps/edr-freight-web/backoffice/src/super-admin/components/sites/SitesPage.tsx
natib21 e6e44e773b fix ui
2026-07-10 11:25:59 +00:00

364 lines
11 KiB
TypeScript

import { useState, useMemo, useEffect } from "react";
import { useSites } from "@/super-admin/hooks/useSites";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { Button } from "@/shared/common/ui/button";
import { AdvancedTable } from "@/shared/common/ui/table/AdvancedTable";
import { getSitesColumnDefn } from "./SitesColumnDefn";
import { Loader, Plus } from "lucide-react";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/shared/common/ui/card";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/shared/common/ui/alert-dialog";
import { SiteDto, SitePayloadDto } from "@/super-admin/dto/SitesDto";
import SiteFormModal from "./SiteFormModal";
import SiteViewModal from "./SiteViewModal";
export default function SitesPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [searchTerm, setSearchTerm] = useState("");
// Modals state
const [formOpen, setFormOpen] = useState(false);
const [viewOpen, setViewOpen] = useState(false);
const [selectedSite, setSelectedSite] = useState<SiteDto | null>(null);
// Dialogs state for archive/delete/restore
const [archiveDialogOpen, setArchiveDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [restoreDialogOpen, setRestoreDialogOpen] = useState(false);
// Query & mutation hook
const {
sitesResponse,
isLoading,
refetch,
createSite,
isCreating,
updateSite,
isUpdating,
archiveSite,
isArchiving,
deleteSite,
isDeleting,
restoreSite,
isRestoring,
} = useSites({
take: pageSize,
skip: pageIndex * pageSize,
orderBy: "createdAt:DESC",
});
useEffect(() => {
setPageIndex(0);
}, [searchTerm, pageSize]);
const handlePageChange = (newPage: number) => {
setPageIndex(newPage);
};
const handleAddNewClick = () => {
setSelectedSite(null);
setFormOpen(true);
};
const handleFormSubmit = (payload: SitePayloadDto) => {
if (selectedSite) {
updateSite(
{ id: selectedSite.id, payload },
{
onSuccess: () => {
setFormOpen(false);
setSelectedSite(null);
},
},
);
} else {
createSite(payload, {
onSuccess: () => {
setFormOpen(false);
},
});
}
};
const handleArchiveConfirm = () => {
if (selectedSite) {
archiveSite(
{ id: selectedSite.id },
{
onSuccess: () => {
setArchiveDialogOpen(false);
setSelectedSite(null);
},
},
);
}
};
const handleDeleteConfirm = () => {
if (selectedSite) {
deleteSite(
{ id: selectedSite.id },
{
onSuccess: () => {
setDeleteDialogOpen(false);
setSelectedSite(null);
},
},
);
}
};
const handleRestoreConfirm = () => {
if (selectedSite) {
restoreSite(
{ id: selectedSite.id },
{
onSuccess: () => {
setRestoreDialogOpen(false);
setSelectedSite(null);
},
},
);
}
};
// Extract all sites from response
const allSites = useMemo(() => sitesResponse?.items || [], [sitesResponse]);
// Existing keys list (for create uniqueness validation)
const existingKeys = useMemo(() => allSites.map((s) => s.key), [allSites]);
// Get table column definitions
const columns = useMemo(
() =>
getSitesColumnDefn({
onView: (site) => {
setSelectedSite(site);
setViewOpen(true);
},
onEdit: (site) => {
setSelectedSite(site);
setFormOpen(true);
},
onArchive: (site) => {
setSelectedSite(site);
setArchiveDialogOpen(true);
},
onRestore: (site) => {
setSelectedSite(site);
setRestoreDialogOpen(true);
},
onDelete: (site) => {
setSelectedSite(site);
setDeleteDialogOpen(true);
},
onBranding: (site) => {
navigate(`/user-management/web-management/Branding/Branding/${site.id}`);
},
}),
[],
);
return (
<div className="p-6 space-y-6">
<Card className="col-span-2 shadow-none border-none bg-transparent px-0">
<CardHeader className="flex flex-row justify-between items-center px-0">
<CardTitle className="text-2xl font-bold text-slate-800 dark:text-slate-100">
{t("sites.title")}
</CardTitle>
</CardHeader>
<CardContent className="px-0">
<AdvancedTable
columns={columns}
data={sitesResponse?.items || []}
tableName={t("sites.tableName")}
toolBarPosition="right"
itemCount={sitesResponse?.count || 0}
pageSize={pageSize}
onGlobalFilterChange={setSearchTerm}
extraToolbar={
<Button
onClick={handleAddNewClick}
className="px-5 py-2 rounded-md text-sm font-medium shadow-md cursor-pointer"
>
<Plus className="w-4 h-4 mr-2" />
{t("sites.addNew")}
</Button>
}
pageIndex={pageIndex}
onPageChange={handlePageChange}
onPageSizeChange={setPageSize}
nextFunction={() => handlePageChange(pageIndex + 1)}
prevFunction={() => handlePageChange(Math.max(pageIndex - 1, 0))}
refresh={refetch}
isLoading={isLoading}
/>
</CardContent>
</Card>
{/* Create / Edit Form Modal */}
<SiteFormModal
isOpen={formOpen}
onClose={() => {
setFormOpen(false);
setSelectedSite(null);
}}
onSubmit={handleFormSubmit}
site={selectedSite}
isSubmitting={isCreating || isUpdating}
existingKeys={existingKeys}
/>
{/* Read-Only View Modal */}
<SiteViewModal
isOpen={viewOpen}
onClose={() => {
setViewOpen(false);
setSelectedSite(null);
}}
site={selectedSite}
/>
{/* Archive Confirmation (Soft Delete) */}
<AlertDialog
open={archiveDialogOpen}
onOpenChange={(open) => !open && setArchiveDialogOpen(false)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="text-lg font-bold">
{t("sites.dialogs.archiveTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("sites.dialogs.archiveDescription", {
name: selectedSite?.name?.en,
})}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
disabled={isArchiving}
onClick={() => {
setArchiveDialogOpen(false);
setSelectedSite(null);
}}
className="cursor-pointer"
>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
disabled={isArchiving}
onClick={handleArchiveConfirm}
className="bg-amber-600 hover:bg-amber-700 text-white cursor-pointer"
>
{isArchiving
? t("sites.dialogs.archiving")
: t("sites.dialogs.archiveConfirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Permanent Delete Confirmation (Hard Delete) */}
<AlertDialog
open={deleteDialogOpen}
onOpenChange={(open) => !open && setDeleteDialogOpen(false)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="text-lg font-bold text-red-600">
{t("sites.dialogs.deleteTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
<span
dangerouslySetInnerHTML={{
__html: t("sites.dialogs.deleteDescription", {
name: selectedSite?.name?.en,
}),
}}
/>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
disabled={isDeleting}
onClick={() => {
setDeleteDialogOpen(false);
setSelectedSite(null);
}}
className="cursor-pointer"
>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
disabled={isDeleting}
onClick={handleDeleteConfirm}
className="bg-red-600 hover:bg-red-700 text-white cursor-pointer"
>
{isDeleting
? t("sites.dialogs.deleting")
: t("sites.dialogs.deleteConfirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Restore Confirmation */}
<AlertDialog
open={restoreDialogOpen}
onOpenChange={(open) => !open && setRestoreDialogOpen(false)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="text-lg font-bold">
{t("sites.dialogs.restoreTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("sites.dialogs.restoreDescription", {
name: selectedSite?.name?.en,
})}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
disabled={isRestoring}
onClick={() => {
setRestoreDialogOpen(false);
setSelectedSite(null);
}}
className="cursor-pointer"
>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
disabled={isRestoring}
onClick={handleRestoreConfirm}
className="bg-emerald-600 hover:bg-emerald-700 text-white cursor-pointer"
>
{isRestoring
? t("sites.dialogs.restoring")
: t("sites.dialogs.restoreConfirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}