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