mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 08:09:00 +00:00
267 lines
8.9 KiB
TypeScript
267 lines
8.9 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import { useNavigate } from "react-router";
|
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutlined";
|
|
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
|
import {
|
|
Alert,
|
|
Box,
|
|
Button,
|
|
Chip,
|
|
CircularProgress,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogContentText,
|
|
DialogTitle,
|
|
IconButton,
|
|
Paper,
|
|
Stack,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TablePagination,
|
|
TableRow,
|
|
TextField,
|
|
Tooltip,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import { pmScheduleCapabilities } from "@/config/pm-schedule-capabilities";
|
|
import type { PmScheduleListItem } from "@/domain/pm-schedules/types/pm-schedule";
|
|
import { useDeletePmSchedule } from "@/domain/pm-schedules/use-cases/use-delete-pm-schedule";
|
|
import { usePmSchedulesList } from "@/domain/pm-schedules/use-cases/use-pm-schedules-list";
|
|
import { useDebounce } from "@/hooks/use-debounce";
|
|
|
|
export default function PmSchedulesListPage() {
|
|
const navigate = useNavigate();
|
|
const [search, setSearch] = useState("");
|
|
const [page, setPage] = useState(0);
|
|
const [pageSize, setPageSize] = useState(12);
|
|
const [deleteTarget, setDeleteTarget] = useState<PmScheduleListItem | null>(null);
|
|
|
|
const debouncedSearch = useDebounce(search, 300);
|
|
const deletePmSchedule = useDeletePmSchedule();
|
|
|
|
const showActionsColumn = pmScheduleCapabilities.canEdit || pmScheduleCapabilities.canDelete;
|
|
const columnCount = showActionsColumn ? 7 : 6;
|
|
|
|
const listParams = useMemo(
|
|
() => ({
|
|
search: debouncedSearch,
|
|
page: page + 1,
|
|
pageSize,
|
|
}),
|
|
[debouncedSearch, page, pageSize],
|
|
);
|
|
|
|
const { data, isLoading, isFetching, error } = usePmSchedulesList(listParams);
|
|
|
|
const rows = data?.items ?? [];
|
|
const totalCount = data?.totalCount ?? 0;
|
|
|
|
const handleDeleteConfirm = () => {
|
|
if (!deleteTarget || !pmScheduleCapabilities.canDelete) return;
|
|
deletePmSchedule.mutate(deleteTarget.id, {
|
|
onSuccess: () => setDeleteTarget(null),
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Box className="flex flex-col gap-4 p-4">
|
|
<Stack
|
|
direction={{ xs: "column", md: "row" }}
|
|
spacing={2}
|
|
sx={{
|
|
alignItems: { xs: "stretch", md: "center" },
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<Stack
|
|
direction="row"
|
|
spacing={2}
|
|
sx={{
|
|
alignItems: "baseline",
|
|
}}
|
|
>
|
|
<Typography variant="h5" component="h1">
|
|
PM Schedules
|
|
</Typography>
|
|
<Typography
|
|
variant="body2"
|
|
sx={{
|
|
color: "text.secondary",
|
|
}}
|
|
>
|
|
{totalCount} total
|
|
</Typography>
|
|
{isFetching && !isLoading && <CircularProgress size={16} />}
|
|
</Stack>
|
|
|
|
<Stack
|
|
direction={{ xs: "column", sm: "row" }}
|
|
spacing={2}
|
|
sx={{
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<TextField
|
|
size="small"
|
|
value={search}
|
|
onChange={(event) => {
|
|
setSearch(event.target.value);
|
|
setPage(0);
|
|
}}
|
|
placeholder="Search PM schedules..."
|
|
className="min-w-[220px]"
|
|
/>
|
|
{pmScheduleCapabilities.canCreate && (
|
|
<Button variant="contained" onClick={() => navigate("/pmschedules/new")}>
|
|
+ New PM Schedule
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
{Boolean(error) && (
|
|
<Alert severity="error">
|
|
{error instanceof Error ? error.message : "Failed to load PM schedules"}
|
|
</Alert>
|
|
)}
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell className="font-semibold">Name</TableCell>
|
|
<TableCell className="font-semibold">Frequency</TableCell>
|
|
<TableCell className="font-semibold">Start Date</TableCell>
|
|
<TableCell className="font-semibold">Status</TableCell>
|
|
<TableCell className="font-semibold">Asset</TableCell>
|
|
<TableCell className="font-semibold">Location</TableCell>
|
|
{showActionsColumn && (
|
|
<TableCell align="right" className="font-semibold">
|
|
Actions
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{isLoading ? (
|
|
<TableRow>
|
|
<TableCell colSpan={columnCount} align="center" className="py-10">
|
|
<CircularProgress size={28} />
|
|
</TableCell>
|
|
</TableRow>
|
|
) : rows.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={columnCount} align="center" className="py-10">
|
|
<Typography
|
|
sx={{
|
|
color: "text.secondary",
|
|
}}
|
|
>
|
|
No PM schedules found
|
|
</Typography>
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
rows.map((row) => (
|
|
<TableRow key={String(row.id)} hover>
|
|
<TableCell>
|
|
{pmScheduleCapabilities.canEdit ? (
|
|
<Button
|
|
variant="text"
|
|
size="small"
|
|
className="font-semibold"
|
|
onClick={() => navigate(`/pmschedules/edit/${row.id}`)}
|
|
>
|
|
{row.name || "—"}
|
|
</Button>
|
|
) : (
|
|
<Typography variant="body2" className="font-semibold">
|
|
{row.name || "—"}
|
|
</Typography>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>{row.frequency || "—"}</TableCell>
|
|
<TableCell>{row.startDate || "—"}</TableCell>
|
|
<TableCell>
|
|
<Chip
|
|
label={row.isActive ? "Active" : "Inactive"}
|
|
size="small"
|
|
color={row.isActive ? "success" : "default"}
|
|
variant="outlined"
|
|
/>
|
|
</TableCell>
|
|
<TableCell>{row.asset || "—"}</TableCell>
|
|
<TableCell>{row.location || "—"}</TableCell>
|
|
{showActionsColumn && (
|
|
<TableCell align="right">
|
|
{pmScheduleCapabilities.canEdit && (
|
|
<Tooltip title="Edit">
|
|
<IconButton
|
|
size="small"
|
|
aria-label="Edit PM schedule"
|
|
onClick={() => navigate(`/pmschedules/edit/${row.id}`)}
|
|
>
|
|
<EditOutlinedIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
{pmScheduleCapabilities.canDelete && (
|
|
<Tooltip title="Delete">
|
|
<IconButton
|
|
size="small"
|
|
aria-label="Delete PM schedule"
|
|
onClick={() => setDeleteTarget(row)}
|
|
>
|
|
<DeleteOutlineIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
<TablePagination
|
|
component="div"
|
|
count={totalCount}
|
|
page={page}
|
|
onPageChange={(_event, nextPage) => setPage(nextPage)}
|
|
rowsPerPage={pageSize}
|
|
onRowsPerPageChange={(event) => {
|
|
setPageSize(Number(event.target.value));
|
|
setPage(0);
|
|
}}
|
|
rowsPerPageOptions={[12, 24, 48]}
|
|
/>
|
|
{pmScheduleCapabilities.canDelete && (
|
|
<Dialog open={Boolean(deleteTarget)} onClose={() => setDeleteTarget(null)}>
|
|
<DialogTitle>Delete PM Schedule</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText>
|
|
Are you sure you want to delete "{deleteTarget?.name}"? This action cannot
|
|
be undone.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setDeleteTarget(null)} disabled={deletePmSchedule.isPending}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
color="error"
|
|
variant="contained"
|
|
onClick={handleDeleteConfirm}
|
|
disabled={deletePmSchedule.isPending}
|
|
>
|
|
{deletePmSchedule.isPending ? "Deleting..." : "Delete"}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|