import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; import { Markdown } from '../markdown.jsx'; const dueFmt = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }); /** Milestone-Gruppe bestimmen: Fertig / In Arbeit / Geplant */ function groupOf(m) { if (m.state === 'closed') return 'done'; return m.closed > 0 ? 'active' : 'planned'; } const GROUPS = [ { id: 'active', tag: '// In Arbeit', empty: 'Gerade nichts in Arbeit.' }, { id: 'planned', tag: '// Geplant', empty: null }, { id: 'done', tag: '// Fertig', empty: null }, ]; function Milestone({ m }) { const total = m.open + m.closed; const pct = total > 0 ? Math.round((m.closed / total) * 100) : m.state === 'closed' ? 100 : 0; return (
{m.title} {m.due_on && m.state !== 'closed' && ( Ziel: {dueFmt.format(new Date(m.due_on))} )}
{m.description && (
)}
{pct}% {total > 0 && {m.closed} / {total} Aufgaben}
); } /** GitHub-Style-Heatmap: 52 Wochen × 7 Tage */ function Heatmap({ days }) { const byDay = new Map(days.map((d) => [d.day, d.n])); const max = Math.max(1, ...days.map((d) => d.n)); const today = new Date(); // Start: Montag vor ~52 Wochen const start = new Date(today.getTime() - 364 * 86400000); start.setDate(start.getDate() - ((start.getDay() + 6) % 7)); const weeks = []; for (let w = 0; w < 53; w++) { const col = []; for (let d = 0; d < 7; d++) { const date = new Date(start.getTime() + (w * 7 + d) * 86400000); if (date > today) break; const key = date.toISOString().slice(0, 10); const n = byDay.get(key) ?? 0; const level = n === 0 ? 0 : Math.min(4, Math.ceil((n / max) * 4)); col.push( ); } weeks.push({col}); } const total = days.reduce((sum, d) => sum + d.n, 0); return (

// Aktivität — {total} Commits in 12 Monaten

{weeks}
); } export default function Roadmap() { const [data, setData] = useState(null); const [wishes, setWishes] = useState([]); const [heatmap, setHeatmap] = useState(null); const [error, setError] = useState(false); useEffect(() => { apiGet('/api/roadmap').then(setData).catch(() => setError(true)); apiGet('/api/wishes').then((d) => setWishes(d.wishes)).catch(() => {}); apiGet('/api/heatmap').then((d) => setHeatmap(d.days)).catch(() => {}); window.scrollTo(0, 0); }, []); return ( <>
ROADMAP
{error &&

Roadmap konnte nicht geladen werden.

} {!error && !data &&

Lade …

} {data?.milestones.length === 0 && (

Noch keine Meilensteine angelegt.

)} {heatmap && heatmap.length > 0 && } {wishes.length > 0 && (

// Community-Wünsche

{wishes.map((w, i) => (
👍 {w.score} {w.idea} {w.author}
))}

Eigene Idee? Im Discord einfach /wunsch benutzen.

)} {GROUPS.map(({ id, tag, empty }) => { const items = (data?.milestones ?? []).filter((m) => groupOf(m) === id); if (items.length === 0 && !empty) return null; return (

{tag}

{items.length === 0 &&

{empty}

} {items.map((m) => ( ))}
); })}
); }