Some checks failed
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Failing after 1m48s
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
Beim AP1.9a-Routing-Umbau fehlten die Imports in zwei Cockpit-Widgets; React brach nach Anmeldung ab. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
3.2 KiB
JavaScript
100 lines
3.2 KiB
JavaScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import { listWorkspaceTodayActions } from '../api/workspace.js'
|
|
import { StatusBadge } from '../components/StatusBadge.jsx'
|
|
import { PriorityBadge } from '../components/PriorityBadge.jsx'
|
|
import { EmptyState } from '../components/EmptyState.jsx'
|
|
import { ErrorState } from '../components/ErrorState.jsx'
|
|
import { LoadingState } from '../components/LoadingState.jsx'
|
|
import { WidgetCard } from '../components/WidgetCard.jsx'
|
|
import { actionPath, scopedPath } from '../utils/routes.js'
|
|
|
|
function formatDue(iso) {
|
|
if (!iso) return null
|
|
try {
|
|
return new Date(iso).toLocaleString('de-DE', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
} catch {
|
|
return iso
|
|
}
|
|
}
|
|
|
|
export function WorkspaceTodayWidget() {
|
|
const [actions, setActions] = useState([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState(null)
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
const data = await listWorkspaceTodayActions(10)
|
|
setActions(Array.isArray(data) ? data : [])
|
|
} catch (err) {
|
|
setError(err.message)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [load])
|
|
|
|
return (
|
|
<WidgetCard
|
|
title="Heute"
|
|
subtitle="Deine Maßnahmen — überfällig und dringend zuerst"
|
|
actions={
|
|
<button type="button" className="btn btn-ghost" onClick={load} disabled={loading}>
|
|
Aktualisieren
|
|
</button>
|
|
}
|
|
footer={
|
|
actions.length > 0 ? (
|
|
<Link to="/work/mine" className="link-inline">
|
|
Alle meine Maßnahmen →
|
|
</Link>
|
|
) : null
|
|
}
|
|
>
|
|
{loading && <LoadingState />}
|
|
{!loading && error && <ErrorState message={error} onRetry={load} />}
|
|
{!loading && !error && actions.length === 0 && (
|
|
<EmptyState message="Keine Maßnahmen für heute — prüfe „Nächste sinnvolle Schritte“ oben." />
|
|
)}
|
|
{!loading && !error && actions.length > 0 && (
|
|
<ul className="item-list">
|
|
{actions.map((action) => (
|
|
<li key={action.id} className="list-item card-list-item">
|
|
<div className="list-item-main">
|
|
{action.is_overdue && (
|
|
<span className="badge status-badge status-blocked">Überfällig</span>
|
|
)}
|
|
<strong>{action.title}</strong>
|
|
{action.initiative_title && (
|
|
<span className="muted list-item-sub">{action.initiative_title}</span>
|
|
)}
|
|
{action.due_at && (
|
|
<span className="muted list-item-sub">Fällig: {formatDue(action.due_at)}</span>
|
|
)}
|
|
</div>
|
|
<div className="list-item-meta">
|
|
<StatusBadge status={action.status} />
|
|
<PriorityBadge priority={action.priority} />
|
|
<Link to={actionPath(action.id)} className="btn btn-secondary btn-sm">
|
|
Öffnen
|
|
</Link>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</WidgetCard>
|
|
)
|
|
}
|