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:
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Revenue Cockpit · Cucina{% endblock %}
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="/static/css/revenue-cockpit.css?v=6" />
|
||||
<link rel="stylesheet" href="/static/css/revenue-cockpit.css?v=12" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="rc-page" :class="{ 'rc-mode-sheet': viewMode === 'sheet' }" x-data="revenueCockpit()" x-init="init()">
|
||||
@@ -41,17 +41,36 @@
|
||||
<span class="rc-sum-label">Deals</span>
|
||||
<strong x-text="filteredProjects().length + '/' + projects.length"></strong>
|
||||
</div>
|
||||
|
||||
<div class="rc-visual-toggle" role="group" aria-label="Weergave">
|
||||
<button type="button" class="rc-vis-btn" :class="{ active: visualMode === 'table' }" @click="setVisualMode('table')">Tabel</button>
|
||||
<button type="button" class="rc-vis-btn" :class="{ active: visualMode === 'bars' }" @click="setVisualMode('bars')">Staven</button>
|
||||
<button type="button" class="rc-vis-btn" :class="{ active: visualMode === 'cards' }" @click="setVisualMode('cards')">Kaarten</button>
|
||||
</div>
|
||||
|
||||
<input type="search" class="rc-filter-search rc-filter-inline" placeholder="Zoek deal…" x-model="filterQ" />
|
||||
<div class="rc-style-dots">
|
||||
<button type="button" class="rc-dot-btn" :class="{ active: !filterStyle }" @click="filterStyle = ''" title="Alle">○</button>
|
||||
<template x-for="s in styleFilters" :key="'d-' + s.id">
|
||||
<button type="button" class="rc-dot-btn" :class="['rc-dot-' + s.id, { active: filterStyle === s.id }]"
|
||||
@click="filterStyle = filterStyle === s.id ? '' : s.id" :title="s.label"></button>
|
||||
|
||||
<div class="rc-style-chips">
|
||||
<button type="button" class="rc-chip" :class="{ active: !filterStyle }" @click="filterStyle = ''">Alle</button>
|
||||
<template x-for="s in styleFilters" :key="'c-' + s.id">
|
||||
<button type="button" class="rc-chip" :class="['rc-chip-' + s.id, { active: filterStyle === s.id }]"
|
||||
@click="filterStyle = filterStyle === s.id ? '' : s.id"
|
||||
x-text="s.label"></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rc-visual-wrap">
|
||||
<div class="rc-goals-banner" x-show="goals.vision_text || goals.horizon_text || goals.tagline">
|
||||
<span x-text="goals.vision_text"></span>
|
||||
<span class="rc-goals-sep" x-show="goals.horizon_text">·</span>
|
||||
<span x-text="goals.horizon_text"></span>
|
||||
<span class="rc-goals-sep" x-show="goals.mid_text">·</span>
|
||||
<span x-text="goals.mid_text"></span>
|
||||
<em x-show="goals.tagline" x-text="goals.tagline"></em>
|
||||
</div>
|
||||
|
||||
<!-- TABEL -->
|
||||
<div class="rc-visual-wrap" x-show="visualMode === 'table'" x-cloak>
|
||||
<table class="rc-visual-table" cellspacing="0" cellpadding="0">
|
||||
<colgroup>
|
||||
<col class="rc-col-deal" />
|
||||
@@ -60,15 +79,6 @@
|
||||
<col class="rc-col-donut" />
|
||||
<col class="rc-col-steps" />
|
||||
</colgroup>
|
||||
|
||||
<!-- Excel rij 1: goals -->
|
||||
<tr class="rc-visual-goals">
|
||||
<td class="rc-vcell" x-text="goals.vision_text || '—'"></td>
|
||||
<td class="rc-vcell" x-text="goals.horizon_text || '—'"></td>
|
||||
<td class="rc-vcell" x-text="goals.mid_text || '—'"></td>
|
||||
<td class="rc-vcell rc-vtagline" colspan="2" x-text="goals.tagline || '—'"></td>
|
||||
</tr>
|
||||
|
||||
<tr class="rc-visual-head">
|
||||
<td class="rc-vcell">Deal</td>
|
||||
<td class="rc-vcell rc-num">Marge mnd</td>
|
||||
@@ -76,84 +86,152 @@
|
||||
<td class="rc-vcell rc-num">Aandeel</td>
|
||||
<td class="rc-vcell">Next steps</td>
|
||||
</tr>
|
||||
|
||||
<template x-for="row in visualRows()" :key="row.source_row + '-' + row.name">
|
||||
<template x-for="row in visualRows()" :key="'t-' + row.source_row + '-' + row.name">
|
||||
<tr class="rc-visual-row"
|
||||
:class="['rc-style-' + (row.row_style || 'white'), 'rc-brand-' + (row.brand || 'none'), { 'rc-row-open': expandedKey === projectKey(row) }]"
|
||||
:class="['rc-style-' + (row.row_style || 'white'), { 'rc-row-open': selectedKey === projectKey(row) }]"
|
||||
@click="toggleExpand(row)">
|
||||
|
||||
<td class="rc-vcell rc-vdeal">
|
||||
<span class="rc-deal-name" x-text="row.name"></span>
|
||||
<span class="rc-brand-tag" x-show="row.logoCell === 'cucina'">Cucina</span>
|
||||
<span class="rc-brand-tag rc-brand-tag-fl" x-show="row.logoCell === 'foodlinkk'">Foodlinkk</span>
|
||||
</td>
|
||||
|
||||
<td class="rc-vcell rc-vnum">
|
||||
<span class="rc-vamount" x-text="row.margin_month != null ? fmtEuroCompact(row.margin_month) : '—'"></span>
|
||||
<div class="rc-mini-bar" x-show="row.margin_month">
|
||||
<div class="rc-mini-bar-fill" :style="'width:' + marginBarPct(row, 'margin_month') + '%;background:' + styleColor(row.row_style)"></div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="rc-vcell rc-vnum">
|
||||
<span class="rc-vamount" x-text="row.margin_year != null ? fmtEuroCompact(row.margin_year) : '—'"></span>
|
||||
<div class="rc-mini-bar" x-show="row.margin_year">
|
||||
<div class="rc-mini-bar-fill rc-mini-bar-year" :style="'width:' + marginBarPct(row, 'margin_year') + '%'"></div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="rc-vcell rc-vdonut">
|
||||
<div class="rc-donut" x-show="row.margin_month" :style="donutStyle(row)">
|
||||
<span class="rc-donut-label" x-text="sharePct(row) + '%'"></span>
|
||||
</div>
|
||||
<span class="rc-donut-empty" x-show="!row.margin_month">—</span>
|
||||
</td>
|
||||
|
||||
<td class="rc-vcell rc-vsteps">
|
||||
<span x-text="truncate(row.next_steps, 72) || '—'"></span>
|
||||
<span class="rc-expand-hint" x-show="row.next_steps && row.next_steps.length > 72">↵</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="rc-expand-row" x-show="expandedKey === projectKey(row)" x-cloak>
|
||||
<td class="rc-expand-cell" colspan="5">
|
||||
<div class="rc-expand-inner">
|
||||
<div class="rc-expand-chart">
|
||||
<div class="rc-bar-chart">
|
||||
<div class="rc-bar-col">
|
||||
<div class="rc-bar-stack" :style="'height:' + marginBarPct(row, 'margin_month') + '%'"></div>
|
||||
<span>Mnd</span>
|
||||
</div>
|
||||
<div class="rc-bar-col">
|
||||
<div class="rc-bar-stack rc-bar-year" :style="'height:' + marginBarPct(row, 'margin_year') + '%'"></div>
|
||||
<span>Jr</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rc-expand-stats">
|
||||
<div><span>Marge mnd</span><strong x-text="fmtEuro(row.margin_month)"></strong></div>
|
||||
<div><span>Marge jr</span><strong x-text="fmtEuro(row.margin_year)"></strong></div>
|
||||
<div><span>Portfolio</span><strong x-text="sharePct(row) + '%'"></strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rc-expand-body">
|
||||
<h4>Next steps</h4>
|
||||
<p x-text="row.next_steps || 'Geen next steps in Excel'"></p>
|
||||
<div class="rc-expand-task" x-show="row.db_id">
|
||||
<div class="rc-inline">
|
||||
<select x-model="taskForm.agent_name">
|
||||
<template x-for="a in agents" :key="a"><option :value="a" x-text="a"></option></template>
|
||||
</select>
|
||||
<input type="text" x-model="taskForm.title" placeholder="Taak…" @click.stop />
|
||||
<button type="button" class="btn btn-sm btn-pulse" @click.stop="assignTask()">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- STAVEN -->
|
||||
<div class="rc-bars-view" x-show="visualMode === 'bars'" x-cloak>
|
||||
<template x-for="row in rankedRows()" :key="'b-' + row.source_row + '-' + row.name">
|
||||
<button type="button" class="rc-bar-row"
|
||||
:class="['rc-style-' + (row.row_style || 'white'), { active: selectedKey === projectKey(row) }]"
|
||||
@click="toggleExpand(row)">
|
||||
<div class="rc-bar-row-top">
|
||||
<span class="rc-bar-name" x-text="row.name"></span>
|
||||
<span class="rc-bar-amt" x-text="row.margin_month != null ? fmtEuroCompact(row.margin_month) : '—'"></span>
|
||||
<span class="rc-bar-share" x-text="sharePct(row) + '%'"></span>
|
||||
</div>
|
||||
<div class="rc-bar-track">
|
||||
<div class="rc-bar-fill" :style="'width:' + marginBarPct(row, 'margin_month') + '%;background:' + styleColor(row.row_style)"></div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- KAARTEN -->
|
||||
<div class="rc-cards-view" x-show="visualMode === 'cards'" x-cloak>
|
||||
<template x-for="row in cardRows()" :key="'k-' + row.source_row + '-' + row.name">
|
||||
<article class="rc-card"
|
||||
:class="['rc-style-' + (row.row_style || 'white'), { active: selectedKey === projectKey(row) }]"
|
||||
@click="toggleExpand(row)">
|
||||
<div class="rc-card-head">
|
||||
<h3 x-text="row.name"></h3>
|
||||
<span class="rc-chip rc-chip-static" :class="'rc-chip-' + (row.row_style || 'white')" x-text="styleLabel(row.row_style)"></span>
|
||||
</div>
|
||||
<div class="rc-card-metrics">
|
||||
<div>
|
||||
<span>mnd</span>
|
||||
<strong x-text="row.margin_month != null ? fmtEuroCompact(row.margin_month) : '—'"></strong>
|
||||
</div>
|
||||
<div x-show="row.margin_year != null">
|
||||
<span>jr</span>
|
||||
<strong x-text="fmtEuroCompact(row.margin_year)"></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>aandeel</span>
|
||||
<strong x-text="sharePct(row) + '%'"></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rc-card-bar" aria-hidden="true">
|
||||
<div :style="'width:' + marginBarPct(row, 'margin_month') + '%;background:' + styleColor(row.row_style)"></div>
|
||||
</div>
|
||||
<p class="rc-card-steps" x-text="truncate(row.next_steps, 100) || 'Geen next step'"></p>
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- DETAIL PANEEL -->
|
||||
<aside class="rc-detail" x-show="selectedProject" x-cloak>
|
||||
<div class="rc-detail-head">
|
||||
<div>
|
||||
<h3 x-text="selectedProject?.name"></h3>
|
||||
<span class="rc-chip rc-chip-static" :class="'rc-chip-' + (selectedProject?.row_style || 'white')"
|
||||
x-text="styleLabel(selectedProject?.row_style)"></span>
|
||||
</div>
|
||||
<button type="button" class="rc-detail-close" @click="selectedKey = null; expandedKey = null">✕</button>
|
||||
</div>
|
||||
<div class="rc-detail-grid">
|
||||
<div class="rc-detail-stat">
|
||||
<span>Marge / mnd</span>
|
||||
<strong x-text="fmtEuro(selectedProject?.margin_month)"></strong>
|
||||
</div>
|
||||
<div class="rc-detail-stat" x-show="selectedProject?.margin_year != null">
|
||||
<span>Marge / jr</span>
|
||||
<strong x-text="fmtEuro(selectedProject?.margin_year)"></strong>
|
||||
</div>
|
||||
<div class="rc-detail-stat">
|
||||
<span>Portfolio</span>
|
||||
<strong x-text="sharePct(selectedProject || {}) + '%'"></strong>
|
||||
</div>
|
||||
<div class="rc-detail-ring" :style="donutStyle(selectedProject || {})">
|
||||
<span x-text="sharePct(selectedProject || {}) + '%'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rc-detail-compare">
|
||||
<div class="rc-detail-compare-col">
|
||||
<div class="rc-detail-vbar" :style="'height:' + marginBarPct(selectedProject || {}, 'margin_month') + '%;background:' + styleColor(selectedProject?.row_style)"></div>
|
||||
<span>Mnd</span>
|
||||
</div>
|
||||
<div class="rc-detail-compare-col" x-show="selectedProject?.margin_year != null">
|
||||
<div class="rc-detail-vbar rc-detail-vbar-year" :style="'height:' + marginBarPct(selectedProject || {}, 'margin_year') + '%'"></div>
|
||||
<span>Jr</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rc-detail-status">
|
||||
<span class="rc-detail-label">Zet status</span>
|
||||
<div class="rc-style-chips rc-style-chips-assign">
|
||||
<template x-for="s in styleFilters" :key="'as-' + s.id">
|
||||
<button type="button" class="rc-chip" :class="['rc-chip-' + s.id, { active: selectedProject?.row_style === s.id }]"
|
||||
@click="setCockpitStyle(s.id)" x-text="s.label"></button>
|
||||
</template>
|
||||
</div>
|
||||
<p class="rc-detail-hint" x-show="selectedProject && !selectedProject.db_id">Sync DB om status permanent te bewaren</p>
|
||||
</div>
|
||||
<div class="rc-detail-steps">
|
||||
<h4>Next steps</h4>
|
||||
<p x-text="selectedProject?.next_steps || 'Geen next steps in Excel'"></p>
|
||||
</div>
|
||||
<div class="rc-detail-task" x-show="selectedProject?.db_id">
|
||||
<div class="rc-inline">
|
||||
<select x-model="taskForm.agent_name">
|
||||
<template x-for="a in agents" :key="a"><option :value="a" x-text="a"></option></template>
|
||||
</select>
|
||||
<input type="text" x-model="taskForm.title" placeholder="Taak…" />
|
||||
<button type="button" class="btn btn-sm btn-pulse" @click="assignTask()">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Bewerken: originele Excel -->
|
||||
@@ -258,5 +336,5 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="/static/js/revenue-cockpit.js?v=6"></script>
|
||||
<script src="/static/js/revenue-cockpit.js?v=12"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user