feat(T-563): integreer Sync-tab in IdeaDetailLayout + page-loader

- TabKey union uitgebreid met 'sync'.
- Sync-tab alleen zichtbaar als syncData !== null && idea.status === 'planned'
  (M12 keuze 6: na Materialiseer-actie).
- page.tsx roept loadIdeaSyncData alleen aan bij PLANNED + pbi_id, anders
  null doorgeven aan layout.
- showSync-flag bepaalt of de tab in TAB_KEYS array zit en in de UI
  gerenderd wordt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-06 00:21:59 +02:00
parent dbf30a2fcb
commit 678069a3d8
2 changed files with 23 additions and 2 deletions

View file

@ -7,6 +7,7 @@ import { prisma } from '@/lib/prisma'
import { productAccessFilter } from '@/lib/product-access' import { productAccessFilter } from '@/lib/product-access'
import { ideaToDto } from '@/lib/idea-dto' import { ideaToDto } from '@/lib/idea-dto'
import { IdeaDetailLayout } from '@/components/ideas/idea-detail-layout' import { IdeaDetailLayout } from '@/components/ideas/idea-detail-layout'
import { loadIdeaSyncData } from './sync-tab-server'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@ -76,6 +77,14 @@ export default async function IdeaDetailPage({ params, searchParams }: PageProps
select: { id: true, question: true, answer: true, status: true, created_at: true }, select: { id: true, question: true, answer: true, status: true, created_at: true },
}) })
// Sync-tab data — alleen geladen als idea PLANNED is en pbi_id gevuld.
// loadIdeaSyncData past zelf user_id-scope toe en retourneert null als
// het idee geen pbi heeft.
const syncData =
idea.status === 'PLANNED' && idea.pbi_id
? await loadIdeaSyncData(id, session.userId)
: null
return ( return (
<IdeaDetailLayout <IdeaDetailLayout
idea={ideaToDto(idea)} idea={ideaToDto(idea)}
@ -107,6 +116,7 @@ export default async function IdeaDetailPage({ params, searchParams }: PageProps
}))} }))}
isDemo={session.isDemo ?? false} isDemo={session.isDemo ?? false}
initialTab={tab ?? 'idee'} initialTab={tab ?? 'idee'}
syncData={syncData}
/> />
) )
} }

View file

@ -25,7 +25,9 @@ import { IdeaRowActions } from '@/components/ideas/idea-row-actions'
import { IdeaMdEditor } from '@/components/ideas/idea-md-editor' import { IdeaMdEditor } from '@/components/ideas/idea-md-editor'
import { IdeaPbiLinkCard } from '@/components/ideas/idea-pbi-link-card' import { IdeaPbiLinkCard } from '@/components/ideas/idea-pbi-link-card'
import { IdeaTimeline } from '@/components/ideas/idea-timeline' import { IdeaTimeline } from '@/components/ideas/idea-timeline'
import { IdeaSyncTab } from '@/components/ideas/idea-sync-tab'
import { DownloadMdButton } from '@/components/ideas/download-md-button' import { DownloadMdButton } from '@/components/ideas/download-md-button'
import type { IdeaSyncData } from '@/app/(app)/ideas/[id]/sync-tab-server'
const API_TO_DB: Record<IdeaStatusApi, Parameters<typeof getIdeaStatusBadge>[0]> = { const API_TO_DB: Record<IdeaStatusApi, Parameters<typeof getIdeaStatusBadge>[0]> = {
draft: 'DRAFT', draft: 'DRAFT',
@ -38,7 +40,7 @@ const API_TO_DB: Record<IdeaStatusApi, Parameters<typeof getIdeaStatusBadge>[0]>
planned: 'PLANNED', planned: 'PLANNED',
} }
type TabKey = 'idee' | 'grill' | 'plan' | 'timeline' type TabKey = 'idee' | 'grill' | 'plan' | 'timeline' | 'sync'
interface IdeaLog { interface IdeaLog {
id: string id: string
@ -82,6 +84,7 @@ interface Props {
userQuestions: IdeaUserQuestionDto[] userQuestions: IdeaUserQuestionDto[]
isDemo: boolean isDemo: boolean
initialTab: string initialTab: string
syncData: IdeaSyncData | null
} }
export function IdeaDetailLayout({ export function IdeaDetailLayout({
@ -94,12 +97,16 @@ export function IdeaDetailLayout({
userQuestions, userQuestions,
isDemo, isDemo,
initialTab, initialTab,
syncData,
}: Props) { }: Props) {
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const [pending, startTransition] = useTransition() const [pending, startTransition] = useTransition()
const TAB_KEYS: TabKey[] = ['idee', 'grill', 'plan', 'timeline'] const showSync = syncData !== null && idea.status === 'planned'
const TAB_KEYS: TabKey[] = showSync
? ['idee', 'grill', 'plan', 'timeline', 'sync']
: ['idee', 'grill', 'plan', 'timeline']
const tab = (TAB_KEYS.includes(initialTab as TabKey) ? initialTab : 'idee') as TabKey const tab = (TAB_KEYS.includes(initialTab as TabKey) ? initialTab : 'idee') as TabKey
function setTab(key: TabKey) { function setTab(key: TabKey) {
@ -170,6 +177,9 @@ export function IdeaDetailLayout({
{ key: 'grill' as TabKey, label: 'Grill', disabled: !grill_md, hasContent: !!grill_md }, { key: 'grill' as TabKey, label: 'Grill', disabled: !grill_md, hasContent: !!grill_md },
{ key: 'plan' as TabKey, label: 'Plan', disabled: !plan_md, hasContent: !!plan_md }, { key: 'plan' as TabKey, label: 'Plan', disabled: !plan_md, hasContent: !!plan_md },
{ key: 'timeline' as TabKey, label: 'Timeline', disabled: false, hasContent: true }, { key: 'timeline' as TabKey, label: 'Timeline', disabled: false, hasContent: true },
...(showSync
? [{ key: 'sync' as TabKey, label: 'Sync', disabled: false, hasContent: true }]
: []),
] as const).map((t) => ( ] as const).map((t) => (
<button <button
key={t.key} key={t.key}
@ -227,6 +237,7 @@ export function IdeaDetailLayout({
/> />
)} )}
{tab === 'timeline' && <IdeaTimeline logs={logs} questions={questions} />} {tab === 'timeline' && <IdeaTimeline logs={logs} questions={questions} />}
{tab === 'sync' && showSync && syncData && <IdeaSyncTab data={syncData} />}
</div> </div>
) )
} }