Kairo-Jinkendo/frontend/src/components/InitiativeForm.jsx

181 lines
5.2 KiB
React
Raw Normal View History

import { useEffect, useState } from 'react'
import { INITIATIVE_STATUSES, PRIORITIES } from '../constants/status.js'
import { INITIATIVE_STATUS_LABELS, PRIORITY_LABELS } from '../constants/status.js'
import { INITIATIVE_ARCHETYPE_LABELS } from '../constants/initiativeArchetypes.js'
import { listEntityArchetypes } from '../api/entityArchetypes.js'
import {
getInitiativeFieldDefinitions,
getInitiativeFields,
listArchetypeFieldDefinitions,
} from '../api/entityFields.js'
import { FieldRenderer } from './FieldRenderer.jsx'
export function InitiativeForm({
initial = {},
initiativeId = null,
onSubmit,
onCancel,
busy = false,
submitLabel = 'Speichern',
}) {
const [archetypes, setArchetypes] = useState(null)
const [archetypeKey, setArchetypeKey] = useState(initial.archetype_key || 'initiative.generic')
const [fieldDefinitions, setFieldDefinitions] = useState([])
const [dynamicValues, setDynamicValues] = useState({})
const [fieldsLoading, setFieldsLoading] = useState(false)
useEffect(() => {
let cancelled = false
listEntityArchetypes('initiative')
.then((items) => {
if (!cancelled) setArchetypes(items)
})
.catch(() => {
if (!cancelled) {
setArchetypes(
Object.entries(INITIATIVE_ARCHETYPE_LABELS).map(([key, label]) => ({
key,
label,
})),
)
}
})
return () => {
cancelled = true
}
}, [])
useEffect(() => {
let cancelled = false
setFieldsLoading(true)
async function loadFields() {
try {
let definitions = []
let values = {}
if (initiativeId) {
definitions = await getInitiativeFieldDefinitions(initiativeId)
values = await getInitiativeFields(initiativeId)
} else if (archetypeKey) {
definitions = await listArchetypeFieldDefinitions('initiative', archetypeKey)
}
if (!cancelled) {
setFieldDefinitions(definitions)
setDynamicValues(values)
}
} catch {
if (!cancelled) {
setFieldDefinitions([])
setDynamicValues({})
}
} finally {
if (!cancelled) setFieldsLoading(false)
}
}
loadFields()
return () => {
cancelled = true
}
}, [initiativeId, archetypeKey])
async function handleSubmit(e) {
e.preventDefault()
const form = e.target
await onSubmit({
title: form.title.value.trim(),
archetype_key: form.archetype_key.value,
goal: form.goal.value,
vision: form.vision.value,
target_state_summary: form.target_state_summary.value,
status: form.status.value,
priority: form.priority.value,
dynamicFields: dynamicValues,
})
}
function handleDynamicChange(fieldKey, value) {
setDynamicValues((prev) => ({ ...prev, [fieldKey]: value }))
}
const formReady = archetypes && !fieldsLoading
return (
<form className="form workspace-form" onSubmit={handleSubmit}>
<label>
Titel
<input name="title" defaultValue={initial.title || ''} required maxLength={255} />
</label>
<label>
Archetyp
<select
name="archetype_key"
value={archetypeKey}
onChange={(e) => setArchetypeKey(e.target.value)}
disabled={!archetypes}
>
{(archetypes || []).map((item) => (
<option key={item.key} value={item.key}>
{item.label}
</option>
))}
</select>
</label>
<label>
Kurzbeschreibung (Ziel)
<textarea name="goal" rows={2} defaultValue={initial.goal || ''} />
</label>
<label>
Vision
<textarea name="vision" rows={4} defaultValue={initial.vision || ''} />
</label>
<label>
Zielzustand (Kurzfassung)
<textarea
name="target_state_summary"
rows={2}
defaultValue={initial.target_state_summary || ''}
/>
</label>
<FieldRenderer
definitions={fieldDefinitions}
values={dynamicValues}
onChange={handleDynamicChange}
disabled={busy || fieldsLoading}
/>
<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>
Priorität
<select name="priority" defaultValue={initial.priority || 'normal'}>
{PRIORITIES.map((p) => (
<option key={p} value={p}>
{PRIORITY_LABELS[p]}
</option>
))}
</select>
</label>
</div>
<div className="form-actions">
<button type="submit" className="btn btn-primary" disabled={busy || !formReady}>
{busy ? 'Speichern …' : submitLabel}
</button>
{onCancel && (
<button type="button" className="btn btn-secondary" onClick={onCancel} disabled={busy}>
Abbrechen
</button>
)}
</div>
</form>
)
}