mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
364 lines
11 KiB
TypeScript
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>
|
|
);
|
|
}
|