Kairo-Jinkendo/frontend/src/components/ProjectForm.jsx
Lars efb45a3ee6
Some checks failed
Test Suite / pytest-backend (push) Failing after 2s
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 3s
Test Suite / compose-smoke (push) Has been skipped
Deploy Development / deploy (push) Failing after 50s
Erfassen und Transparent: Archetyp, Methode und Auspraegung in der UI sichtbar.
Method-Profiles-API und Auswahl beim Anlegen/Profil; Steuerungs-Meta in Listen und Plan; Projekt-Archetyp-Spiegel, Next-Action-Links und Umlaut-Fix fuer testbare Guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 12:36:12 +02:00

158 lines
4.9 KiB
JavaScript

import { INITIATIVE_STATUSES, INITIATIVE_STATUS_LABELS } from '../constants/status.js'
import { GateSelect } from './GateSelect.jsx'
import { flattenProjectOptions } from '../utils/projectTree.js'
import { projectArchetypeLabel } from '../utils/archetypes.js'
const CONTAINER_KINDS = [
{ value: '', label: '— Standard —' },
{ value: 'project', label: 'Projekt' },
{ value: 'stream', label: 'Stream' },
{ value: 'phase', label: 'Phase' },
{ value: 'release', label: 'Release' },
]
export function ProjectForm({
initial = {},
projects = [],
roadmapItems = [],
excludeProjectId = '',
onSubmit,
onCancel,
busy = false,
submitLabel = 'Speichern',
initiativeArchetypeKey = '',
initiativeArchetypes = [],
projectArchetypes = [],
}) {
const parentOptions = flattenProjectOptions(
projects.filter((p) => p.id !== excludeProjectId),
)
async function handleSubmit(e) {
e.preventDefault()
const form = e.target
const parentValue = form.parent_project_id?.value ?? ''
const kindValue = form.container_kind?.value ?? ''
const gateValue = form.roadmap_item_id?.value ?? ''
const targetValue = form.target_date?.value ?? ''
const sortValue = form.sort_order?.value ?? '0'
await onSubmit({
title: form.title.value.trim(),
description: form.description.value,
status: form.status.value,
parent_project_id: parentValue || undefined,
clear_parent_project: parentValue === '',
container_kind: kindValue || undefined,
clear_container_kind: kindValue === '',
roadmap_item_id: gateValue || undefined,
clear_roadmap_item: gateValue === '',
target_date: targetValue || undefined,
clear_target_date: !targetValue,
sort_order: Number.parseInt(sortValue, 10) || 0,
})
}
return (
<form className="form workspace-form project-form" onSubmit={handleSubmit}>
{initiativeArchetypeKey && (
<p className="form-hint muted project-form-archetype-hint">
Archetyp wird vom Vorhaben übernommen:{' '}
<strong>
{projectArchetypeLabel(
`project.${initiativeArchetypeKey.replace(/^initiative\./, '')}`,
projectArchetypes,
initiativeArchetypes,
)}
</strong>
. Art unten ist nur die Struktur-Rolle (Projekt, Stream, Phase ).
</p>
)}
<label>
Titel
<input
name="title"
defaultValue={initial.title || ''}
required
maxLength={255}
placeholder="z. B. Phase 1 — Grundlagen"
/>
</label>
<label>
Beschreibung
<textarea name="description" rows={4} defaultValue={initial.description || ''} />
</label>
<div className="form-row form-row--2">
<label>
Status
<select name="status" defaultValue={initial.status || 'active'}>
{INITIATIVE_STATUSES.map((s) => (
<option key={s} value={s}>
{INITIATIVE_STATUS_LABELS[s]}
</option>
))}
</select>
</label>
<label>
Art
<select name="container_kind" defaultValue={initial.container_kind || ''}>
{CONTAINER_KINDS.map(({ value, label }) => (
<option key={value || 'none'} value={value}>
{label}
</option>
))}
</select>
</label>
</div>
{parentOptions.length > 0 && (
<label>
Übergeordnet (optional)
<select name="parent_project_id" defaultValue={initial.parent_project_id || ''}>
<option value=""> oberste Ebene </option>
{parentOptions.map(({ project, label }) => (
<option key={project.id} value={project.id}>
{label}
</option>
))}
</select>
</label>
)}
{roadmapItems.length > 0 && (
<GateSelect
roadmapItems={roadmapItems}
defaultValue={initial.roadmap_item_id || ''}
/>
)}
<div className="form-row form-row--2">
<label>
Zieldatum (optional)
<input
type="date"
name="target_date"
defaultValue={initial.target_date || ''}
/>
</label>
<label>
Sortierung
<input
type="number"
name="sort_order"
defaultValue={String(initial.sort_order ?? 0)}
min={0}
/>
</label>
</div>
<div className="form-actions">
<button type="submit" className="btn btn-primary" disabled={busy}>
{busy ? 'Speichern …' : submitLabel}
</button>
{onCancel && (
<button type="button" className="btn btn-secondary" onClick={onCancel} disabled={busy}>
Abbrechen
</button>
)}
</div>
</form>
)
}