Scrum4Me/components/jobs/jobs-board.tsx
Madhura68 f79aef077d feat(PBI-61): filter popover + created_at op job-kaart
- Nieuwe JobsColumn met Kind/Status filter-popover per kolom (Actief/Klaar)
- Filterstate persistent in localStorage (whitelist-validatie tegen corrupte waardes)
- Active-filter badges in kolomheader, klikbaar om te wissen
- Aanmaakdatum + tijd rechtsonder op elke JobCard (nl-NL short formaat)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:40:31 +02:00

107 lines
3.3 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 | 'all'; label: string }> = [
{ value: 'all', label: 'Alle' },
{ value: 'queued', label: 'Wacht…' },
{ value: 'claimed', label: 'Geclaimd…' },
{ value: 'running', label: 'Bezig…' },
]
const DONE_STATUS_OPTIONS: Array<{ value: ClaudeJobStatusApi | 'all'; label: string }> = [
{ value: 'all', label: 'Alle' },
{ 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']}
/>
)
}