From b36f785566d8dc9a08efb6ae7fc21eccc70581e7 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 30 Apr 2026 17:15:34 +0200 Subject: [PATCH] feat(backlog): add BacklogStore Zustand store with applyChange reducer State: pbis, storiesByPbi, tasksByStory. setInitialData for server hydration; applyChange(entity, op, data) handles I/U/D for SSE events. Co-Authored-By: Claude Sonnet 4.6 --- stores/backlog-store.ts | 139 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 stores/backlog-store.ts diff --git a/stores/backlog-store.ts b/stores/backlog-store.ts new file mode 100644 index 0000000..67daa62 --- /dev/null +++ b/stores/backlog-store.ts @@ -0,0 +1,139 @@ +import { create } from 'zustand' +import type { PbiStatusApi } from '@/lib/task-status' + +export interface BacklogPbi { + id: string + code: string | null + title: string + priority: number + description?: string | null + created_at: Date + status: PbiStatusApi +} + +export interface BacklogStory { + id: string + code: string | null + title: string + description: string | null + acceptance_criteria: string | null + priority: number + status: string + pbi_id: string + created_at: Date +} + +export interface BacklogTask { + id: string + title: string + description: string | null + priority: number + status: string + sort_order: number + story_id: string + created_at: Date +} + +type Entity = 'pbi' | 'story' | 'task' +type Op = 'I' | 'U' | 'D' + +interface InitialData { + pbis: BacklogPbi[] + storiesByPbi: Record + tasksByStory: Record +} + +interface BacklogStore extends InitialData { + setInitialData: (data: InitialData) => void + applyChange: (entity: Entity, op: Op, data: Record) => void +} + +export const useBacklogStore = create((set) => ({ + pbis: [], + storiesByPbi: {}, + tasksByStory: {}, + + setInitialData: (data) => set(data), + + applyChange: (entity, op, data) => + set((state) => { + if (entity === 'pbi') { + const id = data.id as string + if (op === 'D') { + return { pbis: state.pbis.filter((p) => p.id !== id) } + } + if (op === 'U') { + return { + pbis: state.pbis.map((p) => + p.id === id ? { ...p, ...(data as Partial) } : p + ), + } + } + // I + return { pbis: [...state.pbis, data as unknown as BacklogPbi] } + } + + if (entity === 'story') { + const id = data.id as string + if (op === 'D') { + const storiesByPbi = { ...state.storiesByPbi } + for (const pbiId of Object.keys(storiesByPbi)) { + storiesByPbi[pbiId] = storiesByPbi[pbiId].filter((s) => s.id !== id) + } + return { storiesByPbi } + } + if (op === 'U') { + const storiesByPbi = { ...state.storiesByPbi } + for (const pbiId of Object.keys(storiesByPbi)) { + const idx = storiesByPbi[pbiId].findIndex((s) => s.id === id) + if (idx !== -1) { + storiesByPbi[pbiId] = storiesByPbi[pbiId].map((s) => + s.id === id ? { ...s, ...(data as Partial) } : s + ) + break + } + } + return { storiesByPbi } + } + // I + const pbiId = data.pbi_id as string + return { + storiesByPbi: { + ...state.storiesByPbi, + [pbiId]: [...(state.storiesByPbi[pbiId] ?? []), data as unknown as BacklogStory], + }, + } + } + + // task + const id = data.id as string + if (op === 'D') { + const tasksByStory = { ...state.tasksByStory } + for (const storyId of Object.keys(tasksByStory)) { + tasksByStory[storyId] = tasksByStory[storyId].filter((t) => t.id !== id) + } + return { tasksByStory } + } + if (op === 'U') { + const tasksByStory = { ...state.tasksByStory } + for (const storyId of Object.keys(tasksByStory)) { + const idx = tasksByStory[storyId].findIndex((t) => t.id === id) + if (idx !== -1) { + tasksByStory[storyId] = tasksByStory[storyId].map((t) => + t.id === id ? { ...t, ...(data as Partial) } : t + ) + break + } + } + return { tasksByStory } + } + // I + const storyId = data.story_id as string + return { + tasksByStory: { + ...state.tasksByStory, + [storyId]: [...(state.tasksByStory[storyId] ?? []), data as unknown as BacklogTask], + }, + } + }), +}))