|
- 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 type { AuthenticatedUser } from '../auth/authContracts'
- 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({ user }: { user: AuthenticatedUser }) {
- 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">{user.userName}</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>
- )
- }
|