From 355e0294dfe2bfba3e51d31d63b43a8e76a7a999 Mon Sep 17 00:00:00 2001 From: nano Date: Tue, 5 May 2026 17:31:47 +0000 Subject: [PATCH] Merge branch 1.2 into main (campaign-tracker-client/src/workspace/WorkspaceShell.tsx) --- .../src/workspace/WorkspaceShell.tsx | 374 ++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 campaign-tracker-client/src/workspace/WorkspaceShell.tsx diff --git a/campaign-tracker-client/src/workspace/WorkspaceShell.tsx b/campaign-tracker-client/src/workspace/WorkspaceShell.tsx new file mode 100644 index 0000000..bfc8c18 --- /dev/null +++ b/campaign-tracker-client/src/workspace/WorkspaceShell.tsx @@ -0,0 +1,374 @@ +import { + CheckCircleFilled, + ClockCircleFilled, + CloseCircleFilled, + ExclamationCircleFilled, + InfoCircleFilled, + MenuFoldOutlined, + MenuUnfoldOutlined, +} from '@ant-design/icons' +import { + Alert, + Badge, + Button, + Layout, + Menu, + Space, + Table, + Tag, + Tooltip, + Typography, + theme, + type TableProps, +} from 'antd' +import { useEffect, useState, type CSSProperties } from 'react' +import { + isEditingAvailable, + isRightPanelCollapsible, + semanticStatusColors, + statusDefinitions, + workspaceThemeTokens, + type WorkspaceStatus, +} from './workspaceContracts' +import './WorkspaceShell.css' + +const { Header, Sider, Content } = Layout +const { Text, Title } = Typography + +type CycleRow = { + key: string + municipality: string + cycle: string + service: string + status: WorkspaceStatus + dueDate: string + owner: string +} + +const cycleRows: CycleRow[] = [ + { + key: 'CAM-1042', + municipality: 'Fairview Borough', + cycle: 'Primary 2026', + service: 'Addressing', + status: 'onTrack', + dueDate: 'May 18', + owner: 'Client Services', + }, + { + key: 'CAM-1088', + municipality: 'Lake Township', + cycle: 'Primary 2026', + service: 'Transport', + status: 'atRisk', + dueDate: 'May 12', + owner: 'Production Lead', + }, + { + key: 'CAM-1120', + municipality: 'Northfield City', + cycle: 'Special 2026', + service: 'Sorting', + status: 'blocked', + dueDate: 'May 9', + owner: 'Data Quality', + }, + { + key: 'CAM-1137', + municipality: 'Pine County', + cycle: 'General 2026', + service: 'Office Copies', + status: 'inProgress', + dueDate: 'May 22', + owner: 'Operations', + }, +] + +const riskItems = [ + { + id: 'RQ-18', + title: 'Lake Township transport confirmation', + status: 'atRisk' as WorkspaceStatus, + owner: 'Production Lead', + }, + { + id: 'RQ-21', + title: 'Northfield missing sorting owner', + status: 'blocked' as WorkspaceStatus, + owner: 'Data Quality', + }, + { + id: 'RQ-24', + title: 'Pine County proof approval check', + status: 'inProgress' as WorkspaceStatus, + owner: 'Operations', + }, +] + +const statusIcons = { + onTrack: CheckCircleFilled, + atRisk: ExclamationCircleFilled, + blocked: CloseCircleFilled, + inProgress: InfoCircleFilled, + overdue: ClockCircleFilled, +} satisfies Record + +function useViewportWidth() { + const [width, setWidth] = useState(() => + typeof window === 'undefined' ? 1600 : window.innerWidth, + ) + + useEffect(() => { + const updateWidth = () => setWidth(window.innerWidth) + + updateWidth() + window.addEventListener('resize', updateWidth) + + return () => window.removeEventListener('resize', updateWidth) + }, []) + + return width +} + +function StatusIndicator({ status }: { status: WorkspaceStatus }) { + const definition = statusDefinitions[status] + const Icon = statusIcons[status] + + return ( + + + ) +} + +function WorkspaceNavigation() { + return ( + +
+ Campaign Tracker + Operations +
+ + + ) +} + +function RiskPanel({ + collapsed, + onToggle, + canCollapse, +}: { + collapsed: boolean + onToggle: () => void + canCollapse: boolean +}) { + if (collapsed) { + return ( + + + + + + + + + + {!editingAvailable ? ( + + ) : null} +
+
+
+ Election cycle setup + Municipality work queue +
+ + + + + +
+ + row.status === 'blocked' ? 'workspace-row--blocked' : '' + } + /> +
+ + Last refreshed 12:48 PM. Legacy source context remains read-only; + new workflow updates route through extension records. + + +
+ + + + setRightPanelCollapseRequested((value) => !value)} + /> + + ) +}