// CIL[BEGIN chunk_id="WorkBoardPage_module_header_1784761401" type="module_header" identity="module_header" parent="none"] // CIL[DOC-MACRO purpose="Render an engineering kanban board from workbench_work_item records"] // CIL[DOC-RESPONSIBILITY responsibility="Fetch work items, split into lanes, and link to detail/new pages"] // CIL[DOC-CONTEXT module="workbench.pages.work.WorkBoardPage" layer="frontend-page" related_files="[\"custom/apps/workbench/pages/work/WorkItemDetailPage.tsx\",\"custom/apps/workbench/components/work/WorkItemCard.tsx\",\"custom/apps/workbench/components/work/TeamSwitcher.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX kanban board" errors="fetch errors rendered inline" side_effects="calls /api/admin-data, navigates to detail/new"] // CIL[DOC-MICRO logic_summary="Loads work items, filters by team, separates incident follow-ups, and renders lanes with WorkItemCards" notable_decisions="[]"] // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"] // CIL[DOC-STABILITY stability="experimental"] // CIL[DOC-AUDIENCE audience="developer"] import { useEffect, useMemo, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useAppPath } from '@core/hooks/useAppPath' import { useAuth } from '@core/contexts/AuthContext' import { apiFetch } from '@core/lib/api' import { Button } from '@core/components/ui/button' import { Badge } from '@core/components/ui/badge' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { ScrollArea } from '@core/components/ui/scroll-area' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@core/components/ui/select' import { Plus } from 'lucide-react' import { WorkItemCard } from '../../components/work/WorkItemCard' import { useWorkbenchRole } from '../../hooks/useWorkbenchRole' // CIL[END chunk_id="WorkBoardPage_module_header_1784761401"] // CIL[BEGIN chunk_id="WorkBoardPage_workboardpage_1784761401" type="function" identity="WorkBoardPage" parent="none"] // CIL[DOC-MACRO description="Implementation of WorkBoardPage" macro="Renders and manages WorkBoardPage UI state"] // CIL[DOC-CONTRACT input_spec="See module header or type signature" output_spec="JSX or value" side_effects="none"] // CIL[DOC-STABILITY stability="experimental"] const NORMAL_LANES = ['triage', 'assigned', 'in_progress', 'in_review', 'resolved', 'closed'] const LANE_LABELS: Record = { triage: 'Triage', assigned: 'Assigned', in_progress: 'In Progress', in_review: 'In Review', resolved: 'Resolved', closed: 'Closed', incident: 'Incidents', } const INCIDENT_STATUS_ORDER = ['declared', 'investigating', 'mitigating', 'monitoring', 'resolved', 'postmortem_pending'] const PRIORITY_ORDER = ['p0', 'p1', 'p2', 'p3'] const SEVERITY_ORDER = ['sev1', 'sev2', 'sev3', 'sev4'] function priorityIndex(item: any): number { const idx = PRIORITY_ORDER.indexOf(item.data?.priority || '') return idx === -1 ? 99 : idx } function severityIndex(item: any): number { const idx = SEVERITY_ORDER.indexOf(item.data?.severity || '') return idx === -1 ? 99 : idx } function prioritySort(a: any, b: any) { const ai = priorityIndex(a) const bi = priorityIndex(b) if (ai !== bi) return ai - bi const as = severityIndex(a) const bs = severityIndex(b) if (as !== bs) return as - bs return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime() } function incidentSort(a: any, b: any) { const ai = INCIDENT_STATUS_ORDER.indexOf(a.data?.incident_status || '') const bi = INCIDENT_STATUS_ORDER.indexOf(b.data?.incident_status || '') if (ai !== bi) return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi) const as = severityIndex(a) const bs = severityIndex(b) if (as !== bs) return as - bs return prioritySort(a, b) } export default function WorkBoardPage() { const navigate = useNavigate() const appPath = useAppPath() const { user } = useAuth() const { role } = useWorkbenchRole() const selectedTeam = role const [items, setItems] = useState([]) const [sprints, setSprints] = useState([]) const [loading, setLoading] = useState(true) const [selectedSprint, setSelectedSprint] = useState('*') const hasSetDefaultSprint = useRef(false) const load = () => { setLoading(true) Promise.all([ apiFetch('/api/admin-data?entity=items&type_slug=workbench_work_item&limit=200'), apiFetch('/api/admin-data?entity=items&type_slug=workbench_sprint&limit=500'), ]) .then(([itemsRes, sprintsRes]) => Promise.all([itemsRes.json(), sprintsRes.json()])) .then(([itemsJson, sprintsJson]) => { setItems(Array.isArray(itemsJson?.data) ? itemsJson.data : []) setSprints(Array.isArray(sprintsJson?.data) ? sprintsJson.data : []) }) .catch(() => { setItems([]) setSprints([]) }) .finally(() => setLoading(false)) } useEffect(() => { load() }, []) const teamSprints = useMemo(() => { return sprints .filter(s => selectedTeam === '*' || s.data?.team === selectedTeam || s.data?.team === '*') .sort((a, b) => { const aActive = a.data?.phase === 'active' || a.data?.phase === 'current' const bActive = b.data?.phase === 'active' || b.data?.phase === 'current' if (aActive !== bActive) return aActive ? -1 : 1 return (a.data?.order ?? Infinity) - (b.data?.order ?? Infinity) }) }, [sprints, selectedTeam]) const defaultSprintId = useMemo(() => { const active = teamSprints.find(s => s.data?.phase === 'active' || s.data?.phase === 'current') return active?.id || '*' }, [teamSprints]) useEffect(() => { const valid = selectedSprint === '*' || teamSprints.some(s => s.id === selectedSprint) if (!valid) { setSelectedSprint(defaultSprintId) } else if (!hasSetDefaultSprint.current && teamSprints.length > 0) { hasSetDefaultSprint.current = true setSelectedSprint(defaultSprintId) } }, [teamSprints, defaultSprintId]) const filteredItems = useMemo(() => { let result = items.filter(item => item.data?.work_type !== 'idea') if (selectedTeam !== '*') { result = result.filter(item => { const team = item.data?.team || 'unassigned' return team === selectedTeam || team === 'unassigned' || team === '*' }) } if (selectedSprint !== '*') { result = result.filter(item => { const team = item.data?.team || 'unassigned' return item.data?.sprint_id === selectedSprint || team === 'unassigned' }) } return result }, [items, selectedTeam, selectedSprint]) const incidentItems = useMemo(() => { return filteredItems.filter(item => item.data?.work_type === 'incident_followup' && item.data?.incident_status !== 'postmortem_complete' ).sort(incidentSort) }, [filteredItems]) const normalItems = useMemo(() => { return filteredItems.filter(item => !(item.data?.work_type === 'incident_followup' && item.data?.incident_status !== 'postmortem_complete') ) }, [filteredItems]) const lanes = useMemo(() => { const map: Record = {} for (const lane of NORMAL_LANES) map[lane] = [] for (const item of normalItems) { const status = item.status || 'triage' if (status === 'triage' && item.data?.team && item.data.team !== 'unassigned') { continue } if (NORMAL_LANES.includes(status)) { map[status].push(item) } } for (const lane of NORMAL_LANES) map[lane].sort(prioritySort) return map }, [normalItems]) const visibleLanes = [ ...(incidentItems.length > 0 ? ['incident'] : []), ...NORMAL_LANES.filter(lane => lane !== 'triage' || lanes[lane].length > 0), ] return (

{role ? role[0].toUpperCase() + role.slice(1) : 'Workbench'} Kanban Board

{loading ? (
Loading board…
) : (
{visibleLanes.map(lane => (
{LANE_LABELS[lane]} {lane === 'incident' ? incidentItems.length : lanes[lane].length}
{(lane === 'incident' ? incidentItems : lanes[lane]).map(item => ( navigate(appPath(`/work/${role}/items/${item.id}`))} /> ))}
))}
)}
) } // CIL[END chunk_id="WorkBoardPage_workboardpage_1784761401"]