* 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>
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import { create } from 'zustand'
|
|
import { immer } from 'zustand/middleware/immer'
|
|
import type { JobWithRelations } from '@/actions/jobs-page'
|
|
|
|
type JobsState = {
|
|
activeJobs: JobWithRelations[]
|
|
doneJobs: JobWithRelations[]
|
|
selectedJobId: string | null
|
|
}
|
|
|
|
type JobsActions = {
|
|
initJobs(active: JobWithRelations[], done: JobWithRelations[]): void
|
|
setSelectedJobId(id: string | null): void
|
|
upsertJob(job: Partial<JobWithRelations> & { id: string; status: string }): void
|
|
}
|
|
|
|
export const useJobsStore = create<JobsState & JobsActions>()(
|
|
immer((set) => ({
|
|
activeJobs: [],
|
|
doneJobs: [],
|
|
selectedJobId: null,
|
|
|
|
initJobs(active, done) {
|
|
set((state) => {
|
|
state.activeJobs = active
|
|
state.doneJobs = done
|
|
})
|
|
},
|
|
|
|
setSelectedJobId(id) {
|
|
set((state) => {
|
|
state.selectedJobId = id
|
|
})
|
|
},
|
|
|
|
upsertJob(job) {
|
|
set((state) => {
|
|
const isDone = job.status.toUpperCase() === 'DONE'
|
|
|
|
if (isDone) {
|
|
state.activeJobs = state.activeJobs.filter(j => j.id !== job.id)
|
|
if (!state.doneJobs.find(j => j.id === job.id)) {
|
|
state.doneJobs.unshift(job as JobWithRelations)
|
|
if (state.doneJobs.length > 100) {
|
|
state.doneJobs = state.doneJobs.slice(0, 100)
|
|
}
|
|
}
|
|
} else {
|
|
const idx = state.activeJobs.findIndex(j => j.id === job.id)
|
|
if (idx !== -1) {
|
|
Object.assign(state.activeJobs[idx], job)
|
|
} else {
|
|
state.activeJobs.push(job as JobWithRelations)
|
|
}
|
|
}
|
|
})
|
|
},
|
|
}))
|
|
)
|