Improve CEO cockpit clarity and Revenue visuals, with Hermes briefing.

Tighten overview KPIs/pipeline height, add selectable Revenue views and a resizable sidebar, and keep supporting Hermes/Wereldexport updates.
This commit is contained in:
Aissa
2026-07-30 11:53:43 +00:00
parent 5dd69a1138
commit 62239f0a4e
14 changed files with 1876 additions and 647 deletions
+35 -2
View File
@@ -1,3 +1,5 @@
:root { --sidebar-width: 280px; }
/* Sidebar — clean, uniform nav (loads last, wins over topnav-neo) */
.app-shell {
@@ -10,7 +12,7 @@
top: 0;
left: 0;
bottom: 0;
width: 220px;
width: var(--sidebar-width, 280px);
z-index: 200;
display: flex;
flex-direction: column;
@@ -21,7 +23,7 @@
}
.app-main {
margin-left: 220px;
margin-left: var(--sidebar-width, 280px);
min-width: 0;
min-height: 100vh;
}
@@ -164,6 +166,8 @@
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.86rem;
}
/* Per-item accent colors */
@@ -199,3 +203,32 @@
}
.brand-badge-foodlinkk { color: #7dd3fc; background: rgba(14, 165, 233, 0.12); border: 1px solid rgba(14, 165, 233, 0.25); }
.brand-badge-cucina { color: #fdba74; background: rgba(251, 146, 60, 0.12); border: 1px solid rgba(251, 146, 60, 0.25); }
/* sidebar resize v11 */
.app-sidebar-resizer {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 5;
background: transparent;
}
.app-sidebar-resizer:hover,
.app-sidebar-resizer.is-dragging {
background: rgba(56, 189, 248, 0.35);
}
body.sidebar-resizing {
cursor: col-resize !important;
user-select: none !important;
}
body.sidebar-resizing iframe {
pointer-events: none !important;
}
@media (max-width: 768px) {
.app-sidebar-resizer { display: none; }
}
+495
View File
@@ -894,3 +894,498 @@
.herman-rss-side-item small { color: #64748b; font-size: 0.65rem; }
.herman-rss-col-side .hm-chart-panel { margin-bottom: 0.75rem; }
.herman-rss-col-side h4 { margin: 0 0 0.5rem; font-size: 0.75rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.06em; }
/* CEO clean layout — briefing first, less noise */
.hm-ceo-clean .hm-briefing-hero {
order: -1;
}
.hm-briefing-clean {
grid-template-columns: minmax(0, 1.6fr) minmax(280px, 0.9fr);
gap: 1.25rem;
}
.hm-briefing-clean .briefing-card.summary {
padding: 1.35rem 1.5rem;
}
.hm-briefing-clean .live-digest-body {
font-size: 0.95rem;
line-height: 1.55;
color: #e2e8f0;
}
.hm-briefing-clean .live-digest-section {
margin-bottom: 1.1rem;
padding-bottom: 0.85rem;
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}
.hm-briefing-clean .live-digest-section:last-child {
border-bottom: 0;
}
.hm-ceo-clean .hm-section-title {
letter-spacing: 0.02em;
}
.hm-ceo-clean .beurs-mini-chip {
border-radius: 10px;
padding: 0.4rem 0.75rem;
}
.hm-ceo-clean .hm-eq { display: none; }
@media (max-width: 960px) {
.hm-briefing-clean { grid-template-columns: 1fr; }
}
/* Retail & markt removed from CEO overview — RSS lives in RSS tab */
.hm-ceo-clean [data-widget="retail"] { display: none !important; }
/* CEO overview: only dagmemo, no horizon/activity */
.hm-ceo-clean .briefing-horizon-col,
.hm-ceo-clean .horizon-card,
.hm-ceo-clean .hm-activity-timeline { display: none !important; }
.hm-ceo-clean .hm-briefing-solo {
grid-template-columns: 1fr;
}
.hm-ceo-clean .hm-briefing-solo .briefing-card.summary {
max-width: none;
}
/* Force CEO overview order (prefs/JS cannot override) */
.hm-ceo-clean {
display: flex;
flex-direction: column;
}
.hm-ceo-clean > [data-widget="kpis"] { order: 1; }
.hm-ceo-clean > [data-widget="executive"] { order: 2; }
.hm-ceo-clean > [data-widget="analytics"] { order: 3; }
.hm-ceo-clean > [data-widget="briefing"] { order: 4; }
.hm-ceo-clean > [data-widget="news"] { order: 5; }
.hm-ceo-clean > [data-widget="feed"] { order: 6; }
.hm-ceo-clean > [data-widget="approvals"] { order: 7; }
.hm-ceo-clean > [data-widget="retail"] { order: 99; display: none !important; }
/* ═══ CEO PRO LAYOUT ═══ */
.hm-ceo-pro {
gap: 1.15rem;
}
.hm-ceo-pro .hm-dash-section {
margin: 0;
}
.hm-ceo-pro .hm-section-title { display: block; }
/* KPI row — bold hierarchy */
.hm-ceo-kpi-row {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 0.75rem;
}
@media (max-width: 1100px) {
.hm-ceo-kpi-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
.hm-ceo-kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.hm-ceo-kpi-link { text-decoration: none; color: inherit; display: block; }
.hm-ceo-kpi {
background: linear-gradient(165deg, rgba(18, 28, 42, 0.95), rgba(8, 12, 20, 0.98));
border: 1px solid rgba(255,255,255,0.07);
border-top: 2px solid var(--kpi-accent, #00e5ff);
border-radius: 14px;
padding: 1rem 1.05rem 0.9rem;
min-height: 92px;
transition: border-color 0.15s, transform 0.15s;
}
.hm-ceo-kpi-link:hover .hm-ceo-kpi {
border-color: rgba(0, 229, 255, 0.35);
transform: translateY(-1px);
}
.hm-ceo-kpi-label {
font-size: 0.68rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #8b9cb3;
margin-bottom: 0.35rem;
}
.hm-ceo-kpi-value {
font-size: 1.55rem;
font-weight: 700;
letter-spacing: -0.02em;
color: #f1f5f9;
line-height: 1.15;
font-variant-numeric: tabular-nums;
}
.hm-ceo-kpi-sub {
margin-top: 0.3rem;
font-size: 0.7rem;
color: #64748b;
}
/* B2B | B2C */
.hm-ceo-brand-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.9rem;
}
@media (max-width: 860px) {
.hm-ceo-brand-grid { grid-template-columns: 1fr; }
}
.hm-ceo-brand-card {
background: rgba(10, 16, 26, 0.92);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 16px;
padding: 1.1rem 1.2rem 1rem;
}
.hm-ceo-brand-card.brand-foodlinkk { border-left: 3px solid #38bdf8; }
.hm-ceo-brand-card.brand-cucina { border-left: 3px solid #fb923c; }
.hm-ceo-brand-card header {
display: flex;
align-items: center;
gap: 0.55rem;
margin-bottom: 0.75rem;
}
.hm-ceo-brand-card h3 {
margin: 0;
font-size: 0.95rem;
font-weight: 650;
color: #e2e8f0;
letter-spacing: 0.01em;
}
.hm-ceo-brand-icon { font-size: 1.15rem; }
.hm-ceo-brand-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.hm-ceo-brand-list li {
font-size: 0.84rem;
line-height: 1.45;
color: #cbd5e1;
padding-left: 0.7rem;
border-left: 2px solid rgba(148,163,184,0.2);
}
/* Focus + Trends */
.hm-ceo-mid-grid {
display: grid;
grid-template-columns: 1.25fr 1fr;
gap: 0.9rem;
}
@media (max-width: 860px) {
.hm-ceo-mid-grid { grid-template-columns: 1fr; }
}
.hm-ceo-panel {
background: rgba(10, 16, 26, 0.92);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 16px;
padding: 1rem 1.15rem;
}
.hm-ceo-panel header h3 {
margin: 0 0 0.7rem;
font-size: 0.88rem;
font-weight: 650;
color: #e2e8f0;
}
.hm-ceo-focus { border-top: 2px solid #f472b6; }
.hm-ceo-trends { border-top: 2px solid #34d399; }
.hm-ceo-action-list,
.hm-ceo-trend-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.55rem;
}
.hm-ceo-action-list li,
.hm-ceo-trend-list li {
font-size: 0.88rem;
color: #e2e8f0;
line-height: 1.4;
}
.hm-ceo-bullet {
color: #f472b6;
font-weight: 700;
margin-right: 0.25rem;
}
.hm-ceo-trend-list li {
color: #a7f3d0;
font-weight: 500;
}
/* Herman memo — collapsed by default */
.hm-ceo-memo {
margin-top: 0.9rem;
background: rgba(10, 16, 26, 0.7);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 12px;
padding: 0.55rem 0.9rem;
}
.hm-ceo-memo summary {
cursor: pointer;
color: #94a3b8;
font-size: 0.78rem;
letter-spacing: 0.04em;
text-transform: uppercase;
list-style: none;
}
.hm-ceo-memo summary::-webkit-details-marker { display: none; }
.hm-ceo-memo[open] summary { margin-bottom: 0.65rem; color: #cbd5e1; }
.hm-ceo-memo-body {
font-size: 0.86rem;
color: #cbd5e1;
line-height: 1.55;
}
.hm-ceo-memo-body .live-digest-body {
display: block !important;
grid-template-columns: none !important;
}
/* News collapsible */
.hm-ceo-news-details {
background: rgba(10, 16, 26, 0.85);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 14px;
padding: 0.75rem 1rem 0.9rem;
}
.hm-ceo-news-details summary {
cursor: pointer;
font-size: 0.9rem;
font-weight: 650;
color: #e2e8f0;
list-style: none;
display: flex;
align-items: baseline;
gap: 0.6rem;
}
.hm-ceo-news-details summary::-webkit-details-marker { display: none; }
.hm-ceo-news-details summary small {
font-weight: 400;
font-size: 0.72rem;
color: #64748b;
}
.hm-ceo-news-details summary a { color: #38bdf8; }
.hm-ceo-news-list {
list-style: none;
margin: 0.75rem 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.hm-ceo-news-list li {
display: flex;
flex-direction: column;
gap: 0.15rem;
padding: 0.45rem 0.55rem;
border-radius: 8px;
background: rgba(0,0,0,0.22);
}
.hm-ceo-news-list a {
color: #e2e8f0;
text-decoration: none;
font-size: 0.86rem;
line-height: 1.35;
}
.hm-ceo-news-list a:hover { color: #7dd3fc; }
.hm-ceo-news-list small { color: #64748b; font-size: 0.7rem; }
/* Hide noisy chrome on CEO pro */
.hm-ceo-pro ~ .herman-browser,
.herman-shell:has(.hm-ceo-pro) .hm-eq { display: none; }
.hm-ceo-memo-open {
margin-top: 0.9rem;
background: rgba(10, 16, 26, 0.85);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 14px;
padding: 0.9rem 1.1rem;
}
.hm-ceo-memo-label {
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #94a3b8;
margin-bottom: 0.55rem;
}
/* Pipeline HTML bars — geen Chart.js ghosting */
.hm-pipeline-host { min-height: 140px; }
.hm-pipeline-bars { padding: 0.25rem 0.15rem 0.5rem; }
.hm-pipeline-head {
display: flex; justify-content: space-between; align-items: baseline;
margin-bottom: 0.85rem; gap: 0.75rem;
}
.hm-pipeline-head strong { color: #f1f5f9; font-size: 1.05rem; }
.hm-pipeline-head span { color: #94a3b8; font-size: 0.82rem; }
.hm-pipeline-list { display: flex; flex-direction: column; gap: 0.7rem; }
.hm-pipeline-meta {
display: flex; justify-content: space-between; gap: 0.75rem;
margin-bottom: 0.3rem; font-size: 0.82rem;
}
.hm-pipeline-stage { color: #e2e8f0; font-weight: 600; text-transform: capitalize; }
.hm-pipeline-vals { color: #94a3b8; }
.hm-pipeline-track {
height: 10px; border-radius: 999px;
background: rgba(255,255,255,0.06); overflow: hidden;
}
.hm-pipeline-fill {
height: 100%; border-radius: 999px;
background: linear-gradient(90deg, #fbbf24, #f59e0b);
}
.hm-pipeline-foot { margin: 0.85rem 0 0; font-size: 0.78rem; }
.hm-pipeline-foot a { color: #38bdf8; text-decoration: none; }
/* CEO context-rich overview */
.hm-overview-panel { border-color: rgba(0,229,255,0.14); }
.hm-context-banner {
background: linear-gradient(120deg, rgba(14, 116, 144, 0.18), rgba(15, 23, 42, 0.4));
border: 1px solid rgba(56, 189, 248, 0.2);
border-radius: 12px;
padding: 0.85rem 1rem;
margin-bottom: 0.9rem;
}
.hm-context-banner p {
margin: 0;
color: #e2e8f0;
font-size: 0.92rem;
line-height: 1.5;
}
.hm-exec-grid-rich {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.65rem;
}
@media (max-width: 900px) {
.hm-exec-grid-rich { grid-template-columns: 1fr 1fr; }
}
.hm-exec-rich {
background: rgba(8, 12, 20, 0.65);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 12px;
padding: 0.75rem 0.85rem;
text-decoration: none;
color: inherit;
display: flex;
gap: 0.65rem;
align-items: flex-start;
transition: border-color 0.15s, transform 0.15s;
}
.hm-exec-rich:hover { border-color: rgba(0,229,255,0.35); transform: translateY(-1px); }
.hm-exec-rich .hm-exec-val { font-size: 1.2rem; font-weight: 700; color: #f8fafc; margin-top: 0.15rem; }
.hm-exec-sub { font-size: 0.7rem; color: #64748b; margin-top: 0.15rem; }
.hm-context-block { margin-top: 0.95rem; }
.hm-context-block h4 {
margin: 0 0 0.45rem;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #94a3b8;
}
.hm-deal-chips { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.hm-deal-chip {
display: inline-flex; align-items: center; gap: 0.45rem;
background: rgba(0,0,0,0.28);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 999px;
padding: 0.35rem 0.7rem;
text-decoration: none;
color: #e2e8f0;
font-size: 0.78rem;
}
.hm-deal-chip span { color: #86efac; font-weight: 650; }
.hm-deal-chip em { color: #94a3b8; font-style: normal; font-size: 0.7rem; }
.hm-deal-chip-client span { color: #7dd3fc; }
.hm-context-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.hm-context-list li { font-size: 0.84rem; color: #cbd5e1; }
.hm-score {
display: inline-block; margin-left: 0.25rem;
font-size: 0.68rem; color: #fbbf24;
background: rgba(251,191,36,0.12);
border-radius: 4px; padding: 0.05rem 0.35rem;
}
.hm-ceo-brand-card { min-height: 160px; }
.hm-ceo-pro .hm-briefing-hero, .hm-ceo-pro .hm-ceo-mid { margin-top: 0.15rem; }
/* v32-kpi-six — 6 kerncijfers */
.hm-neo-kpi-row {
display: grid !important;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 0.65rem;
}
@media (max-width: 1400px) {
.hm-neo-kpi-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 800px) {
.hm-neo-kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* v33: CEO pipeline compact — geen lege scroll-ruimte */
.hm-ceo-clean .hm-dash-analytics .hm-analytics-panel,
.hm-ceo-pro .hm-dash-analytics .hm-analytics-panel {
min-height: 0 !important;
height: auto !important;
padding: 0.75rem 0.9rem;
}
.hm-ceo-clean .hm-pipeline-host,
.hm-ceo-pro .hm-pipeline-host,
.hm-ceo-clean .hm-dash-analytics .hm-chart-wrap,
.hm-ceo-pro .hm-dash-analytics .hm-chart-wrap {
height: auto !important;
min-height: 0 !important;
}
.hm-ceo-clean .hm-neo-charts,
.hm-ceo-pro .hm-neo-charts {
display: block;
margin-bottom: 0;
}
.hm-ceo-clean .hm-dash-analytics .hm-section-title {
display: none; /* panel head is genoeg — geen dubbele Pipeline-titel */
}
.hm-ceo-clean .hm-pipeline-bars {
padding: 0;
}
.hm-ceo-clean .hm-pipeline-list {
gap: 0.45rem;
}
.hm-ceo-clean .hm-pipeline-foot {
margin: 0.55rem 0 0;
}
/* Briefing + nieuws compacter */
.hm-ceo-clean .hm-ceo-mid-grid {
gap: 0.65rem;
}
.hm-ceo-clean .hm-ceo-panel {
padding: 0.7rem 0.85rem;
}
.hm-ceo-clean .hm-ceo-memo {
margin-top: 0.65rem;
max-height: none;
}
.hm-ceo-clean .hm-ceo-memo-body {
max-height: 180px;
overflow-y: auto;
font-size: 0.88rem;
line-height: 1.45;
}
.hm-ceo-clean .hm-ceo-memo:not([open]) .hm-ceo-memo-body {
display: none;
}
.hm-ceo-clean .hm-ceo-news-details {
margin: 0;
}
.hm-ceo-clean .hm-ceo-action-list,
.hm-ceo-clean .hm-ceo-trend-list {
max-height: 140px;
overflow-y: auto;
margin: 0;
}
/* Minder verticale gap tussen secties */
.hm-ceo-pro {
gap: 0.85rem !important;
}
.hm-ceo-clean .hm-dash-section {
margin-bottom: 0 !important;
}
+473 -8
View File
@@ -16,10 +16,24 @@
font-size: 11pt;
}
.persona-ceo .main-topnav:has(.rc-excel-page),
.main-topnav:has(.rc-excel-page) {
.persona-ceo .main-topnav:has(.rc-page.rc-mode-sheet),
.main-topnav:has(.rc-page.rc-mode-sheet) {
background: #e8e8e8;
}
/* Cockpit-modus: altijd donker, nooit Excel-grijs */
.persona-ceo .main-topnav:has(.rc-page:not(.rc-mode-sheet)),
.main-topnav:has(.rc-page:not(.rc-mode-sheet)) {
background: transparent !important;
}
.rc-page:not(.rc-mode-sheet) {
background: transparent !important;
}
.rc-page:not(.rc-mode-sheet) .rc-header h1 {
color: #e2e8f0;
}
.rc-page:not(.rc-mode-sheet) .rc-subtitle {
color: #94a3b8;
}
.rc-toolbar {
display: flex;
@@ -371,12 +385,12 @@
}
/* Status = gekleurde linkerrand, neutrale donkere rij */
.rc-visual-row.rc-style-green td { box-shadow: inset 4px 0 0 #4ade80; }
.rc-visual-row.rc-style-red td { box-shadow: inset 4px 0 0 #f87171; }
.rc-visual-row.rc-style-orange td { box-shadow: inset 4px 0 0 #fb923c; }
.rc-visual-row.rc-style-yellow td { box-shadow: inset 4px 0 0 #facc15; color: #fef9c3; }
.rc-visual-row.rc-style-blue td { box-shadow: inset 4px 0 0 #60a5fa; }
.rc-visual-row.rc-style-white td { box-shadow: inset 4px 0 0 #94a3b8; }
.rc-visual-row.rc-style-green td { box-shadow: none; }
.rc-visual-row.rc-style-red td { box-shadow: none; }
.rc-visual-row.rc-style-orange td { box-shadow: none; }
.rc-visual-row.rc-style-yellow td { box-shadow: none; color: #fef9c3; }
.rc-visual-row.rc-style-blue td { box-shadow: none; }
.rc-visual-row.rc-style-white td { box-shadow: none; }
.rc-vdeal { font-weight: 700; }
.rc-deal-name { display: block; font-size: 0.95rem; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -491,3 +505,454 @@
.rc-filter-inline { margin-left: 0; max-width: none; flex: 1 1 100%; }
.rc-expand-inner { flex-direction: column; }
}
/* ── v10: leesbaarheid — géén witte achtergronden in cockpit ── */
.rc-page:not(.rc-mode-sheet) {
color: #e2e8f0;
}
.rc-cockpit .rc-visual-table,
.rc-cockpit .rc-visual-table td,
.rc-cockpit .rc-visual-table .rc-vcell {
background-color: #151c28 !important;
color: #e2e8f0 !important;
border-left: 0 !important;
border-right: 0 !important;
}
.rc-cockpit .rc-visual-head td {
background-color: #1a2332 !important;
color: #cbd5e1 !important;
}
.rc-cockpit .rc-visual-goals td {
background-color: #2a2410 !important;
color: #fde68a !important;
}
.rc-cockpit .rc-visual-row:hover td {
background-color: #1e2a3a !important;
}
.rc-cockpit .rc-visual-row.rc-row-open td {
background-color: #1a3050 !important;
}
.rc-cockpit .rc-deal-name,
.rc-cockpit .rc-vamount {
color: #f8fafc !important;
}
.rc-cockpit .rc-vamount { color: #86efac !important; }
.rc-cockpit .rc-vsteps { color: #cbd5e1 !important; }
.rc-cockpit .rc-expand-row td,
.rc-cockpit .rc-expand-inner {
background-color: #0a0e12 !important;
color: #e2e8f0 !important;
}
.rc-cockpit .rc-summary-strip {
background: #1a2332 !important;
color: #e2e8f0 !important;
}
.rc-cockpit .rc-sum-item strong { color: #86efac !important; }
.rc-cockpit .rc-sum-label { color: #94a3b8 !important; }
.rc-cockpit .rc-filter-search,
.rc-cockpit input[type="search"],
.rc-cockpit input[type="text"],
.rc-cockpit select {
background: #0f1419 !important;
color: #e2e8f0 !important;
border: 1px solid rgba(148,163,184,0.25) !important;
}
.rc-cockpit .rc-filter-search::placeholder {
color: #64748b !important;
}
/* Excel wit mag alleen in Bewerken-modus */
.rc-page:not(.rc-mode-sheet) .rc-btn,
.rc-page:not(.rc-mode-sheet) .rc-toggle-btn {
background: #1e293b;
color: #e2e8f0;
border-color: rgba(148,163,184,0.3);
}
.rc-page:not(.rc-mode-sheet) .rc-toggle-btn.active {
background: #14532d;
color: #bbf7d0;
border-color: #22c55e;
}
.rc-visual-row td + td { box-shadow: none !important; }
/* ── v11: wit vlak + verticale lijn weg ── */
.main-topnav:has(.rc-page:not(.rc-mode-sheet)),
.persona-ceo .main-topnav:has(.rc-page:not(.rc-mode-sheet)) {
background: transparent !important;
background-color: transparent !important;
}
.rc-page:not(.rc-mode-sheet),
.rc-page:not(.rc-mode-sheet) .rc-header,
.rc-page:not(.rc-mode-sheet) .rc-cockpit {
background: transparent !important;
background-color: transparent !important;
}
.rc-page:not(.rc-mode-sheet) .rc-header h1 { color: #f1f5f9 !important; }
.rc-page:not(.rc-mode-sheet) .rc-header .live-badge { color: #86efac; }
/* Geen statusstreep meer links in de tabel (= die witte verticale lijn) */
.rc-cockpit .rc-visual-row td,
.rc-cockpit .rc-visual-row td:first-child {
box-shadow: none !important;
border-left: 0 !important;
}
.rc-cockpit .rc-visual-wrap {
border: 1px solid rgba(148,163,184,0.12);
box-shadow: none;
}
/* Statuskleur als klein bolletje bij de dealnaam i.p.v. streep */
.rc-deal-name::before {
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
margin-right: 0.45rem;
vertical-align: middle;
background: #64748b;
}
.rc-style-green .rc-deal-name::before { background: #4ade80; }
.rc-style-red .rc-deal-name::before { background: #f87171; }
.rc-style-orange .rc-deal-name::before { background: #fb923c; }
.rc-style-yellow .rc-deal-name::before { background: #facc15; }
.rc-style-blue .rc-deal-name::before { background: #60a5fa; }
.rc-style-white .rc-deal-name::before { background: #64748b; }
/* ── v12: selecteerbare visuals ── */
.rc-visual-toggle {
display: inline-flex;
border: 1px solid rgba(148,163,184,0.22);
border-radius: 8px;
overflow: hidden;
margin-left: 0.25rem;
}
.rc-vis-btn {
border: none;
background: #121a24;
color: #94a3b8;
padding: 0.32rem 0.7rem;
font-size: 0.72rem;
font-weight: 650;
letter-spacing: 0.02em;
cursor: pointer;
}
.rc-vis-btn + .rc-vis-btn { border-left: 1px solid rgba(148,163,184,0.18); }
.rc-vis-btn.active {
background: #14532d;
color: #bbf7d0;
}
.rc-vis-btn:hover { color: #e2e8f0; }
.rc-style-chips {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
align-items: center;
}
.rc-chip {
border: 1px solid rgba(148,163,184,0.22);
background: #121a24;
color: #cbd5e1;
border-radius: 999px;
padding: 0.18rem 0.55rem;
font-size: 0.68rem;
font-weight: 650;
cursor: pointer;
line-height: 1.2;
}
.rc-chip:hover { border-color: rgba(148,163,184,0.45); }
.rc-chip.active {
box-shadow: 0 0 0 1px rgba(56,189,248,0.55);
color: #f8fafc;
}
.rc-chip-static { cursor: default; }
.rc-chip-green { border-color: rgba(74,222,128,0.45); color: #86efac; }
.rc-chip-red { border-color: rgba(248,113,113,0.45); color: #fca5a5; }
.rc-chip-orange { border-color: rgba(251,146,60,0.45); color: #fdba74; }
.rc-chip-yellow { border-color: rgba(250,204,21,0.45); color: #fde047; }
.rc-chip-blue { border-color: rgba(96,165,250,0.45); color: #93c5fd; }
.rc-chip-white { border-color: rgba(148,163,184,0.35); color: #94a3b8; }
.rc-chip-green.active { background: rgba(22,101,52,0.45); }
.rc-chip-red.active { background: rgba(127,29,29,0.4); }
.rc-chip-orange.active { background: rgba(154,52,18,0.4); }
.rc-chip-yellow.active { background: rgba(113,63,18,0.4); }
.rc-chip-blue.active { background: rgba(30,64,175,0.4); }
.rc-chip-white.active { background: rgba(51,65,85,0.5); }
.rc-goals-banner {
display: flex;
flex-wrap: wrap;
gap: 0.35rem 0.55rem;
align-items: baseline;
margin-bottom: 0.75rem;
padding: 0.55rem 0.75rem;
border-radius: 10px;
background: #1a1520;
border: 1px solid rgba(250,204,21,0.22);
color: #fde68a;
font-size: 0.78rem;
line-height: 1.35;
}
.rc-goals-banner em {
margin-left: auto;
color: #facc15;
font-style: italic;
}
.rc-goals-sep { color: #78716c; }
/* Bars view */
.rc-bars-view {
display: flex;
flex-direction: column;
gap: 0.4rem;
background: #0b1017;
border: 1px solid rgba(148,163,184,0.12);
border-radius: 12px;
padding: 0.65rem;
}
.rc-bar-row {
display: block;
width: 100%;
text-align: left;
border: 1px solid rgba(148,163,184,0.1);
background: #141c28;
border-radius: 10px;
padding: 0.55rem 0.7rem;
cursor: pointer;
color: inherit;
}
.rc-bar-row:hover { border-color: rgba(148,163,184,0.28); background: #182232; }
.rc-bar-row.active { border-color: rgba(56,189,248,0.4); background: #152033; }
.rc-bar-row-top {
display: grid;
grid-template-columns: minmax(0,1fr) auto auto;
gap: 0.65rem;
align-items: center;
margin-bottom: 0.35rem;
}
.rc-bar-name {
font-size: 0.86rem;
font-weight: 700;
color: #f1f5f9;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rc-bar-amt { font-weight: 800; color: #86efac; font-variant-numeric: tabular-nums; font-size: 0.86rem; }
.rc-bar-share { font-size: 0.72rem; color: #94a3b8; min-width: 2.5rem; text-align: right; }
.rc-bar-track {
height: 8px;
background: rgba(255,255,255,0.06);
border-radius: 999px;
overflow: hidden;
}
.rc-bar-fill { height: 100%; border-radius: 999px; min-width: 2px; }
/* Cards view */
.rc-cards-view {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.65rem;
}
@media (max-width: 1100px) {
.rc-cards-view { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
.rc-cards-view { grid-template-columns: 1fr; }
}
.rc-card {
background: #141c28;
border: 1px solid rgba(148,163,184,0.12);
border-radius: 12px;
padding: 0.75rem 0.85rem;
cursor: pointer;
min-height: 140px;
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.rc-card:hover { border-color: rgba(148,163,184,0.3); background: #182232; }
.rc-card.active { border-color: rgba(56,189,248,0.4); background: #152033; }
.rc-card-head {
display: flex;
justify-content: space-between;
gap: 0.5rem;
align-items: flex-start;
}
.rc-card-head h3 {
margin: 0;
font-size: 0.9rem;
color: #f8fafc;
line-height: 1.3;
}
.rc-card-metrics {
display: flex;
gap: 0.85rem;
}
.rc-card-metrics div { display: flex; flex-direction: column; gap: 0.1rem; }
.rc-card-metrics span {
font-size: 0.58rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #64748b;
}
.rc-card-metrics strong {
font-size: 0.92rem;
color: #86efac;
font-variant-numeric: tabular-nums;
}
.rc-card-bar {
height: 4px;
background: rgba(255,255,255,0.06);
border-radius: 999px;
overflow: hidden;
}
.rc-card-bar > div { height: 100%; border-radius: 999px; }
.rc-card-steps {
margin: 0;
margin-top: auto;
font-size: 0.75rem;
color: #94a3b8;
line-height: 1.35;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Detail panel */
.rc-detail {
margin-top: 0.85rem;
background: #0f1520;
border: 1px solid rgba(56,189,248,0.22);
border-radius: 14px;
padding: 0.9rem 1rem;
}
.rc-detail-head {
display: flex;
justify-content: space-between;
gap: 1rem;
align-items: flex-start;
margin-bottom: 0.75rem;
}
.rc-detail-head h3 {
margin: 0 0 0.35rem;
font-size: 1.05rem;
color: #f8fafc;
}
.rc-detail-close {
border: 1px solid rgba(148,163,184,0.25);
background: #1e293b;
color: #cbd5e1;
border-radius: 8px;
width: 32px; height: 32px;
cursor: pointer;
}
.rc-detail-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0,1fr)) auto;
gap: 0.75rem;
align-items: center;
margin-bottom: 0.75rem;
}
.rc-detail-stat span {
display: block;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
margin-bottom: 0.2rem;
}
.rc-detail-stat strong {
font-size: 1.15rem;
color: #86efac;
font-variant-numeric: tabular-nums;
}
.rc-detail-ring {
width: 56px; height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.rc-detail-ring span {
font-size: 0.7rem;
font-weight: 800;
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,0.55);
}
.rc-detail-compare {
display: flex;
gap: 1rem;
align-items: flex-end;
height: 72px;
margin-bottom: 0.85rem;
padding: 0.35rem 0.5rem;
background: rgba(8,12,18,0.55);
border-radius: 10px;
}
.rc-detail-compare-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
gap: 0.25rem;
height: 100%;
}
.rc-detail-compare-col span { font-size: 0.62rem; color: #64748b; }
.rc-detail-vbar {
width: 28px;
min-height: 4px;
border-radius: 4px 4px 0 0;
background: #22c55e;
}
.rc-detail-vbar-year { background: #38bdf8; }
.rc-detail-status { margin-bottom: 0.75rem; }
.rc-detail-label {
display: block;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #64748b;
margin-bottom: 0.35rem;
}
.rc-detail-hint {
margin: 0.35rem 0 0;
font-size: 0.7rem;
color: #94a3b8;
}
.rc-detail-steps h4 {
margin: 0 0 0.3rem;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
}
.rc-detail-steps p {
margin: 0;
color: #e2e8f0;
font-size: 0.88rem;
line-height: 1.5;
white-space: pre-wrap;
}
.rc-detail-task { margin-top: 0.75rem; }
.rc-detail-task .rc-inline select,
.rc-detail-task .rc-inline input {
border: 1px solid rgba(148,163,184,0.25);
background: #0f1419 !important;
color: #e2e8f0 !important;
padding: 0.3rem 0.45rem;
font-size: 0.78rem;
border-radius: 4px;
}
.rc-summary-strip { gap: 0.65rem 0.85rem; }
.rc-style-dots { display: none !important; }
@media (max-width: 900px) {
.rc-detail-grid { grid-template-columns: 1fr 1fr; }
.rc-visual-toggle { order: 5; flex: 1 1 100%; }
.rc-style-chips { order: 6; flex: 1 1 100%; }
}
+248 -205
View File
@@ -26,6 +26,8 @@ window.BriefingCharts = (function () {
function typewriter(el, text, speed) {
if (!el) return;
el.textContent = text || '';
return;
if (typewriterTimer) clearInterval(typewriterTimer);
el.textContent = '';
if (!text) { el.textContent = 'Klik «Genereer dagrapport» voor je persoonlijke Herman briefing.'; return; }
@@ -58,16 +60,81 @@ window.BriefingCharts = (function () {
}
function renderPipeline(canvas, stats) {
if (!canvas || typeof Chart === 'undefined') return;
var rows = stats.deals_by_stage || [];
var c = chartColors();
if (charts.pipeline) charts.pipeline.destroy();
charts.pipeline = new Chart(canvas, {
type: 'bar',
data: { labels: rows.map(function (r) { return r.stage || '?'; }), datasets: [{ label: 'EUR', data: rows.map(function (r) { return Number(r.total) || 0; }), backgroundColor: c.gold, borderRadius: 6 }] },
options: { responsive: 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 } } } },
});
// Compact HTML bars — geen vaste canvas-hoogte / geen Chart.js
if (!canvas) return;
if (charts.pipeline) {
try { charts.pipeline.destroy(); } catch (e) {}
charts.pipeline = null;
}
var host = canvas.parentElement;
if (!host) return;
canvas.style.display = 'none';
canvas.setAttribute('hidden', '');
var panel = host.querySelector('.hm-pipeline-bars');
if (!panel) {
panel = document.createElement('div');
panel.className = 'hm-pipeline-bars';
host.appendChild(panel);
}
var rev = (stats.revenue && stats.revenue.stats) || {};
var crmRows = stats.pipeline_by_stage || stats.deal_stages || [];
var crmTotal = Math.round(stats.pipeline_eur || 0);
var rows = [];
// Prefer revenue top deals when CRM pipeline is empty/useless
var projects = (stats.revenue && (stats.revenue.projects || stats.revenue.top_projects)) || [];
if (!projects.length && stats.revenue && stats.revenue.by_brand) {
var bb = stats.revenue.by_brand;
projects = [].concat(bb.cucina || [], bb.foodlinkk || [], bb.shared || [], bb.other || []);
}
if ((!crmRows || !crmRows.length || crmTotal === 0) && projects.length) {
rows = projects
.filter(function (p) { return Number(p.margin_month || 0) > 0; })
.sort(function (a, b) { return Number(b.margin_month || 0) - Number(a.margin_month || 0); })
.slice(0, 6)
.map(function (p) {
return { name: p.name || 'Deal', count: 1, value: Number(p.margin_month || 0) };
});
var sub = document.getElementById('ceo-pipeline-sub');
if (sub) sub.textContent = 'top marge/mnd · Revenue';
} else if (crmRows && crmRows.length) {
rows = crmRows.map(function (r) {
return {
name: r.stage || r.name || 'Stage',
count: r.count || r.deals || 0,
value: Number(r.value || r.eur || r.pipeline_eur || 0),
};
});
var sub2 = document.getElementById('ceo-pipeline-sub');
if (sub2) sub2.textContent = 'CRM stages';
}
if (!rows.length) {
var dealCount = stats.deals || rev.active_deals || 0;
panel.innerHTML =
'<div class="hm-pipeline-head"><strong>' + dealCount + ' deals</strong><span>geen stage-waarde</span></div>' +
'<p class="hm-pipeline-foot"><a href="/revenue-cockpit">Revenue Cockpit →</a> · <a href="/deals">CRM deals →</a></p>';
return;
}
var maxV = Math.max.apply(null, rows.map(function (r) { return r.value || 0 }).concat([1]));
var totalEur = rows.reduce(function (s, r) { return s + (r.value || 0); }, 0);
var totalCnt = rows.reduce(function (s, r) { return s + (r.count || 0); }, 0);
panel.innerHTML =
'<div class="hm-pipeline-head">' +
'<strong>€' + Math.round(totalEur).toLocaleString('nl-NL') + '</strong>' +
'<span>' + (totalCnt || rows.length) + ' regels</span>' +
'</div>' +
'<div class="hm-pipeline-list">' + rows.map(function (r) {
var pct = Math.max(4, Math.round(((r.value || 0) / maxV) * 100));
return '<div class="hm-pipeline-row">' +
'<div class="hm-pipeline-meta"><span class="hm-pipeline-stage">' + String(r.name).replace(/</g, '') + '</span>' +
'<span class="hm-pipeline-vals">€' + Math.round(r.value || 0).toLocaleString('nl-NL') + '</span></div>' +
'<div class="hm-pipeline-track"><div class="hm-pipeline-fill" style="width:' + pct + '%"></div></div>' +
'</div>';
}).join('') + '</div>' +
'<p class="hm-pipeline-foot"><a href="/revenue-cockpit">Revenue Cockpit →</a> · <a href="/deals">CRM deals →</a></p>';
}
function renderSentiment(canvas, stats) {
@@ -236,6 +303,22 @@ window.BriefingCharts = (function () {
var trendCount = (stats.trending_food || stats.food_market_highlights || []).length;
switch (id) {
case 'pipeline': return '€' + Math.round(stats.pipeline_eur || 0).toLocaleString('nl-NL');
case 'margin_month': {
var rev = (stats.revenue && stats.revenue.stats) || {};
return '€' + Math.round(rev.total_margin_month || 0).toLocaleString('nl-NL');
}
case 'margin_year': {
var revY = (stats.revenue && stats.revenue.stats) || {};
return '€' + Math.round(revY.total_margin_year || 0).toLocaleString('nl-NL');
}
case 'open_steps': {
var revO = (stats.revenue && stats.revenue.stats) || {};
return String(revO.open_objectives || 0);
}
case 'revenue_deals': {
var revD = (stats.revenue && stats.revenue.stats) || {};
return String(revD.active_deals || revD.active_projects || 0);
}
case 'clients_active': return (stats.clients_active || 0) + ' / ' + (stats.clients_total || stats.clients || 0);
case 'clients_total': return String(stats.clients_total || stats.clients || 0);
case 'crm_partnerships': return String(stats.crm_partnerships || 0);
@@ -260,6 +343,13 @@ window.BriefingCharts = (function () {
var trendCount = (stats.trending_food || stats.food_market_highlights || []).length;
switch (id) {
case 'pipeline': return '72%';
case 'margin_month': return '90%';
case 'margin_year': return '85%';
case 'open_steps': {
var ro = (stats.revenue && stats.revenue.stats) || {};
return Math.min(95, Math.round((ro.open_objectives || 0) / 50 * 100)) + '%';
}
case 'revenue_deals': return '80%';
case 'clients_active':
return Math.min(95, Math.round(((stats.clients_active || 0) / Math.max(stats.clients_total || 1, 1)) * 100)) + '%';
case 'crm_partnerships': return '45%';
@@ -276,17 +366,22 @@ window.BriefingCharts = (function () {
if (!container) return;
var meta = (window.DashboardLayout && window.DashboardLayout.KPI_META) || {};
var order = selectedKpis || window._dashboardKpis ||
(window.DashboardLayout && window.DashboardLayout.CEO_KPIS) ||
(window.DashboardLayout && window.DashboardLayout.DEFAULT_KPIS) ||
['pipeline', 'clients_active', 'crm_partnerships', 'supermarkets', 'trending_food', 'pending_approvals'];
['margin_month', 'margin_year', 'open_steps', 'revenue_deals', 'crm_partnerships', 'supermarkets'];
if (window.DashboardLayout && window.DashboardLayout.ceoKpis) {
order = window.DashboardLayout.ceoKpis({ dashboard_kpis: order });
}
order = order.slice(0, 6);
var items = order.map(function (id) {
var m = meta[id] || { label: id, sub: '', icon: '▪', color: '#94a3b8' };
return {
label: m.label, sub: m.sub, icon: m.icon, color: m.color || '#94a3b8',
id: id, label: m.label, sub: m.sub, icon: m.icon, color: m.color || '#94a3b8',
pct: kpiPct(id, stats), val: kpiValue(id, stats), link: m.link,
};
});
if (!items.length) {
container.innerHTML = '<p class="empty-state">Geen KPI\'s geselecteerd — klik <strong>Dashboard instellen</strong>.</p>';
container.innerHTML = '<p class="empty-state">Geen KPI\'s</p>';
return;
}
container.className = 'hm-neo-kpi-row';
@@ -299,6 +394,7 @@ window.BriefingCharts = (function () {
}).join('');
}
function renderRssFeed(container, stats) {
if (!container) return;
var items = stats.rss_live || stats.trending_food || stats.food_market_highlights || [];
@@ -347,67 +443,94 @@ window.BriefingCharts = (function () {
function renderExecutiveSummary(container, stats) {
if (!container) return;
var items = [
{ icon: '🤝', label: 'Actieve klanten', val: (stats.clients_active || 0) + ' van ' + (stats.clients_total || stats.clients || 0), link: '/clients' },
{ icon: '💰', label: 'Pipeline', val: '€' + Math.round(stats.pipeline_eur || 0).toLocaleString('nl-NL'), link: '/deals' },
{ icon: '🏪', label: 'CRM partnerships', val: stats.crm_partnerships || 0, link: '/retail' },
{ icon: '🛒', label: 'Supermarkten DB', val: (stats.supermarkets || 0).toLocaleString('nl-NL'), link: '/retail' },
{ icon: '📦', label: 'Groothandels', val: stats.wholesalers || 0, link: '/retail' },
{ icon: '✓', label: 'Goedkeuringen open', val: stats.pending_approvals || 0, link: '/' },
{ icon: '📁', label: 'Actieve promo\'s', val: stats.promo_campaigns || '—', link: '/marketing?tab=reclame' },
{ icon: '📊', label: 'NAS documenten', val: stats.nas_docs || 0, link: '/documents' },
];
var trending = (stats.trending_food || stats.food_market_highlights || []).length;
var rssN = stats.rss_items || (stats.rss_live || []).length || trending;
var ms = (stats.milestones_pending || []).slice(0, 3);
var html = '<div class="hm-exec-grid">' + items.map(function (it) {
return '<a href="' + it.link + '" class="hm-exec-item"><span class="hm-exec-icon">' + it.icon + '</span>' +
'<div><strong>' + it.label + '</strong><div class="hm-exec-val">' + it.val + '</div></div></a>';
}).join('') + '</div>';
if (rssN) {
html += '<p class="hm-rss-teaser" style="margin-top:1rem">' + rssN + ' RSS feeds — <a href="/?tab=rss">bekijk in RSS tab →</a></p>';
}
if (ms.length) {
html += '<h4 style="margin:1rem 0 0.5rem;font-size:0.8rem;color:#94a3b8">Open milestones</h4><ul class="hm-exec-list">';
ms.forEach(function (m) {
html += '<li>' + (m.title || '') + ' · ' + (m.chain || '') + ' ' + (m.store_name || '') + '</li>';
var rev = (stats.revenue && stats.revenue.stats) || {};
var by = (stats.revenue && stats.revenue.by_brand) || {};
var cu = by.cucina || [];
var fl = by.foodlinkk || [];
var clients = stats.recent_clients || [];
var opp = stats.top_opportunities || [];
var deals = stats.deals || 0;
var pipeline = Math.round(stats.pipeline_eur || 0);
var marginM = Math.round(rev.total_margin_month || 0);
var openObj = rev.open_objectives || 0;
// Narrative context
var narrative = [];
if (marginM > 0) narrative.push('Cucina + Foodlinkk draaien €' + marginM.toLocaleString('nl-NL') + '/mnd marge over ' + (rev.active_projects || 0) + ' projecten.');
if (openObj) narrative.push(openObj + ' open stappen vragen opvolging.');
if ((stats.crm_partnerships || 0) > 0) narrative.push(stats.crm_partnerships + ' retail-partnerships · ' + (stats.supermarkets || 0).toLocaleString('nl-NL') + ' filialen bereikbaar.');
if (pipeline === 0 && deals > 0) narrative.push(deals + ' CRM-deals staan klaar — pipeline-waarde nog €0 (nog geen getekende budgetten).');
if (!narrative.length) narrative.push('Live business data laden…');
var topDeals = cu.filter(function (p) { return p.margin_month; }).slice(0, 3);
var html = '';
html += '<div class="hm-context-banner"><p>' + escHtml(narrative.join(' ')) + '</p></div>';
html += '<div class="hm-exec-grid hm-exec-grid-rich">' +
[
{ icon: '💶', label: 'Marge / mnd', val: '€' + marginM.toLocaleString('nl-NL'), sub: (rev.active_projects || 0) + ' projecten', link: '/revenue-cockpit' },
{ icon: '💰', label: 'CRM pipeline', val: '€' + pipeline.toLocaleString('nl-NL'), sub: deals + ' deals', link: '/deals' },
{ icon: '🤝', label: 'Klanten', val: String(stats.clients_total || stats.clients || 0), sub: (stats.clients_active || 0) + ' actief', link: '/clients' },
{ icon: '🏪', label: 'Partnerships', val: String(stats.crm_partnerships || 0), sub: (stats.supermarkets || 0).toLocaleString('nl-NL') + ' filialen', link: '/retail' },
].map(function (it) {
return '<a href="' + it.link + '" class="hm-exec-item hm-exec-rich">' +
'<span class="hm-exec-icon">' + it.icon + '</span>' +
'<div><strong>' + it.label + '</strong><div class="hm-exec-val">' + it.val + '</div>' +
'<div class="hm-exec-sub">' + it.sub + '</div></div></a>';
}).join('') + '</div>';
if (topDeals.length) {
html += '<div class="hm-context-block"><h4>Top Cucina deals</h4><div class="hm-deal-chips">';
topDeals.forEach(function (d) {
html += '<a class="hm-deal-chip" href="/revenue-cockpit"><strong>' + escHtml(d.name) + '</strong>' +
'<span>€' + Math.round(d.margin_month || 0).toLocaleString('nl-NL') + '/m</span>' +
(d.open_objectives ? '<em>' + d.open_objectives + ' stappen</em>' : '') + '</a>';
});
html += '</ul>';
html += '</div></div>';
}
var pending = stats.pending_approval_requests || [];
if (pending.length) {
html += '<h4 style="margin:1rem 0 0.5rem;font-size:0.8rem;color:#f59e0b">⏳ Open goedkeuringen</h4><ul class="hm-exec-list">';
pending.forEach(function (p) {
html += '<li><strong>@' + (p.agent_key || '') + '</strong> · ' + (p.title || '') + '</li>';
if (clients.length) {
html += '<div class="hm-context-block"><h4>Recente klanten</h4><div class="hm-deal-chips">';
clients.slice(0, 4).forEach(function (c) {
html += '<a class="hm-deal-chip hm-deal-chip-client" href="/clients"><strong>' + escHtml(c.name || '') + '</strong>' +
'<span>' + escHtml(c.stage || '') + '</span></a>';
});
html += '</ul>';
html += '</div></div>';
}
html += '<p class="hm-exec-footer" style="margin-top:1rem;font-size:0.8rem;color:#64748b">' +
'<a href="http://10.4.7.18:3001/aissa/foodlinkk-command-center" target="_blank" rel="noopener">Gitea</a> · ' +
'<a href="/ops">IT Ops</a> · <a href="/packaging">Packaging</a> · ' +
'<a href="/marketing?tab=publish">Automatisering</a> · ' +
'<a href="/marketing">Marketing Hub</a> · <a href="/analytics">Analytics</a></p>';
if (opp.length) {
html += '<div class="hm-context-block"><h4>Halal-kansen</h4><ul class="hm-context-list">';
opp.slice(0, 3).forEach(function (o) {
var score = Math.round(o.halal_opportunity_score || o.score || 0);
html += '<li><strong>' + escHtml(o.chain || o.name || '') + '</strong> · ' +
escHtml(o.city || '') + ' <span class="hm-score">score ' + score + '</span></li>';
});
html += '</ul></div>';
}
html += '<p class="hm-exec-footer">' +
'<a href="/revenue-cockpit">Revenue Cockpit</a> · <a href="/retail">Retail 360</a> · ' +
'<a href="/clients">Clients</a> · <a href="/deals">Deals</a></p>';
container.innerHTML = html;
}
function renderText(root, content, createdAt) {
if (!root) return;
var parsed = parseContent(content);
var meta = document.getElementById('briefing-meta');
if (meta && createdAt) meta.textContent = 'Laatst bijgewerkt: ' + String(createdAt).substring(0, 19).replace('T', ' ');
var sumEl = document.getElementById('briefing-summary-text');
if (sumEl && parsed.summary) {
var lines = parsed.summary.split(/(?<=[.!?])\s+/).filter(Boolean);
typewriterLiveDigest(sumEl, {
sections: [{ brand: 'platform', title: 'Herman rapport', icon: '📝', lines: lines.length ? lines : [parsed.summary] }],
}, { charMs: 10, linePause: 90 });
}
var actEl = document.getElementById('briefing-actions-list');
if (actEl) typewriterListItems(actEl, parsed.actions.length ? parsed.actions : ['Genereer dagrapport voor actiepunten'], { charMs: 8 });
var longEl = document.getElementById('briefing-longterm-list');
if (longEl) setTimeout(function () {
typewriterListItems(longEl, parsed.longTerm.length ? parsed.longTerm : ['Halal kant-en-klaar partnerships schalen'], { charMs: 8 });
}, 300);
if (!sumEl || !content) return;
sumEl.dataset.aiMemo = '1';
// Markdown-achtige tekst direct tonen (geen typewriter)
var html = escHtml(content)
.replace(/^### (.+)$/gm, '<h5>$1</h5>')
.replace(/^## (.+)$/gm, '<h4>$1</h4>')
.replace(/^# (.+)$/gm, '<h3>$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/^\- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, function (m) { return '<ul class="live-digest-list">' + m + '</ul>'; })
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
sumEl.innerHTML = '<div class="hm-ceo-memo-md"><p>' + html + '</p></div>';
}
function escHtml(s) {
@@ -441,152 +564,77 @@ window.BriefingCharts = (function () {
}
function typewriterLiveDigest(container, digest, opts) {
if (!container) return;
opts = opts || {};
var charMs = opts.charMs || 14;
var linePause = opts.linePause || 120;
var sectionPause = opts.sectionPause || 200;
if (typewriterAbort) { typewriterAbort(); typewriterAbort = null; }
if (typewriterTimer) { clearInterval(typewriterTimer); typewriterTimer = null; }
var sections = (digest && digest.sections) || [];
if (!sections.length) {
var summary = (digest && digest.summary) || '';
if (summary) {
typewriter(container, summary, charMs);
} else {
container.innerHTML = '<p class="muted">Live samenvatting laden…</p>';
}
return;
}
container.innerHTML = sections.map(function (sec) {
var lines = (sec.lines || []).filter(Boolean);
var lis = lines.length
? lines.map(function () { return '<li class="tw-line"></li>'; }).join('')
: '<li class="muted">Geen data</li>';
return '<div class="live-digest-section brand-' + escHtml(sec.brand || 'shared') + '">' +
'<h5 class="tw-heading">' + escHtml((sec.icon || '') + ' ' + (sec.title || '')) + '</h5>' +
'<ul class="live-digest-list">' + lis + '</ul></div>';
}).join('');
var queue = [];
container.querySelectorAll('.live-digest-section').forEach(function (secEl, si) {
var lines = (sections[si] && sections[si].lines) || [];
var lis = secEl.querySelectorAll('li.tw-line');
for (var i = 0; i < lis.length; i++) {
queue.push({ el: lis[i], text: lines[i] || '', section: si });
}
});
var cancelled = false;
typewriterAbort = function () { cancelled = true; };
container.classList.add('is-typing');
function tick(qi, ci, prevSection) {
if (cancelled) return;
if (qi >= queue.length) {
container.classList.remove('is-typing');
container.querySelectorAll('.is-typing').forEach(function (n) { n.classList.remove('is-typing'); });
return;
}
var item = queue[qi];
var el = item.el;
var text = item.text;
if (prevSection !== item.section && ci === 0) {
setTimeout(function () { if (!cancelled) tick(qi, ci, item.section); }, sectionPause);
return;
}
el.classList.add('is-typing');
container.querySelectorAll('li.is-typing').forEach(function (n) { if (n !== el) n.classList.remove('is-typing'); });
if (ci < text.length) {
el.textContent = text.substring(0, ci + 1);
setTimeout(function () { tick(qi, ci + 1, item.section); }, charMs);
} else {
el.classList.remove('is-typing');
setTimeout(function () { tick(qi + 1, 0, item.section); }, linePause);
}
}
tick(0, 0, -1);
// Typewriter uit — altijd direct tonen
fillDigestInstant(container, digest);
}
function typewriterListItems(listEl, items, opts) {
if (!listEl || !items || !items.length) return;
opts = opts || {};
var charMs = opts.charMs || 10;
listEl.innerHTML = items.map(function () { return '<li class="tw-line"></li>'; }).join('');
var lis = listEl.querySelectorAll('li.tw-line');
var qi = 0, ci = 0, cancelled = false;
function next() {
if (cancelled || qi >= items.length) return;
var el = lis[qi];
var text = items[qi];
el.classList.add('is-typing');
if (ci < text.length) {
el.textContent = text.substring(0, ci + 1);
ci++;
setTimeout(next, charMs);
} else {
el.classList.remove('is-typing');
qi++; ci = 0;
setTimeout(next, 90);
}
}
next();
return function () { cancelled = true; };
fillListInstant(listEl, items || []);
}
function fillListInstant(listEl, items) {
if (!listEl) return;
var rows = (items && items.length) ? items : ['Geen urgente acties — check agents & retail'];
listEl.innerHTML = rows.map(function (t) { return '<li>' + escHtml(t) + '</li>'; }).join('');
}
function renderLiveSummary(digest, pollRefresh) {
if (!digest) return;
var sumEl = document.getElementById('briefing-summary-text');
var actEl = document.getElementById('briefing-actions-list');
var longEl = document.getElementById('briefing-longterm-list');
var meta = document.getElementById('briefing-meta');
var sig = digestSignature(digest);
var unchanged = pollRefresh && sig === lastDigestSig;
if (!unchanged) lastDigestSig = sig;
if (meta) {
var ts = String(digest.updated_at || '').substring(0, 19).replace('T', ' ');
var n = digest.activity_count != null ? ' · ' + digest.activity_count + ' activiteiten' : '';
meta.textContent = 'Live · ververst ' + ts + ' UTC' + n;
}
if (unchanged) {
if (actEl) fillListInstant(actEl, digest.actions || []);
if (longEl) fillListInstant(longEl, digest.long_term || []);
function fillSimpleList(el, items, emptyMsg) {
if (!el) return;
var rows = (items || []).filter(Boolean);
if (!rows.length) {
el.innerHTML = '<li class="muted">' + escHtml(emptyMsg || 'Geen items') + '</li>';
return;
}
el.innerHTML = rows.map(function (x) {
if (typeof x === 'string') return '<li><span class="hm-ceo-bullet">→</span> ' + escHtml(x) + '</li>';
var title = escHtml(x.title || x);
var link = x.link ? ('<a href="' + escHtml(x.link) + '" target="_blank" rel="noopener">' + title + '</a>') : title;
var src = x.source ? ('<small>' + escHtml(x.source) + '</small>') : '';
return '<li>' + link + src + '</li>';
}).join('');
}
var isUpdate = !!lastDigestSig && !pollRefresh;
var twOpts = { charMs: isUpdate ? 8 : 12, linePause: isUpdate ? 70 : 100, sectionPause: isUpdate ? 120 : 180 };
function fillBrandCard(el, lines) {
if (!el) return;
var list = el.querySelector('.hm-ceo-brand-list') || el;
var rows = (lines || []).filter(Boolean);
list.innerHTML = rows.length
? rows.map(function (l) { return '<li>' + escHtml(l) + '</li>'; }).join('')
: '<li class="muted">Geen data</li>';
}
if (sumEl) {
if (pollRefresh) fillDigestInstant(sumEl, digest);
else typewriterLiveDigest(sumEl, digest, twOpts);
function renderCeoDigest(digest) {
if (!digest) return;
var b2b = digest.b2b, b2c = digest.b2c, focus = digest.focus, trends = digest.trends, news = digest.news;
if (!b2b || !b2c) {
(digest.sections || []).forEach(function (s) {
if (s.brand === 'foodlinkk') b2b = s.lines;
if (s.brand === 'cucina') b2c = s.lines;
if (s.brand === 'platform' || (s.title || '').indexOf('Focus') >= 0) focus = s.lines;
});
}
fillBrandCard(document.getElementById('ceo-b2b'), b2b);
fillBrandCard(document.getElementById('ceo-b2c'), b2c);
fillSimpleList(document.getElementById('ceo-focus-list'), focus || digest.actions, 'Geen urgente acties');
fillSimpleList(document.getElementById('ceo-trends-list'), trends, 'Geen trends');
fillSimpleList(document.getElementById('ceo-news-list'), news, 'Geen nieuws — open RSS tab');
}
var actions = digest.actions || [];
if (actEl) {
if (pollRefresh) fillListInstant(actEl, actions.length ? actions : ['Geen urgente acties — check agents & retail']);
else typewriterListItems(actEl, actions.length ? actions : ['Geen urgente acties — check agents & retail'], { charMs: isUpdate ? 6 : 8 });
function renderLiveSummary(digest, pollRefresh) {
if (!digest) return;
var meta = document.getElementById('briefing-meta');
lastDigestSig = digestSignature(digest);
if (meta) {
var ts = String(digest.updated_at || '').substring(0, 19).replace('T', ' ');
meta.textContent = 'Live · ververst ' + ts + ' UTC';
}
var lt = digest.long_term || [];
if (longEl) {
if (pollRefresh) fillListInstant(longEl, lt.length ? lt : ['Halal kant-en-klaar partnerships schalen']);
else setTimeout(function () {
typewriterListItems(longEl, lt.length ? lt : ['Halal kant-en-klaar partnerships schalen'], { charMs: isUpdate ? 6 : 8 });
}, isUpdate ? 200 : 400);
if (document.querySelector('.hm-ceo-pro')) {
renderCeoDigest(digest);
return;
}
var sumEl = document.getElementById('briefing-summary-text');
if (sumEl) fillDigestInstant(sumEl, digest);
}
function renderLive(root, stats, vizMode, kpiSelection, liveDigest, opts) {
@@ -597,24 +645,19 @@ window.BriefingCharts = (function () {
if (liveDigest) renderLiveSummary(liveDigest, !!opts.pollRefresh);
else if (stats.live_digest) renderLiveSummary(stats.live_digest, !!opts.pollRefresh);
renderKpis(document.getElementById('briefing-kpis'), stats, kpis);
renderFoodHighlights(document.getElementById('briefing-food-highlights'), stats);
renderExecutiveSummary(document.getElementById('briefing-executive-summary'), stats);
renderRetail(document.getElementById('briefing-retail'), stats);
renderMilestones(document.getElementById('briefing-milestones'), stats);
var actEl = document.getElementById('briefing-activity-log');
if (actEl) {
var log = stats.activity_log || [];
actEl.innerHTML = log.length ? log.slice(0, 12).map(function (e) { return '<li>' + e + '</li>'; }).join('')
: '<li class="muted">Nog geen agent-acties vandaag.</li>';
}
// activity log removed from CEO overview
renderPipeline(document.getElementById('chart-pipeline'), stats);
renderWords(document.getElementById('chart-words'), stats);
if (window.VizEngine) {
VizEngine.renderSentiment(mode, document.getElementById('chart-sentiment'), document.getElementById('viz-sentiment-alt'), stats);
VizEngine.renderAgents(mode, document.getElementById('chart-agents'), document.getElementById('viz-agents-alt'), stats);
} else {
renderSentiment(document.getElementById('chart-sentiment'), stats);
renderAgents(document.getElementById('chart-agents'), stats);
// CEO overview: geen sentiment/words/agents-charts (lege hoogte + troep)
if (!document.querySelector('.hm-ceo-clean')) {
renderWords(document.getElementById('chart-words'), stats);
if (window.VizEngine) {
VizEngine.renderSentiment(mode, document.getElementById('chart-sentiment'), document.getElementById('viz-sentiment-alt'), stats);
VizEngine.renderAgents(mode, document.getElementById('chart-agents'), document.getElementById('viz-agents-alt'), stats);
} else {
renderSentiment(document.getElementById('chart-sentiment'), stats);
renderAgents(document.getElementById('chart-agents'), stats);
}
}
}
+38 -8
View File
@@ -2,8 +2,8 @@
var WIDGET_META = {
kpis: { title: 'CEO KPI\'s', icon: '📊' },
executive: { title: 'Alles op een rij', icon: '📋' },
briefing: { title: 'Herman briefing', icon: '📝' },
retail: { title: 'Retail operatie', icon: '🏪' },
briefing: { title: 'Focus & trends', icon: '🎯' },
news: { title: 'Laatste nieuws', icon: '📰' },
analytics: { title: 'Data & analytics', icon: '📈' },
approvals: { title: 'Agent goedkeuringen', icon: '✓' },
feed: { title: 'Agent feed', icon: '⚡' },
@@ -26,10 +26,16 @@
nas_docs: { label: 'NAS documenten', sub: 'geanalyseerd', icon: '📊', color: '#818cf8', link: '/documents' },
promo_campaigns: { label: 'Promo campagnes', sub: 'actief', icon: '📁', color: '#c084fc', link: '/marketing?tab=reclame' },
market_best: { label: 'Aandeel top', sub: 'retail markt', icon: '📈', color: '#34d399', link: '/analytics' },
margin_month: { label: 'Marge / mnd', sub: 'Revenue Cockpit', icon: '💶', color: '#fbbf24', link: '/revenue-cockpit' },
margin_year: { label: 'Marge / jr', sub: 'Revenue Cockpit', icon: '📅', color: '#38bdf8', link: '/revenue-cockpit' },
open_steps: { label: 'Open stappen', sub: 'opvolging nodig', icon: '🎯', color: '#f472b6', link: '/revenue-cockpit' },
revenue_deals: { label: 'Actieve deals', sub: 'met marge', icon: '💼', color: '#4ade80', link: '/revenue-cockpit' },
};
var DEFAULT_LAYOUT = Object.keys(WIDGET_META);
var DEFAULT_KPIS = ['pipeline', 'clients_active', 'crm_partnerships', 'supermarkets', 'trending_food', 'pending_approvals'];
var DEFAULT_LAYOUT = ['kpis', 'executive', 'analytics', 'briefing', 'news', 'approvals', 'feed'];
var CEO_LAYOUT = ['kpis', 'executive', 'analytics', 'briefing', 'news'];
var DEFAULT_KPIS = ['margin_month', 'margin_year', 'open_steps', 'revenue_deals', 'crm_partnerships', 'supermarkets'];
var CEO_KPIS = ['margin_month', 'margin_year', 'open_steps', 'revenue_deals', 'crm_partnerships', 'supermarkets'];
function loadPrefs() {
return fetch('/api/preferences/ui').then(function (r) { return r.json(); }).catch(function () {
@@ -73,21 +79,42 @@
return savePrefs(patch);
}
function ceoKpis(prefs) {
var k = (prefs && prefs.dashboard_kpis) || [];
if (!k.length || k.indexOf('margin_month') < 0) return CEO_KPIS.slice();
// ensure core revenue KPIs present without dropping user extras
var need = ['margin_month', 'open_steps', 'revenue_deals'];
need.forEach(function (id) {
if (k.indexOf(id) < 0) k = [id].concat(k);
});
return k.slice(0, 6);
}
function visibleLayout(prefs) {
var layout = prefs.dashboard_layout || DEFAULT_LAYOUT.slice();
var widgets = prefs.dashboard_widgets || defaultWidgets();
return layout.filter(function (id) { return widgets[id] !== false; });
var rest = (prefs.dashboard_layout || DEFAULT_LAYOUT.slice()).filter(function (id) {
return CEO_LAYOUT.indexOf(id) < 0 && widgets[id] !== false;
});
var top = CEO_LAYOUT.filter(function (id) { return widgets[id] !== false; });
return top.concat(rest);
}
function applyOrder(container, order) {
if (!container) return;
var map = {};
var leftovers = [];
Array.from(container.children).forEach(function (el) {
if (el.dataset && el.dataset.widget) map[el.dataset.widget] = el;
else leftovers.push(el);
});
order.forEach(function (id) {
if (map[id]) container.appendChild(map[id]);
var seen = {};
(order || []).forEach(function (id) {
if (map[id]) { container.appendChild(map[id]); seen[id] = true; }
});
Object.keys(map).forEach(function (id) {
if (!seen[id]) container.appendChild(map[id]);
});
leftovers.forEach(function (el) { container.appendChild(el); });
}
function applyVisibility(container, widgets) {
@@ -257,12 +284,15 @@
WIDGET_META: WIDGET_META,
KPI_META: KPI_META,
DEFAULT_KPIS: DEFAULT_KPIS,
CEO_KPIS: CEO_KPIS,
ceoKpis: ceoKpis,
loadPrefs: loadPrefs,
saveLayout: saveLayout,
saveViz: saveViz,
savePrefs: savePrefs,
saveDashboardConfig: saveDashboardConfig,
visibleLayout: visibleLayout,
CEO_LAYOUT: CEO_LAYOUT,
applyOrder: applyOrder,
applyVisibility: applyVisibility,
initDrag: initDrag,
+50
View File
@@ -5,6 +5,7 @@ function revenueCockpit() {
savedAt: '',
_pollStop: null,
viewMode: 'cockpit',
visualMode: 'table',
goals: { vision_text: '', horizon_text: '', mid_text: '', tagline: '' },
projects: [],
dbProjects: [],
@@ -45,6 +46,8 @@ function revenueCockpit() {
async init() {
localStorage.setItem('foodlinkk-persona', 'ceo');
const saved = localStorage.getItem('rc-visual-mode');
if (saved === 'table' || saved === 'bars' || saved === 'cards') this.visualMode = saved;
await this.loadLive();
await this.loadDbQuiet();
if (window.CockpitLive) {
@@ -126,6 +129,53 @@ function revenueCockpit() {
return t.length <= n ? t : t.slice(0, n).trim() + '…';
},
setVisualMode(mode) {
if (mode !== 'table' && mode !== 'bars' && mode !== 'cards') return;
this.visualMode = mode;
localStorage.setItem('rc-visual-mode', mode);
},
styleLabel(style) {
const map = { green: 'Live', red: 'Inactief', orange: 'Oranje', yellow: 'Strategisch', blue: 'Blauw', white: 'Neutraal' };
return map[style] || 'Neutraal';
},
rankedRows() {
const rows = this.visualRows().slice();
rows.sort((a, b) => (Number(b.margin_month) || 0) - (Number(a.margin_month) || 0));
return rows;
},
cardRows() {
return this.rankedRows().filter((r) => r.margin_month != null || (r.next_steps || '').trim());
},
async setCockpitStyle(style) {
const p = this.selectedProject;
if (!p) return;
if (!p.db_id) {
p.row_style = style;
if (window.Cockpit) Cockpit.toast('Lokaal gezet — sync DB om te bewaren', 'info');
return;
}
try {
await fetch('/api/revenue-cockpit/projects/' + p.db_id, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ row_style: style }),
});
p.row_style = style;
const pi = this.projects.findIndex((x) => this.projectKey(x) === this.projectKey(p));
if (pi >= 0) this.projects[pi].row_style = style;
const di = this.dbProjects.findIndex((x) => x.id === p.db_id);
if (di >= 0) this.dbProjects[di].row_style = style;
if (window.Cockpit) Cockpit.toast('Status → ' + this.styleLabel(style), 'success');
} catch (e) {
if (window.Cockpit) Cockpit.toast('Kleur wijzigen mislukt', 'error');
}
},
styleColor(style) {
const map = {
green: '#22c55e', red: '#ef4444', orange: '#f59e0b',