feat(PBI-98/T-1088): ProductRowActions (Activeer/Docs/Backlog + dropdown Archive)

- components/dashboard/product-row-actions.tsx: inline knoppen
  Activeer (via ActivateProductButton) of "Actief"-badge,
  Docs (BookOpen-icon → /docs), Open backlog (ArrowRight-icon →
  /products/[id]). Dropdown (•••) met Archiveer/Herstel toggle.
- Archive: archiveProductAction (redirect /dashboard) of
  restoreProductAction (success + router.refresh).
- Verwijderen-item komt in T-1089 zodra deleteProductAction bestaat.
- DemoTooltip op dropdown-trigger (writes blokkeren); Docs/Backlog
  blijven klikbaar voor demo.
- Wires ProductRowActions in ProductsTable acties-kolom.
- 1028 tests blijven groen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-16 15:37:12 +02:00
parent ba37c8e8f2
commit 972e415fc9
2 changed files with 146 additions and 4 deletions

View file

@ -0,0 +1,139 @@
'use client'
// Per-rij acties in de Dashboard ProductsTable. Inline: Activeer (of
// "Actief"-badge), Docs, Open backlog. Dropdown: Archive/Restore toggle.
// Verwijderen-item wordt in T-1089 toegevoegd zodra deleteProductAction
// bestaat. Alle write-knoppen DemoTooltip-wrapped + stopPropagation om
// te voorkomen dat een klik de rij-edit-dialog opent.
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useTransition } from 'react'
import { ArrowRight, BookOpen, MoreHorizontal } from 'lucide-react'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { ActivateProductButton } from '@/components/shared/activate-product-button'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
import {
archiveProductAction,
restoreProductAction,
} from '@/actions/products'
import { debugProps } from '@/lib/debug'
interface Props {
productId: string
isActive: boolean
isArchived: boolean
isDemo: boolean
}
export function ProductRowActions({
productId,
isActive,
isArchived,
isDemo,
}: Props) {
const router = useRouter()
const [submitting, startSubmit] = useTransition()
function handleArchiveToggle() {
startSubmit(async () => {
if (isArchived) {
const r = await restoreProductAction(productId)
if (r && 'error' in r && r.error) {
toast.error(r.error)
} else {
toast.success('Product hersteld')
router.refresh()
}
} else {
// archiveProductAction doet redirect('/dashboard') — geen handmatige
// router.refresh nodig; bij fout returnt 'ie { error }.
const r = await archiveProductAction(productId)
if (r && 'error' in r && r.error) {
toast.error(r.error)
} else {
toast.success('Product gearchiveerd')
}
}
})
}
return (
<div
className="inline-flex items-center gap-1"
{...debugProps(
'product-row-actions',
'ProductRowActions',
'components/dashboard/product-row-actions.tsx',
)}
>
{isActive ? (
<Badge className="bg-primary-container text-primary-container-foreground text-[10px] px-1.5 py-0">
Actief
</Badge>
) : (
!isArchived && (
<ActivateProductButton productId={productId} isDemo={isDemo} />
)
)}
<Button
size="icon-sm"
variant="ghost"
aria-label="Docs"
title="Docs"
render={<Link href={`/products/${productId}/docs`} />}
data-debug-id="product-row-actions__docs"
>
<BookOpen className="size-3.5" />
</Button>
<Button
size="icon-sm"
variant="ghost"
aria-label="Open backlog"
title="Open backlog"
render={<Link href={`/products/${productId}`} />}
data-debug-id="product-row-actions__open"
>
<ArrowRight className="size-3.5" />
</Button>
<DropdownMenu>
<DemoTooltip show={isDemo}>
<DropdownMenuTrigger
render={
<Button
size="icon-sm"
variant="ghost"
aria-label="Meer acties"
disabled={isDemo || submitting}
data-debug-id="product-row-actions__more"
>
<MoreHorizontal className="size-3.5" />
</Button>
}
/>
</DemoTooltip>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={handleArchiveToggle}
data-debug-id="product-row-actions__archive-toggle"
>
{isArchived ? 'Herstel' : 'Archiveer'}
</DropdownMenuItem>
{/* TODO T-1089 (C2): Verwijderen-item met DeleteProductConfirm */}
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}

View file

@ -27,6 +27,7 @@ import {
ProductDialog,
type ProductDialogProduct,
} from '@/components/dialogs/product-dialog'
import { ProductRowActions } from '@/components/dashboard/product-row-actions'
import { useUserSettingsStore } from '@/stores/user-settings/store'
import { debugProps } from '@/lib/debug'
import { cn } from '@/lib/utils'
@ -240,10 +241,12 @@ export function ProductsTable({
onClick={(e) => e.stopPropagation()}
data-debug-id="products-table__actions-cell"
>
{/* TODO T-1088 (C1): <ProductRowActions productId={product.id} isActive={product.id === activeProductId} isArchived={product.archived} isDemo={isDemo} /> */}
<span className="text-xs text-muted-foreground italic">
acties (T-1088)
</span>
<ProductRowActions
productId={product.id}
isActive={product.id === activeProductId}
isArchived={product.archived}
isDemo={isDemo}
/>
</TableCell>
</TableRow>
))