Files
Aissa 4dd1b8265f Add live CEO briefing, Wereldexport CRM, halal engine, and realtime cockpit.
Ship export intel with contact filters and CRM push, Herman live digest with Telegram briefing, halal recommendation engine, revenue cockpit, and dashboard polling/WebSocket fixes.
2026-07-19 18:25:21 +00:00

263 lines
13 KiB
HTML

{% extends "base.html" %}
{% block title %}Revenue Cockpit · Cucina{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/revenue-cockpit.css?v=6" />
{% endblock %}
{% block content %}
<div class="rc-page" :class="{ 'rc-mode-sheet': viewMode === 'sheet' }" x-data="revenueCockpit()" x-init="init()">
<header class="rc-header">
<div>
<h1>Revenue Cockpit <span class="live-badge">Excel Live</span></h1>
<p class="rc-subtitle" x-show="meta.file_mtime">
<span x-text="meta.source_file || 'Succes Sheet .xlsx'"></span>
· <span x-text="fmtDate(meta.file_mtime)"></span>
</p>
</div>
<div class="rc-header-actions">
<div class="rc-view-toggle">
<button type="button" class="rc-toggle-btn" :class="{ active: viewMode === 'cockpit' }" @click="viewMode = 'cockpit'">Cockpit</button>
<button type="button" class="rc-toggle-btn" :class="{ active: viewMode === 'sheet' }" @click="switchToSheet()">Bewerken</button>
</div>
<button type="button" class="btn btn-sm btn-pulse" @click="loadLive()" :disabled="loading"></button>
<button type="button" class="btn btn-sm" @click="takeSnapshot()" title="Snapshot">📸</button>
<button type="button" class="btn btn-sm btn-pulse-green" @click="importExcel()" title="Sync DB">📥</button>
</div>
</header>
<!-- Cockpit: Excel-layout met charts -->
<div class="rc-cockpit" x-show="viewMode === 'cockpit'" :class="{ 'rc-loading': loading }">
<div class="rc-summary-strip">
<div class="rc-sum-item">
<span class="rc-sum-label">Marge / mnd</span>
<strong x-text="fmtEuro(aggregates.total_margin_month)"></strong>
</div>
<div class="rc-sum-item">
<span class="rc-sum-label">Marge / jr</span>
<strong x-text="fmtEuro(aggregates.total_margin_year)"></strong>
</div>
<div class="rc-sum-item">
<span class="rc-sum-label">Deals</span>
<strong x-text="filteredProjects().length + '/' + projects.length"></strong>
</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>
</template>
</div>
</div>
<div class="rc-visual-wrap">
<table class="rc-visual-table" cellspacing="0" cellpadding="0">
<colgroup>
<col class="rc-col-deal" />
<col class="rc-col-chart-num" />
<col class="rc-col-chart-num" />
<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>
<td class="rc-vcell rc-num">Marge jr</td>
<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">
<tr class="rc-visual-row"
:class="['rc-style-' + (row.row_style || 'white'), 'rc-brand-' + (row.brand || 'none'), { 'rc-row-open': expandedKey === 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>
</div>
<!-- Bewerken: originele Excel -->
<div class="rc-excel-page" x-show="viewMode === 'sheet'" x-cloak>
<div class="rc-toolbar">
<span class="rc-toolbar-title">Bewerken · DB</span>
<div class="rc-toolbar-actions">
<span class="rc-save-hint" x-show="saving" x-cloak>Opslaan…</span>
<span class="rc-save-hint rc-saved" x-show="savedAt && !saving" x-cloak x-text="'Opgeslagen ' + savedAt"></span>
</div>
</div>
<div class="rc-sheet-wrap" :class="{ 'rc-loading': loading }">
<table class="rc-sheet" cellspacing="0" cellpadding="0">
<colgroup>
<col class="rc-col-logo" />
<col class="rc-col-deal" />
<col class="rc-col-num" />
<col class="rc-col-num" />
<col class="rc-col-steps" />
</colgroup>
<tr class="rc-row rc-row-goals">
<td class="rc-logo-cell"></td>
<td class="rc-cell rc-cell-edit" contenteditable="true"
x-init="$el.textContent = goals.vision_text || ''"
@blur="saveGoalsField('vision_text', $event.target)" @focus="editStart($event)"></td>
<td class="rc-cell rc-cell-edit" contenteditable="true"
x-init="$el.textContent = goals.horizon_text || ''"
@blur="saveGoalsField('horizon_text', $event.target)" @focus="editStart($event)"></td>
<td class="rc-cell rc-cell-edit" contenteditable="true"
x-init="$el.textContent = goals.mid_text || ''"
@blur="saveGoalsField('mid_text', $event.target)" @focus="editStart($event)"></td>
<td class="rc-cell rc-cell-edit rc-cell-tagline" contenteditable="true"
x-init="$el.textContent = goals.tagline || ''"
@blur="saveGoalsField('tagline', $event.target)" @focus="editStart($event)"></td>
</tr>
<tr class="rc-row rc-row-head">
<td class="rc-logo-cell"></td>
<td class="rc-cell rc-th">Deal</td>
<td class="rc-cell rc-th rc-num">Marge Month</td>
<td class="rc-cell rc-th rc-num">Marge Year</td>
<td class="rc-cell rc-th">Next steps</td>
</tr>
<template x-for="row in sheetRows" :key="row.id || row.source_row">
<tr class="rc-row rc-data-row"
:class="['rc-style-' + row.row_style, { 'rc-selected': selectedId === row.id }]"
@click="selectRow(row)">
<template x-if="row.logoCell === 'cucina'">
<td class="rc-logo-cell" :rowspan="row.logoSpan">
<div class="rc-logo rc-logo-cucina"><span class="rc-logo-ring">CUCINA<br>HALAL<br>FOOD</span></div>
</td>
</template>
<template x-if="row.logoCell === 'foodlinkk'">
<td class="rc-logo-cell" :rowspan="row.logoSpan">
<div class="rc-logo rc-logo-foodlinkk">
<span class="rc-fl-brand">Foodlinkk</span>
<span class="rc-fl-sub">FOOD MARKETING AGENCY</span>
</div>
</td>
</template>
<td class="rc-cell rc-cell-deal rc-cell-edit" contenteditable="true"
x-init="$el.textContent = row.name || ''"
@blur="saveProjectField(row, 'name', $event.target)" @focus="editStart($event)" @click.stop></td>
<td class="rc-cell rc-cell-num rc-cell-edit" contenteditable="true"
x-init="$el.textContent = fmtNum(row.margin_month)"
@blur="saveProjectField(row, 'margin_month', $event.target)" @focus="editStart($event)" @click.stop></td>
<td class="rc-cell rc-cell-num rc-cell-edit" contenteditable="true"
x-init="$el.textContent = fmtNum(row.margin_year)"
@blur="saveProjectField(row, 'margin_year', $event.target)" @focus="editStart($event)" @click.stop></td>
<td class="rc-cell rc-cell-steps rc-cell-edit" contenteditable="true"
x-init="$el.textContent = row.next_steps || ''"
@blur="saveProjectField(row, 'next_steps', $event.target)" @focus="editStart($event)" @click.stop></td>
</tr>
</template>
</table>
</div>
<div class="rc-row-panel" x-show="selectedRow" x-cloak @click.outside="selectedId = null">
<div class="rc-row-panel-head">
<strong x-text="selectedRow?.name"></strong>
<select class="rc-style-select" :value="selectedRow?.row_style" @change="setRowStyle($event.target.value)">
<option value="green">Groen (live)</option>
<option value="red">Rood (inactief)</option>
<option value="orange">Oranje</option>
<option value="yellow">Geel (sectie)</option>
<option value="blue">Blauw</option>
<option value="white">Wit</option>
</select>
</div>
<div class="rc-row-panel-body">
<label>Agent taak
<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 titel…" />
<button type="button" class="rc-btn rc-btn-primary" @click="assignTask()"></button>
</div>
</label>
</div>
</div>
<p class="rc-footnote">Klik in een cel om te bewerken · bron: Excel op NAS</p>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/revenue-cockpit.js?v=6"></script>
{% endblock %}