Platform bundle: marketing publish, IT ops, packaging, agents mesh.
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
window.BriefingCharts = (function () {
|
||||
var charts = {};
|
||||
|
||||
function destroyAll() {
|
||||
Object.keys(charts).forEach(function (k) {
|
||||
if (charts[k]) { charts[k].destroy(); charts[k] = null; }
|
||||
});
|
||||
}
|
||||
|
||||
function parseContent(content) {
|
||||
var parts = (content || '').split(/\n---\n/);
|
||||
var ai = parts[0] || '';
|
||||
var summary = '';
|
||||
var actions = [];
|
||||
var sm = ai.match(/##\s*Samenvatting\s*\n([\s\S]*?)(?=##\s*Actiepunten|$)/i);
|
||||
if (sm) summary = sm[1].trim();
|
||||
var am = ai.match(/##\s*Actiepunten[^\n]*\n([\s\S]*)/i);
|
||||
if (am) {
|
||||
actions = am[1].split('\n').map(function (l) { return l.replace(/^[-*]\s*/, '').trim(); }).filter(Boolean);
|
||||
}
|
||||
if (!summary && ai.trim()) summary = ai.trim().slice(0, 600);
|
||||
return { summary: summary, actions: actions };
|
||||
}
|
||||
|
||||
function chartColors() {
|
||||
return {
|
||||
gold: 'rgba(232, 168, 56, 0.85)',
|
||||
goldDim: 'rgba(232, 168, 56, 0.35)',
|
||||
cyan: 'rgba(56, 189, 248, 0.85)',
|
||||
green: 'rgba(62, 207, 142, 0.85)',
|
||||
red: 'rgba(239, 68, 68, 0.85)',
|
||||
gray: 'rgba(107, 114, 128, 0.85)',
|
||||
grid: 'rgba(148, 163, 184, 0.12)',
|
||||
text: '#94a3b8',
|
||||
};
|
||||
}
|
||||
|
||||
function renderPipeline(canvas, stats) {
|
||||
if (!canvas) return;
|
||||
var rows = stats.deals_by_stage || [];
|
||||
var c = chartColors();
|
||||
charts.pipeline = new Chart(canvas, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: rows.map(function (r) { return r.stage || '?'; }),
|
||||
datasets: [{
|
||||
label: 'Pipeline EUR',
|
||||
data: rows.map(function (r) { return Number(r.total) || 0; }),
|
||||
backgroundColor: c.gold,
|
||||
borderRadius: 6,
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: { legend: { display: false }, title: { display: true, text: 'Pipeline per stage', color: c.text } },
|
||||
scales: {
|
||||
y: { ticks: { color: c.text, callback: function (v) { return '€' + v.toLocaleString('nl-NL'); } }, grid: { color: c.grid } },
|
||||
x: { ticks: { color: c.text }, grid: { display: false } },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderSentiment(canvas, stats) {
|
||||
if (!canvas) return;
|
||||
var files = stats.nas_files || [];
|
||||
var counts = { positive: 0, neutral: 0, negative: 0 };
|
||||
files.forEach(function (f) {
|
||||
var s = (f.sentiment_label || 'neutral').toLowerCase();
|
||||
if (counts[s] !== undefined) counts[s]++;
|
||||
});
|
||||
if (!files.length) { counts.neutral = 1; }
|
||||
var c = chartColors();
|
||||
charts.sentiment = new Chart(canvas, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Positief', 'Neutraal', 'Negatief'],
|
||||
datasets: [{ data: [counts.positive, counts.neutral, counts.negative], backgroundColor: [c.green, c.gray, c.red], borderWidth: 0 }],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: { legend: { position: 'bottom', labels: { color: c.text } }, title: { display: true, text: 'NAS sentiment', color: c.text } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderWords(canvas, stats) {
|
||||
if (!canvas) return;
|
||||
var rows = (stats.top_words || []).slice(0, 8);
|
||||
var c = chartColors();
|
||||
charts.words = new Chart(canvas, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: rows.map(function (r) { return r.lemma; }),
|
||||
datasets: [{
|
||||
label: 'Count',
|
||||
data: rows.map(function (r) { return Number(r.total) || 0; }),
|
||||
backgroundColor: c.cyan,
|
||||
borderRadius: 6,
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: { legend: { display: false }, title: { display: true, text: 'Top woorden NAS', color: c.text } },
|
||||
scales: {
|
||||
x: { ticks: { color: c.text }, grid: { color: c.grid } },
|
||||
y: { ticks: { color: c.text }, grid: { display: false } },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderAgents(canvas, stats) {
|
||||
if (!canvas) return;
|
||||
var events = stats.recent_events || [];
|
||||
var map = {};
|
||||
events.forEach(function (e) {
|
||||
var a = e.agent_name || 'other';
|
||||
map[a] = (map[a] || 0) + 1;
|
||||
});
|
||||
var labels = Object.keys(map);
|
||||
var values = labels.map(function (k) { return map[k]; });
|
||||
var c = chartColors();
|
||||
charts.agents = new Chart(canvas, {
|
||||
type: 'polarArea',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{ data: values, backgroundColor: [c.gold, c.cyan, c.green, c.red, c.gray, c.goldDim] }],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: { legend: { position: 'bottom', labels: { color: c.text } }, title: { display: true, text: 'Agent activiteit', color: c.text } },
|
||||
scales: { r: { ticks: { display: false }, grid: { color: c.grid } } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderKpis(container, stats) {
|
||||
if (!container) return;
|
||||
var items = [
|
||||
{ label: 'Pipeline', value: '€' + Math.round(stats.pipeline_eur || 0).toLocaleString('nl-NL'), cls: 'green' },
|
||||
{ label: 'Klanten', value: stats.clients || 0, cls: 'purple' },
|
||||
{ label: 'Deals', value: stats.deals || 0, cls: '' },
|
||||
{ label: 'NAS docs', value: stats.nas_docs || 0, cls: 'cyan' },
|
||||
{ label: 'Goedkeuringen', value: stats.pending_approvals || 0, cls: 'amber' },
|
||||
];
|
||||
container.innerHTML = items.map(function (it) {
|
||||
return '<div class="briefing-stat ' + it.cls + '"><span class="briefing-stat-label">' + it.label + '</span><span class="briefing-stat-value">' + it.value + '</span></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderNasList(container, stats) {
|
||||
if (!container) return;
|
||||
var files = stats.nas_files || [];
|
||||
if (!files.length) { container.innerHTML = '<p class="empty-state">Geen NAS documenten.</p>'; return; }
|
||||
container.innerHTML = '<table class="data-table"><thead><tr><th>Bestand</th><th>Type</th><th>Sentiment</th><th>Woorden</th></tr></thead><tbody>' +
|
||||
files.map(function (f) {
|
||||
var badge = 'badge-' + (f.sentiment_label || 'neutral');
|
||||
return '<tr><td>' + (f.filename || '') + '</td><td>' + (f.doc_type || '') + '</td><td><span class="badge ' + badge + '">' + (f.sentiment_label || '') + '</span></td><td>' + (f.word_count || '—') + '</td></tr>';
|
||||
}).join('') + '</tbody></table>';
|
||||
}
|
||||
|
||||
function renderPending(container, stats) {
|
||||
if (!container) return;
|
||||
var items = stats.pending_items || [];
|
||||
if (!items.length) { container.innerHTML = '<p class="empty-state">Geen openstaande goedkeuringen 🎉</p>'; return; }
|
||||
container.innerHTML = items.map(function (it) {
|
||||
return '<div class="briefing-pending-item"><span class="badge badge-agent-' + (it.agent_name || '').toLowerCase() + '">' + (it.agent_name || '') + '</span> ' + (it.title || it.event_type || '') + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function render(root, stats, content, createdAt) {
|
||||
if (!root || !stats) return;
|
||||
destroyAll();
|
||||
var parsed = parseContent(content);
|
||||
|
||||
root.style.display = 'block';
|
||||
var empty = document.getElementById('briefing-empty');
|
||||
if (empty) empty.style.display = 'none';
|
||||
|
||||
var meta = document.getElementById('briefing-meta');
|
||||
if (meta) meta.textContent = createdAt ? ('Laatst bijgewerkt: ' + String(createdAt).substring(0, 19).replace('T', ' ')) : '';
|
||||
|
||||
var sumEl = document.getElementById('briefing-summary-text');
|
||||
if (sumEl) sumEl.textContent = parsed.summary || (content ? 'Geen samenvatting.' : 'Klik op «Genereer dagrapport» voor een AI-samenvatting en actiepunten.');
|
||||
|
||||
var actEl = document.getElementById('briefing-actions-list');
|
||||
if (actEl) {
|
||||
actEl.innerHTML = parsed.actions.length
|
||||
? parsed.actions.map(function (a) { return '<li>' + a + '</li>'; }).join('')
|
||||
: '<li>Geen actiepunten — genereer opnieuw.</li>';
|
||||
}
|
||||
|
||||
renderKpis(document.getElementById('briefing-kpis'), stats);
|
||||
renderPipeline(document.getElementById('chart-pipeline'), stats);
|
||||
renderSentiment(document.getElementById('chart-sentiment'), stats);
|
||||
renderWords(document.getElementById('chart-words'), stats);
|
||||
renderAgents(document.getElementById('chart-agents'), stats);
|
||||
renderNasList(document.getElementById('briefing-nas-list'), stats);
|
||||
renderPending(document.getElementById('briefing-pending-list'), stats);
|
||||
|
||||
var full = document.getElementById('briefing-full-text');
|
||||
if (full) full.textContent = content || '';
|
||||
}
|
||||
|
||||
return { render: render, destroyAll: destroyAll, parseContent: parseContent };
|
||||
})();
|
||||
Reference in New Issue
Block a user