188 lines
6.9 KiB
HTML
188 lines
6.9 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Statistiques{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header d-flex justify-content-between align-items-center">
|
|
<h1><i class="bi bi-bar-chart-line me-2 text-success"></i>Statistiques</h1>
|
|
<a href="{{ url_for('export_csv') }}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-download me-1"></i>Export CSV
|
|
</a>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<!-- Par client -->
|
|
<div class="col-md-7">
|
|
<div class="card">
|
|
<div class="card-header py-3">CA par client</div>
|
|
<div class="card-body p-0">
|
|
{% if by_client %}
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr><th>Client</th><th>Commandes</th><th>Marge moy.</th><th class="text-end">CA total</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% set total_rev = by_client | sum(attribute='revenue') %}
|
|
{% for c in by_client %}
|
|
<tr>
|
|
<td class="fw-semibold">{{ c.name }}</td>
|
|
<td>{{ c.jobs }}</td>
|
|
<td><span class="badge bg-light text-dark">{{ c.avg_margin }}%</span></td>
|
|
<td class="text-end">
|
|
<span class="fw-bold" style="color:#ff6b35">{{ c.revenue }} €</span>
|
|
{% if total_rev > 0 %}
|
|
<div class="progress mt-1" style="height:4px">
|
|
<div class="progress-bar" style="width:{{ (c.revenue / total_rev * 100)|round(0) }}%;background:#ff6b35"></div>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">Aucune donnée disponible.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Par mois -->
|
|
<div class="col-md-5">
|
|
<div class="card">
|
|
<div class="card-header py-3">CA par mois (12 derniers)</div>
|
|
<div class="card-body p-0">
|
|
{% if by_month %}
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr><th>Mois</th><th>Cmds</th><th class="text-end">CA</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% set max_rev = by_month | map(attribute='revenue') | max %}
|
|
{% for m in by_month %}
|
|
<tr>
|
|
<td class="fw-semibold">{{ m.month }}</td>
|
|
<td>{{ m.jobs }}</td>
|
|
<td class="text-end">
|
|
<span class="fw-bold">{{ m.revenue }} €</span>
|
|
{% if max_rev > 0 %}
|
|
<div class="progress mt-1" style="height:4px">
|
|
<div class="progress-bar bg-success" style="width:{{ (m.revenue / max_rev * 100)|round(0) }}%"></div>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">Aucune donnée disponible.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Gaspillage & Échecs ──────────────────────────────────────────────────── -->
|
|
<div class="row g-4 mt-0">
|
|
|
|
<!-- Gaspillage par matière -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header py-3">
|
|
<i class="bi bi-trash3 text-danger me-2"></i>Filament gaspillé par matière
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if waste_by_material %}
|
|
{% set max_waste = waste_by_material | map(attribute='wasted_g') | max %}
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Matière</th>
|
|
<th class="text-end">Gaspillé</th>
|
|
<th class="text-end text-muted small">Stock restant</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for m in waste_by_material %}
|
|
<tr>
|
|
<td>
|
|
{% if m.color %}
|
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;
|
|
background:{{ m.color | css_color }};border:1px solid #ccc;
|
|
vertical-align:middle;margin-right:5px"></span>
|
|
{% endif %}
|
|
<span class="fw-semibold">{{ m.name }}</span>
|
|
{% if m.type %}<span class="badge bg-light text-dark ms-1 small">{{ m.type }}</span>{% endif %}
|
|
</td>
|
|
<td class="text-end">
|
|
<span class="text-danger fw-bold">{{ m.wasted_g }} g</span>
|
|
{% if max_waste > 0 %}
|
|
<div class="progress mt-1" style="height:3px">
|
|
<div class="progress-bar bg-danger" style="width:{{ (m.wasted_g / max_waste * 100)|round(0) }}%"></div>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end text-muted small">{{ m.stock_g }} g</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-check-circle text-success fs-2 d-block mb-2"></i>
|
|
Aucun gaspillage enregistré.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Taux d'échec par imprimante -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header py-3">
|
|
<i class="bi bi-printer text-warning me-2"></i>Taux d'échec par imprimante
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if failure_by_printer %}
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Imprimante</th>
|
|
<th class="text-center">Taux</th>
|
|
<th class="text-end">Gaspillé</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for p in failure_by_printer %}
|
|
{% set rate = (p.failed_slots / p.total_slots * 100) | round(1) %}
|
|
<tr>
|
|
<td>
|
|
<div class="fw-semibold">{{ p.name }}</div>
|
|
<div class="text-muted small">{{ p.failed_slots }}/{{ p.total_slots }} créneaux</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<span class="badge {% if rate >= 20 %}bg-danger{% elif rate >= 10 %}bg-warning text-dark{% else %}bg-success{% endif %}">
|
|
{{ rate }}%
|
|
</span>
|
|
</td>
|
|
<td class="text-end {% if p.wasted_g > 0 %}text-danger{% else %}text-muted{% endif %} fw-semibold">
|
|
{{ p.wasted_g }} g
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-check-circle text-success fs-2 d-block mb-2"></i>
|
|
Aucun échec enregistré.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock %}
|