// Finance data + chart components (ported & retuned for desktop) const NUMS = { fontVariantNumeric: 'tabular-nums', fontFeatureSettings: '"tnum"' }; const finPalette = (t) => ({ pos: t.green, neg: t.red, posBar: t.dark ? 'rgba(48,209,88,0.45)' : 'rgba(52,199,89,0.42)', negBar: t.dark ? 'rgba(255,69,58,0.42)' : 'rgba(255,59,48,0.36)', grid: t.dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.07)', }); // Monthly income/expense for the year (Mai 2026 = current) const FIN_BARS = [ { key: '2025-11', label: 'Nov', inc: 1640, exp: 720 }, { key: '2025-12', label: 'Dez', inc: 980, exp: 540 }, { key: '2026-01', label: 'Jan', inc: 1320, exp: 880 }, { key: '2026-02', label: 'Feb', inc: 1180, exp: 410 }, { key: '2026-03', label: 'Mär', inc: 2040, exp: 1260 }, { key: '2026-04', label: 'Apr', inc: 2260, exp: 690 }, { key: '2026-05', label: 'Mai', inc: 2480, exp: 760 }, ]; const SEED_TX = [ { id: 't1', kind: 'in', label: 'Hochzeit Müller', sub: 'JBL EON 712 · 4 Tage', amount: 180, date: '18. Mai 2026', dateISO: '2026-05-18' }, { id: 't2', kind: 'in', label: 'Gartenfest Bauer', sub: 'Shure SM58 ×4 · 2 Tage', amount: 60, date: '19. Mai 2026', dateISO: '2026-05-19' }, { id: 't3', kind: 'out', label: 'Ersatz-Speakon Kabel', sub: 'Zubehör Tontechnik', amount: 48, date: '16. Mai 2026', dateISO: '2026-05-16' }, { id: 't4', kind: 'in', label: 'Vereinsfest TSV', sub: 'Shure SM58 ×4 · 2 Tage', amount: 60, date: '27. Apr 2026', dateISO: '2026-04-27' }, { id: 't5', kind: 'in', label: 'Umzug Schmidt', sub: 'Anhänger 750kg · 1 Tag', amount: 40, date: '15. Mai 2026', dateISO: '2026-05-15' }, { id: 't6', kind: 'out', label: 'TÜV Anhänger 750kg', sub: 'Werkstatt · HU/AU', amount: 124, date: '12. Mai 2026', dateISO: '2026-05-12' }, { id: 't7', kind: 'in', label: 'Anzahlung Klein', sub: 'PA-Komplettset · Reservierung', amount: 80, date: '10. Mai 2026', dateISO: '2026-05-10' }, { id: 't8', kind: 'out', label: 'Versicherung Q2', sub: 'Geräte-Inhaltsversicherung', amount: 95, date: '04. Mai 2026', dateISO: '2026-05-04' }, ]; // Per-month historical detail (used in the Monatsverlauf drill-down) const CURRENT_MONTH_KEY = '2026-05'; const MONTHS_DETAIL = { '2026-01': { label: 'Januar 2026', short: 'Jan', income: 980, expenses: 320, bars: [{ label: 'W1', inc: 220, exp: 80 }, { label: 'W2', inc: 320, exp: 120 }, { label: 'W3', inc: 180, exp: 0 }, { label: 'W4', inc: 260, exp: 120 }], transactions: [ { id: 'jan1', kind: 'in', label: 'Silvesterparty', sub: 'PA-Anlage · 2 Tage', amount: 420, date: '02.01.' }, { id: 'jan2', kind: 'out', label: 'Anhänger Wartung', sub: 'TÜV + Reparatur', amount: 380, date: '14.01.' }, { id: 'jan3', kind: 'in', label: 'Firmenfeier Bauer', sub: 'Mikro-Set · 1 Tag', amount: 95, date: '17.01.' }, { id: 'jan4', kind: 'in', label: 'Geburtstag Klein', sub: 'JBL · 2 Tage', amount: 90, date: '24.01.' }, ], }, '2026-02': { label: 'Februar 2026', short: 'Feb', income: 1120, expenses: 480, bars: [{ label: 'W1', inc: 180, exp: 0 }, { label: 'W2', inc: 220, exp: 80 }, { label: 'W3', inc: 380, exp: 400 }, { label: 'W4', inc: 340, exp: 0 }], transactions: [ { id: 'feb1', kind: 'out', label: 'Sennheiser EW100', sub: 'Neues Equipment', amount: 680, date: '12.02.' }, { id: 'feb2', kind: 'in', label: 'Hochzeit Schmidt', sub: 'Komplett-Set · 3 Tage', amount: 380, date: '15.02.' }, { id: 'feb3', kind: 'in', label: 'Schulfest', sub: 'PA · 1 Tag', amount: 90, date: '22.02.' }, ], }, '2026-03': { label: 'März 2026', short: 'Mär', income: 1840, expenses: 220, bars: [{ label: 'W1', inc: 240, exp: 0 }, { label: 'W2', inc: 380, exp: 120 }, { label: 'W3', inc: 750, exp: 0 }, { label: 'W4', inc: 470, exp: 100 }], transactions: [ { id: 'mar1', kind: 'in', label: 'Messeveranstaltung', sub: 'Komplett-Set · 5 Tage', amount: 750, date: '14.03.' }, { id: 'mar2', kind: 'in', label: 'Vereinsjubiläum', sub: 'PA + Mikros', amount: 320, date: '21.03.' }, { id: 'mar3', kind: 'out', label: 'Kabel-Set', sub: 'Verbrauchsmaterial', amount: 120, date: '08.03.' }, { id: 'mar4', kind: 'in', label: 'Hochzeit Weber', sub: '2 Tage', amount: 280, date: '28.03.' }, ], }, '2026-04': { label: 'April 2026', short: 'Apr', income: 2100, expenses: 960, bars: [{ label: 'W1', inc: 380, exp: 0 }, { label: 'W2', inc: 580, exp: 880 }, { label: 'W3', inc: 620, exp: 0 }, { label: 'W4', inc: 520, exp: 80 }], transactions: [ { id: 'apr1', kind: 'out', label: 'JBL SRX835P', sub: 'Neues Equipment', amount: 880, date: '11.04.' }, { id: 'apr2', kind: 'in', label: 'Open Air Festival', sub: 'PA · 4 Tage', amount: 720, date: '18.04.' }, { id: 'apr3', kind: 'in', label: 'Firmenevent Audi', sub: 'Komplett · 2 Tage', amount: 580, date: '24.04.' }, { id: 'apr4', kind: 'out', label: 'Versicherung Q2', sub: 'Geräte-Inhalt', amount: 80, date: '28.04.' }, ], }, '2026-05': { label: 'Mai 2026', short: 'Mai', income: 2480, expenses: 680, bars: [ { label: 'Mo', inc: 0, exp: 0 }, { label: 'Di', inc: 180, exp: 0 }, { label: 'Mi', inc: 340, exp: 320 }, { label: 'Do', inc: 180, exp: 0 }, { label: 'Fr', inc: 580, exp: 240 }, { label: 'Sa', inc: 760, exp: 0 }, { label: 'So', inc: 440, exp: 120 }, ], transactions: [ { id: 'may1', kind: 'in', label: 'Hochzeit Müller', sub: 'JBL EON 712 · 4 Tage', amount: 180, date: '20.05.' }, { id: 'may2', kind: 'in', label: 'Vereinsfest TSV', sub: 'SM58 Set · 2 Tage', amount: 60, date: '19.05.' }, { id: 'may3', kind: 'out', label: 'Mikrofonständer ×4', sub: 'König & Meyer', amount: 240, date: '18.05.' }, { id: 'may4', kind: 'in', label: 'Open Air Konzert', sub: 'Komplett-Set · 3 Tage', amount: 320, date: '17.05.' }, { id: 'may5', kind: 'out', label: 'Kabelreparatur', sub: 'Werkstatt Müller', amount: 85, date: '16.05.' }, { id: 'may6', kind: 'in', label: 'Geburtstag Klein', sub: 'Anhänger · 1 Tag', amount: 40, date: '15.05.' }, { id: 'may7', kind: 'out', label: 'Versicherung Anhänger', sub: 'Halbjährlich', amount: 355, date: '14.05.' }, { id: 'may8', kind: 'in', label: 'Schulfest', sub: 'JBL EON 712 · 1 Tag', amount: 45, date: '12.05.' }, ], }, }; // Percent-change helper (returns null when previous = 0). const pctF = (cur, prev) => { if (!prev) return null; const v = Math.round((cur - prev) / Math.abs(prev) * 100); return { val: Math.abs(v), up: v >= 0 }; }; function buildFinanceSummary(bars) { const cur = bars[bars.length - 1]; const prev = bars[bars.length - 2] || { inc: 0, exp: 0 }; const ytdInc = bars.reduce((s, b) => s + b.inc, 0); const ytdExp = bars.reduce((s, b) => s + b.exp, 0); return { income: cur.inc, expenses: cur.exp, net: cur.inc - cur.exp, prevNet: prev.inc - prev.exp, ytdInc, ytdExp, ytdNet: ytdInc - ytdExp, }; } // ── Bar chart (hover tooltip) ── function ChartBars({ bars }) { const t = useTheme(); const p = finPalette(t); const max = Math.max(...bars.map((b) => Math.max(b.inc, b.exp)), 1); const H = 200; const [active, setActive] = useState(null); return (