'use client' import { useState, useTransition, useEffect, useActionState } from 'react' import { useFormStatus } from 'react-dom' import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter, } from '@dnd-kit/core' import { SortableContext, useSortable, horizontalListSortingStrategy, arrayMove, sortableKeyboardCoordinates, } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet' import { PanelNavBar } from '@/components/shared/panel-nav-bar' import { useSelectionStore } from '@/stores/selection-store' import { usePlannerStore } from '@/stores/planner-store' import { createStoryAction, updateStoryAction, deleteStoryAction, reorderStoriesAction, getStoryLogsAction } from '@/actions/stories' import { StoryLog } from '@/components/shared/story-log' import { cn } from '@/lib/utils' const PRIORITY_LABELS: Record = { 1: 'Kritiek', 2: 'Hoog', 3: 'Gemiddeld', 4: 'Laag' } const PRIORITY_COLORS: Record = { 1: 'bg-priority-critical/15 text-priority-critical border-priority-critical/30', 2: 'bg-priority-high/15 text-priority-high border-priority-high/30', 3: 'bg-priority-medium/15 text-priority-medium border-priority-medium/30', 4: 'bg-priority-low/15 text-priority-low border-priority-low/30', } const STATUS_COLORS: Record = { OPEN: 'bg-status-todo/15 text-status-todo border-status-todo/30', IN_SPRINT: 'bg-status-in-progress/15 text-status-in-progress border-status-in-progress/30', DONE: 'bg-status-done/15 text-status-done border-status-done/30', } const STATUS_LABELS: Record = { OPEN: 'Open', IN_SPRINT: 'In Sprint', DONE: 'Klaar', } export interface Story { id: string title: string description: string | null acceptance_criteria: string | null priority: number status: string pbi_id: string } interface StoryPanelProps { productId: string storiesByPbi: Record isDemo: boolean } // --- Sortable story block --- function SortableStoryBlock({ story, onClick, }: { story: Story onClick: () => void }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: story.id, }) const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.4 : 1, } return (

{story.title}

{PRIORITY_LABELS[story.priority]} {STATUS_LABELS[story.status] ?? story.status}
) } // --- Story detail slide-over --- function StoryDetailSheet({ story, productId: _productId, pbiId: _pbiId, onClose, isDemo, }: { story: Story productId: string pbiId: string onClose: () => void isDemo: boolean }) { const [confirmDelete, setConfirmDelete] = useState(false) const [isDeleting, startDeleteTransition] = useTransition() const [logs, setLogs] = useState> | null>(null) useEffect(() => { getStoryLogsAction(story.id).then(setLogs) }, [story.id]) const [state, formAction] = useActionState( async (_prev: unknown, fd: FormData) => { const result = await updateStoryAction(_prev, fd) if (result?.success) { toast.success('Story opgeslagen'); onClose() } return result }, undefined ) function handleDelete() { startDeleteTransition(async () => { const result = await deleteStoryAction(story.id) if (result && 'error' in result) toast.error(result.error ?? 'Verwijderen mislukt') else toast.success('Story verwijderd') onClose() }) } const fieldError = (field: string) => { const err = state?.error if (!err || typeof err === 'string') return undefined return (err as Record)[field]?.[0] } return ( { if (!open) onClose() }}> {story.title}
{PRIORITY_LABELS[story.priority]} {STATUS_LABELS[story.status]}
{!isDemo ? (
{fieldError('title') &&

{fieldError('title')}

}