270 lines
13 KiB
HTML
270 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=5" />
|
|
{% 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-logo" />
|
|
<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-logo-cell"></td>
|
|
<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-logo-cell"></td>
|
|
<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-logo-cell rc-logo-mini">
|
|
<template x-if="row.logoCell === 'cucina'">
|
|
<div class="rc-logo rc-logo-cucina rc-logo-xs"><span class="rc-logo-ring">C</span></div>
|
|
</template>
|
|
<template x-if="row.logoCell === 'foodlinkk'">
|
|
<div class="rc-logo rc-logo-foodlinkk rc-logo-xs"><span class="rc-fl-brand">F</span></div>
|
|
</template>
|
|
</td>
|
|
|
|
<td class="rc-vcell rc-vdeal" x-text="row.name"></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="6">
|
|
<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=4"></script>
|
|
{% endblock %}
|