diff --git a/background.js b/background.js index 2fd907b..1796ab7 100644 --- a/background.js +++ b/background.js @@ -1184,6 +1184,11 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { url: `${origin}/`, // racine → EV redirige vers SSO si besoin active: true }); + // v2026.5.16 : surveiller cet onglet — si on tombe sur la page de + // login manuel portail.etat-de-vaud.ch/iamlogin/, rediriger vers + // portail.etat-de-vaud.ch/iam/accueil/ qui déclenche le Windows + // SSO Kerberos automatiquement. + watchReconnectTabForIamLogin(tab.id); sendResponse({ ok: true, tabId: tab.id, origin }); } catch (err) { sendResponse({ ok: false, error: err.message || String(err) }); diff --git a/manifest.json b/manifest.json index 8529913..b30b26d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planification", - "version": "2026.5.21", + "version": "2026.5.22", "description": "Vue claire et rapide du planning des techniciens EasyVista. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.", "browser_specific_settings": { "gecko": { diff --git a/viewer.css b/viewer.css index d7c9eb0..15e2ca3 100644 --- a/viewer.css +++ b/viewer.css @@ -2480,3 +2480,433 @@ header.topbar::before { .banner-reconnect-failed .banner-btn-primary:hover { background: #f8d7da; } + +/* ========================================================================== + v2026.5.16 : responsive topbar + ========================================================================== */ + +/* Breakpoint medium : entre 1000 et 1300px, on compacte un peu */ +@media (max-width: 1300px) { + .app-clock-date { font-size: 11px; } + .app-clock-time { font-size: 20px; } + .topbar-right .btn-action .btn-action-label, + .topbar-right .btn-refresh .btn-refresh-label { + font-size: 12px; + } +} + +/* Breakpoint small : moins de 1000px, on masque les labels de boutons action + et on réduit encore l'horloge. Les icônes restent, titres restent. */ +@media (max-width: 1000px) { + .topbar { padding: 8px 14px; gap: 8px; } + .topbar h1 { font-size: 16px; } + .app-clock { font-size: smaller; } + .app-clock-date { font-size: 10px; } + .app-clock-time { font-size: 18px; } + .btn-action .btn-action-label, + .btn-refresh .btn-refresh-label { + display: none; + } + .btn-action, .btn-refresh { + padding: 6px 10px; + } + .capture-info { display: none; } +} + +/* Breakpoint très petit : moins de 720px, on cache la date complète (garde + juste l'heure) et on autorise le wrap total */ +@media (max-width: 720px) { + .topbar { + flex-wrap: wrap; + padding: 6px 10px; + } + .app-clock { + position: static; + transform: none; + margin: 0 auto; + } + .app-clock-date { display: none; } + .topbar-left { flex-wrap: wrap; } + .date-nav { margin-top: 4px; } + .date-picker-day { min-width: 46px; font-size: 12px; } + .topbar-right { flex-wrap: wrap; justify-content: flex-end; } +} + +/* Breakpoint minuscule : masque aussi les labels de refresh, boutons deviennent + vraiment iconifiés */ +@media (max-width: 520px) { + .app-clock-time { font-size: 16px; } + .topbar h1 { font-size: 14px; } + .btn-today { padding: 4px 6px; font-size: 11px; } + .btn-nav { min-width: 26px; padding: 4px 6px; } +} + +/* ========================================================================== + v2026.5.17 : topbar des popups épinglés (3 boutons : _ ▭ 📍) + ========================================================================== */ +.pinned-popup { + /* Laisser un peu de place en haut pour la topbar */ + padding-top: 30px !important; +} +/* v2026.5.18 : masquer le conteneur d'actions d'origine (↻ reload + 📌 pin) + dans les popups épinglés — leur place est reprise par notre .pinned-popup-topbar */ +.pinned-popup .tooltip-actions { + display: none !important; +} +.pinned-popup-topbar { + position: absolute; + top: 4px; + right: 4px; + display: flex; + gap: 2px; + align-items: center; + z-index: 10; +} +.pinned-popup-btn { + width: 26px; + height: 22px; + padding: 0; + font-size: 13px; + line-height: 1; + background: transparent; + color: var(--text-muted); + border: 1px solid transparent; + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, color 0.1s, border-color 0.1s; + font-family: inherit; + display: inline-flex; + align-items: center; + justify-content: center; +} +.pinned-popup-btn:hover { + background: var(--bg-muted); + color: var(--text); + border-color: var(--border); +} +.pinned-popup-unpin { + font-size: 14px; +} + +/* ========================================================================== + v2026.5.17 : mode Minimisé (popup flottant compact, juste la ref) + v2026.5.19 : refonte — élément .pinned-popup-minref créé à la volée + v2026.5.21 : agrandi pour que la ref tienne sans déborder + v2026.5.22 : encore agrandi + plus d'espace entre dragbar et topbar + ========================================================================== */ +.pinned-popup.pinned-popup-minimized { + min-width: 300px !important; + max-width: 360px !important; + width: 300px !important; + height: auto !important; + min-height: 80px !important; + padding: 44px 16px 16px 16px !important; + overflow: visible; + background: var(--bg-elevated) !important; + border: 1px solid var(--border) !important; +} +/* Séparer visuellement la dragbar (collée en haut) des boutons topbar */ +.pinned-popup.pinned-popup-minimized .pinned-popup-topbar { + top: 14px !important; /* sous la dragbar (qui fait ~6-8px) */ +} +/* Masquer tous les enfants directs du popup minimisé */ +.pinned-popup.pinned-popup-minimized > *:not(.pinned-popup-topbar):not(.pinned-popup-dragbar):not(.pinned-popup-minref) { + display: none !important; +} +/* L'élément ref dédié, centré et gros */ +.pinned-popup-minref { + display: block; + text-align: center; + padding: 10px 12px; + font-family: var(--mono, monospace); + font-size: 15px; + font-weight: 700; + color: var(--text); + cursor: pointer; + user-select: none; + border-radius: 4px; + transition: background 0.12s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.pinned-popup-minref:hover { + background: var(--bg-muted); +} + +/* ========================================================================== + v2026.5.17 : mode Réduit (docké en bas de l'écran) + taskbar + ========================================================================== */ +.pinned-popup.pinned-popup-reduced { + display: none !important; +} +.pinned-popups-dock { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 50; + display: none; + flex-wrap: wrap; + gap: 6px; + padding: 6px 10px; + background: var(--bg-elevated); + border-top: 1px solid var(--border); + box-shadow: 0 -2px 10px rgba(0,0,0,0.2); + align-items: center; +} +.pinned-popups-dock.visible { + display: flex; +} +.pinned-popup-dock-pill { + display: inline-flex; + align-items: center; + padding: 6px 14px; + background: var(--bg-muted); + color: var(--text); + border: 1px solid var(--border); + border-radius: 16px; + font-family: var(--mono, monospace); + font-size: 13px; + font-weight: 700; + cursor: pointer; + transition: background 0.15s, transform 0.15s, filter 0.15s; + white-space: nowrap; +} +.pinned-popup-dock-pill:hover { + transform: translateY(-1px); + filter: brightness(1.1); +} +/* v2026.5.18 : couleurs par catégorie (fond = couleur, texte blanc) */ +.pinned-popup-dock-pill.color-livraison { background: var(--c-livraison); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-installation { background: var(--c-installation); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-recup { background: var(--c-recup); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-remplacement { background: var(--c-remplacement); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-incident { background: var(--c-incident); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-rollout { background: var(--c-rollout); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-reservation { background: var(--c-reservation); color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-absence { background: #2a2f36; color: white; border-color: transparent; } +.pinned-popup-dock-pill.color-autre { background: var(--c-autre); color: white; border-color: transparent; } + +/* v2026.5.18 : bouton "Fermer tous" à droite du dock */ +.pinned-popups-close-all { + margin-left: auto; + padding: 6px 12px; + background: transparent; + color: var(--text-muted); + border: 1px solid var(--border); + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + font-family: inherit; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} +.pinned-popups-close-all:hover { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; + border-color: #ef4444; +} + +/* ========================================================================== + v2026.5.17 : popup user-badge avec ligne session + ========================================================================== */ +.user-name-popup-name { + font-weight: 600; + margin-bottom: 4px; +} +.user-name-popup-session { + font-size: 12px; + font-variant-numeric: tabular-nums; + padding-top: 4px; + border-top: 1px solid var(--border); +} +.user-name-popup-session.session-ok { color: var(--text-muted); } +.user-name-popup-session.session-warn { color: #f59e0b; font-weight: 600; } +.user-name-popup-session.session-critical { color: #ef4444; font-weight: 700; } + +/* ========================================================================== + v2026.5.17 : popup alerte session qui glisse depuis haut-gauche + ========================================================================== */ +.session-slide-alert { + position: fixed; + top: 60px; + left: -420px; /* hors écran au départ */ + width: 380px; + max-width: calc(100vw - 40px); + padding: 14px 18px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-left: 4px solid #f59e0b; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.25); + z-index: 1000; + transition: left 0.28s ease-out, opacity 0.28s; + opacity: 0; +} +.session-slide-alert.visible { + left: 20px; + opacity: 1; +} +.session-slide-alert.urgent { + border-left-color: #ef4444; + animation: session-pulse 1.4s ease-in-out infinite; +} +@keyframes session-pulse { + 0%, 100% { box-shadow: 0 8px 24px rgba(0,0,0,0.25); } + 50% { box-shadow: 0 8px 24px rgba(239,68,68,0.5); } +} +.session-slide-alert-title { + font-size: 14px; + font-weight: 600; + color: var(--text); + margin-bottom: 12px; +} +.session-slide-alert-actions { + display: flex; + gap: 8px; + justify-content: flex-end; +} +.session-slide-alert-extend, +.session-slide-alert-later { + padding: 6px 14px; + font-size: 13px; + border-radius: 6px; + border: 1px solid var(--border); + cursor: pointer; + font-family: inherit; +} +.session-slide-alert-extend { + background: #10b981; + color: white; + border-color: #10b981; + font-weight: 600; +} +.session-slide-alert-extend:hover { background: #059669; } +.session-slide-alert-extend:disabled { opacity: 0.6; cursor: wait; } +.session-slide-alert-later { + background: transparent; + color: var(--text-muted); +} +.session-slide-alert-later:hover { + background: var(--bg-muted); + color: var(--text); +} + +/* ========================================================================== + v2026.5.19 : nouveaux éléments + ========================================================================== */ + +/* Bouton Actualiser (↻) dans la topbar du popup épinglé — animation spin */ +.pinned-popup-refresh { + font-size: 14px; + line-height: 1; +} +.pinned-popup-refresh svg { + width: 14px; + height: 14px; +} +.pinned-popup-refresh.spinning svg { + animation: pinned-popup-refresh-spin 0.6s linear infinite; + transform-origin: 50% 50%; +} +.pinned-popup-refresh.spinning { + pointer-events: none; +} +@keyframes pinned-popup-refresh-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* Pendant le drag d'un popup, ignorer les hover sur les cartes pour ne pas + ouvrir des tooltips parasites */ +body.popup-dragging .intervention-v2, +body.popup-dragging .card { + pointer-events: none; +} +/* Mais garder les popups épinglés cliquables */ +body.popup-dragging .pinned-popup { + pointer-events: auto; +} + +/* Pastille dock à 2 lignes : ref (gras) + date (petit) */ +.pinned-popup-dock-pill { + flex-direction: column !important; + align-items: center !important; + padding: 4px 14px !important; + line-height: 1.1; + gap: 1px !important; +} +.pinned-popup-dock-pill-ref { + display: block; + font-size: 13px; + font-weight: 700; + font-family: var(--mono, monospace); +} +.pinned-popup-dock-pill-date { + display: block; + font-size: 10px; + font-weight: 500; + opacity: 0.85; + font-family: var(--mono, monospace); +} + +/* ========================================================================== + v2026.5.20 : mini-menu au survol d'une pastille dock + ========================================================================== */ +.pill-hover-menu { + position: fixed; + z-index: 60; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + padding: 4px; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 130px; + animation: pill-hover-menu-appear 0.12s ease-out; +} +@keyframes pill-hover-menu-appear { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} +.pill-hover-menu-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + background: transparent; + color: var(--text); + border: none; + border-radius: 4px; + font-family: inherit; + font-size: 13px; + font-weight: 500; + cursor: pointer; + text-align: left; + transition: background 0.12s; +} +.pill-hover-menu-btn:hover { + background: var(--bg-muted); +} +.pill-hover-menu-btn.pill-hover-menu-close:hover { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} +.pill-menu-ico { + font-size: 14px; + width: 16px; + text-align: center; +} + +/* ========================================================================== + v2026.5.21 : icône 📍 "active" dans le tooltip hover = déjà épinglée + ========================================================================== */ +.tooltip-pinbtn.tooltip-pinbtn-active { + opacity: 1 !important; + filter: none !important; + background: rgba(239, 68, 68, 0.15); + border-radius: 4px; +} diff --git a/viewer.js b/viewer.js index 6523a70..859823b 100644 --- a/viewer.js +++ b/viewer.js @@ -7419,6 +7419,11 @@ function _attachPopupDragHandler(popup, dragbar) { startLeft = parseFloat(popup.style.left) || 0; startTop = parseFloat(popup.style.top) || 0; popup.classList.add("dragging"); + // v2026.5.19 : flag global pour que showTooltip ignore les mouseenter + // pendant le drag. Ajout d'une classe sur
qui désactive les + // pointer-events sur les cartes. + state._popupDragging = true; + document.body.classList.add("popup-dragging"); document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }); @@ -7624,10 +7629,10 @@ function bindTooltipInteractions() { } }); - // Double-Ctrl : v4.3.0 - // - Si 0 popup épinglé ET un tooltip live visible : épingler - // - Si EXACTEMENT 1 popup épinglé ET souris pas dessus : le fermer - // - Si 2+ popups épinglés : ne fait rien (ambigu, user doit utiliser Échap) + // Double-Ctrl : v2026.5.21 — toggle sur l'intervention survolée + // - Si tooltip visible et iv pas encore épinglée pour cette date : épingle + // - Si tooltip visible et iv déjà épinglée pour cette date : désépingle + // - Sinon (pas de tooltip visible) : rien // On détecte 2 keydown Control dans une fenêtre de 400 ms. let lastCtrlTs = 0; document.addEventListener("keydown", (e) => { @@ -7636,17 +7641,36 @@ function bindTooltipInteractions() { const now = performance.now(); if (now - lastCtrlTs < 400) { lastCtrlTs = 0; - if (pinnedPopups.length === 0) { - // Aucun popup épinglé : épingler le tooltip live s'il y en a un - if (state.currentTooltipIv) pinTooltip(); - } else if (pinnedPopups.length === 1) { - // 1 popup épinglé : le fermer si la souris n'est pas dessus - const p = pinnedPopups[0]; - if (!p.el.matches(":hover")) { - _closePinnedPopup(p.el); + const iv = state.currentTooltipIv; + if (!iv) return; + const pinState = _getPinStateForIv(iv); + if (pinState.isPinned && pinState.popup) { + // Déjà épinglée : désépingle (ferme le popup correspondant) + const idx = pinnedPopups.findIndex(p => p.el === pinState.popup); + if (idx >= 0) pinnedPopups.splice(idx, 1); + // Fermer aussi la pastille dock si elle existe + if (pinState.popup._linkedPill) { + try { pinState.popup._linkedPill.remove(); } catch (e) {} } + try { pinState.popup.remove(); } catch (e) {} + // Nettoyer dock si vide + const dock = document.getElementById("pinned-popups-dock"); + if (dock && dock.querySelectorAll(".pinned-popup-dock-pill").length === 0) { + dock.classList.remove("visible"); + const closeAllBtn = document.getElementById("pinned-popups-close-all"); + if (closeAllBtn) closeAllBtn.remove(); + } else { + _ensureDockCloseAllBtn(); + } + // Mettre à jour le tooltip hover (📍 → 📌) + const tip = tooltipEl(); + if (tip && tip.classList.contains("visible")) { + tip.innerHTML = buildTooltipHTML(iv); + } + } else { + // Pas encore épinglée : épingle + pinTooltip(); } - // 2+ popups : rien faire (Échap pour tout fermer) } else { lastCtrlTs = now; } @@ -7660,8 +7684,12 @@ function bindTooltipInteractions() { e.preventDefault(); const action = btn.dataset.action; if (action === "pin") { - // v4.3.0 : toujours épingler (le tooltip live clone son contenu en popup - // détaché). Pour désépingler, l'user utilise × sur le popup, ou Échap. + // v2026.5.22 : clic sur 📌/📍 dans le tooltip hover = TOUJOURS réépingler + // à la position actuelle. Si un popup existe déjà pour cette iv+date, + // il est supprimé avant d'en créer un nouveau à côté de la carte survolée. + // (La suppression de l'ancien est faite dans pinTooltip() qui gère + // l'unicité actionId+date — v2026.5.21.) + // Pour désépingler : bouton 📍 dans la topbar du popup, double-Ctrl, ou Échap. if (state.currentTooltipIv) { pinTooltip(); } @@ -7804,12 +7832,25 @@ function buildTooltipHTML(iv) { rows.push(`