Polish CEO dashboard UI with fixed live dock and cleaner layout.
Remove page scroll hijacking during briefing generation, replace nested scroll panels with timeline and grid layouts, and refine the command bar and hero for a more professional feel.
This commit is contained in:
@@ -1,6 +1,174 @@
|
||||
/* Herman dashboard — Hermes neo style (KPI cards animate, grote panelen vast) */
|
||||
|
||||
.hm-neo { --hm-cyan: #00e5ff; --hm-lime: #b8ff3c; --hm-orange: #ff9f43; --hm-purple: #a855f7; --hm-gold: #ffd700; --hm-panel: #0d1219; --hm-border: rgba(0, 229, 255, 0.12); }
|
||||
.hm-neo { --hm-cyan: #00e5ff; --hm-lime: #b8ff3c; --hm-orange: #ff9f43; --hm-purple: #a855f7; --hm-gold: #ffd700; --hm-panel: #0d1219; --hm-border: rgba(0, 229, 255, 0.12); --hm-radius: 16px; --hm-shadow: 0 12px 40px rgba(0, 0, 0, 0.35); }
|
||||
|
||||
.herman-shell.hm-neo { scroll-behavior: auto; }
|
||||
|
||||
.hm-hero-pro {
|
||||
padding: 1.35rem 1.5rem 1.1rem;
|
||||
box-shadow: var(--hm-shadow);
|
||||
}
|
||||
.hm-hero-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.85rem;
|
||||
}
|
||||
.hm-hero-eyebrow {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hm-hero-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(0, 229, 255, 0.18);
|
||||
font-size: 0.72rem;
|
||||
color: #cbd5e1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hm-live-badge {
|
||||
font-size: 0.62rem;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.35rem;
|
||||
padding: 0.18rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
border: 1px solid rgba(34, 197, 94, 0.35);
|
||||
color: #86efac;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.hm-command-bar {
|
||||
position: sticky;
|
||||
top: 0.5rem;
|
||||
z-index: 30;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.85rem 1rem;
|
||||
border-radius: var(--hm-radius);
|
||||
background: rgba(8, 12, 18, 0.92);
|
||||
border: 1px solid var(--hm-border);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
.briefing-meta {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.72rem;
|
||||
color: #94a3b8;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.hm-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
.hm-card-head h4 { margin: 0; }
|
||||
.hm-card-link {
|
||||
font-size: 0.68rem;
|
||||
color: #38bdf8;
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.hm-card-link:hover { color: #7dd3fc; }
|
||||
|
||||
.hm-activity-card { border: 1px solid rgba(148, 163, 184, 0.12); }
|
||||
.hm-activity-timeline {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.hm-activity-item {
|
||||
display: grid;
|
||||
grid-template-columns: 10px 1fr;
|
||||
gap: 0.65rem;
|
||||
align-items: start;
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
border: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.hm-activity-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-top: 0.35rem;
|
||||
background: var(--hm-cyan);
|
||||
box-shadow: 0 0 8px rgba(0, 229, 255, 0.45);
|
||||
}
|
||||
.hm-activity-text {
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.45;
|
||||
color: #dbeafe;
|
||||
}
|
||||
.hm-activity-empty {
|
||||
font-size: 0.78rem;
|
||||
color: #64748b;
|
||||
padding: 0.35rem 0;
|
||||
}
|
||||
|
||||
.hm-live-dock {
|
||||
position: fixed;
|
||||
right: 1.25rem;
|
||||
bottom: 1.25rem;
|
||||
width: min(420px, calc(100vw - 2rem));
|
||||
z-index: 120;
|
||||
padding: 0.85rem 1rem;
|
||||
background: rgba(8, 12, 18, 0.97);
|
||||
border: 1px solid rgba(0, 229, 255, 0.35);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.55), 0 0 24px rgba(0, 229, 255, 0.1);
|
||||
backdrop-filter: blur(14px);
|
||||
animation: hmDockIn 0.28s ease-out;
|
||||
}
|
||||
@keyframes hmDockIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.hm-live-dock-active { border-color: var(--hm-cyan); }
|
||||
.hm-live-dock-done { border-color: rgba(34, 197, 94, 0.45); }
|
||||
.hm-live-dock-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
.hm-live-log-compact {
|
||||
max-height: min(36vh, 280px);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 229, 255, 0.35) transparent;
|
||||
}
|
||||
.hm-live-log-compact::-webkit-scrollbar { width: 6px; }
|
||||
.hm-live-log-compact::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 229, 255, 0.28);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.hm-live-dock {
|
||||
right: 0.75rem;
|
||||
left: 0.75rem;
|
||||
width: auto;
|
||||
}
|
||||
.hm-command-bar { position: relative; top: auto; }
|
||||
}
|
||||
|
||||
.herman-shell.hm-neo .herman-hero {
|
||||
background: linear-gradient(160deg, rgba(13, 18, 25, 0.95), rgba(8, 12, 18, 0.98));
|
||||
@@ -114,8 +282,11 @@
|
||||
}
|
||||
|
||||
.briefing-card.summary,
|
||||
.horizon-card {
|
||||
background: var(--hm-panel); border-radius: 14px; padding: 1rem;
|
||||
.horizon-card,
|
||||
.briefing-card.hm-activity-card {
|
||||
background: var(--hm-panel); border-radius: var(--hm-radius); padding: 1rem 1.05rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.briefing-card.summary { border: 1px solid var(--hm-border); }
|
||||
.briefing-card.summary h4,
|
||||
@@ -237,7 +408,8 @@
|
||||
|
||||
.hm-chart-panel {
|
||||
background: var(--hm-panel); border: 1px solid var(--hm-border);
|
||||
border-radius: 14px; padding: 1rem 1.1rem; min-height: 260px;
|
||||
border-radius: var(--hm-radius); padding: 1rem 1.1rem; min-height: 260px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.hm-chart-head {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem;
|
||||
@@ -276,16 +448,7 @@
|
||||
.page-viz-panel.viz-mode-alt .viz-eq-row { font-size: 0.82rem; padding: 0.15rem 0; }
|
||||
|
||||
.hm-live-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: rgba(8, 12, 18, 0.97);
|
||||
border: 1px solid rgba(0, 229, 255, 0.35);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), 0 0 20px rgba(0, 229, 255, 0.08);
|
||||
backdrop-filter: blur(8px);
|
||||
display: none;
|
||||
}
|
||||
.hm-live-bar-active { border-color: var(--hm-cyan); animation: hmLivePulse 2s ease-in-out infinite; }
|
||||
.hm-live-bar-done { border-color: rgba(34, 197, 94, 0.45); }
|
||||
@@ -509,9 +672,17 @@
|
||||
.app-topbar .page-tip { display: none; }
|
||||
}
|
||||
|
||||
.hm-highlight-scroll { max-height: 220px; overflow-y: auto; }
|
||||
.hm-highlight-scroll,
|
||||
.hm-highlight-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 0.65rem;
|
||||
}
|
||||
.hm-highlight-item {
|
||||
padding: 0.55rem 0; border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
padding: 0.75rem 0.85rem;
|
||||
border-radius: 12px;
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.hm-highlight-item strong { color: #e2e8f0; font-size: 0.85rem; text-decoration: none; }
|
||||
.hm-highlight-item a { color: inherit; text-decoration: none; }
|
||||
@@ -707,13 +878,10 @@
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 1000px) { .herman-rss-grid { grid-template-columns: 1fr; } }
|
||||
.herman-rss-list {
|
||||
max-height: 68vh;
|
||||
overflow-y: auto;
|
||||
.hm-rss-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.herman-rss-card { margin: 0; }
|
||||
.herman-rss-side-item {
|
||||
|
||||
@@ -2,15 +2,24 @@
|
||||
{% block extra_head %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/hermes.css" />
|
||||
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=21" />
|
||||
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=22" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="herman-shell hm-neo">
|
||||
|
||||
<div class="herman-hero">
|
||||
<h1>CEO Dashboard · Cucina <span class="live-badge">Live</span></h1>
|
||||
<p class="hero-sub">Business overzicht · retail · deals · briefing · marketing</p>
|
||||
<div class="hm-hero-links beurs-mini-row" style="margin-top:0.85rem">
|
||||
<div class="herman-hero hm-hero-pro">
|
||||
<div class="hm-hero-top">
|
||||
<div>
|
||||
<p class="hm-hero-eyebrow">Foodlinkk Command Center</p>
|
||||
<h1>CEO Dashboard <span class="live-badge hm-live-badge">Live</span></h1>
|
||||
<p class="hero-sub">Cucina B2C · retail · pipeline · Herman briefing</p>
|
||||
</div>
|
||||
<div class="hm-hero-status" id="hero-live-status">
|
||||
<span class="hm-live-dot"></span>
|
||||
<span id="briefing-meta-hero">Live data laden…</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hm-hero-links beurs-mini-row">
|
||||
<a href="/clients" class="beurs-mini-chip">👥 Klanten</a>
|
||||
<a href="/deals" class="beurs-mini-chip">💼 Deals</a>
|
||||
<a href="/packaging" class="beurs-mini-chip">📐 Packaging</a>
|
||||
@@ -34,7 +43,7 @@
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<header class="herman-briefing-header">
|
||||
<header class="herman-briefing-header hm-command-bar">
|
||||
<div>
|
||||
<h2>Dagelijkse briefing</h2>
|
||||
<div class="briefing-meta" id="briefing-meta"></div>
|
||||
@@ -49,14 +58,14 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="hm-live-bar" class="hm-live-bar" x-show="loading || liveLog.length" x-cloak :class="loading ? 'hm-live-bar-active' : 'hm-live-bar-done'">
|
||||
<div class="hm-live-bar-head">
|
||||
<div id="hm-live-bar" class="hm-live-dock" x-show="loading || liveLog.length" x-cloak :class="loading ? 'hm-live-dock-active' : 'hm-live-dock-done'">
|
||||
<div class="hm-live-dock-head">
|
||||
<span class="hm-live-dot"></span>
|
||||
<strong class="hm-live-title" x-text="loading ? loadingMsg : 'Dagrapport voltooid'"></strong>
|
||||
<span class="hm-live-progress" x-show="loading" x-text="liveProgress"></span>
|
||||
<button type="button" class="btn btn-sm hm-live-close" x-show="!loading && liveLog.length" @click="liveLog = []">✕</button>
|
||||
<button type="button" class="btn btn-sm hm-live-close" x-show="!loading && liveLog.length" @click="liveLog = []" title="Sluiten">✕</button>
|
||||
</div>
|
||||
<ol id="hm-live-log" class="hm-live-log">
|
||||
<ol id="hm-live-log" class="hm-live-log hm-live-log-compact">
|
||||
<template x-for="(entry, idx) in liveLog" :key="idx">
|
||||
<li :class="'hm-live-entry hm-live-' + (entry.status || 'ok') + (entry.type === 'agent_msg' ? ' hm-live-agent' : '')">
|
||||
<span class="hm-live-src" x-text="entry.source || entry.agent"></span>
|
||||
@@ -96,9 +105,12 @@
|
||||
<h4>Samenvatting · live</h4>
|
||||
<div id="briefing-summary-text" class="live-digest-body"></div>
|
||||
</div>
|
||||
<div class="briefing-card" style="max-height:220px;overflow-y:auto">
|
||||
<h4>📋 Herman activiteitenlog</h4>
|
||||
<ul id="briefing-activity-log" class="hm-exec-list" style="margin:0;padding-left:1.1rem;font-size:0.82rem"></ul>
|
||||
<div class="briefing-card hm-activity-card">
|
||||
<div class="hm-card-head">
|
||||
<h4>Activiteitenlog</h4>
|
||||
<a href="/agents" class="hm-card-link">Agents →</a>
|
||||
</div>
|
||||
<ul id="briefing-activity-log" class="hm-activity-timeline"></ul>
|
||||
</div>
|
||||
<div class="briefing-horizon-col">
|
||||
<div class="horizon-card short">
|
||||
@@ -129,7 +141,7 @@
|
||||
</div>
|
||||
<div class="hm-chart-panel hm-dash-trends-only" style="margin-top:1rem">
|
||||
<div class="hm-chart-head"><h4>Markt highlights</h4><small>M&A · retail</small></div>
|
||||
<div id="briefing-food-highlights" class="hm-highlight-scroll"></div>
|
||||
<div id="briefing-food-highlights" class="hm-highlight-grid"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -442,9 +454,12 @@ function dashboardBriefing() {
|
||||
renderActivityLog(entries) {
|
||||
const el = document.getElementById('briefing-activity-log');
|
||||
if (!el) return;
|
||||
el.innerHTML = (entries || []).length
|
||||
? entries.slice(0, 12).map(e => '<li>' + e + '</li>').join('')
|
||||
: '<li class="muted">Nog geen agent-acties vandaag — Herman documenteert automatisch.</li>';
|
||||
const rows = (entries || []).slice(0, 10);
|
||||
el.innerHTML = rows.length
|
||||
? rows.map(function (e, i) {
|
||||
return '<li class="hm-activity-item"><span class="hm-activity-dot"></span><span class="hm-activity-text">' + e + '</span></li>';
|
||||
}).join('')
|
||||
: '<li class="hm-activity-empty">Nog geen agent-acties vandaag — Herman documenteert automatisch.</li>';
|
||||
},
|
||||
renderClientsMini(stats) {
|
||||
const el = document.getElementById('briefing-clients-mini');
|
||||
@@ -458,12 +473,13 @@ function dashboardBriefing() {
|
||||
updateLiveMeta(at, digest) {
|
||||
const tsRaw = at || (digest && digest.updated_at) || '';
|
||||
const ts = String(tsRaw).substring(0, 19).replace('T', ' ');
|
||||
const n = digest && digest.activity_count != null ? digest.activity_count + ' activiteiten' : '';
|
||||
const line = ts ? ('Ververst ' + ts + ' UTC' + (n ? ' · ' + n : '')) : 'Live data laden…';
|
||||
const meta = document.getElementById('briefing-meta');
|
||||
if (meta && ts) {
|
||||
const n = digest && digest.activity_count != null ? ' · ' + digest.activity_count + ' activiteiten' : '';
|
||||
meta.textContent = 'Live · ververst ' + ts + ' UTC' + n;
|
||||
}
|
||||
const badge = document.querySelector('.herman-hero .live-badge');
|
||||
if (meta) meta.textContent = line;
|
||||
const heroMeta = document.getElementById('briefing-meta-hero');
|
||||
if (heroMeta) heroMeta.textContent = line;
|
||||
const badge = document.querySelector('.hm-live-badge');
|
||||
if (badge && tsRaw) {
|
||||
try {
|
||||
badge.textContent = 'Live · ' + new Date(tsRaw).toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });
|
||||
@@ -510,8 +526,6 @@ function dashboardBriefing() {
|
||||
this.$nextTick(() => {
|
||||
const log = document.getElementById('hm-live-log');
|
||||
if (log) log.scrollTop = log.scrollHeight;
|
||||
const bar = document.getElementById('hm-live-bar');
|
||||
if (bar && this.loading) bar.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
},
|
||||
async generate() {
|
||||
@@ -519,9 +533,6 @@ function dashboardBriefing() {
|
||||
this.liveLog = [];
|
||||
this.liveProgress = '';
|
||||
this.loadingMsg = 'Herman start dagrapport…';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
const bar = document.getElementById('hm-live-bar');
|
||||
if (bar) bar.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
try {
|
||||
const resp = await fetch('/api/herman/briefing/stream', { method: 'POST' });
|
||||
if (!resp.ok) throw new Error('Stream mislukt (' + resp.status + ')');
|
||||
|
||||
Reference in New Issue
Block a user