Kairo-Jinkendo/frontend/src/components/MilestonesSection.jsx
Lars a520d54bcf
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 1m26s
Test Suite / lint-backend (push) Successful in 1s
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 12s
AP1.1b: Vorhaben-Detail als Steuerungsfläche — Action-Hub statt paralleler Listen
Leitfrage oben, Maßnahmen mit verknüpftem Kontext aus dem Snapshot, vollständiges Meilenstein-Formular, Backlog/Entscheidungen/Nachweise unter Erweitert. Version 0.11.2-ap1.1b.
2026-07-05 17:39:18 +02:00

153 lines
4.6 KiB
JavaScript

import { useState } from 'react'
import {
MILESTONE_STATUSES,
MILESTONE_STATUS_LABELS,
} from '../constants/status.js'
import { StatusBadge } from './StatusBadge.jsx'
import { EmptyState } from './EmptyState.jsx'
function formatDate(value) {
if (!value) return null
try {
return new Date(value.includes('T') ? value : `${value}T12:00:00`).toLocaleDateString(
'de-DE'
)
} catch {
return value
}
}
export function MilestonesSection({
milestones,
canManage,
onCreate,
onUpdateStatus,
onDelete,
busy,
compact = false,
}) {
const [title, setTitle] = useState('')
const [goalDescription, setGoalDescription] = useState('')
const [targetDate, setTargetDate] = useState('')
const [showForm, setShowForm] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
if (!title.trim()) return
await onCreate({
title: title.trim(),
goal_description: goalDescription.trim(),
target_date: targetDate || undefined,
})
setTitle('')
setGoalDescription('')
setTargetDate('')
setShowForm(false)
}
return (
<section className={`card${compact ? ' card--secondary' : ''}`}>
<div className="section-header">
<div>
<h2>Meilensteine</h2>
<p className="section-lead muted">
Überprüfbare Zielpunkte kein Task. Ziel und Termin machen den Horizont steuerbar.
</p>
</div>
{canManage && (
<button
type="button"
className="btn btn-primary btn-block-mobile"
onClick={() => setShowForm((v) => !v)}
>
{showForm ? 'Abbrechen' : 'Meilenstein'}
</button>
)}
</div>
{showForm && canManage && (
<form className="inline-form-block milestone-form" onSubmit={handleSubmit}>
<label>
Titel
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
maxLength={255}
required
placeholder="z. B. MVP nutzbar im Alltag"
/>
</label>
<label>
Ziel / Definition of Done
<textarea
value={goalDescription}
onChange={(e) => setGoalDescription(e.target.value)}
rows={3}
placeholder="Woran erkennst du, dass dieser Meilenstein erreicht ist?"
/>
</label>
<label>
Zieltermin
<input
type="date"
value={targetDate}
onChange={(e) => setTargetDate(e.target.value)}
/>
</label>
<button type="submit" className="btn btn-primary" disabled={busy}>
Anlegen
</button>
</form>
)}
{milestones.length === 0 && (
<EmptyState message="Noch keine Meilensteine — strukturiere das Vorhaben in überprüfbare Zielpunkte." />
)}
<ul className="item-list milestone-list">
{milestones.map((milestone) => (
<li key={milestone.id} className="list-item card-list-item milestone-list-item">
<div className="list-item-main">
<strong>{milestone.title}</strong>
{milestone.goal_description && (
<p className="list-item-desc">{milestone.goal_description}</p>
)}
{milestone.target_date && (
<p className="muted list-item-sub">
Zieltermin: {formatDate(milestone.target_date)}
</p>
)}
</div>
<div className="list-item-meta action-controls">
<StatusBadge kind="milestone" status={milestone.status} />
{canManage && (
<>
<select
className="inline-select"
value={milestone.status}
onChange={(e) => onUpdateStatus(milestone.id, e.target.value)}
aria-label="Meilenstein-Status"
>
{MILESTONE_STATUSES.map((s) => (
<option key={s} value={s}>
{MILESTONE_STATUS_LABELS[s]}
</option>
))}
</select>
<button
type="button"
className="btn btn-secondary"
onClick={() => onDelete(milestone.id)}
>
Löschen
</button>
</>
)}
</div>
</li>
))}
</ul>
</section>
)
}