From 754d0336693cc91003121c6af1518217adb93d70 Mon Sep 17 00:00:00 2001 From: janpeter visser Date: Mon, 27 Apr 2026 19:18:00 +0200 Subject: [PATCH] feat(ST-905): add Activeer button per product row in dashboard and product header --- app/(app)/dashboard/page.tsx | 14 +++++--- app/(app)/products/[id]/page.tsx | 11 ++++-- components/dashboard/product-list.tsx | 26 +++++++++++++- components/shared/activate-product-button.tsx | 35 +++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 components/shared/activate-product-button.tsx diff --git a/app/(app)/dashboard/page.tsx b/app/(app)/dashboard/page.tsx index d8ec087..9497e54 100644 --- a/app/(app)/dashboard/page.tsx +++ b/app/(app)/dashboard/page.tsx @@ -16,10 +16,15 @@ export default async function DashboardPage({ searchParams }: Props) { const { archived } = await searchParams const showArchived = archived === '1' - const products = await prisma.product.findMany({ - where: { archived: showArchived, ...productAccessFilter(session.userId) }, - orderBy: { created_at: 'desc' }, - }) + const [products, user] = await Promise.all([ + prisma.product.findMany({ + where: { archived: showArchived, ...productAccessFilter(session.userId) }, + orderBy: { created_at: 'desc' }, + }), + session.userId + ? prisma.user.findUnique({ where: { id: session.userId }, select: { active_product_id: true } }) + : null, + ]) return (
@@ -47,6 +52,7 @@ export default async function DashboardPage({ searchParams }: Props) { products={products} isDemo={session.isDemo ?? false} showArchived={showArchived} + activeProductId={user?.active_product_id ?? null} />
) diff --git a/app/(app)/products/[id]/page.tsx b/app/(app)/products/[id]/page.tsx index 77cfe1d..c0bc24e 100644 --- a/app/(app)/products/[id]/page.tsx +++ b/app/(app)/products/[id]/page.tsx @@ -7,6 +7,7 @@ import { PbiList } from '@/components/backlog/pbi-list' import { StoryPanel } from '@/components/backlog/story-panel' import type { Story } from '@/components/backlog/story-panel' import { StartSprintButton } from '@/components/sprint/start-sprint-button' +import { ActivateProductButton } from '@/components/shared/activate-product-button' import Link from 'next/link' interface Props { @@ -21,9 +22,10 @@ export default async function ProductBacklogPage({ params }: Props) { const product = await getAccessibleProduct(id, session.userId) if (!product) notFound() - const activeSprint = await prisma.sprint.findFirst({ - where: { product_id: id, status: 'ACTIVE' }, - }) + const [activeSprint, user] = await Promise.all([ + prisma.sprint.findFirst({ where: { product_id: id, status: 'ACTIVE' } }), + prisma.user.findUnique({ where: { id: session.userId! }, select: { active_product_id: true } }), + ]) const pbis = await prisma.pbi.findMany({ where: { product_id: id }, @@ -66,6 +68,9 @@ export default async function ProductBacklogPage({ params }: Props) { )}
+ {user?.active_product_id !== id && ( + + )} {activeSprint ? ( Sprint actief → diff --git a/components/dashboard/product-list.tsx b/components/dashboard/product-list.tsx index 4b41d54..fa1280b 100644 --- a/components/dashboard/product-list.tsx +++ b/components/dashboard/product-list.tsx @@ -5,8 +5,10 @@ import { useRouter } from 'next/navigation' import { useTransition } from 'react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' import { CodeBadge } from '@/components/shared/code-badge' import { restoreProductAction } from '@/actions/products' +import { setActiveProductAction } from '@/actions/active-product' interface Product { id: string @@ -20,9 +22,10 @@ interface ProductListProps { products: Product[] isDemo: boolean showArchived?: boolean + activeProductId: string | null } -export function ProductList({ products, isDemo, showArchived = false }: ProductListProps) { +export function ProductList({ products, isDemo, showArchived = false, activeProductId }: ProductListProps) { const router = useRouter() const [, startTransition] = useTransition() @@ -34,6 +37,15 @@ export function ProductList({ products, isDemo, showArchived = false }: ProductL }) } + function handleActivate(id: string) { + if (isDemo) { toast.error('Niet beschikbaar in demo-modus'); return } + startTransition(async () => { + const result = await setActiveProductAction(id) + if (result?.error) toast.error(typeof result.error === 'string' ? result.error : 'Activeren mislukt') + else router.push(`/products/${id}`) + }) + } + if (products.length === 0) { return (
@@ -87,6 +99,18 @@ export function ProductList({ products, isDemo, showArchived = false }: ProductL Repo )} + {!showArchived && ( + product.id === activeProductId + ? Actief + : ( + + ) + )} {showArchived && !isDemo && ( + ) +}