diff --git a/manifest.json b/manifest.json index 69e60df..2a0c2c4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planning Techniciens — Vue claire", - "version": "2.0.0", + "version": "2.0.1", "description": "Réaffiche le planning du jour (itsma.vd.ch) avec pompier, absents, tooltips enrichis et thème clair/sombre.", "permissions": [ "activeTab", diff --git a/viewer.css b/viewer.css index 24908d8..3ceda63 100644 --- a/viewer.css +++ b/viewer.css @@ -176,10 +176,29 @@ html, body { } .stats { - padding: 10px 20px 0 20px; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + padding: 12px 20px 4px 20px; + font-size: 13px; color: var(--text-muted); +} +.global-stat b { + color: var(--text); + font-weight: 600; +} +.global-stat-main b { + font-size: 16px; +} +.global-stat-sub { + color: var(--text-faint); font-size: 12px; } +.global-stat-sep { + color: var(--text-faint); + opacity: 0.5; +} /* ========================================================================== Grille de cartes @@ -284,19 +303,6 @@ html, body { background: var(--danger-soft); } -.timeline-pompier::before { - content: "En pompier toute la journée"; - position: absolute; - top: 2px; - right: 14px; - font-size: 10px; - color: var(--danger); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; - opacity: 0.7; -} - .timeline-bar { position: relative; height: 20px; @@ -387,23 +393,43 @@ html, body { font-family: var(--mono); } -/* Stats matin / après-midi / total */ +/* Stats par carte : total en gros, matin/aprem en secondaire */ .card-stats { display: flex; - gap: 16px; - padding: 6px 14px 8px 14px; + align-items: baseline; + justify-content: space-between; + padding: 10px 14px; + background: var(--bg-muted); + border-bottom: 1px solid var(--border); +} +.stat-total { + display: flex; + align-items: baseline; + gap: 6px; +} +.stat-total-num { + font-size: 22px; + font-weight: 700; + color: var(--text); + line-height: 1; +} +.stat-total-lbl { font-size: 12px; color: var(--text-muted); - border-bottom: 1px solid var(--border); - background: var(--bg-muted); } -.card-stats .stat-chunk b { - color: var(--text); +.stat-split { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text-faint); +} +.stat-split-item b { + color: var(--text-muted); font-weight: 600; } -.card-stats .stat-chunk.total { - margin-left: auto; - color: var(--text); +.stat-split-sep { + opacity: 0.4; } /* Note de statut pompier/absent en haut de carte */ @@ -482,31 +508,44 @@ html, body { min-width: 0; display: flex; flex-direction: column; - gap: 2px; + gap: 1px; } -.intervention-title { +/* La référence S260xxx_xxxxx est mise en avant */ +.intervention-refhdr { + font-family: var(--mono); font-size: 13px; + font-weight: 600; color: var(--text); + letter-spacing: 0.02em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.intervention-refhdr.no-ref { + font-family: var(--font); + font-weight: normal; + color: var(--text-faint); +} + +/* Titre type d'intervention en secondaire */ +.intervention-title { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .intervention-meta { - font-size: 12px; - color: var(--text-muted); + font-size: 11px; + color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.intervention-ref { - font-family: var(--mono); - font-size: 11px; - color: var(--text-faint); -} - .intervention-copy { flex-shrink: 0; padding: 4px 8px; diff --git a/viewer.js b/viewer.js index ab6b493..fc7a439 100644 --- a/viewer.js +++ b/viewer.js @@ -683,21 +683,34 @@ function ddmmyyyyToDateNum(s) { } function computeStats(techs, targetDate) { - let pompiers = 0, absents = 0, activeInterventions = 0; + let pompiers = 0, absents = 0; + let totalInterventions = 0, morning = 0, afternoon = 0; + for (const tech of techs) { const isPompier = tech.interventions.some(iv => iv.isPompier); const isAbsent = isTechAbsent(tech, targetDate); if (isPompier) pompiers++; if (isAbsent) absents++; - if (!isPompier && !isAbsent) { - activeInterventions += tech.interventions.length; + + // Compter TOUTES les vraies interventions (même celles des pompiers) + const real = tech.interventions.filter(iv => + iv.type !== "AL-Absence" && !iv.isPompier + ); + totalInterventions += real.length; + for (const iv of real) { + const s = timeToMinutes(iv.startTime); + if (s !== null && s < 12 * 60) morning++; + else if (s !== null) afternoon++; } } + return { totalTechs: techs.length, pompiers, absents, - activeInterventions + totalInterventions, + morning, + afternoon }; } @@ -768,11 +781,16 @@ function renderCaptureInfo(data) { function renderStats(data) { const el = document.getElementById("stats"); const s = data.stats; - el.textContent = - `${s.totalTechs} techs · ` + - `${s.pompiers} pompier${s.pompiers > 1 ? "s" : ""} · ` + - `${s.absents} absent${s.absents > 1 ? "s" : ""} · ` + - `${s.activeInterventions} intervention${s.activeInterventions > 1 ? "s" : ""} active${s.activeInterventions > 1 ? "s" : ""}`; + el.innerHTML = ` + ${s.totalInterventions} intervention${s.totalInterventions > 1 ? "s" : ""} + (${s.morning} matin · ${s.afternoon} après-midi) + · + ${s.totalTechs} techs + · + ${s.pompiers} pompier${s.pompiers > 1 ? "s" : ""} + · + ${s.absents} absent${s.absents > 1 ? "s" : ""} + `; el.classList.remove("hidden"); } @@ -817,19 +835,20 @@ function buildCard(tech, targetDate) { nameEl.textContent = tech.name; header.appendChild(nameEl); - const badge = document.createElement("div"); - badge.className = "card-tech-badge"; - if (isPompier) { - badge.classList.add("badge-pompier"); - badge.textContent = "Pompier" + (realInterventions.length > 0 ? ` · ${realInterventions.length} int.` : ""); - } else if (isAbsent) { - badge.classList.add("badge-absent"); - badge.textContent = "Absent" + (realInterventions.length > 0 ? ` · ${realInterventions.length} int.` : ""); - } else { - badge.classList.add("badge-count"); - badge.textContent = realInterventions.length + " int."; + // Badge de statut (pompier/absent uniquement). Les techs actifs n'ont pas de badge + // — leurs stats complètes sont juste en dessous. + if (isPompier || isAbsent) { + const badge = document.createElement("div"); + badge.className = "card-tech-badge"; + if (isPompier) { + badge.classList.add("badge-pompier"); + badge.textContent = "Pompier"; + } else { + badge.classList.add("badge-absent"); + badge.textContent = "Absent"; + } + header.appendChild(badge); } - header.appendChild(badge); card.appendChild(header); // --- Body --- @@ -899,14 +918,20 @@ function buildCard(tech, targetDate) { // 4. Timeline (pour pompier aussi, avec fond spécial ; pour absent si interventions) body.appendChild(buildTimeline(realInterventions, pompierBlocks, absenceBlocks, card, isPompier, isAbsent)); - // 5. Mini-stats matin/après-midi + // 5. Stats matin/après-midi (total bien mis en avant) if (realInterventions.length > 0) { const stats = document.createElement("div"); stats.className = "card-stats"; stats.innerHTML = ` - ${morning} matin - ${afternoon} après-midi - ${realInterventions.length} total +
+ ${realInterventions.length} + intervention${realInterventions.length > 1 ? "s" : ""} +
+
+ ${morning} matin + · + ${afternoon} après-midi +
`; body.appendChild(stats); } @@ -951,25 +976,33 @@ function buildInterventionRow(iv, cardEl) { } row.appendChild(timeEl); - // Contenu (titre + meta) + // Contenu (ref en avant + titre + meta) const content = document.createElement("div"); content.className = "intervention-content"; + // Ligne 1 : la référence S260xxx_xxxxx (en évidence) + const refHeader = document.createElement("div"); + refHeader.className = "intervention-refhdr"; + if (iv.ref) { + refHeader.textContent = iv.ref; + } else { + refHeader.textContent = "—"; + refHeader.classList.add("no-ref"); + } + content.appendChild(refHeader); + + // Ligne 2 : type (livraison / récupération / etc.) en plus discret const title = document.createElement("div"); title.className = "intervention-title"; title.textContent = shortTitle(iv); content.appendChild(title); + // Ligne 3 : contact / lieu const meta = document.createElement("div"); meta.className = "intervention-meta"; meta.textContent = shortMeta(iv); content.appendChild(meta); - const refEl = document.createElement("div"); - refEl.className = "intervention-ref"; - refEl.textContent = iv.ref || ""; - content.appendChild(refEl); - row.appendChild(content); // Bouton copier