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
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>
158 lines
4.9 KiB
JavaScript
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>
|
|
)
|
|
}
|