Files
kanbante/app/templates/forecast.html
T
Spark 71e705ea64 fix(dashboard): tri par date des tableaux, tri/regroupement sur les Épinglés
- tablesort.js : parseFloat lisait l'année des dates ISO (2026)
  et mettait toutes les lignes à égalité — comparaison numérique
  seulement pour les valeurs entièrement numériques.
- dashboard.js : pinned-grid rejoint les grilles triées et
  regroupées (son exclusion rendait les deux outils invisibles
  là où l'œil se pose) ; dates comparées via Date.parse.
- data-first-dir=desc sur Dernière activité / Écart / Zombies.
2026-10-12 14:54:24 -04:00

121 lines
6.3 KiB
HTML

<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token }}">
<title>Prévisions — Kanbante</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
{% include "_header.html" %}
<main class="focus-main">
<section class="briefing-card">
<h1>🔮 Prévisions — « on finit quand ? »</h1>
<p>Le tableau de bord des jalons, <strong>tous projets confondus</strong>, trié par risque : l'écart entre la date estimée (au débit réel du journal) et l'échéance. Réponse honnête — fourchettes plutôt que faux chiffres précis, et aucune prévision sans historique de fermetures.</p>
<p class="forecast-summary">
<span class="chip chip-total">🎯 {{ summary.total }} jalon(s) ouvert(s)</span>
{% if summary.late %}<span class="chip chip-late">⚠️ {{ summary.late }} en retard à ce rythme</span>{% endif %}
{% if summary.ontime %}<span class="chip chip-ok">✅ {{ summary.ontime }} dans les temps</span>{% endif %}
</p>
<div class="search-bar forecast-filter">
<input type="text" id="ms-filter" placeholder="🔍 Filtrer par projet ou jalon..." autocomplete="off">
</div>
</section>
{% if rows %}
<table class="proj-table pilot-table">
<thead>
<tr>
<th data-sort="project">Projet</th>
<th data-sort="title">Jalon</th>
<th data-sort="due">Échéance</th>
<th>Progression</th>
<th>ETA</th>
<th data-sort="gap" data-first-dir="desc">Écart</th>
<th data-sort="throughput">Débit / sem.</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr{% if row.anchor %} id="ms-{{ row.owner }}-{{ row.repo }}"{% endif %}
data-project="{{ row.project|lower }}" data-title="{{ row.title|lower }}"
data-due="{{ row.due_sort }}" data-gap="{{ row.gap_days if row.gap_days is not none else '' }}"
data-throughput="{{ row.throughput if row.throughput is not none else '' }}"
data-search="{{ (row.project ~ ' ' ~ row.title)|lower }}">
<td><a href="/project/{{ row.project }}">{{ row.project }}</a></td>
<td><a href="/board/{{ row.project }}?milestone={{ row.title|urlencode }}"><strong>{{ row.title }}</strong></a></td>
<td>{{ row.due_label }}</td>
<td>
{% if row.total %}
<div class="progress ms-progress pilot-progress"><span style="width: {{ row.pct }}%"></span></div>
<span class="ms-meta">{{ row.closed }}/{{ row.total }}</span>
{% else %}—{% endif %}
</td>
<td>{{ row.eta or '—' }}</td>
<td>
{% if row.verdict == 'late' %}
<span class="verdict verdict-late">⚠️ +{{ row.gap_days }} j</span>
{% elif row.verdict == 'ontime' %}
<span class="verdict verdict-ok">✅ dans les temps{% if row.gap_days is not none and row.gap_days < 0 %} ({{ -row.gap_days }} j d'avance){% endif %}</span>
{% elif row.verdict == 'nodue' %}
<span class="verdict">sans échéance</span>
{% else %}
<span class="verdict">pas d'ETA</span>
{% endif %}
</td>
<td>{{ row.throughput if row.throughput is not none else '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<section class="briefing-card"><p>Aucun jalon ouvert dans les projets actifs. Crée des jalons dans Gitea pour piloter par échéances.</p></section>
{% endif %}
<section class="briefing-card">
<h2>📈 Débit par projet</h2>
{% for project in projects %}
<div class="forecast-flex">
<div class="forecast-proj-name">📂 <a href="/project/{{ project.full_name }}">{{ project.full_name }}</a></div>
<svg class="sparkline" viewBox="0 0 168 48" role="img" aria-label="Fermetures par semaine">
{% for bar in project.bars %}
{% set px = (bar.h * 0.42)|round|int %}
<rect x="{{ loop.index0 * 21 }}" y="{{ 44 - px }}" width="15" height="{{ px if px > 0 else 1 }}" rx="2">
<title>{{ bar.label }} : {{ bar.count }} fermée(s)</title>
</rect>
{% endfor %}
</svg>
<div class="forecast-nums">
{% if project.stats %}
<p>Débit : <strong>{{ project.stats.avg }} / semaine</strong> (min {{ project.stats.min }}, max {{ project.stats.max }})</p>
{% else %}
<p>Débit : <strong>pas assez d'historique</strong>.</p>
{% endif %}
<p>Restant ouvert : <strong>{{ project.remaining }}</strong>{% if project.eta_label %} · 🔮 backlog {{ project.eta_label }}{% endif %}</p>
</div>
</div>
{% else %}
<p>Aucun projet actif.</p>
{% endfor %}
</section>
</main>
<footer>
<span>Kanbante v{{ version }} · prévisions = journal des fermetures, pas boule de cristal</span>
</footer>
{% include "_palette.html" %}
<script src="/static/js/palette.js" defer></script>
<script src="/static/js/live.js" defer></script>
<script src="/static/js/tablesort.js" defer></script>
<script>
document.getElementById('ms-filter')?.addEventListener('input', function (e) {
var q = e.target.value.toLowerCase();
document.querySelectorAll('.pilot-table tbody tr').forEach(function (row) {
row.style.display = !q || (row.dataset.search || '').includes(q) ? '' : 'none';
});
});
</script>
</body>
</html>