From 75bbb1ad7386b12ae9d6f2b53f9c90724bf7b436 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 30 Apr 2026 17:15:41 +0200 Subject: [PATCH] feat(backlog): server-fetch tasks + hydrate BacklogStore on page load Page now fetches tasks parallel to stories and groups by story_id. BacklogHydrationWrapper calls setInitialData on mount so the store is ready for downstream SSE consumers. Co-Authored-By: Claude Sonnet 4.6 --- app/(app)/products/[id]/page.tsx | 100 ++++++++++++------ .../backlog/backlog-hydration-wrapper.tsx | 26 +++++ 2 files changed, 92 insertions(+), 34 deletions(-) create mode 100644 components/backlog/backlog-hydration-wrapper.tsx diff --git a/app/(app)/products/[id]/page.tsx b/app/(app)/products/[id]/page.tsx index 89ec6e4..b5041e8 100644 --- a/app/(app)/products/[id]/page.tsx +++ b/app/(app)/products/[id]/page.tsx @@ -7,6 +7,7 @@ import { SplitPane } from '@/components/split-pane/split-pane' import { PbiList } from '@/components/backlog/pbi-list' import { StoryPanel } from '@/components/backlog/story-panel' import type { Story } from '@/components/backlog/story-panel' +import { BacklogHydrationWrapper } from '@/components/backlog/backlog-hydration-wrapper' import { StartSprintButton } from '@/components/sprint/start-sprint-button' import { ActivateProductButton } from '@/components/shared/activate-product-button' import Link from 'next/link' @@ -33,21 +34,37 @@ export default async function ProductBacklogPage({ params }: Props) { orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], }) - const stories = await prisma.story.findMany({ - where: { product_id: id }, - orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], - select: { - id: true, - code: true, - title: true, - description: true, - acceptance_criteria: true, - priority: true, - status: true, - pbi_id: true, - created_at: true, - }, - }) + const [stories, tasks] = await Promise.all([ + prisma.story.findMany({ + where: { product_id: id }, + orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + select: { + id: true, + code: true, + title: true, + description: true, + acceptance_criteria: true, + priority: true, + status: true, + pbi_id: true, + created_at: true, + }, + }), + prisma.task.findMany({ + where: { story: { pbi: { product_id: id } } }, + select: { + id: true, + title: true, + description: true, + priority: true, + status: true, + sort_order: true, + story_id: true, + created_at: true, + }, + orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + }), + ]) // Group stories by PBI id const storiesByPbi: Record = {} @@ -56,6 +73,13 @@ export default async function ProductBacklogPage({ params }: Props) { storiesByPbi[story.pbi_id].push(story) } + // Group tasks by story id + const tasksByStory: Record = {} + for (const task of tasks) { + if (!tasksByStory[task.story_id]) tasksByStory[task.story_id] = [] + tasksByStory[task.story_id].push(task) + } + const isDemo = session.isDemo ?? false return ( @@ -90,25 +114,33 @@ export default async function ProductBacklogPage({ params }: Props) { {/* Split pane */}
- ({ id: p.id, code: p.code, title: p.title, priority: p.priority, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) }))} - isDemo={isDemo} - />, - , - ]} - /> + ({ id: p.id, code: p.code, title: p.title, priority: p.priority, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) })), + storiesByPbi, + tasksByStory, + }} + > + ({ id: p.id, code: p.code, title: p.title, priority: p.priority, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) }))} + isDemo={isDemo} + />, + , + ]} + /> +
) diff --git a/components/backlog/backlog-hydration-wrapper.tsx b/components/backlog/backlog-hydration-wrapper.tsx new file mode 100644 index 0000000..83cbb62 --- /dev/null +++ b/components/backlog/backlog-hydration-wrapper.tsx @@ -0,0 +1,26 @@ +'use client' + +import { useEffect } from 'react' +import { useBacklogStore, type BacklogPbi, type BacklogStory, type BacklogTask } from '@/stores/backlog-store' + +interface InitialData { + pbis: BacklogPbi[] + storiesByPbi: Record + tasksByStory: Record +} + +interface BacklogHydrationWrapperProps { + initialData: InitialData + children: React.ReactNode +} + +export function BacklogHydrationWrapper({ initialData, children }: BacklogHydrationWrapperProps) { + const setInitialData = useBacklogStore((s) => s.setInitialData) + + useEffect(() => { + setInitialData(initialData) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return <>{children} +}