diff --git a/background.js b/background.js index 0cccf61..3dd483d 100644 --- a/background.js +++ b/background.js @@ -1,11 +1,10 @@ // background.js — Service worker (Manifest V3) // -// Quand l'utilisateur clique sur l'icône de l'extension : -// 1. On vérifie qu'il est bien sur itsma.vd.ch -// 2. On injecte un script dans l'onglet qui récupère le HTML complet -// de la page + tous les liens vers les fiches d'intervention -// 3. On stocke ça dans chrome.storage.local -// 4. On ouvre un nouvel onglet avec viewer.html +// Au clic sur l'icône : +// 1. Vérifier qu'on est bien sur itsma.vd.ch (sinon message d'erreur) +// 2. Injecter un script dans la page qui récupère le HTML complet +// 3. Stocker dans chrome.storage.local (persistant, sert de "dernière capture") +// 4. Ouvrir viewer.html chrome.action.onClicked.addListener(async (tab) => { try { @@ -19,7 +18,6 @@ chrome.action.onClicked.addListener(async (tab) => { return; } - // Injecter un script dans l'onglet pour extraire le HTML const results = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: extractPlanningFromPage @@ -30,13 +28,12 @@ chrome.action.onClicked.addListener(async (tab) => { await chrome.storage.local.set({ planningError: "Impossible de lire le contenu de la page. " + - "Assure-toi d'être bien sur la page du planning des techniciens." + "Assure-toi d'être sur la page du planning des techniciens." }); await openViewer(); return; } - // Stocker le HTML capturé + l'URL de base pour les requêtes futures await chrome.storage.local.set({ planningHtml: data.html, planningUrl: tab.url, @@ -54,12 +51,8 @@ chrome.action.onClicked.addListener(async (tab) => { } }); -// Fonction injectée dans la page du planning -// (elle s'exécute dans le contexte de itsma.vd.ch, pas dans le service worker) function extractPlanningFromPage() { - // Récupérer tout le HTML de la page - const html = document.documentElement.outerHTML; - return { html: html }; + return { html: document.documentElement.outerHTML }; } async function openViewer() { diff --git a/manifest.json b/manifest.json index f4cb770..69e60df 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Planning Techniciens — Vue claire", - "version": "1.0.0", - "description": "Réaffiche le planning du jour (itsma.vd.ch) avec pompier, absents et détails d'interventions à la demande.", + "version": "2.0.0", + "description": "Réaffiche le planning du jour (itsma.vd.ch) avec pompier, absents, tooltips enrichis et thème clair/sombre.", "permissions": [ "activeTab", "scripting", diff --git a/viewer.css b/viewer.css index 867ecf0..24908d8 100644 --- a/viewer.css +++ b/viewer.css @@ -1,360 +1,601 @@ -/* viewer.css — Style de la vue claire du planning */ +/* ========================================================================== + Thème clair (défaut) + ========================================================================== */ +:root { + --bg: #f4f5f7; + --bg-elevated: #ffffff; + --bg-muted: #f0f1f3; + --bg-hover: #f7f8fa; + --border: #e2e4e8; + --border-strong: #cfd3da; + --text: #1a1f2b; + --text-muted: #5b6573; + --text-faint: #8892a0; + --accent: #0f4f8b; + --accent-soft: #e1ecf7; + --danger: #b03030; + --danger-soft: #fbe6e6; + --warn: #b87a00; + --warn-soft: #fff2d6; + --ok: #2e7b4a; + --ok-soft: #dff0e4; -* { - box-sizing: border-box; + /* Palette par type d'intervention (clair & lisible) */ + --c-livraison: #2563eb; /* bleu */ + --c-livraison-soft: #dbeafe; + --c-recup: #16a34a; /* vert */ + --c-recup-soft: #dcfce7; + --c-remplacement: #ea580c; /* orange */ + --c-remplacement-soft: #fed7aa; + --c-autre: #6b7280; /* gris */ + --c-autre-soft: #e5e7eb; + + --shadow: 0 1px 3px rgba(20, 30, 50, 0.06), 0 1px 2px rgba(20, 30, 50, 0.04); + --shadow-hover: 0 2px 8px rgba(20, 30, 50, 0.08); + --radius: 8px; + --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + --mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace; +} + +[data-theme="dark"] { + --bg: #16181d; + --bg-elevated: #21242b; + --bg-muted: #1c1f25; + --bg-hover: #2a2e36; + --border: #2e333c; + --border-strong: #414754; + --text: #e6e8ec; + --text-muted: #9ba2ad; + --text-faint: #6a727e; + --accent: #5ea8e8; + --accent-soft: #223348; + --danger: #e87878; + --danger-soft: #3b2626; + --warn: #d9a753; + --warn-soft: #3a2e1a; + --ok: #78c59a; + --ok-soft: #1f3a2b; + + /* Palette sombre — tons plus doux mais toujours distincts */ + --c-livraison: #60a5fa; + --c-livraison-soft: #1e3a5f; + --c-recup: #4ade80; + --c-recup-soft: #14432a; + --c-remplacement: #fb923c; + --c-remplacement-soft: #4a2512; + --c-autre: #9ca3af; + --c-autre-soft: #2a2e36; + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-hover: 0 2px 10px rgba(0, 0, 0, 0.4); +} + +/* ========================================================================== + Base + ========================================================================== */ +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - background: #f5f7fa; - color: #1f2937; + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 14px; line-height: 1.5; - min-height: 100vh; - padding-bottom: 40px; } -.hidden { - display: none !important; -} +.hidden { display: none !important; } -/* === Top bar === */ +/* ========================================================================== + Topbar + ========================================================================== */ .topbar { - background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%); - color: white; - padding: 20px 32px; + position: sticky; + top: 0; + z-index: 10; display: flex; justify-content: space-between; align-items: center; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - flex-wrap: wrap; - gap: 16px; + padding: 10px 20px; + background: var(--bg-elevated); + border-bottom: 1px solid var(--border); + box-shadow: var(--shadow); +} + +.topbar-left { + display: flex; + align-items: baseline; + gap: 14px; } .topbar h1 { - font-size: 24px; + margin: 0; + font-size: 18px; font-weight: 600; + color: var(--text); } -.subtitle { - font-size: 13px; - opacity: 0.85; - margin-top: 4px; +.capture-info { + font-size: 12px; + color: var(--text-muted); } .topbar-right { display: flex; - gap: 12px; + gap: 8px; } -.topbar button { - background: rgba(255,255,255,0.15); - color: white; - border: 1px solid rgba(255,255,255,0.3); - padding: 8px 16px; +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: var(--bg-muted); + color: var(--text); + border: 1px solid var(--border); border-radius: 6px; + font-size: 13px; + font-family: inherit; cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background 0.2s; + transition: background 0.1s, border-color 0.1s; } -.topbar button:hover { - background: rgba(255,255,255,0.25); +.btn:hover { + background: var(--bg-hover); + border-color: var(--border-strong); } -.topbar button:disabled { - opacity: 0.5; - cursor: not-allowed; +.btn:active { + transform: translateY(1px); } -/* === Error zone === */ -.error-zone { - margin: 24px 32px; - padding: 16px 20px; - background: #fef2f2; - border: 1px solid #fca5a5; - color: #991b1b; - border-radius: 8px; +.btn-icon { + padding: 6px 10px; + font-size: 15px; } -/* === Summary cards (pompier, absents, stats) === */ -.summary { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 16px; - margin: 24px 32px; -} - -.summary-card { - background: white; - padding: 18px 22px; - border-radius: 10px; - box-shadow: 0 1px 3px rgba(0,0,0,0.05); - border-left: 4px solid #e5e7eb; -} - -.summary-card.summary-pompier { - border-left-color: #dc2626; -} - -.summary-card.summary-absents { - border-left-color: #f59e0b; -} - -.summary-card.summary-stats { - border-left-color: #10b981; -} - -.summary-label { - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: #6b7280; - font-weight: 600; - margin-bottom: 6px; -} - -.summary-value { - font-size: 20px; - font-weight: 600; - color: #111827; -} - -.summary-value .muted { - font-weight: 400; - color: #6b7280; - font-size: 14px; -} - -/* === Main content : cartes techniciens === */ -#main-content { - margin: 0 32px; -} - -.placeholder { - background: white; - padding: 48px 32px; - border-radius: 10px; +/* ========================================================================== + État initial + ========================================================================== */ +.loading { + padding: 40px 20px; text-align: center; - color: #9ca3af; - font-size: 16px; + color: var(--text-muted); + font-size: 14px; } -.tech-grid { +.error-box { + margin: 20px; + padding: 14px 18px; + background: var(--danger-soft); + color: var(--danger); + border: 1px solid var(--danger); + border-radius: var(--radius); + font-size: 14px; + line-height: 1.55; +} + +.stats { + padding: 10px 20px 0 20px; + color: var(--text-muted); + font-size: 12px; +} + +/* ========================================================================== + Grille de cartes + ========================================================================== */ +.cards { display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); - gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 14px; + padding: 14px 20px 40px 20px; } -.tech-card { - background: white; - border-radius: 10px; - box-shadow: 0 1px 3px rgba(0,0,0,0.05); +.card { + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); overflow: hidden; display: flex; flex-direction: column; } -.tech-card.tech-absent { - opacity: 0.7; - background: #fafafa; -} - -.tech-card.tech-pompier { - box-shadow: 0 0 0 2px #dc2626, 0 2px 6px rgba(220,38,38,0.2); -} - -.tech-header { - padding: 14px 18px; - background: #f9fafb; - border-bottom: 1px solid #e5e7eb; +.card-header { + padding: 10px 14px; + border-bottom: 1px solid var(--border); + background: var(--bg-muted); display: flex; justify-content: space-between; align-items: center; + gap: 10px; } -.tech-card.tech-pompier .tech-header { - background: #fef2f2; -} - -.tech-card.tech-absent .tech-header { - background: #fef3c7; -} - -.tech-name { +.card-tech-name { font-weight: 600; - font-size: 16px; - color: #111827; + font-size: 14px; + color: var(--text); } -.tech-badge { +.card-tech-badge { font-size: 11px; - padding: 3px 8px; - border-radius: 12px; - font-weight: 600; + padding: 2px 8px; + border-radius: 10px; text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 600; } -.tech-badge.badge-pompier { - background: #dc2626; - color: white; +.badge-pompier { + background: var(--danger-soft); + color: var(--danger); } -.tech-badge.badge-absent { - background: #f59e0b; - color: white; +.badge-absent { + background: var(--bg-muted); + color: var(--text-faint); + border: 1px solid var(--border); } -.tech-badge.badge-count { - background: #e5e7eb; - color: #4b5563; +.badge-count { + background: var(--accent-soft); + color: var(--accent); } -.tech-interventions { +.card-body { padding: 8px 0; flex: 1; } -.tech-empty { - padding: 24px; - text-align: center; - color: #9ca3af; +.card-empty { + padding: 14px; + color: var(--text-faint); + font-size: 13px; font-style: italic; - font-size: 14px; + text-align: center; } -/* === Intervention items === */ -.intervention { - padding: 10px 18px; - border-bottom: 1px solid #f3f4f6; - cursor: pointer; - transition: background 0.15s; +/* Cartes pompier : liseré rouge discret */ +.card.is-pompier { + border-left: 3px solid var(--danger); +} + +/* Cartes absent : teinte neutre */ +.card.is-absent { + opacity: 0.85; +} + +.card.is-absent .card-header { + background: var(--bg); +} + +/* ========================================================================== + Frise de temps + ========================================================================== */ +.timeline { + padding: 12px 14px 6px 14px; + background: var(--bg-muted); + border-bottom: 1px solid var(--border); position: relative; } -.intervention:last-child { - border-bottom: none; +/* Fond rouge discret quand la carte est "pompier" */ +.timeline-pompier { + background: var(--danger-soft); } -.intervention:hover { - background: #eff6ff; -} - -.interv-header { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 8px; -} - -.interv-time { +.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; - color: #1e40af; - font-size: 14px; - font-variant-numeric: tabular-nums; - white-space: nowrap; + text-transform: uppercase; + letter-spacing: 0.04em; + opacity: 0.7; } -.interv-ref { - font-size: 12px; - color: #6b7280; - font-family: ui-monospace, "SF Mono", Consolas, monospace; +.timeline-bar { + position: relative; + height: 20px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 3px; + overflow: hidden; } -.interv-summary { - font-size: 13px; - color: #374151; +/* Trous (zones libres) : fond diagonal discret + vert léger au survol */ +.timeline-hole { + position: absolute; + top: 0; + bottom: 0; + background: repeating-linear-gradient( + 45deg, + transparent 0 4px, + rgba(0, 0, 0, 0.035) 4px 8px + ); + cursor: help; + transition: background 0.1s; +} +[data-theme="dark"] .timeline-hole { + background: repeating-linear-gradient( + 45deg, + transparent 0 4px, + rgba(255, 255, 255, 0.04) 4px 8px + ); +} +.timeline-hole:hover { + background: var(--ok-soft); +} + +/* Blocs occupés : couleurs selon type */ +.timeline-slot { + position: absolute; + top: 0; + bottom: 0; + cursor: help; + transition: filter 0.1s; + border-right: 1px solid var(--bg-elevated); +} + +.timeline-slot.color-livraison { background: var(--c-livraison); } +.timeline-slot.color-recup { background: var(--c-recup); } +.timeline-slot.color-remplacement { background: var(--c-remplacement); } +.timeline-slot.color-autre { background: var(--c-autre); } + +.timeline-slot.kind-absence { + background: repeating-linear-gradient( + 45deg, + var(--text-faint) 0 6px, + var(--bg-muted) 6px 12px + ); + opacity: 0.6; +} + +.timeline-slot:hover, +.timeline-slot.highlight { + filter: brightness(1.12); + outline: 2px solid var(--text); + outline-offset: -2px; + z-index: 2; +} + +/* Ligne de midi : marqueur vertical discret */ +.timeline-noon { + position: absolute; + top: -2px; + bottom: -2px; + width: 1px; + background: var(--border-strong); + z-index: 1; + pointer-events: none; +} + +.timeline-scale { + position: relative; + height: 14px; margin-top: 4px; - line-height: 1.4; } -.interv-contact { +.timeline-tick { + position: absolute; + transform: translateX(-50%); + font-size: 10px; + color: var(--text-faint); + font-family: var(--mono); +} + +/* Stats matin / après-midi / total */ +.card-stats { + display: flex; + gap: 16px; + padding: 6px 14px 8px 14px; + 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); + font-weight: 600; +} +.card-stats .stat-chunk.total { + margin-left: auto; + color: var(--text); +} + +/* Note de statut pompier/absent en haut de carte */ +.card-status-note { + padding: 8px 14px; + font-size: 12px; font-weight: 500; + text-align: center; } - -.interv-location { - color: #6b7280; +.card-status-note.pompier { + background: var(--danger-soft); + color: var(--danger); + border-bottom: 1px solid var(--border); } - -.interv-details { - margin-top: 12px; - padding: 12px; - background: #f9fafb; - border-radius: 6px; - font-size: 13px; - line-height: 1.6; - white-space: pre-wrap; - max-height: 400px; - overflow-y: auto; -} - -.interv-details.loading { - color: #9ca3af; +.card-status-note.absent { + background: var(--bg); + color: var(--text-muted); + border-bottom: 1px solid var(--border); font-style: italic; } -.interv-details.error { - color: #991b1b; - background: #fef2f2; -} - -.interv-open-link { - display: inline-block; - margin-top: 8px; +.card-empty.subtle { font-size: 12px; - color: #2563eb; - text-decoration: none; + opacity: 0.7; } -.interv-open-link:hover { - text-decoration: underline; +/* Highlight réciproque */ +.intervention.highlight { + background: var(--bg-hover); } -/* === Absence item (affichée dans la carte du tech absent) === */ -.absence-info { - padding: 12px 18px; - font-size: 14px; - color: #78350f; - background: #fef3c7; - border-left: 3px solid #f59e0b; +/* ========================================================================== + Interventions (lignes dans la carte) + ========================================================================== */ +.intervention { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 14px 8px 10px; + border-top: 1px solid var(--border); + cursor: default; + transition: background 0.08s; } -.pompier-info { - padding: 12px 18px; - font-size: 14px; - color: #7f1d1d; - background: #fee2e2; - border-left: 3px solid #dc2626; - font-weight: 500; +.intervention:first-child { + border-top: none; } -/* === Tooltip flottant === */ +.intervention:hover { + background: var(--bg-hover); +} + +/* Pastille colorée à gauche, rappel visuel du type */ +.intervention-dot { + flex-shrink: 0; + width: 4px; + align-self: stretch; + margin: 2px 4px 2px 0; + border-radius: 2px; +} +.intervention.color-livraison .intervention-dot { background: var(--c-livraison); } +.intervention.color-recup .intervention-dot { background: var(--c-recup); } +.intervention.color-remplacement .intervention-dot { background: var(--c-remplacement); } +.intervention.color-autre .intervention-dot { background: var(--c-autre); } + +.intervention-time { + flex-shrink: 0; + font-family: var(--mono); + font-size: 12px; + color: var(--text-muted); + min-width: 86px; +} + +.intervention-content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.intervention-title { + font-size: 13px; + color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.intervention-meta { + font-size: 12px; + color: var(--text-muted); + 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; + background: transparent; + color: var(--text-faint); + border: 1px solid transparent; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + opacity: 0; + transition: opacity 0.1s, background 0.1s, color 0.1s; + font-family: inherit; +} + +.intervention:hover .intervention-copy { + opacity: 1; +} + +.intervention-copy:hover { + background: var(--bg-muted); + color: var(--text); + border-color: var(--border); +} + +.intervention-copy.copied { + color: var(--ok); + background: var(--ok-soft); + opacity: 1; +} + +/* Intervention de type pompier */ +.intervention.is-pompier-line .intervention-time { + color: var(--danger); + font-weight: 600; +} + +/* ========================================================================== + Tooltip au survol + ========================================================================== */ .tooltip { position: fixed; - background: #1f2937; - color: white; + z-index: 100; + max-width: 420px; padding: 12px 14px; - border-radius: 6px; + background: var(--bg-elevated); + color: var(--text); + border: 1px solid var(--border-strong); + border-radius: 8px; + box-shadow: var(--shadow-hover); font-size: 13px; line-height: 1.5; - max-width: 400px; - z-index: 1000; pointer-events: none; - white-space: pre-wrap; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); + opacity: 0; + transition: opacity 0.1s; } -/* === Responsive === */ -@media (max-width: 768px) { - .topbar { - padding: 16px 20px; - } - .summary, #main-content { - margin: 20px 16px; - } - .tech-grid { - grid-template-columns: 1fr; - } +.tooltip.visible { + opacity: 1; +} + +.tooltip dl { + margin: 0; + display: grid; + grid-template-columns: auto 1fr; + column-gap: 10px; + row-gap: 4px; +} + +.tooltip dt { + color: var(--text-muted); + font-size: 12px; + font-weight: 500; + white-space: nowrap; +} + +.tooltip dd { + margin: 0; + color: var(--text); + font-size: 13px; + word-break: break-word; +} + +.tooltip dd.description { + white-space: pre-wrap; +} + +.tooltip hr { + border: none; + border-top: 1px solid var(--border); + margin: 8px 0; + grid-column: 1 / -1; } diff --git a/viewer.html b/viewer.html index 64a3d26..f805e2e 100644 --- a/viewer.html +++ b/viewer.html @@ -1,49 +1,34 @@ - +
- -