- app/(app)/products/[id]/docs/settings/page.tsx: server-route met breadcrumb + intro, geeft isOwner door aan toggle-component. - components/product-docs/product-doc-folder-toggle.tsx: client met 8 checkboxes (één per ProductDocFolder enum-lid). Owner kan toggelen → toggleProductDocFolderAction (optimistic update + rollback bij error). ProductMember (niet-owner) krijgt waarschuwing en disabled checkboxes. DemoTooltip-wrapped, demo kan niets togglen. - Voetnoot legt anti-data-loss uit: "Folders uitzetten verwijdert geen bestaande docs". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
'use client'
|
|
|
|
// Folder-toggle UI voor /docs/settings. 8 checkboxes (één per
|
|
// ProductDocFolder enum-lid). Owner kan toggelen; ProductMember ziet
|
|
// read-only checkboxes met waarschuwing.
|
|
//
|
|
// DemoTooltip-wrapped per checkbox; demo-user kan niets togglen.
|
|
|
|
import { useState, useTransition } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { toast } from 'sonner'
|
|
|
|
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
|
import { debugProps } from '@/lib/debug'
|
|
import {
|
|
PRODUCT_DOC_FOLDERS,
|
|
type ProductDocFolderApi,
|
|
} from '@/lib/schemas/product-doc'
|
|
import { productDocFolderToApi } from '@/lib/product-doc-folder'
|
|
import { toggleProductDocFolderAction } from '@/actions/product-docs'
|
|
|
|
import type { ProductDocFolder } from '@prisma/client'
|
|
|
|
const FOLDER_LABELS: Record<ProductDocFolderApi, string> = {
|
|
adr: 'ADRs — Architecture Decision Records',
|
|
architecture: 'Architecture — Topische arch-bestanden',
|
|
patterns: 'Patterns — Herbruikbare code-patronen',
|
|
plans: 'Plans — Feature- en PBI-plannen',
|
|
runbooks: 'Runbooks — Operationele procedures',
|
|
specs: 'Specs — Functionele specificaties',
|
|
manual: 'Manual — Developer manual',
|
|
api: 'API — API-contract details',
|
|
}
|
|
|
|
interface Props {
|
|
productId: string
|
|
/** Folders die nu enabled zijn (DB-enum). */
|
|
initialEnabledFolders: ProductDocFolder[]
|
|
isOwner: boolean
|
|
isDemo: boolean
|
|
}
|
|
|
|
export function ProductDocFolderToggle({
|
|
productId,
|
|
initialEnabledFolders,
|
|
isOwner,
|
|
isDemo,
|
|
}: Props) {
|
|
const router = useRouter()
|
|
const initialApiSet = new Set(
|
|
initialEnabledFolders.map((f) => productDocFolderToApi(f)),
|
|
)
|
|
const [enabledSet, setEnabledSet] = useState<Set<ProductDocFolderApi>>(initialApiSet)
|
|
const [pendingFolder, setPendingFolder] = useState<ProductDocFolderApi | null>(null)
|
|
const [submitting, startSubmit] = useTransition()
|
|
|
|
function toggle(folder: ProductDocFolderApi) {
|
|
const currentlyEnabled = enabledSet.has(folder)
|
|
const targetEnabled = !currentlyEnabled
|
|
|
|
// Optimistic update
|
|
const next = new Set(enabledSet)
|
|
if (targetEnabled) next.add(folder)
|
|
else next.delete(folder)
|
|
setEnabledSet(next)
|
|
setPendingFolder(folder)
|
|
|
|
startSubmit(async () => {
|
|
const r = await toggleProductDocFolderAction({
|
|
product_id: productId,
|
|
folder,
|
|
enabled: targetEnabled,
|
|
})
|
|
if ('error' in r) {
|
|
// Rollback
|
|
setEnabledSet(enabledSet)
|
|
toast.error(r.error)
|
|
} else {
|
|
toast.success(targetEnabled ? `Folder ${folder} aangezet` : `Folder ${folder} uitgezet`)
|
|
router.refresh()
|
|
}
|
|
setPendingFolder(null)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="space-y-3"
|
|
{...debugProps(
|
|
'product-doc-folder-toggle',
|
|
'ProductDocFolderToggle',
|
|
'components/product-docs/product-doc-folder-toggle.tsx',
|
|
)}
|
|
>
|
|
{!isOwner && (
|
|
<p className="text-xs text-muted-foreground italic">
|
|
Alleen de eigenaar van dit product kan folders aan- of uitzetten.
|
|
</p>
|
|
)}
|
|
|
|
<ul className="space-y-2">
|
|
{PRODUCT_DOC_FOLDERS.map((folder) => {
|
|
const checked = enabledSet.has(folder)
|
|
const isPending = pendingFolder === folder
|
|
const disabled = !isOwner || isDemo || submitting
|
|
|
|
return (
|
|
<li key={folder}>
|
|
<DemoTooltip show={isDemo}>
|
|
<label
|
|
className={`flex items-start gap-2 rounded-md border border-border p-2 ${
|
|
disabled ? 'opacity-70' : 'hover:bg-surface-container/60 cursor-pointer'
|
|
}`}
|
|
data-debug-id={`product-doc-folder-toggle__row--${folder}`}
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={checked}
|
|
disabled={disabled}
|
|
onChange={() => toggle(folder)}
|
|
className="mt-0.5 accent-primary"
|
|
/>
|
|
<div className="flex-1 text-xs">
|
|
<p className="font-medium">{FOLDER_LABELS[folder]}</p>
|
|
{isPending && (
|
|
<p className="text-muted-foreground text-[10px]">Bezig…</p>
|
|
)}
|
|
</div>
|
|
</label>
|
|
</DemoTooltip>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
Folders uitzetten verwijdert geen bestaande docs — die blijven leesbaar
|
|
via directe URL en kunnen worden verwijderd voor cleanup.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|