Kairo-Jinkendo/frontend/src/components/RecurringSection.jsx
Lars a025542545
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 1m6s
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 21s
Test Suite / playwright-smoke (push) Successful in 14s
AP0.9: Operating Model Extension II (Evidence, Decision, Review, Recurring, due_at)
Schließt den zweiten OM-Slice entlang der Roadmap: neue Entitäten parallel im Vorhaben, erweiterte Action-Status/Fälligkeit und Attention-Regeln 7–9.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 15:27:03 +02:00

115 lines
3.4 KiB
JavaScript

import { useState } from 'react'
import { RECURRING_STATUSES, RECURRING_STATUS_LABELS } from '../constants/status.js'
import { StatusBadge } from './StatusBadge.jsx'
import { EmptyState } from './EmptyState.jsx'
function formatDueAt(iso) {
if (!iso) return null
try {
return new Date(iso).toLocaleString('de-DE')
} catch {
return iso
}
}
export function RecurringSection({ items, canManage, onCreate, onUpdateStatus, onDelete, busy }) {
const [title, setTitle] = useState('')
const [nextDueAt, setNextDueAt] = useState('')
const [showForm, setShowForm] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
if (!title.trim()) return
await onCreate({
title: title.trim(),
next_due_at: nextDueAt ? new Date(nextDueAt).toISOString() : null,
})
setTitle('')
setNextDueAt('')
setShowForm(false)
}
return (
<section className="card">
<div className="section-header">
<h2>Wiederkehrend</h2>
{canManage && (
<button
type="button"
className="btn btn-primary btn-block-mobile"
onClick={() => setShowForm((v) => !v)}
>
{showForm ? 'Abbrechen' : 'Element anlegen'}
</button>
)}
</div>
{showForm && canManage && (
<form className="inline-form-block" onSubmit={handleSubmit}>
<label>
Titel
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
maxLength={255}
required
/>
</label>
<label>
Nächste Fälligkeit
<input
type="datetime-local"
value={nextDueAt}
onChange={(e) => setNextDueAt(e.target.value)}
/>
</label>
<button type="submit" className="btn btn-primary" disabled={busy}>
Anlegen
</button>
</form>
)}
{items.length === 0 && <EmptyState message="Keine wiederkehrenden Elemente." />}
<ul className="item-list">
{items.map((item) => (
<li key={item.id} className="list-item card-list-item">
<div className="list-item-main">
<strong>{item.title}</strong>
{item.next_due_at && (
<p className="muted list-item-sub">Fällig: {formatDueAt(item.next_due_at)}</p>
)}
</div>
<div className="list-item-meta action-controls">
<StatusBadge kind="recurring" status={item.status} />
{canManage && (
<>
<select
className="inline-select"
value={item.status}
onChange={(e) => onUpdateStatus(item.id, e.target.value)}
aria-label="Recurring-Status"
>
{RECURRING_STATUSES.map((s) => (
<option key={s} value={s}>
{RECURRING_STATUS_LABELS[s]}
</option>
))}
</select>
<button
type="button"
className="btn btn-secondary"
onClick={() => onDelete(item.id)}
>
Löschen
</button>
</>
)}
</div>
</li>
))}
</ul>
</section>
)
}