86 lines
2.6 KiB
JavaScript
86 lines
2.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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;
|