Files
krow_workforce_owliver/src/pages/CompliancePage.tsx
2026-09-16 20:11:27 +05:30

295 lines
11 KiB
TypeScript

import {
AlertTriangle,
FileStack,
FolderKanban,
ShieldAlert,
ShieldCheck,
Shirt,
} from "lucide-react"
import { useSearchParams } from "react-router-dom"
import { MetricCard } from "@/components/shared/MetricCard"
import { Page, PageIntro, SectionCard } from "@/components/shared/Page"
import { StatusBadge } from "@/components/shared/StatusBadge"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { COMPLIANCE_RISKS, STAFF } from "@/data/mockData"
import { complianceFlagCount, staffWithComplianceIssues } from "@/data/selectors"
import { useWorkforceRevision } from "@/data/store"
const CATALOG = [
{ name: "Right to Work", required: "All staff", holders: 44, of: 46, renewal: "On hire" },
{ name: "Manual Handling", required: "Warehouse & Logistics", holders: 15, of: 16, renewal: "2 years" },
{ name: "Forklift Licence", required: "Forklift Drivers", holders: 3, of: 4, renewal: "3 years" },
{ name: "Food Hygiene L2", required: "Event & Hospitality", holders: 17, of: 18, renewal: "3 years" },
{ name: "Personal Licence", required: "Bar Supervisors", holders: 2, of: 2, renewal: "10 years" },
{ name: "COSHH Awareness", required: "Facilities & Cleaning", holders: 11, of: 12, renewal: "1 year" },
]
const DOCUMENTS = [
{ staff: "Rosa Delgado", doc: "Right to Work", status: "Expired", updated: "Pending upload" },
{ staff: "Aisha Patel", doc: "Policy Acknowledgement", status: "Warning", updated: "Awaiting signature" },
{ staff: "Samuel Okoro", doc: "Manual Handling Certificate", status: "Expired", updated: "Expired 02 Sep 2025" },
{ staff: "Tomas Ferreira", doc: "Forklift Licence", status: "Warning", updated: "Expires 17 Sep 2025" },
{ staff: "Leila Haddad", doc: "Right to Work", status: "Verified", updated: "Verified 12 Jul 2025" },
{ staff: "Marcus Vance", doc: "Supervisor Training", status: "Verified", updated: "Verified 03 Aug 2025" },
]
const ATTIRE = [
{ staff: "Priya Raman", item: "Event uniform · size exchange", due: "05 Oct 2025", status: "Review" },
{ staff: "Liam O'Connor", item: "Bar tunic · replacement", due: "05 Oct 2025", status: "Review" },
{ staff: "Rosa Delgado", item: "Hi-vis & safety boots", due: "05 Oct 2025", status: "Review" },
{ staff: "Owen Bradley", item: "Hi-vis · annual check", due: "18 Nov 2025", status: "Verified" },
]
export function CompliancePage() {
useWorkforceRevision()
const [params, setParams] = useSearchParams()
const tab = params.get("tab") ?? "certificates"
const high = COMPLIANCE_RISKS.filter((r) => r.severity === "High")
const expiringSoon = STAFF.filter((s) => s.compliance === "Warning")
return (
<Page>
<PageIntro
title="Compliance"
description="Certificates, documents, policies and attire checks across the workforce."
actions={<Button variant="outline">Export register</Button>}
/>
<div className="grid gap-2.5 grid-cols-2 lg:grid-cols-4">
<MetricCard
label="Open Flags"
value={String(complianceFlagCount())}
sub={`Across ${COMPLIANCE_RISKS.length} risk categories`}
icon={ShieldAlert}
badge="Review"
badgeTone="warn"
tone="warn"
/>
<MetricCard
label="High Severity"
value={String(high.reduce((s, r) => s + r.staffCount, 0))}
sub="Block assignment until cleared"
icon={AlertTriangle}
badge="Urgent"
badgeTone="bad"
tone="bad"
/>
<MetricCard
label="Staff Affected"
value={String(staffWithComplianceIssues().length)}
sub={`of ${STAFF.length} tracked staff`}
icon={ShieldCheck}
tone="primary"
/>
<MetricCard
label="Expiring Soon"
value={String(expiringSoon.length)}
sub="Within the next 14 days"
icon={FileStack}
badge="Watch"
badgeTone="warn"
tone="primary"
/>
</div>
<SectionCard title="Compliance Risk Overview" icon={ShieldAlert} bodyClassName="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-surface-soft">
<TableHead>Risk</TableHead>
<TableHead>Category</TableHead>
<TableHead>Affected staff</TableHead>
<TableHead>Severity</TableHead>
<TableHead>Expires</TableHead>
<TableHead className="text-right">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{COMPLIANCE_RISKS.map((r) => (
<TableRow key={r.id}>
<TableCell className="font-medium text-ink">{r.risk}</TableCell>
<TableCell>
<Badge tone="neutral" size="sm">
{r.category}
</Badge>
</TableCell>
<TableCell>
<div className="w-[168px]" title={r.staffNames.join(", ")}>
<span className="font-semibold text-ink">{r.staffCount}</span>
<div className="truncate text-[12px] text-ink-3">
{r.staffNames.join(", ")}
</div>
</div>
</TableCell>
<TableCell>
<StatusBadge value={r.severity} size="sm" />
</TableCell>
<TableCell className="whitespace-nowrap">{r.expires.replace(" 2025", "")}</TableCell>
<TableCell className="text-right">
<Button size="sm" variant="outline">
{r.action}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</SectionCard>
<Tabs value={tab} onValueChange={(v) => setParams({ tab: v })}>
<TabsList>
<TabsTrigger value="certificates">
<ShieldCheck className="size-3.5" />
Staff Certificates
</TabsTrigger>
<TabsTrigger value="documents">
<FolderKanban className="size-3.5" />
Employee Documents
</TabsTrigger>
<TabsTrigger value="attire">
<Shirt className="size-3.5" />
Attire Review
</TabsTrigger>
<TabsTrigger value="catalog">
<FileStack className="size-3.5" />
Compliance Catalog
</TabsTrigger>
</TabsList>
<TabsContent value="certificates">
<SectionCard title="Staff Certificates" icon={ShieldCheck} bodyClassName="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-surface-soft">
<TableHead>Staff</TableHead>
<TableHead>Role</TableHead>
<TableHead>Standing</TableHead>
<TableHead>Note</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{STAFF.map((s) => (
<TableRow key={s.id}>
<TableCell className="font-medium text-ink">{s.name}</TableCell>
<TableCell>{s.role}</TableCell>
<TableCell>
<StatusBadge value={s.compliance} size="sm" />
</TableCell>
<TableCell className="text-ink-3">
{s.complianceNote ?? "All certificates current"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</SectionCard>
</TabsContent>
<TabsContent value="documents">
<SectionCard title="Employee Documents" icon={FolderKanban} bodyClassName="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-surface-soft">
<TableHead>Staff</TableHead>
<TableHead>Document</TableHead>
<TableHead>Status</TableHead>
<TableHead>Last update</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{DOCUMENTS.map((d) => (
<TableRow key={`${d.staff}-${d.doc}`}>
<TableCell className="font-medium text-ink">{d.staff}</TableCell>
<TableCell>{d.doc}</TableCell>
<TableCell>
<StatusBadge value={d.status} size="sm" />
</TableCell>
<TableCell className="text-ink-3">{d.updated}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</SectionCard>
</TabsContent>
<TabsContent value="attire">
<SectionCard title="Attire Review" icon={Shirt} bodyClassName="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-surface-soft">
<TableHead>Staff</TableHead>
<TableHead>Item</TableHead>
<TableHead>Due</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ATTIRE.map((a) => (
<TableRow key={`${a.staff}-${a.item}`}>
<TableCell className="font-medium text-ink">{a.staff}</TableCell>
<TableCell>{a.item}</TableCell>
<TableCell className="whitespace-nowrap">{a.due}</TableCell>
<TableCell>
<StatusBadge value={a.status} size="sm" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</SectionCard>
</TabsContent>
<TabsContent value="catalog">
<SectionCard title="Compliance Catalog" icon={FileStack} bodyClassName="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-surface-soft">
<TableHead>Requirement</TableHead>
<TableHead>Applies to</TableHead>
<TableHead>Coverage</TableHead>
<TableHead>Renewal</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{CATALOG.map((c) => {
const complete = c.holders === c.of
return (
<TableRow key={c.name}>
<TableCell className="font-medium text-ink">{c.name}</TableCell>
<TableCell>{c.required}</TableCell>
<TableCell>
<span className="flex items-center gap-2">
<span className="font-semibold text-ink">
{c.holders}/{c.of}
</span>
<StatusBadge
value={complete ? "Verified" : "Warning"}
size="sm"
/>
</span>
</TableCell>
<TableCell className="text-ink-3">{c.renewal}</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</SectionCard>
</TabsContent>
</Tabs>
</Page>
)
}