feat: add dashboard change summaries

This commit is contained in:
Christian Krakau-Louis
2026-05-22 21:40:59 +02:00
parent 40b7bc459a
commit 67e5fedce5
6 changed files with 361 additions and 8 deletions
+75
View File
@@ -116,6 +116,23 @@
{% endcall %}
</div>
<!-- What Changed -->
<div x-show="hasDomainData" x-cloak>
{% call card() %}
{% call card_header() %}
{% call card_title() %}What Changed{% endcall %}
{% call card_description() %}
New sending sources and sharp compliance changes from the last 30 days
{% endcall %}
{% endcall %}
{% call card_content() %}
<div id="change-summary-list" class="space-y-3">
<!-- Change summaries will be populated here via JavaScript -->
</div>
{% endcall %}
{% endcall %}
</div>
<!-- Top Sending Sources -->
<div x-show="hasDomainData" x-cloak>
{% call card() %}
@@ -309,12 +326,14 @@ function dashboardApp() {
} else {
this.hasDomainData = false;
this.clearDashboardCharts();
this.populateChangeSummary([]);
this.populateTopSources([]);
}
} catch (error) {
console.error('Error fetching domain summary:', error);
this.hasDomainData = false;
this.clearDashboardCharts();
this.populateChangeSummary([]);
this.populateTopSources([]);
}
},
@@ -329,6 +348,7 @@ function dashboardApp() {
const data = await response.json();
this.renderDashboardCharts(data.compliance_trend || []);
this.populateChangeSummary(data.change_summary || []);
this.populateTopSources(data.top_sources || []);
} catch (error) {
console.error('Error fetching dashboard stats:', error);
@@ -507,6 +527,61 @@ function dashboardApp() {
return new Intl.NumberFormat(undefined, { notation: 'compact' }).format(value);
},
populateChangeSummary(changes) {
const list = document.getElementById('change-summary-list');
if (!list) return;
list.textContent = '';
if (!changes || !changes.length) {
const empty = document.createElement('p');
empty.className = 'text-sm text-muted-foreground';
empty.textContent = 'No major sender or compliance changes detected.';
list.appendChild(empty);
return;
}
changes.forEach(change => {
const item = document.createElement('div');
item.className = 'rounded-md border border-base-300 bg-base-100 p-3';
const header = document.createElement('div');
header.className = 'flex items-start gap-2';
const dot = document.createElement('span');
dot.className = `mt-1 inline-flex h-2.5 w-2.5 flex-none rounded-full ${this.changeSeverityClass(change.severity)}`;
header.appendChild(dot);
const content = document.createElement('div');
content.className = 'min-w-0';
const title = document.createElement('p');
title.className = 'text-sm font-semibold';
title.textContent = change.title || 'Change detected';
content.appendChild(title);
const detail = document.createElement('p');
detail.className = 'mt-1 text-sm text-muted-foreground';
detail.textContent = change.detail || '';
content.appendChild(detail);
const action = document.createElement('p');
action.className = 'mt-1 text-sm';
action.textContent = change.action || '';
content.appendChild(action);
header.appendChild(content);
item.appendChild(header);
list.appendChild(item);
});
},
changeSeverityClass(severity) {
if (severity === 'error') return 'bg-red-500';
if (severity === 'warning') return 'bg-yellow-500';
return 'bg-blue-500';
},
populateTopSources(sources) {
const tableBody = document.getElementById('top-sources-table-body');
if (!tableBody) return;