Kairo-Jinkendo/frontend/src/components/ActionTaskPanel.jsx
Lars ba28a86fe5
Some checks failed
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Failing after 1m57s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
AP1.5d: Rekursive Tasks im Plan-Knoten Arbeit.
parent_task_id mit Validierung und Roll-up; aufklappbarer Task-Baum unter Arbeitspaketen im Plan-Modus (max. 3 UI-Ebenen).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 06:18:55 +02:00

195 lines
4.9 KiB
JavaScript

import { useEffect, useMemo, useState } from 'react'
import {
createActionTask,
deleteTask,
listActionTasks,
updateTask,
} from '../api/tasks.js'
import { buildTasksByParent } from '../utils/taskTree.js'
import { Modal } from './Modal.jsx'
import { TaskForm, TaskTreeNodes } from './TaskForm.jsx'
export function ActionTaskPanel({
actionId,
roadmapItems,
canManage,
expanded,
onToggle,
openTaskCount,
}) {
const [tasks, setTasks] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [busy, setBusy] = useState(false)
const [modal, setModal] = useState(null)
const [reloadKey, setReloadKey] = useState(0)
useEffect(() => {
if (!expanded || !actionId) return undefined
let cancelled = false
setLoading(true)
setError(null)
listActionTasks(actionId)
.then((data) => {
if (!cancelled) setTasks(data)
})
.catch((err) => {
if (!cancelled) setError(err.message)
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [expanded, actionId, reloadKey])
const tasksByParent = useMemo(() => buildTasksByParent(tasks), [tasks])
async function reload() {
setReloadKey((value) => value + 1)
}
async function handleCreate(payload) {
setBusy(true)
setError(null)
try {
await createActionTask(actionId, payload)
setModal(null)
await reload()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
async function handleUpdate(taskId, payload) {
setBusy(true)
setError(null)
try {
await updateTask(taskId, payload)
setModal(null)
await reload()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
async function handleStatus(taskId, status) {
setBusy(true)
setError(null)
try {
await updateTask(taskId, { status })
await reload()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
async function handleDelete(taskId) {
setBusy(true)
setError(null)
try {
await deleteTask(taskId)
await reload()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
const taskLabel = openTaskCount != null ? ` (${openTaskCount} offen)` : ''
return (
<div className="action-task-panel">
<button
type="button"
className="action-task-panel__toggle"
onClick={onToggle}
aria-expanded={expanded}
>
{expanded ? '▾' : '▸'} Aufgaben{taskLabel}
</button>
{expanded && (
<div className="action-task-panel__body">
{error && <p className="error">{error}</p>}
{loading && <p className="muted">Aufgaben werden geladen </p>}
{!loading && tasks.length === 0 && (
<p className="muted">Noch keine Aufgaben für dieses Arbeitspaket.</p>
)}
{!loading && tasks.length > 0 && (
<TaskTreeNodes
tasksByParent={tasksByParent}
roadmapItems={roadmapItems}
canManage={canManage}
busy={busy}
onEdit={(task) => setModal({ mode: 'edit', task, key: task.id })}
onAddSubtask={(task) =>
setModal({
mode: 'create',
parentTaskId: task.id,
parentLabel: task.title,
key: `sub-${task.id}`,
})
}
onStatusChange={handleStatus}
onDelete={handleDelete}
/>
)}
{canManage && (
<button
type="button"
className="btn btn-secondary btn-sm action-task-panel__add"
disabled={busy}
onClick={() => setModal({ mode: 'create', key: 'root' })}
>
Aufgabe hinzufügen
</button>
)}
</div>
)}
<Modal
open={Boolean(modal)}
title={
modal?.mode === 'edit'
? 'Aufgabe bearbeiten'
: modal?.parentLabel
? 'Unteraufgabe anlegen'
: 'Aufgabe anlegen'
}
onClose={() => setModal(null)}
>
{modal?.mode === 'edit' ? (
<TaskForm
initial={modal.task}
roadmapItems={roadmapItems}
onSubmit={(payload) => handleUpdate(modal.task.id, payload)}
onCancel={() => setModal(null)}
busy={busy}
/>
) : (
modal && (
<TaskForm
parentTaskId={modal.parentTaskId}
parentLabel={modal.parentLabel}
roadmapItems={roadmapItems}
onSubmit={handleCreate}
onCancel={() => setModal(null)}
busy={busy}
submitLabel="Anlegen"
/>
)
)}
</Modal>
</div>
)
}