import { useEffect, useMemo, useState } from 'react' import { StatusBadge } from './StatusBadge.jsx' import { PriorityBadge } from './PriorityBadge.jsx' import { EmptyState } from './EmptyState.jsx' import { Modal } from './Modal.jsx' import { BacklogItemForm } from './BacklogItemForm.jsx' import { ReorderControls } from './ReorderControls.jsx' import { gateTitleById } from './GateSelect.jsx' import { computeDropPatches, computeMovePatches, sortByOrder } from '../utils/reorder.js' import { useMinWidth } from '../hooks/useMinWidth.js' const PLANNING_STATUSES = new Set(['planned', 'active', 'at_risk']) export function BacklogSection({ items, roadmapItems = [], workCycles = [], activeWorkCycle = null, sprintPlanningEnabled = false, canManage, onCreate, onUpdate, onReorder, onConvert, onBulkConvert, onDelete, busy, sectionTitle = 'Product Backlog', sectionLead = 'Eingang vor dem Commit — triagieren, dann in den Sprint planen.', }) { const [modalMode, setModalMode] = useState(null) const [dragItemId, setDragItemId] = useState('') const [dropTargetId, setDropTargetId] = useState('') const [selectedIds, setSelectedIds] = useState(() => new Set()) const [sprintTargetId, setSprintTargetId] = useState('') const isDesktop = useMinWidth(1024) const canReorder = canManage && typeof onReorder === 'function' const planableSprints = useMemo( () => workCycles.filter((cycle) => PLANNING_STATUSES.has(cycle.status)), [workCycles], ) const showSprintPlanning = sprintPlanningEnabled && planableSprints.length > 0 useEffect(() => { if (!showSprintPlanning) { setSprintTargetId('') return } if (sprintTargetId && planableSprints.some((cycle) => cycle.id === sprintTargetId)) { return } const preferred = activeWorkCycle?.id || planableSprints[0]?.id || '' setSprintTargetId(preferred) }, [showSprintPlanning, sprintTargetId, planableSprints, activeWorkCycle?.id]) const selectedSprint = planableSprints.find((cycle) => cycle.id === sprintTargetId) || null const convertLabel = selectedSprint ? `In Sprint „${selectedSprint.title}" planen` : 'In Arbeitspaket umwandeln' const sortedItems = useMemo( () => sortByOrder(items.filter((item) => item.status !== 'converted')), [items], ) function closeModal() { setModalMode(null) } async function handleCreateSubmit(payload) { await onCreate({ title: payload.title, description: payload.description, priority: payload.priority, roadmap_item_id: payload.roadmap_item_id, status: payload.status || 'new', }) closeModal() } async function handleEditSubmit(payload) { if (!modalMode?.item) return const ok = await onUpdate(modalMode.item.id, { title: payload.title, description: payload.description, status: payload.status, priority: payload.priority, roadmap_item_id: payload.roadmap_item_id, clear_roadmap_item: payload.clear_roadmap_item, }) if (ok !== false) closeModal() } async function applyPatches(patches) { if (!patches.length) return await onReorder(patches) } async function handleMove(itemId, direction) { const patches = computeMovePatches(sortedItems, itemId, direction) await applyPatches(patches) } function handleDragStart(event, itemId) { if (!canReorder || !isDesktop) return setDragItemId(itemId) event.dataTransfer.effectAllowed = 'move' event.dataTransfer.setData('text/plain', itemId) } function handleDragEnd() { setDragItemId('') setDropTargetId('') } function handleDragOver(event, itemId) { if (!canReorder || !isDesktop || !dragItemId) return event.preventDefault() event.dataTransfer.dropEffect = 'move' setDropTargetId(itemId) } async function handleDrop(event, targetId) { event.preventDefault() if (!canReorder || !isDesktop || !dragItemId) { handleDragEnd() return } const patches = computeDropPatches(sortedItems, dragItemId, targetId) handleDragEnd() await applyPatches(patches) } const modalTitle = modalMode?.kind === 'create' ? 'Backlog-Item anlegen' : 'Backlog-Item bearbeiten' const acceptedItems = sortedItems.filter((item) => item.status === 'accepted') const selectedAccepted = acceptedItems.filter((item) => selectedIds.has(item.id)) function toggleSelected(itemId) { setSelectedIds((prev) => { const next = new Set(prev) if (next.has(itemId)) next.delete(itemId) else next.add(itemId) return next }) } function convertOptions() { return sprintTargetId ? { work_cycle_id: sprintTargetId } : {} } async function handleSingleConvert(itemId) { await onConvert(itemId, convertOptions()) setSelectedIds((prev) => { const next = new Set(prev) next.delete(itemId) return next }) } async function handleBulkPlan() { if (!selectedAccepted.length) return if (typeof onBulkConvert === 'function') { await onBulkConvert( selectedAccepted.map((item) => item.id), convertOptions(), ) } else { for (const item of selectedAccepted) { await onConvert(item.id, convertOptions()) } } setSelectedIds(new Set()) } return (

{sectionTitle}

{sectionLead}

{canManage && ( )}
{showSprintPlanning && canManage && acceptedItems.length > 0 && (
{selectedAccepted.length > 0 && ( )}

Items markieren und in den gewählten Sprint committen — Planung unter Plan → Sprint.

)} {sprintPlanningEnabled && canManage && acceptedItems.length > 0 && planableSprints.length === 0 && (

Lege zuerst einen Sprint unter Plan → Sprint an, um Backlog-Items zu planen.

)} {items.length === 0 && } {modalMode?.kind === 'create' && ( )} {modalMode?.kind === 'edit' && modalMode.item && ( )}
) }