Upload app/webui/components/dashboard-kpis.js
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user