diff --git a/components/ideas/idea-row-actions.tsx b/components/ideas/idea-row-actions.tsx index ae1e2ff..916f480 100644 --- a/components/ideas/idea-row-actions.tsx +++ b/components/ideas/idea-row-actions.tsx @@ -1,16 +1,46 @@ 'use client' -// IdeaRowActions — placeholder shell voor M12 T-507. De volledige -// disabled-rules + Grill/Make-Plan/Materialiseer-knoppen komen in T-508. +// IdeaRowActions — Grill Me / Make Plan / Materialiseer / Archive / Open. +// Disabled-rules per M12 T-508: // -// Voor nu: alleen een "Open" link en een Archive-knop, zodat de lijst -// compileert en navigatie + archief al werken. +// Grill Me: niet in GRILLING|PLANNING; vereist product-met-repo + +// connectedWorkers > 0 +// Make Plan: alleen in GRILLED|PLAN_FAILED|PLAN_READY (re-plan); idem +// voorwaarden +// Materialiseer: alleen in PLAN_READY (geen worker nodig — synchrone parser) +// PLANNED: alle drie disabled, "Bekijk PBI" link +// *_FAILED: "Probeer opnieuw" knop (= start-job opnieuw) +// +// Demo-tooltip om elke muteer-knop. connectedWorkers wordt gelezen uit +// useSoloStore (M12 grill-keuze 16 — geen lift voor v1). +import { useTransition } from 'react' import { useRouter } from 'next/navigation' -import { Archive, ArrowRight } from 'lucide-react' +import { + Archive, + ArrowRight, + ExternalLink, + Flame, + Layers, + RotateCw, + Sparkles, +} from 'lucide-react' +import { toast } from 'sonner' import { Button } from '@/components/ui/button' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' import { DemoTooltip } from '@/components/shared/demo-tooltip' +import { useSoloStore } from '@/stores/solo-store' +import { + startGrillJobAction, + startMakePlanJobAction, + materializeIdeaPlanAction, +} from '@/actions/ideas' import type { IdeaDto } from '@/lib/idea-dto' interface IdeaRowActionsProps { @@ -21,23 +51,175 @@ interface IdeaRowActionsProps { export function IdeaRowActions({ idea, isDemo, onArchive }: IdeaRowActionsProps) { const router = useRouter() + const connectedWorkers = useSoloStore((s) => s.connectedWorkers) + const [pending, startTransition] = useTransition() + + const hasProductWithRepo = idea.product != null && idea.product.repo_url !== null + const workerOk = connectedWorkers > 0 + const status = idea.status + + // ---- Grill Me ---- + const grillBlockedReason = (() => { + if (status === 'grilling' || status === 'planning') return 'Job loopt al' + if (status === 'planned') return 'Idee is gepland — open de PBI' + if (!hasProductWithRepo) return 'Idee heeft een product met repo nodig' + if (!workerOk) return 'Geen Claude-worker actief' + return null + })() + const grillEnabled = !grillBlockedReason && !isDemo && !pending + + // ---- Make Plan ---- + const makePlanAllowedStates = ['grilled', 'plan_failed', 'plan_ready'] + const makePlanBlockedReason = (() => { + if (!makePlanAllowedStates.includes(status)) { + if (status === 'draft' || status === 'grill_failed') return 'Eerst grillen' + if (status === 'grilling' || status === 'planning') return 'Job loopt al' + if (status === 'planned') return 'Idee is gepland — open de PBI' + return null + } + if (!hasProductWithRepo) return 'Idee heeft een product met repo nodig' + if (!workerOk) return 'Geen Claude-worker actief' + return null + })() + const makePlanEnabled = !makePlanBlockedReason && !isDemo && !pending + + // ---- Materialiseer ---- + const materializeBlockedReason = (() => { + if (status !== 'plan_ready') return 'Plan is niet klaar' + return null + })() + const materializeEnabled = !materializeBlockedReason && !isDemo && !pending + + // ---- Failed-states tonen "Probeer opnieuw" ---- + const isFailedState = status === 'grill_failed' || status === 'plan_failed' + + function runStart(action: typeof startGrillJobAction | typeof startMakePlanJobAction) { + startTransition(async () => { + const r = await action(idea.id) + if ('error' in r) { + toast.error(r.error) + return + } + toast.success('Job in de wachtrij — een worker pakt hem op.') + router.refresh() + }) + } + + function handleMaterialize() { + if (!confirm('Plan materialiseren? Dit maakt PBI + stories + taken aan.')) return + startTransition(async () => { + const r = await materializeIdeaPlanAction(idea.id) + if ('error' in r) { + toast.error(r.error) + return + } + toast.success(`Gematerialiseerd als ${r.data?.pbi_code}`) + // Navigeer naar de nieuwe PBI in de product-backlog + if (r.data?.pbi_id && idea.product_id) { + router.push(`/products/${idea.product_id}/backlog#pbi-${r.data.pbi_code}`) + } else { + router.refresh() + } + }) + } + + // PLANNED-state: kortere variant met "Bekijk PBI"-link + if (status === 'planned' && idea.pbi && idea.product_id) { + return ( +
+ + +
+ ) + } return ( -
+
+ {/* Grill Me */} + } + enabled={grillEnabled} + blockedReason={grillBlockedReason} + isDemo={isDemo} + onClick={() => runStart(startGrillJobAction)} + /> + + {/* Make Plan */} + } + enabled={makePlanEnabled} + blockedReason={makePlanBlockedReason} + isDemo={isDemo} + onClick={() => runStart(startMakePlanJobAction)} + /> + + {/* Materialiseer */} + } + enabled={materializeEnabled} + blockedReason={materializeBlockedReason} + isDemo={isDemo} + onClick={handleMaterialize} + variant="default" + /> + + {/* Failed-states: kleine retry-shortcut */} + {isFailedState && ( + + + + )} + + {/* Open detail */} + + {/* Archive */}
) } + +interface ActionButtonProps { + label: string + icon: React.ReactNode + enabled: boolean + blockedReason: string | null + isDemo: boolean + onClick: () => void + variant?: 'default' | 'outline' +} + +function ActionButton({ + label, + icon, + enabled, + blockedReason, + isDemo, + onClick, + variant = 'outline', +}: ActionButtonProps) { + // Bij demo: DemoTooltip toont reden. Bij niet-demo + reden: gewone tooltip. + if (isDemo) { + return ( + + + + ) + } + + if (!enabled && blockedReason) { + return ( + + + }> + + + {blockedReason} + + + ) + } + + return ( + + ) +}