diff --git a/app/webui/components/dashboard-kpis.js b/app/webui/components/dashboard-kpis.js new file mode 100644 index 0000000..3a7d293 --- /dev/null +++ b/app/webui/components/dashboard-kpis.js @@ -0,0 +1,85 @@ +/** + * Dashboard-KPIs + Activity-Feed Component (Alpine.js) + * + * Used in: dashboard.html and app.html (default landing page) + * + * Loads: + * GET /api/v1/dashboard/kpis → { open_deals_count, pipeline_value, won_this_month, conversion_rate } + * GET /api/v1/dashboard/feed?limit=20 → ActivityFeedItem[] + * + * Renders: 4 KPI cards + an activity feed list. + * + * R-5: x-text only. User-controlled strings are not interpolated as HTML. + */ + +import { api } from '/js/api.js'; + +export function dashboardKpis() { + return { + loading: true, + kpis: null, + feed: [], + error: null, + + async load() { + this.loading = true; + this.error = null; + try { + // Fetch KPIs and feed in parallel + const [kpis, feed] = await Promise.all([ + api.get('/dashboard/kpis'), + api.get('/dashboard/feed?limit=20'), + ]); + this.kpis = kpis; + this.feed = Array.isArray(feed) ? feed : (feed && feed.items) || []; + } catch (err) { + this.error = err.message || 'Fehler beim Laden der Dashboard-Daten.'; + if (window.Alpine) { + Alpine.store('notifications').error(this.error); + } + } finally { + this.loading = false; + } + }, + + formatCurrency(value) { + if (value === null || value === undefined) return '–'; + const num = Number(value); + if (Number.isNaN(num)) return '–'; + return num.toLocaleString('de-DE', { + style: 'currency', + currency: 'EUR', + maximumFractionDigits: 0, + }); + }, + + formatPercent(value) { + if (value === null || value === undefined) return '–'; + const num = Number(value); + if (Number.isNaN(num)) return '–'; + // Backend may return 0-1 or 0-100. Heuristic: >1 → already in %. + const pct = num > 1 ? num : num * 100; + return pct.toFixed(1) + ' %'; + }, + + formatDate(iso) { + if (!iso) return ''; + try { + return new Date(iso).toLocaleDateString('de-DE'); + } catch { + return iso; + } + }, + }; +} + +// Register globally for `x-data="dashboardKpis()"` +if (typeof window !== 'undefined') { + document.addEventListener('alpine:init', () => { + if (window.Alpine) { + window.Alpine.data('dashboardKpis', dashboardKpis); + } + }); +} + +export default dashboardKpis;