shoc-frontend-new/src/app/(protected)/dashboard/_components/dashboard-card.tsx
Codex Review Integration 1271d3c11e feat(dashboard): dispatcher scope picker, status distribution, region drilldown, perf counts
- SH-336: add the dispatcher-scope picker (All / My WOs / individual) for
  Scheduler/Admin via a new viewAllDispatchersOnDashboard role gate, a static
  'My WOs' label for Dispatcher, and hide the dispatcher tables (and skip their
  queries) when the viewer cannot see all dispatchers. Scope drives dispatcherId
  on stats, workload, performance, and regions (SH-347 contract).
- SH-352: add the Status Distribution module with its empty state and status
  drill-down.
- SH-347: show Assigned/Completed counts and colour the completion rate
  (green >=90, amber 70-89, red <70) on Dispatcher Performance.
- SH-348: drill a region bar into the board region filter; hold the
  unconfirmed 'Unmapped/Other' bucket off the chart.
- SH-349: label Vendor Insights as all-time, company-wide.
- Make the drilldown-filters effect fire only on URL change; add a deterministic
  clock to the status-drilldown test; add the dashboard visual regression spec
  and baselines.
2026-09-17 02:56:31 -03:00

52 lines
1.5 KiB
TypeScript

import type { ReactNode } from "react";
import { Box, CircularProgress, Paper } from "@mui/material";
import { QueryErrorPanel } from "@/components/ui/query-error-panel";
import { Text } from "@/components/ui/text";
interface DashboardCardProps {
title: string;
/** Optional scope caption shown beside the title (e.g. SH-349's all-time note). */
caption?: string;
isLoading?: boolean;
error?: unknown;
isEmpty: boolean;
emptyMessage: string;
children: ReactNode;
}
export function DashboardCard({
title,
caption,
isLoading = false,
error,
isEmpty,
emptyMessage,
children,
}: DashboardCardProps) {
return (
<Paper variant="outlined" className="flex flex-col overflow-hidden">
<Box className="flex items-baseline justify-between gap-2 border-b border-border px-4 py-3">
<Text variant="heading">{title}</Text>
{!!caption && (
<Text variant="description" tone="muted">
{caption}
</Text>
)}
</Box>
{isLoading && (
<Box className="flex items-center justify-center p-8">
<CircularProgress size={24} />
</Box>
)}
{!isLoading && Boolean(error) && <QueryErrorPanel compact className="m-4" />}
{!isLoading && !error && isEmpty && (
<Box className="p-6">
<Text variant="description" tone="muted">
{emptyMessage}
</Text>
</Box>
)}
{!isLoading && !error && !isEmpty && <Box className="p-4">{children}</Box>}
</Paper>
);
}