refonte(ui): design system moderne et intuitif sur les 5 pages

- en-tête vitré sticky, navigation en pastilles avec état actif,
  logo en tuile dégradée, fil d'Ariane projet
- dashboard: hero + tuiles de synthèse (actifs, fermées/déplacements
  30 j, cycle moyen), barre de progression par projet, états vides
  guidés, raccourci clavier /
- board: sélecteur de milestones + bandeau de progression, colonnes
  à défilement indépendant, placeholders de colonnes vides, hint
  sur le panneau de notes, toast et drawer animés
- live.js: recharge douce conditionnelle (visible, pas de drawer,
  pas de saisie, pas de drag) sinon pastille d'actualisation
- accessibilité: anneau de focus visible, prefers-reduced-motion,
  responsive retravaillé; board.js: drapeau de drag partagé avec
  live.js + raccourci / vers le filtre
This commit is contained in:
Spark
2026-10-11 10:23:50 -04:00
parent 23e63c7f6e
commit dbda3bcf5a
8 changed files with 672 additions and 259 deletions
+10
View File
@@ -29,8 +29,10 @@ document.addEventListener('DOMContentLoaded', () => {
group: 'kanban',
animation: 200,
ghostClass: 'dragging',
onStart: function () { window.__kanbanteDragging = true; },
dragClass: 'dragging',
onEnd: function (evt) {
window.__kanbanteDragging = false;
if (evt.from === evt.to && evt.oldIndex === evt.newIndex) return;
const card = evt.item;
const formData = new FormData();
@@ -128,6 +130,14 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// Keyboard: "/" jumps to the filter box.
document.addEventListener('keydown', (e) => {
if (e.key === '/' && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName || '')) {
e.preventDefault();
document.getElementById('card-filter')?.focus();
}
});
// ── Card detail drawer ─────────────────────────────────
// A click opens the panel; a drag must not. Track pointer travel.
let downX = 0, downY = 0;
+44
View File
@@ -0,0 +1,44 @@
// Kanbante — live updates via Server-Sent Events.
// When Gitea reports activity (webhook -> /api/stream), reload the
// page softly if it is safe (visible tab, no drawer open, no text
// being typed, no drag in progress); otherwise show a pill the
// user can click when ready.
(function () {
if (!window.EventSource) return;
var pill = null;
var timer = null;
function showPill() {
if (pill) return;
pill = document.createElement('button');
pill.id = 'live-pill';
pill.type = 'button';
pill.textContent = '🔄 Nouvelle activité Gitea — actualiser';
pill.addEventListener('click', function () { window.location.reload(); });
document.body.appendChild(pill);
}
function safeToReload() {
if (document.visibilityState !== 'visible') return false;
if (window.__kanbanteDragging) return false;
var drawer = document.getElementById('card-detail');
if (drawer && !drawer.hidden) return false;
var active = document.activeElement;
if (active && (active.tagName === 'TEXTAREA' ||
(active.tagName === 'INPUT' && active.type === 'text'))) return false;
return true;
}
function scheduleReload() {
if (timer) return;
timer = setTimeout(function () {
timer = null;
if (safeToReload()) window.location.reload();
else showPill();
}, 1200);
}
var source = new EventSource('/api/stream');
source.addEventListener('gitea', scheduleReload);
})();