| @@ -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<WorkspaceStatus, typeof CheckCircleFilled> | |||||
| 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 ( | |||||
| <Tag | |||||
| className="workspace-status" | |||||
| style={{ | |||||
| borderColor: definition.color, | |||||
| color: definition.color, | |||||
| }} | |||||
| aria-label={`${definition.iconLabel}: ${definition.label}`} | |||||
| > | |||||
| <Icon aria-hidden="true" /> | |||||
| <span>{definition.label}</span> | |||||
| </Tag> | |||||
| ) | |||||
| } | |||||
| function WorkspaceNavigation() { | |||||
| return ( | |||||
| <Sider width={248} className="workspace-nav" theme="light"> | |||||
| <div className="workspace-brand"> | |||||
| <Text className="workspace-brand__eyebrow">Campaign Tracker</Text> | |||||
| <Title level={1}>Operations</Title> | |||||
| </div> | |||||
| <Menu | |||||
| mode="inline" | |||||
| defaultSelectedKeys={['cycles']} | |||||
| items={[ | |||||
| { key: 'cycles', label: 'Election Cycles' }, | |||||
| { key: 'municipalities', label: 'Municipalities' }, | |||||
| { key: 'milestones', label: 'Milestones' }, | |||||
| { key: 'reports', label: 'Reports' }, | |||||
| ]} | |||||
| /> | |||||
| </Sider> | |||||
| ) | |||||
| } | |||||
| function RiskPanel({ | |||||
| collapsed, | |||||
| onToggle, | |||||
| canCollapse, | |||||
| }: { | |||||
| collapsed: boolean | |||||
| onToggle: () => void | |||||
| canCollapse: boolean | |||||
| }) { | |||||
| if (collapsed) { | |||||
| return ( | |||||
| <Sider width={48} className="workspace-inspector workspace-inspector--rail"> | |||||
| <Tooltip title="Open risk panel" placement="left"> | |||||
| <Button | |||||
| type="text" | |||||
| icon={<MenuUnfoldOutlined />} | |||||
| aria-label="Open risk panel" | |||||
| onClick={onToggle} | |||||
| /> | |||||
| </Tooltip> | |||||
| </Sider> | |||||
| ) | |||||
| } | |||||
| return ( | |||||
| <Sider width={336} className="workspace-inspector" theme="light"> | |||||
| <div className="workspace-inspector__header"> | |||||
| <div> | |||||
| <Text className="workspace-kicker">Risk queue</Text> | |||||
| <Title level={2}>Cutoff Watch</Title> | |||||
| </div> | |||||
| {canCollapse ? ( | |||||
| <Tooltip title="Collapse risk panel" placement="left"> | |||||
| <Button | |||||
| type="text" | |||||
| icon={<MenuFoldOutlined />} | |||||
| aria-label="Collapse risk panel" | |||||
| onClick={onToggle} | |||||
| /> | |||||
| </Tooltip> | |||||
| ) : null} | |||||
| </div> | |||||
| <Space direction="vertical" size={8} className="workspace-risk-list"> | |||||
| {riskItems.map((item) => ( | |||||
| <article className="workspace-risk-item" key={item.id}> | |||||
| <div> | |||||
| <Text className="workspace-risk-item__id">{item.id}</Text> | |||||
| <Text strong>{item.title}</Text> | |||||
| </div> | |||||
| <StatusIndicator status={item.status} /> | |||||
| <Text type="secondary">{item.owner}</Text> | |||||
| </article> | |||||
| ))} | |||||
| </Space> | |||||
| </Sider> | |||||
| ) | |||||
| } | |||||
| export function WorkspaceShell() { | |||||
| const width = useViewportWidth() | |||||
| const editingAvailable = isEditingAvailable(width) | |||||
| const canCollapseRightPanel = isRightPanelCollapsible(width) | |||||
| const [rightPanelCollapseRequested, setRightPanelCollapseRequested] = | |||||
| useState(false) | |||||
| const rightPanelCollapsed = | |||||
| canCollapseRightPanel && rightPanelCollapseRequested | |||||
| const { token } = theme.useToken() | |||||
| const columns: TableProps<CycleRow>['columns'] = [ | |||||
| { | |||||
| title: 'Record', | |||||
| dataIndex: 'key', | |||||
| key: 'key', | |||||
| render: (value: string) => <Text code>{value}</Text>, | |||||
| }, | |||||
| { | |||||
| title: 'Municipality', | |||||
| dataIndex: 'municipality', | |||||
| key: 'municipality', | |||||
| }, | |||||
| { | |||||
| title: 'Cycle', | |||||
| dataIndex: 'cycle', | |||||
| key: 'cycle', | |||||
| }, | |||||
| { | |||||
| title: 'Service', | |||||
| dataIndex: 'service', | |||||
| key: 'service', | |||||
| }, | |||||
| { | |||||
| title: 'Status', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| render: (status: WorkspaceStatus) => <StatusIndicator status={status} />, | |||||
| }, | |||||
| { | |||||
| title: 'Due', | |||||
| dataIndex: 'dueDate', | |||||
| key: 'dueDate', | |||||
| }, | |||||
| { | |||||
| title: 'Owner', | |||||
| dataIndex: 'owner', | |||||
| key: 'owner', | |||||
| }, | |||||
| ] | |||||
| return ( | |||||
| <Layout | |||||
| className="workspace-shell" | |||||
| style={ | |||||
| { | |||||
| '--workspace-secondary': semanticStatusColors.secondary, | |||||
| '--workspace-focus': workspaceThemeTokens.colorInfo, | |||||
| '--workspace-border': workspaceThemeTokens.colorBorder, | |||||
| '--workspace-surface': '#FFFFFF', | |||||
| '--workspace-text-secondary': workspaceThemeTokens.colorTextSecondary, | |||||
| } as CSSProperties | |||||
| } | |||||
| > | |||||
| <WorkspaceNavigation /> | |||||
| <Layout className="workspace-main"> | |||||
| <Header className="workspace-header"> | |||||
| <Space align="center" size={12}> | |||||
| <Badge color={workspaceThemeTokens.colorPrimary} text="Primary workspace" /> | |||||
| <Text type="secondary">Authenticated operations shell</Text> | |||||
| </Space> | |||||
| <Space> | |||||
| <Button disabled={!editingAvailable}>Save View</Button> | |||||
| <Tooltip | |||||
| title={ | |||||
| editingAvailable | |||||
| ? 'Commit selected operational updates' | |||||
| : 'Use a 1280px or wider desktop viewport for editing' | |||||
| } | |||||
| > | |||||
| <Button type="primary" disabled={!editingAvailable}> | |||||
| Commit Update | |||||
| </Button> | |||||
| </Tooltip> | |||||
| </Space> | |||||
| </Header> | |||||
| <Content className="workspace-content"> | |||||
| {!editingAvailable ? ( | |||||
| <Alert | |||||
| className="workspace-support-notice" | |||||
| type="info" | |||||
| showIcon | |||||
| message="Reduced read mode" | |||||
| description="This viewport is below the 1280px operational minimum. Review is available, but editing and commit actions are disabled until the workspace is opened on a supported desktop width." | |||||
| /> | |||||
| ) : null} | |||||
| <section | |||||
| className="workspace-board" | |||||
| aria-label="Election cycle operations workspace" | |||||
| > | |||||
| <div className="workspace-board__header"> | |||||
| <div> | |||||
| <Text className="workspace-kicker">Election cycle setup</Text> | |||||
| <Title level={2}>Municipality work queue</Title> | |||||
| </div> | |||||
| <Space size={8} wrap> | |||||
| <StatusIndicator status="onTrack" /> | |||||
| <StatusIndicator status="atRisk" /> | |||||
| <StatusIndicator status="blocked" /> | |||||
| </Space> | |||||
| </div> | |||||
| <Table | |||||
| className="workspace-table" | |||||
| columns={columns} | |||||
| dataSource={cycleRows} | |||||
| pagination={false} | |||||
| size="small" | |||||
| scroll={{ x: 960 }} | |||||
| rowClassName={(row) => | |||||
| row.status === 'blocked' ? 'workspace-row--blocked' : '' | |||||
| } | |||||
| /> | |||||
| <div className="workspace-board__footer"> | |||||
| <Text type="secondary"> | |||||
| Last refreshed 12:48 PM. Legacy source context remains read-only; | |||||
| new workflow updates route through extension records. | |||||
| </Text> | |||||
| <Button | |||||
| style={{ borderColor: token.colorBorder }} | |||||
| disabled={!editingAvailable} | |||||
| > | |||||
| Open Inspector | |||||
| </Button> | |||||
| </div> | |||||
| </section> | |||||
| </Content> | |||||
| </Layout> | |||||
| <RiskPanel | |||||
| collapsed={rightPanelCollapsed} | |||||
| canCollapse={canCollapseRightPanel} | |||||
| onToggle={() => setRightPanelCollapseRequested((value) => !value)} | |||||
| /> | |||||
| </Layout> | |||||
| ) | |||||
| } | |||||
Powered by TurnKey Linux.