diff --git a/manifest.json b/manifest.json index a9a5029..c085ee8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planification", - "version": "2026.5.18", + "version": "2026.5.19", "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.", "permissions": ["activeTab", "scripting", "storage", "tabs", "alarms"], "host_permissions": [ diff --git a/viewer.css b/viewer.css index 48208a9..569f1c3 100644 --- a/viewer.css +++ b/viewer.css @@ -2590,46 +2590,39 @@ header.topbar::before { /* ========================================================================== v2026.5.17 : mode Minimisé (popup flottant compact, juste la ref) - v2026.5.18 : fix affichage — on masque TOUT sauf la ref et la topbar + v2026.5.19 : refonte — élément .pinned-popup-minref créé à la volée ========================================================================== */ .pinned-popup.pinned-popup-minimized { min-width: 180px !important; max-width: 260px !important; width: auto !important; height: auto !important; - padding-top: 28px !important; - padding-bottom: 8px !important; + min-height: 60px !important; + padding: 28px 10px 10px 10px !important; overflow: hidden; + background: var(--bg-elevated) !important; + border: 1px solid var(--border) !important; } -/* Masquer tous les descendants sauf ceux qu'on veut voir */ -.pinned-popup.pinned-popup-minimized * { +/* 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; } -/* Réafficher la topbar et ses boutons */ -.pinned-popup.pinned-popup-minimized .pinned-popup-topbar, -.pinned-popup.pinned-popup-minimized .pinned-popup-topbar * { - display: flex !important; -} -/* Réafficher la dragbar */ -.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar { - display: block !important; -} -/* Réafficher la ref (peut être dans une row imbriquée) */ -.pinned-popup.pinned-popup-minimized .iv-ref-header { - display: block !important; - text-align: center !important; - padding: 6px 12px !important; - grid-column: unset !important; - font-size: 14px !important; - font-weight: 700 !important; - color: var(--text) !important; +/* L'élément ref dédié, centré et gros */ +.pinned-popup-minref { + display: block; + text-align: center; + padding: 6px 8px; + font-family: var(--mono, monospace); + font-size: 14px; + font-weight: 700; + color: var(--text); cursor: pointer; + user-select: none; + border-radius: 4px; + transition: background 0.12s; } -/* Et si la ref est dans une row grid, reshowrow pour qu'elle s'affiche */ -.pinned-popup.pinned-popup-minimized .intervention-v2, -.pinned-popup.pinned-popup-minimized .tt-ref-cell { - display: block !important; - grid-template: none !important; +.pinned-popup-minref:hover { + background: var(--bg-muted); } /* ========================================================================== @@ -2790,3 +2783,54 @@ header.topbar::before { 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.spinning { + animation: pinned-popup-refresh-spin 0.6s linear infinite; + 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); +} diff --git a/viewer.js b/viewer.js index f3aa5a6..f4e457f 100644 --- a/viewer.js +++ b/viewer.js @@ -6094,6 +6094,11 @@ let bulleState = { }; function showTooltip(e, iv, rowEl) { + // v2026.5.19 : pendant qu'un popup épinglé est en cours de drag, on ignore + // les mouseenter sur les cartes — sinon en survolant une carte on déclenche + // l'ouverture d'un nouveau tooltip par-dessus ce qu'on est en train de bouger. + if (state._popupDragging) return; + // v4.1.15 : si la bulle est épinglée sur une autre iv, on NE REMPLACE PAS // son contenu (l'user veut garder la fiche épinglée même en survolant // d'autres cartes). @@ -6485,6 +6490,9 @@ function pinTooltip() { popup.dataset.ref = iv.ref || ""; popup.dataset.colorKey = (typeof deriveColorKey === "function" ? deriveColorKey(iv) : "autre") || "autre"; + // v2026.5.19 : mémoriser aussi la date pour l'afficher sur la pastille dock + popup.dataset.originDate = state.currentDate || ""; + // v2026.5.17 : masquer l'icône 📌 du contenu cloné (redondante car le // popup a sa propre topbar avec le bouton "désépingler" 📍 explicite) const oldPin = popup.querySelector('.tooltip-pinbtn[data-action="pin"]'); @@ -6522,6 +6530,26 @@ function pinTooltip() { }); topbar.appendChild(minBtn); + // v2026.5.19 : Bouton Actualiser (icône ↻) + // Re-fetch la fiche de l'intervention pour mettre à jour les infos (statut, + // commentaires, action text) sans recharger le planning entier. + const refreshBtn = document.createElement("button"); + refreshBtn.type = "button"; + refreshBtn.className = "pinned-popup-btn pinned-popup-refresh"; + refreshBtn.innerHTML = "↻"; + refreshBtn.title = "Actualiser les informations de cette intervention"; + refreshBtn.addEventListener("click", async (e) => { + e.stopPropagation(); + if (refreshBtn.classList.contains("spinning")) return; + refreshBtn.classList.add("spinning"); + try { + await _refreshPinnedPopupIv(popup, iv); + } finally { + setTimeout(() => refreshBtn.classList.remove("spinning"), 300); + } + }); + topbar.appendChild(refreshBtn); + // Bouton Désépingler (icône épingle plantée) const unpinBtn = document.createElement("button"); unpinBtn.type = "button"; @@ -6614,6 +6642,44 @@ function _closePinnedPopup(el) { el.remove(); } +/** + * v2026.5.19 : re-fetch les infos d'une intervention et met à jour le contenu + * du popup épinglé correspondant. Utilise fetchAndUpdateIntervention qui fait + * xhr2 + fiche, puis régénère le HTML du tooltip avec buildTooltipHTML. + */ +async function _refreshPinnedPopupIv(popup, iv) { + if (!popup || !iv) return; + try { + // Forcer le refetch : on invalide les flags qui disent "déjà fetché" + iv.xhr2Fetched = false; + iv.xhr2Fetching = false; + iv.ficheFetched = false; + iv.ficheFetching = false; + + // Token de refresh actuel (pour que fetchAndUpdateIntervention ne soit + // pas abortée par les checks isRefreshAborted) + const token = (typeof currentRefreshToken !== "undefined") ? currentRefreshToken : 0; + + await fetchAndUpdateIntervention(iv, token); + + // Régénérer le HTML du tooltip avec les nouvelles infos. + // On doit réinjecter juste le contenu, en gardant la topbar et la dragbar + // (qui ne sont PAS dans le tooltip source, elles sont propres au popup). + const topbar = popup.querySelector(".pinned-popup-topbar"); + const dragbar = popup.querySelector(".pinned-popup-dragbar"); + const newHtml = buildTooltipHTML(iv); + popup.innerHTML = newHtml; + // Virer aussi la vieille icône 📌 si elle revient dans le rebuild + const oldPin = popup.querySelector('.tooltip-pinbtn[data-action="pin"]'); + if (oldPin) oldPin.remove(); + // Remettre topbar et dragbar + if (topbar) popup.appendChild(topbar); + if (dragbar) popup.appendChild(dragbar); + } catch (err) { + console.warn("[refresh-popup]", err); + } +} + /** * Désépinglage "mou" : la popup n'est plus considérée épinglée (elle n'est * plus dans pinnedPopups, donc le comptage pour Ctrl×2 etc. ignore) mais on @@ -6686,12 +6752,16 @@ function _softUnpinPopup(el) { /** * Passe un popup épinglé en mode Minimisé : on ne montre plus que la ref, * dans un petit cadre flottant toujours drag-able. + * + * v2026.5.19 : au lieu de masquer tout le contenu via CSS et tenter de + * réafficher la ref (fragile), on crée un élément dédié `.pinned-popup-minref` + * qui contient juste la ref + la date. Cet élément est ajouté/retiré au besoin. */ function _minimizePinnedPopup(popup) { if (!popup) return; popup.classList.add("pinned-popup-minimized"); - // Adapter les boutons topbar : [_] devient [⬆] (agrandir) + // Adapter les boutons topbar : [▭] devient [⬆] (agrandir) const minBtn = popup.querySelector(".pinned-popup-minimize"); if (minBtn) { minBtn.innerHTML = "⬆"; @@ -6705,20 +6775,22 @@ function _minimizePinnedPopup(popup) { }); } - // Clic sur la ref (dans iv-ref-header) = agrandir aussi - const refEl = popup.querySelector(".iv-ref-header"); - if (refEl) { - refEl.style.cursor = "pointer"; - refEl.title = "Cliquer pour agrandir"; - refEl.addEventListener("click", _onMinimizedRefClick); + // Créer un élément dédié pour afficher la ref en mode minimisé + let minRef = popup.querySelector(".pinned-popup-minref"); + if (!minRef) { + minRef = document.createElement("div"); + minRef.className = "pinned-popup-minref"; + const refText = popup.dataset.ref || "(sans ref)"; + minRef.textContent = refText; + minRef.title = "Cliquer pour agrandir"; + minRef.addEventListener("click", (e) => { + e.stopPropagation(); + _expandPinnedPopup(popup); + }); + popup.appendChild(minRef); } } -function _onMinimizedRefClick(e) { - const popup = e.currentTarget.closest(".pinned-popup"); - if (popup) _expandPinnedPopup(popup); -} - /** * Repasse un popup minimisé en mode Normal (complet). */ @@ -6739,13 +6811,9 @@ function _expandPinnedPopup(popup) { }); } - // Retirer listener du clic-agrandir sur la ref - const refEl = popup.querySelector(".iv-ref-header"); - if (refEl) { - refEl.style.cursor = ""; - refEl.title = ""; - refEl.removeEventListener("click", _onMinimizedRefClick); - } + // Retirer l'élément ref dédié (s'il existe) + const minRef = popup.querySelector(".pinned-popup-minref"); + if (minRef) minRef.remove(); } /** @@ -6778,12 +6846,26 @@ function _reducePinnedPopup(popup) { // Créer la pastille dock // v2026.5.18 : le fond de la pastille prend la couleur de catégorie // (via la classe color-XXX déjà utilisée ailleurs dans le CSS) + // v2026.5.19 : pastille à 2 lignes — ref (gras) + date origine (petit) const pill = document.createElement("button"); pill.type = "button"; pill.className = "pinned-popup-dock-pill color-" + colorKey; - pill.textContent = label; pill.title = "Cliquer pour agrandir"; + const pillRef = document.createElement("span"); + pillRef.className = "pinned-popup-dock-pill-ref"; + pillRef.textContent = label; + pill.appendChild(pillRef); + + // Date d'origine (ex: "21.04") + const originDate = popup.dataset.originDate || ""; + if (originDate) { + const pillDate = document.createElement("span"); + pillDate.className = "pinned-popup-dock-pill-date"; + pillDate.textContent = _formatDateShort(originDate); + pill.appendChild(pillDate); + } + // Mémoriser la position/taille du popup avant de le masquer const rect = popup.getBoundingClientRect(); popup.dataset.prevLeft = popup.style.left || (rect.left + "px"); @@ -6811,7 +6893,7 @@ function _reducePinnedPopup(popup) { } /** - * v2026.5.18 : réduit TOUS les popups épinglés actuellement ouverts (en mode + * v2026.5.19 : réduit TOUS les popups épinglés actuellement ouverts (en mode * normal ou minimisé) dans la taskbar du bas. Appelé au changement de date. */ function _reduceAllPinnedPopups() { @@ -6821,6 +6903,16 @@ function _reduceAllPinnedPopups() { }); } +/** + * v2026.5.19 : ISO date (YYYY-MM-DD) → format court "DD.MM" pour le dock. + */ +function _formatDateShort(iso) { + if (!iso) return ""; + const m = String(iso).match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!m) return iso; + return `${m[3]}.${m[2]}`; +} + /** * v2026.5.18 : ajoute (ou met à jour) le bouton "Fermer tous" dans le dock * quand au moins 2 popups épinglés existent (réduits OU affichés). @@ -6997,6 +7089,11 @@ function _attachPopupDragHandler(popup, dragbar) { popup.classList.remove("dragging"); document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); + // v2026.5.19 : retirer la classe body et le flag global après un petit + // délai pour laisser le temps au mouseleave de la carte de se propager + // sans déclencher de tooltip parasite. + document.body.classList.remove("popup-dragging"); + setTimeout(() => { state._popupDragging = false; }, 50); // Mettre à jour le rect mémorisé pour la détection de chevauchement const entry = pinnedPopups.find(p => p.el === popup); @@ -7019,6 +7116,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); });