Scrum4Me/components/jobs/jobs-board.tsx
Janpeter Visser 10bf25dadd
feat(PBI-61): multi-select op kind- en status-filter (#159)
- Filter-pills zijn nu toggle-knoppen; meerdere waardes per dimensie selecteerbaar
- "Alle"-pill wist de selectie binnen die dimensie
- Eén active-badge per geselecteerde waarde, klikbaar om losse selectie te wissen
- localStorage formaat is nu CSV met whitelist-validatie (oude 'all'-waarde valt vanzelf weg → leeg = geen filter)
- Filtercount in trigger toont som van actieve selecties

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:21:09 +02:00

105 lines
3.2 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { SplitPane } from '@/components/split-pane/split-pane'
import JobsColumn from './jobs-column'
import JobDetailPane from './job-detail-pane'
import JobUsagePane from './job-usage-pane'
import SprintSubTasksPane from './sprint-sub-tasks-pane'
import { useJobsStore } from '@/stores/jobs-store'
import useJobsRealtime from '@/hooks/use-jobs-realtime'
import type { ClaudeJobStatusApi } from '@/lib/job-status'
import type { JobWithRelations } from '@/actions/jobs-page'
interface JobsBoardProps {
initialActiveJobs: JobWithRelations[]
initialDoneJobs: JobWithRelations[]
}
type View = 'detail' | 'usage'
const ACTIVE_STATUS_OPTIONS: Array<{ value: ClaudeJobStatusApi; label: string }> = [
{ value: 'queued', label: 'Wacht…' },
{ value: 'claimed', label: 'Geclaimd…' },
{ value: 'running', label: 'Bezig…' },
]
const DONE_STATUS_OPTIONS: Array<{ value: ClaudeJobStatusApi; label: string }> = [
{ value: 'done', label: 'Klaar' },
{ value: 'failed', label: 'Mislukt' },
{ value: 'cancelled', label: 'Geannuleerd' },
{ value: 'skipped', label: 'Overgeslagen' },
]
export default function JobsBoard({ initialActiveJobs, initialDoneJobs }: JobsBoardProps) {
const { activeJobs, doneJobs, selectedJobId, initJobs, setSelectedJobId } = useJobsStore()
const [view, setView] = useState<View>('detail')
useJobsRealtime()
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { initJobs(initialActiveJobs, initialDoneJobs) }, [])
const selectedJob = [...activeJobs, ...doneJobs].find(j => j.id === selectedJobId) ?? null
const leftPane = (
<JobsColumn
title="Actief"
jobs={activeJobs}
selectedJobId={selectedJobId}
onSelect={setSelectedJobId}
storageKeyPrefix="scrum4me:jobs_active"
statusOptions={ACTIVE_STATUS_OPTIONS}
emptyText="Geen actieve jobs"
/>
)
const middlePane = (
<div className="flex flex-col h-full overflow-hidden">
<SprintSubTasksPane
jobId={selectedJobId}
isSprintJob={selectedJob?.kind === 'SPRINT_IMPLEMENTATION'}
/>
<div className="flex gap-1 px-3 pt-3 pb-2 border-b shrink-0">
<Button
size="sm"
variant={view === 'detail' ? 'default' : 'outline'}
onClick={() => setView('detail')}
>
Detail
</Button>
<Button
size="sm"
variant={view === 'usage' ? 'default' : 'outline'}
onClick={() => setView('usage')}
>
Usage
</Button>
</div>
<div className="flex-1 overflow-y-auto">
{view === 'detail' ? <JobDetailPane job={selectedJob} /> : <JobUsagePane job={selectedJob} />}
</div>
</div>
)
const rightPane = (
<JobsColumn
title="Klaar"
jobs={doneJobs}
selectedJobId={selectedJobId}
onSelect={setSelectedJobId}
storageKeyPrefix="scrum4me:jobs_done"
statusOptions={DONE_STATUS_OPTIONS}
emptyText="Nog geen afgeronde jobs"
/>
)
return (
<SplitPane
panes={[leftPane, middlePane, rightPane]}
defaultSplit={[25, 50, 25]}
cookieKey="jobs"
tabLabels={['Actief', 'Details', 'Klaar']}
/>
)
}