- Product aanmaken/bewerken/archiveren/herstellen (ST-101, ST-103) - SplitPane component met versleepbare splitter en localStorage (ST-104) - PanelNavBar herbruikbaar paneelheader component (ST-105) - PbiList met prioriteitsgroepen, inline aanmaken, filter en verwijderen (ST-106-ST-110) - StoryPanel placeholder rechter paneel met selectie via Zustand (ST-109) - App iconen geinstalleerd: favicon, apple-icon, PWA manifest (192/512px) - AppIcon SVG component in navigatiebar - Root layout metadata bijgewerkt naar Nederlands Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { notFound } from 'next/navigation'
|
|
import { cookies } from 'next/headers'
|
|
import { getIronSession } from 'iron-session'
|
|
import { SessionData, sessionOptions } from '@/lib/session'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { SplitPane } from '@/components/split-pane/split-pane'
|
|
import { PbiList } from '@/components/backlog/pbi-list'
|
|
import { StoryPanel } from '@/components/backlog/story-panel'
|
|
|
|
interface Props {
|
|
params: Promise<{ id: string }>
|
|
}
|
|
|
|
export default async function ProductBacklogPage({ params }: Props) {
|
|
const { id } = await params
|
|
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
|
|
|
|
const product = await prisma.product.findFirst({
|
|
where: { id, user_id: session.userId },
|
|
})
|
|
if (!product) notFound()
|
|
|
|
const pbis = await prisma.pbi.findMany({
|
|
where: { product_id: id },
|
|
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, title: true, status: true, pbi_id: true },
|
|
})
|
|
|
|
// Group stories by PBI id
|
|
const storiesByPbi: Record<string, typeof stories> = {}
|
|
for (const story of stories) {
|
|
if (!storiesByPbi[story.pbi_id]) storiesByPbi[story.pbi_id] = []
|
|
storiesByPbi[story.pbi_id].push(story)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
{/* Product header */}
|
|
<div className="px-4 py-3 border-b border-border bg-surface-container-low shrink-0 flex items-center justify-between">
|
|
<div>
|
|
<h1 className="font-medium text-foreground">{product.name}</h1>
|
|
{product.description && (
|
|
<p className="text-xs text-muted-foreground mt-0.5">{product.description}</p>
|
|
)}
|
|
</div>
|
|
<a
|
|
href={`/products/${id}/settings`}
|
|
className="text-xs text-muted-foreground hover:text-foreground"
|
|
>
|
|
Instellingen
|
|
</a>
|
|
</div>
|
|
|
|
{/* Split pane */}
|
|
<div className="flex-1 overflow-hidden">
|
|
<SplitPane
|
|
storageKey={`backlog-${id}`}
|
|
left={
|
|
<PbiList
|
|
productId={id}
|
|
pbis={pbis.map(p => ({ id: p.id, title: p.title, priority: p.priority }))}
|
|
isDemo={session.isDemo ?? false}
|
|
/>
|
|
}
|
|
right={
|
|
<StoryPanel
|
|
storiesByPbi={storiesByPbi}
|
|
isDemo={session.isDemo ?? false}
|
|
/>
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|