|
- import {
- CheckCircleFilled,
- ClockCircleFilled,
- CloseCircleFilled,
- ExclamationCircleFilled,
- InfoCircleFilled,
- LogoutOutlined,
- MenuFoldOutlined,
- MenuUnfoldOutlined,
- } from '@ant-design/icons'
- import {
- Alert,
- Badge,
- Button,
- Layout,
- Menu,
- Popconfirm,
- 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 { LegacySchemaCheckPanel } from '../admin/LegacySchemaCheckPanel'
- import { MunicipalityProfilePanel } from '../municipalities/MunicipalityProfilePanel'
- import {
- fetchLegacySchemaCheckHistory,
- runLegacySchemaCheck,
- } from '../admin/legacySchemaContracts'
- 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({
- user,
- selectedKey,
- onSelect,
- }: {
- user: AuthenticatedUser
- selectedKey: string
- onSelect: (key: string) => void
- }) {
- const menuItems = [
- user.permissions.canViewMunicipalityProfile
- ? { key: 'municipalities', label: 'Municipalities' }
- : null,
- user.permissions.canCreateElectionCycle
- ? { key: 'cycles', label: 'Election Cycles' }
- : null,
- user.permissions.canViewProductionQueue
- ? { key: 'production', label: 'Production' }
- : null,
- user.permissions.canAccessTransportation
- ? { key: 'transportation', label: 'Transportation' }
- : null,
- user.permissions.canAccessSupport ? { key: 'support', label: 'Support' } : null,
- user.permissions.canAccessAdmin ? { key: 'admin', label: 'Admin' } : null,
- { key: 'reports', label: 'Reports' },
- ].filter((item): item is { key: string; label: string } => item !== null)
-
- 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"
- selectedKeys={[selectedKey]}
- items={menuItems}
- onSelect={({ key }) => onSelect(key)}
- />
- </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,
- onLogout,
- adminFetch,
- }: {
- user: AuthenticatedUser
- onLogout: () => Promise<void>
- adminFetch: typeof fetch
- }) {
- 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 initialView = user.permissions.canViewMunicipalityProfile
- ? 'municipalities'
- : user.permissions.canCreateElectionCycle
- ? 'cycles'
- : user.permissions.canViewProductionQueue
- ? 'production'
- : user.permissions.canAccessTransportation
- ? 'transportation'
- : user.permissions.canAccessSupport
- ? 'support'
- : user.permissions.canAccessAdmin
- ? 'admin'
- : 'reports'
- const [selectedView, setSelectedView] = useState<string>(initialView)
-
- 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 user={user} selectedKey={selectedView} onSelect={setSelectedView} />
- <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 && user.permissions.canCreateElectionCycle
- ? 'Commit selected operational updates'
- : user.permissions.canCreateElectionCycle
- ? 'Use a 1280px or wider desktop viewport for editing'
- : 'Your Keycloak role does not allow election-cycle updates'
- }
- >
- <Button
- type="primary"
- disabled={!editingAvailable || !user.permissions.canCreateElectionCycle}
- >
- Commit Update
- </Button>
- </Tooltip>
- <Popconfirm
- title="Log Out"
- description="Are you sure you want to end your session?"
- onConfirm={onLogout}
- okText="Log Out"
- cancelText="Cancel"
- okButtonProps={{ danger: true }}
- >
- <Button icon={<LogoutOutlined aria-hidden="true" />} aria-label="Log out of Campaign Tracker">
- Log Out
- </Button>
- </Popconfirm>
- </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}
- {selectedView === 'admin' && user.permissions.canAccessAdmin ? (
- <LegacySchemaCheckPanel
- loadHistory={() => fetchLegacySchemaCheckHistory(adminFetch)}
- runCheck={() => runLegacySchemaCheck(adminFetch)}
- />
- ) : selectedView === 'municipalities' && user.permissions.canViewMunicipalityProfile ? (
- <MunicipalityProfilePanel />
- ) : (
- <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 || !user.permissions.canViewMunicipalityProfile}
- >
- Open Inspector
- </Button>
- </div>
- </section>
- )}
- </Content>
- </Layout>
- <RiskPanel
- collapsed={rightPanelCollapsed}
- canCollapse={canCollapseRightPanel}
- onToggle={() => setRightPanelCollapseRequested((value) => !value)}
- />
- </Layout>
- )
- }
|