feat(ST-905): add Activeer button per product row in dashboard and product header
This commit is contained in:
parent
b7033c40ae
commit
754d033669
4 changed files with 78 additions and 8 deletions
|
|
@ -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 (
|
||||
<div className="p-6 max-w-4xl mx-auto w-full">
|
||||
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{user?.active_product_id !== id && (
|
||||
<ActivateProductButton productId={id} isDemo={isDemo} />
|
||||
)}
|
||||
{activeSprint ? (
|
||||
<Link href={`/products/${id}/sprint`} className="text-xs text-primary hover:underline font-medium">
|
||||
Sprint actief →
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="bg-surface-container-low rounded-xl border border-border p-12 text-center space-y-3">
|
||||
|
|
@ -87,6 +99,18 @@ export function ProductList({ products, isDemo, showArchived = false }: ProductL
|
|||
Repo
|
||||
</a>
|
||||
)}
|
||||
{!showArchived && (
|
||||
product.id === activeProductId
|
||||
? <Badge className="bg-primary-container text-primary-container-foreground text-xs px-2 py-0">Actief</Badge>
|
||||
: (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleActivate(product.id) }}
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
Activeer
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
{showArchived && !isDemo && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleRestore(product.id) }}
|
||||
|
|
|
|||
35
components/shared/activate-product-button.tsx
Normal file
35
components/shared/activate-product-button.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTransition } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { setActiveProductAction } from '@/actions/active-product'
|
||||
|
||||
interface Props {
|
||||
productId: string
|
||||
isDemo: boolean
|
||||
}
|
||||
|
||||
export function ActivateProductButton({ productId, isDemo }: Props) {
|
||||
const router = useRouter()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
function handleActivate() {
|
||||
if (isDemo) { toast.error('Niet beschikbaar in demo-modus'); return }
|
||||
startTransition(async () => {
|
||||
const result = await setActiveProductAction(productId)
|
||||
if (result?.error) toast.error(typeof result.error === 'string' ? result.error : 'Activeren mislukt')
|
||||
else router.push(`/products/${productId}`)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleActivate}
|
||||
disabled={isPending}
|
||||
className="text-xs text-primary hover:underline font-medium disabled:opacity-50"
|
||||
>
|
||||
Activeer
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue