Kairo-Jinkendo/frontend/src/components/RoadmapItemForm.jsx
Lars 4f8725e234
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 2m17s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 13s
AP1.4d: Graph Engine mit blocked/ready Read Models.
Migration 020 fuer Kantenarten und Parallelgruppe; steering/graph berechnet Fortschritt; UI zeigt Bereit/Blockiert in Liste und Graph.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 07:59:18 +02:00

133 lines
3.9 KiB
JavaScript

import {
MILESTONE_STATUSES,
MILESTONE_STATUS_LABELS,
ROADMAP_ITEM_TYPE_LABELS,
} from '../constants/status.js'
const ITEM_TYPES = ['milestone', 'review_gate', 'maturity_stage']
const EDITABLE_STATUSES = MILESTONE_STATUSES.filter(
(s) => !['reached', 'moved', 'discarded'].includes(s),
)
export function RoadmapItemForm({
initial = {},
onSubmit,
onCancel,
busy = false,
submitLabel = 'Anlegen',
mode = 'create',
}) {
async function handleSubmit(e) {
e.preventDefault()
const form = e.target
const payload = {
title: form.title.value.trim(),
goal_description: form.goal_description.value.trim(),
target_date: form.target_date.value || undefined,
item_type: form.item_type.value,
sequencing_mode: initial.sequencing_mode || 'sequential',
}
if (mode === 'create') {
const initialCriterion = form.initial_criterion_title?.value?.trim()
if (initialCriterion) {
payload.initial_criterion_title = initialCriterion
}
}
if (mode === 'edit') {
payload.status = form.status.value
}
await onSubmit(payload)
}
const statusLocked = ['reached', 'moved', 'discarded'].includes(initial.status)
return (
<form className="form workspace-form roadmap-item-form" onSubmit={handleSubmit}>
{mode === 'create' && (
<label>
Typ
<select name="item_type" defaultValue={initial.item_type || 'milestone'}>
{ITEM_TYPES.map((t) => (
<option key={t} value={t}>
{ROADMAP_ITEM_TYPE_LABELS[t] || t}
</option>
))}
</select>
</label>
)}
<label>
Titel
<input
name="title"
defaultValue={initial.title || ''}
maxLength={255}
required
placeholder="z. B. MVP nutzbar im Alltag"
disabled={statusLocked}
/>
</label>
<label>
Zielkontext (optional)
<textarea
name="goal_description"
rows={3}
defaultValue={initial.goal_description || ''}
placeholder="Kurzer Kontext — prüfbare Akzeptanz legst du als Kriterien an."
disabled={statusLocked}
/>
<span className="muted form-hint">
Die Checkliste auf der Gate-Detailseite ist führend für Verify.
</span>
</label>
{mode === 'create' && (
<label>
Erstes Kriterium (optional)
<input
name="initial_criterion_title"
maxLength={500}
placeholder="z. B. Demo mit Stakeholdern durchgeführt"
/>
<span className="muted form-hint">
Ohne Angabe wird Gate allgemein als Standard-Kriterium angelegt.
</span>
</label>
)}
<label>
Zieltermin
<input
type="date"
name="target_date"
defaultValue={initial.target_date || ''}
disabled={statusLocked}
/>
</label>
<p className="muted form-hint">
Reihenfolge und Parallelität steuerst du über Kanten auf der Gate-Detailseite nicht mehr
über ein Abfolge-Feld.
</p>
{mode === 'edit' && (
<label>
Status
<select name="status" defaultValue={initial.status || 'planned'} disabled={statusLocked}>
{EDITABLE_STATUSES.map((s) => (
<option key={s} value={s}>
{MILESTONE_STATUS_LABELS[s]}
</option>
))}
</select>
</label>
)}
<div className="form-actions">
<button type="submit" className="btn btn-primary" disabled={busy || statusLocked}>
{busy ? 'Speichern …' : submitLabel}
</button>
{onCancel && (
<button type="button" className="btn btn-secondary" onClick={onCancel} disabled={busy}>
Abbrechen
</button>
)}
</div>
</form>
)
}