Sprint: Jobs scherm (#209)
* refactor(jobs): extraheer job-mapper naar lib/jobs-mapper.ts + voeg breadcrumb-velden toe Verplaatst JobWithRelations, JOB_INCLUDE, RawJob, PriceRow, pickDescription, computeCost en mapJob naar lib/jobs-mapper.ts (zonder 'use server'). Voegt buildPriceMap helper toe en breidt de types uit met productCode, storyCode en pbiCode via task->story->pbi en product.code includes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(jobs): voeg GET /api/jobs/[id] route toe + tests * feat(jobs): useJobsRealtime fetch-on-unknown met dedup-Set Wanneer een SSE-event een onbekend job_id bevat, haalt de hook de volledige job op via GET /api/jobs/[id] en upsert die in de store. Een inFlight-Set voorkomt gelijktijdige dubbele fetches voor hetzelfde job_id. Bekende jobs blijven de bestaande partial-upsert gebruiken. Zelfde logica in jobs_initial. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(jobs): JobCard breadcrumb + datum-fallback per kind Voeg productCode/pbiCode/storyCode/startedAt/finishedAt toe aan JobCardProps; bouw breadcrumb per job-kind en toon finishedAt → startedAt → createdAt als datum. JobsColumn geeft de nieuwe velden door. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(jobs): JobCard breadcrumb + datum-fallback tests --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3d52fe4958
commit
2a6386163c
10 changed files with 605 additions and 156 deletions
|
|
@ -24,6 +24,22 @@ export default function useJobsRealtime() {
|
|||
let es: EventSource | null = null
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let active = true
|
||||
const inFlight = new Set<string>()
|
||||
|
||||
async function fetchAndUpsert(jobId: string) {
|
||||
if (inFlight.has(jobId)) return
|
||||
inFlight.add(jobId)
|
||||
try {
|
||||
const res = await fetch(`/api/jobs/${jobId}`)
|
||||
if (!res.ok) return
|
||||
const job = await res.json()
|
||||
if (active) upsertJob(job)
|
||||
} catch {
|
||||
// netwerk-/parse-fout: stil
|
||||
} finally {
|
||||
inFlight.delete(jobId)
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (!active) return
|
||||
|
|
@ -34,20 +50,25 @@ export default function useJobsRealtime() {
|
|||
// De server stuurt JobPayload[] (met `job_id`), niet JobWithRelations[].
|
||||
// Daarom geen initJobs-overwrite — de SSR-fetch heeft de volledige
|
||||
// shape al in de store geplaatst. We reconcileren alleen status/branch
|
||||
// van bekende jobs en pushen onbekende jobs (nieuw aangemaakt tussen
|
||||
// SSR en SSE-connect) als partials.
|
||||
// van bekende jobs en fetchen onbekende jobs volledig via REST.
|
||||
try {
|
||||
const payload = JSON.parse(event.data)
|
||||
if (!Array.isArray(payload)) return
|
||||
const { activeJobs, doneJobs } = useJobsStore.getState()
|
||||
for (const p of payload as JobStatusPayload[]) {
|
||||
if (!p.job_id) continue
|
||||
upsertJob({
|
||||
id: p.job_id,
|
||||
status: p.status as ClaudeJobStatus,
|
||||
branch: p.branch ?? null,
|
||||
error: p.error ?? null,
|
||||
summary: p.summary ?? null,
|
||||
})
|
||||
const known = activeJobs.some(j => j.id === p.job_id) || doneJobs.some(j => j.id === p.job_id)
|
||||
if (!known) {
|
||||
void fetchAndUpsert(p.job_id)
|
||||
} else {
|
||||
upsertJob({
|
||||
id: p.job_id,
|
||||
status: p.status as ClaudeJobStatus,
|
||||
branch: p.branch ?? null,
|
||||
error: p.error ?? null,
|
||||
summary: p.summary ?? null,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// malformed JSON
|
||||
|
|
@ -58,6 +79,12 @@ export default function useJobsRealtime() {
|
|||
try {
|
||||
const payload = JSON.parse(event.data) as JobStatusPayload
|
||||
if (!payload.job_id) return
|
||||
const { activeJobs, doneJobs } = useJobsStore.getState()
|
||||
const known = activeJobs.some(j => j.id === payload.job_id) || doneJobs.some(j => j.id === payload.job_id)
|
||||
if (!known) {
|
||||
void fetchAndUpsert(payload.job_id)
|
||||
return
|
||||
}
|
||||
upsertJob({
|
||||
id: payload.job_id,
|
||||
status: payload.status as ClaudeJobStatus,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue