feat(pilotage): Prévisions en tableau des jalons, Santé en tableau de sévérité (chantier C)
- /forecast : une ligne par jalon, tous projets, triée par risque (écart décroissant), filtrable, ancres ms-<owner>-<repo> pointées par le bloc jalon des cartes du dashboard ; débit par projet conservé en tableau compact. - /health : tableau trié par sévérité (pastille, zombies, goulot) ; page détail : sélecteur de projet (health.js). - Tri des tableaux mutualisé dans static/js/tablesort.js (dashboard, forecast, health).
This commit is contained in:
@@ -1443,3 +1443,35 @@ body.sidebar-collapsed .side-foot { justify-content: center; }
|
||||
.proj-table { display: block; overflow-x: auto; }
|
||||
.dash-view-toggle { margin-left: 0; }
|
||||
}
|
||||
|
||||
/* ── Pilotage 0.19.0 : tableaux Prévisions/Santé (chantier C) ── */
|
||||
.pilot-table { margin-bottom: 18px; }
|
||||
.pilot-table tr[id] { scroll-margin-top: 76px; }
|
||||
.pilot-progress { min-width: 90px; }
|
||||
.forecast-summary { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 4px; }
|
||||
.forecast-summary .chip {
|
||||
font-size: 12.5px; padding: 3px 10px; border-radius: 999px;
|
||||
background: rgba(255, 255, 255, .07); border: 1px solid var(--border, #333);
|
||||
}
|
||||
.forecast-summary .chip-late { background: rgba(248, 81, 73, .15); }
|
||||
.forecast-summary .chip-ok { background: rgba(63, 185, 80, .15); }
|
||||
.forecast-filter { margin-top: 10px; max-width: 420px; }
|
||||
.forecast-filter input { width: 100%; }
|
||||
.forecast-proj-name { font-weight: 600; min-width: 180px; }
|
||||
.verdict {
|
||||
font-size: 12.5px; padding: 2px 9px; border-radius: 999px; white-space: nowrap;
|
||||
background: rgba(255, 255, 255, .06);
|
||||
}
|
||||
.verdict-late { background: rgba(248, 81, 73, .18); }
|
||||
.verdict-ok { background: rgba(63, 185, 80, .16); }
|
||||
a.ms-title { color: inherit; text-decoration: none; }
|
||||
a.ms-title:hover { text-decoration: underline; }
|
||||
.health-switch-bar {
|
||||
display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap;
|
||||
}
|
||||
.health-switch-bar select {
|
||||
padding: 8px 10px; border-radius: 8px; font-size: 14px; min-height: 38px;
|
||||
background: var(--panel-2, #1c2330); color: inherit;
|
||||
border: 1px solid var(--border, #333);
|
||||
}
|
||||
.health-back { font-size: 13.5px; }
|
||||
|
||||
+2
-29
@@ -154,33 +154,6 @@
|
||||
if (e.key === 'Escape') closeMenus();
|
||||
});
|
||||
|
||||
/* ── Tri des tableaux denses (#22) ─────────────────────── */
|
||||
document.querySelectorAll('.proj-table').forEach(function (table) {
|
||||
var tbody = table.querySelector('tbody');
|
||||
table.querySelectorAll('th[data-sort]').forEach(function (th) {
|
||||
th.classList.add('sortable');
|
||||
th.addEventListener('click', function () {
|
||||
var key = th.getAttribute('data-sort');
|
||||
var dir = th.getAttribute('data-dir') === 'asc' ? 'desc' : 'asc';
|
||||
table.querySelectorAll('th').forEach(function (h) {
|
||||
h.removeAttribute('data-dir');
|
||||
});
|
||||
th.setAttribute('data-dir', dir);
|
||||
var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
|
||||
rows.sort(function (a, b) {
|
||||
var va = a.getAttribute('data-' + key) || '';
|
||||
var vb = b.getAttribute('data-' + key) || '';
|
||||
var na = parseFloat(va), nb = parseFloat(vb);
|
||||
var cmp;
|
||||
if (!isNaN(na) && !isNaN(nb) && va !== '' && vb !== '') {
|
||||
cmp = na - nb;
|
||||
} else {
|
||||
cmp = va < vb ? -1 : (va > vb ? 1 : 0);
|
||||
}
|
||||
return dir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
rows.forEach(function (r) { tbody.appendChild(r); });
|
||||
});
|
||||
});
|
||||
});
|
||||
/* Le tri des tableaux denses (#22) vit dans tablesort.js,
|
||||
partagé avec /forecast et /health (0.19.0). */
|
||||
})();
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
(function () {
|
||||
var csrf = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
|
||||
|
||||
/* Project switcher (0.19.0): jump between projects without
|
||||
going back to the health index. */
|
||||
var switcher = document.getElementById('health-switch');
|
||||
if (switcher) {
|
||||
switcher.addEventListener('change', function () {
|
||||
window.location.href = '/health/' + switcher.value;
|
||||
});
|
||||
}
|
||||
|
||||
function post(url, fields) {
|
||||
var data = new FormData();
|
||||
Object.keys(fields).forEach(function (k) { data.append(k, fields[k]); });
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Kanbante — tri des tableaux « pilotage » (.proj-table), partagé
|
||||
* entre le dashboard, /forecast et /health (0.19.0).
|
||||
* Tri client par colonne : les lignes portent leurs valeurs dans
|
||||
* des attributs data-<clé> ; cliquer un <th data-sort="clé">
|
||||
* trie dans un sens puis dans l'autre.
|
||||
*/
|
||||
(function () {
|
||||
document.querySelectorAll('.proj-table').forEach(function (table) {
|
||||
var tbody = table.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
table.querySelectorAll('th[data-sort]').forEach(function (th) {
|
||||
th.classList.add('sortable');
|
||||
th.addEventListener('click', function () {
|
||||
var key = th.getAttribute('data-sort');
|
||||
var dir = th.getAttribute('data-dir') === 'asc' ? 'desc' : 'asc';
|
||||
table.querySelectorAll('th').forEach(function (h) {
|
||||
h.removeAttribute('data-dir');
|
||||
});
|
||||
th.setAttribute('data-dir', dir);
|
||||
var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
|
||||
rows.sort(function (a, b) {
|
||||
var va = a.getAttribute('data-' + key) || '';
|
||||
var vb = b.getAttribute('data-' + key) || '';
|
||||
var na = parseFloat(va), nb = parseFloat(vb);
|
||||
var cmp;
|
||||
if (!isNaN(na) && !isNaN(nb) && va !== '' && vb !== '') {
|
||||
cmp = na - nb;
|
||||
} else {
|
||||
cmp = va < vb ? -1 : (va > vb ? 1 : 0);
|
||||
}
|
||||
return dir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
rows.forEach(function (r) { tbody.appendChild(r); });
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user