shoc-frontend-new/src/app/(protected)/index.tsx
2026-07-24 11:32:58 -03:00

240 lines
7.3 KiB
TypeScript

import type { ReactNode } from "react";
import { useNavigate } from "react-router";
import AddIcon from "@mui/icons-material/Add";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutlined";
import FolderOpenOutlinedIcon from "@mui/icons-material/FolderOpenOutlined";
import ScheduleOutlinedIcon from "@mui/icons-material/ScheduleOutlined";
import {
Alert,
Box,
CircularProgress,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import { useDashboardStats } from "@/domain/dashboard/use-cases/use-dashboard-stats";
const INVOICE_STATUSES = ["Draft", "Challenged", "Pending Approval", "Approved", "Paid"] as const;
const PRIORITY_LEVELS = [5, 4, 3, 2, 1] as const;
interface KpiCardProps {
title: string;
description?: string;
value?: string | number;
icon: ReactNode;
color: "success" | "primary" | "info" | "warning";
onClick: () => void;
loading?: boolean;
}
function KpiCard({ title, description, value, icon, color, onClick, loading }: KpiCardProps) {
const colorMap = {
success: "success.main",
primary: "primary.main",
info: "info.main",
warning: "warning.main",
} as const;
return (
<Paper
variant="outlined"
onClick={onClick}
className="flex min-h-[140px] flex-1 cursor-pointer transition-shadow hover:shadow-md"
sx={{
borderLeftWidth: 4,
borderLeftStyle: "solid",
borderLeftColor: colorMap[color],
}}
>
<Box className="flex w-full gap-4 p-4">
<Box
className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full"
sx={{ bgcolor: `${colorMap[color]}22`, color: colorMap[color] }}
>
{icon}
</Box>
<Box className="flex min-w-0 flex-col justify-center gap-1">
<Typography variant="subtitle1" className="font-semibold">
{title}
</Typography>
{value !== undefined && (
<Typography variant="h4" component="p" className="font-bold leading-tight">
{loading ? "..." : typeof value === "number" ? value.toLocaleString() : value}
</Typography>
)}
{Boolean(description) && (
<Typography
variant="body2"
sx={{
color: "text.secondary",
}}
>
{description}
</Typography>
)}
</Box>
</Box>
</Paper>
);
}
export default function DashboardPage() {
const navigate = useNavigate();
const { data: stats, isLoading, isFetching, error } = useDashboardStats();
const formatStat = (value: number | undefined) => (isLoading ? undefined : (value ?? 0));
return (
<Box className="flex flex-col gap-4 p-4">
<Stack
direction="row"
spacing={2}
sx={{
alignItems: "baseline",
}}
>
<Typography variant="h5" component="h1">
Dashboard
</Typography>
{isFetching && !isLoading && <CircularProgress size={16} />}
</Stack>
{Boolean(error) && (
<Alert severity="error">
{error instanceof Error ? error.message : "Failed to load dashboard stats"}
</Alert>
)}
<Stack
direction={{ xs: "column", lg: "row" }}
spacing={2}
useFlexGap
sx={{
flexWrap: "wrap",
}}
>
<KpiCard
title="Create a Work Order"
description="Report a problem you need fixed"
icon={<AddIcon />}
color="success"
onClick={() => navigate("/workorders/new")}
/>
<KpiCard
title="Open Work Orders"
value={formatStat(stats?.open)}
icon={<FolderOpenOutlinedIcon />}
color="primary"
loading={isLoading}
onClick={() => navigate("/workorders")}
/>
<KpiCard
title="Not Dispatched"
value={formatStat(stats?.notDispatched)}
icon={<ScheduleOutlinedIcon />}
color="info"
loading={isLoading}
onClick={() => navigate("/workorders")}
/>
<KpiCard
title="Completed Orders"
value={formatStat(stats?.completed)}
icon={<CheckCircleOutlineIcon />}
color="warning"
loading={isLoading}
onClick={() => navigate("/workorders")}
/>
</Stack>
<Paper variant="outlined" className="overflow-hidden">
<Box className="border-b border-border px-4 py-3">
<Typography variant="h6" component="h2">
Priority Analysis
</Typography>
<Typography
variant="body2"
sx={{
color: "text.secondary",
}}
>
Age of open Work Orders by Priority
</Typography>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell className="font-semibold">Priority</TableCell>
<TableCell align="right" className="font-semibold">
Total
</TableCell>
<TableCell align="right" className="font-semibold">
0-14 Days
</TableCell>
<TableCell align="right" className="font-semibold">
15-29 Days
</TableCell>
<TableCell align="right" className="font-semibold">
30-59 Days
</TableCell>
<TableCell align="right" className="font-semibold">
60+ Days
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{PRIORITY_LEVELS.map((priority) => (
<TableRow key={priority}>
<TableCell>{priority}</TableCell>
<TableCell align="right">—</TableCell>
<TableCell align="right">—</TableCell>
<TableCell align="right">—</TableCell>
<TableCell align="right">—</TableCell>
<TableCell align="right">—</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
<Paper variant="outlined" className="overflow-hidden">
<Box className="border-b border-border px-4 py-3">
<Typography variant="h6" component="h2">
Invoice Analysis
</Typography>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell className="font-semibold">Status</TableCell>
<TableCell align="right" className="font-semibold">
Count
</TableCell>
<TableCell align="right" className="font-semibold">
Amount
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{INVOICE_STATUSES.map((status) => (
<TableRow key={status}>
<TableCell>{status}</TableCell>
<TableCell align="right">0</TableCell>
<TableCell align="right">$0.00</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
</Box>
);
}