Some checks failed
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Failing after 4m46s
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 2s
Test Suite / compose-smoke (push) Has been skipped
Activity Set pro Stufe statt Gate-Titel-Recurring; Single-Active-Stufe bei Reopen; Complete heute schliesst Next Action; Heute-Panel fuer alle Uebungen der aktiven Stufe. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
3.6 KiB
JavaScript
105 lines
3.6 KiB
JavaScript
import { Link } from 'react-router-dom'
|
|
import { RECURRING_STATUS_LABELS } from '../constants/status.js'
|
|
import { EmptyState } from './EmptyState.jsx'
|
|
import { scopedPath } from '../utils/routes.js'
|
|
|
|
function formatDueAt(iso) {
|
|
if (!iso) return null
|
|
try {
|
|
return new Date(iso).toLocaleString('de-DE')
|
|
} catch {
|
|
return iso
|
|
}
|
|
}
|
|
|
|
function activeMaturityStage(roadmapItems = []) {
|
|
return [...roadmapItems]
|
|
.filter((item) => item.item_type === 'maturity_stage' && item.status === 'active')
|
|
.sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))[0]
|
|
}
|
|
|
|
function practicesForStage(recurringItems = [], stageId) {
|
|
if (!stageId) {
|
|
return (recurringItems || []).filter((item) => item.roadmap_item_id)
|
|
}
|
|
return (recurringItems || []).filter((item) => item.roadmap_item_id === stageId)
|
|
}
|
|
|
|
export function RecurringRhythmPanel({
|
|
initiativeId,
|
|
roadmapItems = [],
|
|
recurringItems = [],
|
|
canManage = false,
|
|
embedded = true,
|
|
}) {
|
|
const stage = activeMaturityStage(roadmapItems)
|
|
const stagePractices = practicesForStage(recurringItems, stage?.id)
|
|
const active = stagePractices.filter((item) => item.status === 'active')
|
|
const paused = stagePractices.filter((item) => item.status === 'paused')
|
|
|
|
const body =
|
|
!stage ? (
|
|
<EmptyState message="Keine aktive Reifegrad-Stufe — Plan → Stufen prüfen." />
|
|
) : stagePractices.length === 0 ? (
|
|
<EmptyState message="Keine Übungen für diese Stufe — Starter-Kit oder Stufenwechsel prüfen." />
|
|
) : (
|
|
<>
|
|
<p className="recurring-rhythm-summary muted">
|
|
Stufe <strong>{stage.title}</strong> — {active.length} aktiv
|
|
{paused.length > 0 ? `, ${paused.length} pausiert` : ''}.
|
|
</p>
|
|
<ul className="item-list recurring-rhythm-list">
|
|
{[...active, ...paused].map((item) => (
|
|
<li
|
|
key={item.id}
|
|
className={
|
|
'list-item card-list-item recurring-rhythm-item' +
|
|
(item.status === 'active' ? ' recurring-rhythm-item--active' : '') +
|
|
(item.today_completed ? ' recurring-rhythm-item--done-today' : '')
|
|
}
|
|
>
|
|
<div className="list-item-main">
|
|
<strong>{item.title}</strong>
|
|
<span className="muted list-item-sub">
|
|
{RECURRING_STATUS_LABELS[item.status] || item.status}
|
|
{item.today_completed ? ' · heute erledigt' : ''}
|
|
{!item.today_completed && item.next_due_at
|
|
? ` · Fällig: ${formatDueAt(item.next_due_at)}`
|
|
: ''}
|
|
</span>
|
|
{item.today_measurement_note && (
|
|
<span className="muted list-item-sub">Messwert: {item.today_measurement_note}</span>
|
|
)}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<p className="muted recurring-rhythm-hint">
|
|
{canManage
|
|
? 'Übungen anlegen, pausieren oder löschen unter '
|
|
: 'Rhythmen pflegen unter '}
|
|
<Link to={scopedPath('/control/journey', { initiativeId })} className="link-inline">
|
|
Kontrolle → Journey → Wiederkehrend
|
|
</Link>
|
|
.
|
|
</p>
|
|
</>
|
|
)
|
|
|
|
if (!embedded) return body
|
|
|
|
return (
|
|
<section className="card recurring-rhythm-panel">
|
|
<div className="section-header">
|
|
<div>
|
|
<h2>Übungen der Stufe</h2>
|
|
<p className="section-lead muted">
|
|
Activity Set der aktiven Reifegrad-Stufe — nicht die Gates selbst (A1).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{body}
|
|
</section>
|
|
)
|
|
}
|