* feat(PBI-58): Vitest-tests voor SoloTaskCard veldmapping en 4-regels layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): server action fetchJobsPageData voor jobs-pagina Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): SSE-route /api/realtime/jobs voor user-scoped job-events Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): JobCard component voor jobs-pagina Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): JobDetailPane component voor jobs-pagina Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): API route GET /api/jobs/[id]/sub-tasks voor sprint task executions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): SprintSubTasksPane component voor jobs-pagina Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): Zustand store useJobsStore voor jobs-pagina Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): useJobsRealtime hook met SSE-verbinding en store-updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): JobsBoard 3-kolom SplitPane client component Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): /jobs server page met JobsBoard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(PBI-59): Jobs nav-link toevoegen aan NavBar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2 KiB
TypeScript
67 lines
2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { cn } from '@/lib/utils'
|
|
import { JOB_STATUS_LABELS, JOB_STATUS_COLORS } from '@/components/shared/job-status'
|
|
import type { ClaudeJobStatusApi } from '@/lib/job-status'
|
|
|
|
type SubTask = {
|
|
id: string
|
|
taskCode: string | null
|
|
taskTitle: string
|
|
status: string
|
|
}
|
|
|
|
interface SprintSubTasksPaneProps {
|
|
jobId: string | null
|
|
isSprintJob: boolean
|
|
}
|
|
|
|
function SubTaskList({ jobId }: { jobId: string }) {
|
|
const [subTasks, setSubTasks] = useState<SubTask[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
const controller = new AbortController()
|
|
|
|
fetch(`/api/jobs/${jobId}/sub-tasks`, { signal: controller.signal })
|
|
.then(res => res.json())
|
|
.then((data: SubTask[]) => {
|
|
setSubTasks(data)
|
|
setLoading(false)
|
|
})
|
|
.catch(() => {
|
|
setLoading(false)
|
|
})
|
|
|
|
return () => controller.abort()
|
|
}, [jobId])
|
|
|
|
if (loading) {
|
|
return <div className="text-xs text-muted-foreground p-3">Laden…</div>
|
|
}
|
|
|
|
if (subTasks.length === 0) return null
|
|
|
|
return (
|
|
<div className="border-b p-2 space-y-1 max-h-44 overflow-y-auto shrink-0">
|
|
{subTasks.map(t => {
|
|
const apiStatus = t.status.toLowerCase() as ClaudeJobStatusApi
|
|
return (
|
|
<div key={t.id} className="flex items-center gap-2 py-1 px-2 rounded hover:bg-surface-container text-sm">
|
|
<span className="text-xs font-mono text-muted-foreground w-16 shrink-0 truncate">{t.taskCode}</span>
|
|
<span className="flex-1 truncate">{t.taskTitle}</span>
|
|
<span className={cn('text-xs px-1.5 py-0.5 rounded-full border', JOB_STATUS_COLORS[apiStatus])}>
|
|
{JOB_STATUS_LABELS[apiStatus] ?? t.status}
|
|
</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function SprintSubTasksPane({ jobId, isSprintJob }: SprintSubTasksPaneProps) {
|
|
if (!isSprintJob || !jobId) return null
|
|
return <SubTaskList key={jobId} jobId={jobId} />
|
|
}
|