Files
vela-platform/backend/templates/dashboard.html
oimwiodev d1160673a7 🎉 v1.0.0 — Vela Platform launch
Self-hosted P&L tracking app with component-level pricing.
Offers: Atlas/Atlas+/Rif/Rif+ with granular cost breakdown.
API + MCP + multi-user auth.
2026-06-15 23:05:59 +01:00

160 lines
5.6 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard — Vela Platform{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="mb-0"><i class="bi bi-speedometer2"></i> Dashboard</h2>
<span class="badge bg-primary fs-6">{{ pnl.month }}</span>
</div>
<!-- P&L Summary Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3 col-6">
<div class="card card-stat h-100">
<div class="card-body">
<small class="text-muted">Revenue</small>
<h4 class="mt-1">{{ "%.0f"|format(pnl.total_revenue) }} MAD</h4>
</div>
</div>
</div>
<div class="col-md-3 col-6">
<div class="card card-stat h-100">
<div class="card-body">
<small class="text-muted">Cost</small>
<h4 class="mt-1">{{ "%.0f"|format(pnl.total_cost) }} MAD</h4>
</div>
</div>
</div>
<div class="col-md-3 col-6">
<div class="card card-stat green h-100">
<div class="card-body">
<small class="text-muted">Gross Profit</small>
<h4 class="mt-1">{{ "%.0f"|format(pnl.gross_profit) }} MAD</h4>
</div>
</div>
</div>
<div class="col-md-3 col-6">
<div class="card card-stat {{ 'green' if pnl.net_profit >= 0 else 'red' }} h-100">
<div class="card-body">
<small class="text-muted">Net Profit</small>
<h4 class="mt-1">{{ "%.0f"|format(pnl.net_profit) }} MAD</h4>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">Margin Breakdown</div>
<div class="card-body">
<table class="table table-sm mb-0">
<tr><td>Gross Margin</td><td class="text-end">{{ "%.1f"|format(pnl.gross_margin_pct) }}%</td></tr>
<tr><td>OpEx (fixed)</td><td class="text-end">{{ "%.0f"|format(pnl.opex) }} MAD</td></tr>
<tr class="fw-bold"><td>Net Margin</td><td class="text-end">{{ "%.1f"|format(pnl.net_margin_pct) }}%</td></tr>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">Active Offers — Cost Breakdown</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>Offer</th>
<th class="text-end">Sell</th>
<th class="text-end">Cost</th>
<th class="text-end">Margin</th>
<th class="text-end">Components</th>
</tr>
</thead>
<tbody>
{% for s in services %}
<tr>
<td><strong>{{ s.name }}</strong></td>
<td class="text-end">{{ "%.0f"|format(s.sell_price) }} MAD</td>
<td class="text-end text-muted">{{ "%.0f"|format(s.cost_price) }} MAD</td>
<td class="text-end {{ 'text-success' if (s.sell_price - s.cost_price) > 0 else 'text-danger' }}">
{{ "%.0f"|format(s.sell_price - s.cost_price) }} MAD
</td>
<td class="text-end text-muted">
<small>{{ s.components|length }} items</small>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 12-Month Trend Chart -->
<div class="card mb-4">
<div class="card-header">12-Month Trend</div>
<div class="card-body">
<canvas id="trendChart" height="80"></canvas>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const trendData = {{ trend | tojson }};
const labels = trendData.map(d => d.month);
const revenue = trendData.map(d => d.total_revenue);
const cost = trendData.map(d => d.total_cost);
const netProfit = trendData.map(d => d.net_profit);
new Chart(document.getElementById('trendChart'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Revenue',
data: revenue,
borderColor: '#0d6efd',
backgroundColor: 'rgba(13,110,253,0.1)',
fill: true,
tension: 0.3,
},
{
label: 'Cost',
data: cost,
borderColor: '#dc3545',
backgroundColor: 'rgba(220,53,69,0.1)',
fill: true,
tension: 0.3,
},
{
label: 'Net Profit',
data: netProfit,
borderColor: '#198754',
backgroundColor: 'rgba(25,135,84,0.1)',
fill: true,
tension: 0.3,
borderWidth: 2,
},
],
},
options: {
responsive: true,
plugins: {
legend: { position: 'bottom' },
},
scales: {
y: {
ticks: { callback: v => v + ' MAD' },
},
},
},
});
</script>
{% endblock %}