From b0a8102c29395c2e103559f61850b9957f27dd3e Mon Sep 17 00:00:00 2001 From: Quentin Rouiller Date: Fri, 24 Apr 2026 12:00:00 +0200 Subject: [PATCH] =?UTF-8?q?Version=202026.5.32=20=E2=80=94=20Vue=20horizon?= =?UTF-8?q?tale=20togglable=20(VIEW=5FMODE=5FKEY,=20=5FapplyViewMode)=20[c?= =?UTF-8?q?ode=20interpol=C3=A9]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- viewer.css | 44 ++++++++++++++++++- viewer.html | 2 +- viewer.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 154 insertions(+), 12 deletions(-) diff --git a/manifest.json b/manifest.json index 94dd6b9..253f86e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planification", - "version": "2026.5.31", + "version": "2026.5.32", "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 afd692a..224a886 100644 --- a/viewer.css +++ b/viewer.css @@ -2648,11 +2648,51 @@ header.topbar::before { flex-shrink: 0; padding: 0 2px; } -/* Masquer tous les enfants directs du popup minimisé */ + +/* La dragbar devient un simple "handle" à gauche (≡) */ +.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar { + position: static !important; + top: auto !important; + left: auto !important; + right: auto !important; + order: 1; + flex-shrink: 0; + width: 18px !important; + height: 22px !important; + background: transparent !important; + border: none !important; + cursor: grab; + display: flex !important; + align-items: center; + justify-content: center; + color: var(--text-faint); + opacity: 0.6; + transition: opacity 0.12s, color 0.12s; +} +.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar::before { + content: "≡" !important; + font-size: 15px; + line-height: 1; + /* v2026.5.24 : annuler les propriétés du ::before normal (barre grise) */ + width: auto !important; + height: auto !important; + background: transparent !important; + border-radius: 0 !important; +} +.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar:hover { + opacity: 1; + color: var(--text); +} +.pinned-popup.pinned-popup-minimized.dragging .pinned-popup-dragbar { + cursor: grabbing; +} + +/* Masquer tous les enfants directs SAUF topbar, dragbar et minref */ .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 */ + +/* La ref au centre, cliquable pour agrandir */ .pinned-popup-minref { display: block; text-align: center; diff --git a/viewer.html b/viewer.html index d552c82..cf61b84 100644 --- a/viewer.html +++ b/viewer.html @@ -17,7 +17,7 @@ fetch aboutit. En cas d'échec persistant, reste sur "?". --> + title="Utilisateur — cliquer pour accéder aux paramètres">?

Planification

diff --git a/viewer.js b/viewer.js index 7c54337..6bceb52 100644 --- a/viewer.js +++ b/viewer.js @@ -7015,11 +7015,52 @@ function pinTooltip() { // source visuelle du clic/hover). Fallback sur le segment timeline. Enfin // la row (qui marche en vue classique). let rowEl = null; - if (iv.actionId) { - rowEl = document.querySelector(`.intervention-v2[data-action-id="${iv.actionId}"]`); + const _isHorizontalView = document.documentElement.classList.contains("view-horizontal"); + + if (_isHorizontalView) { + console.log("[pinTooltip] vue horizontale → source = tooltip visible ou segment timeline"); + // Priorité 1 : le tooltip actuellement affiché (meilleure source visuelle) + const currentTip = tooltipEl(); + if (currentTip && currentTip.classList.contains("visible")) { + const r = currentTip.getBoundingClientRect(); + if (r.width > 0 && r.height > 0) { + rowEl = currentTip; + console.log(`[pinTooltip] source = tooltip visible (${Math.round(r.left)}, ${Math.round(r.top)})`); + } + } + // Priorité 2 : segment timeline pour cette actionId + if (!rowEl && iv.actionId) { + // Il peut y avoir plusieurs .timeline-slot avec le même ivIdx si plusieurs + // cartes. On prend celui dont la row parente a le bon actionId. + const allSlots = document.querySelectorAll(".timeline-slot[data-iv-idx]"); + for (const slot of allSlots) { + const card = slot.closest(".card"); + if (!card) continue; + const ivIdx = slot.dataset.ivIdx; + const row = card.querySelector(`.intervention-v2[data-iv-idx="${ivIdx}"]`); + if (row && row.dataset.actionId === iv.actionId) { + const r = slot.getBoundingClientRect(); + if (r.width > 0 && r.height > 0) { + rowEl = slot; + console.log(`[pinTooltip] source = segment timeline actionId=${iv.actionId}`); + break; + } + } + } + } + } else { + // Vue classique : chercher la row .intervention-v2 (comportement d'origine) + if (iv.actionId) { + rowEl = document.querySelector(`.intervention-v2[data-action-id="${iv.actionId}"]`); + if (rowEl) { + console.log(`[pinTooltip] vue classique → source = row intervention-v2`); + } + } } + + // Fallback final : le tooltip live (même s'il n'est pas visible) — position actuelle if (!rowEl) { - // Fallback : utiliser la position actuelle du tooltip live + console.warn("[pinTooltip] aucune source visible trouvée → fallback tooltip live"); rowEl = srcEl; } @@ -7037,6 +7078,49 @@ function pinTooltip() { // v2026.5.19 : mémoriser aussi la date pour l'afficher sur la pastille dock popup.dataset.originDate = state.currentDate || ""; + // v2026.5.25 : mémoriser aussi lieu + service pour la pastille enrichie + try { + const info = iv.infobulle || {}; + const lieuRaw = info.lieu || iv.bulleLieu || ""; + // Format pour pastille : "VILLE, Adresse" (on extrait les 2 premières parties) + const { ville, adresse } = (typeof splitLieu === "function") + ? splitLieu(lieuRaw) + : { ville: null, adresse: null }; + let lieuFmt = ""; + if (ville && adresse) lieuFmt = ville.toUpperCase() + ", " + adresse; + else if (adresse) lieuFmt = adresse; + else if (ville) lieuFmt = ville.toUpperCase(); + popup.dataset.lieu = lieuFmt; + + // Service : prendre les 2 dernières parties séparées par "/" + // Ex: "AdmCV/DJES/SSCM/SSCM - Admin/TEO - OS" → "SSCM - Admin/TEO - OS" + // "AdmCV/OJV/JPX/JPX Lavaux-Oron" → "JPX/JPX Lavaux-Oron" + // v2026.5.26 : dans le DERNIER segment seulement, couper au " - " (espace + // tiret espace) pour retirer les descriptions longues. Les + // noms propres comme "Lavaux-Oron" (sans espaces) sont préservés. + // Ex: "AdmCV/DJES/SSCM/SPOP - Centre administratif" → "SSCM/SPOP" + // "AdmCV/OJV/JPX/JPX Lavaux-Oron" → "JPX/JPX Lavaux-Oron" (inchangé) + const serviceRaw = (info.service) || iv.categoryLine || ""; + if (serviceRaw) { + const parts = serviceRaw.split("/").map(s => s.trim()).filter(Boolean); + let last2 = parts.slice(-2); + // Couper au " - " dans le DERNIER élément de last2 + if (last2.length > 0) { + const lastIdx = last2.length - 1; + const lastSeg = last2[lastIdx]; + const dashIdx = lastSeg.indexOf(" - "); + if (dashIdx > 0) { + last2[lastIdx] = lastSeg.substring(0, dashIdx).trim(); + } + } + popup.dataset.service = last2.join("/"); + } else { + popup.dataset.service = ""; + } + } catch (err) { + console.warn("[pin] lieu/service build failed", err); + } + // 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"]'); @@ -7165,6 +7249,9 @@ function pinTooltip() { // Enregistrer dans la liste pinnedPopups.push({ el: popup, iv: iv, rect: pos.rect }); + // v2026.5.34 : stocker une référence à l'iv sur l'élément DOM — utilisée + // par _softUnpinPopup pour pouvoir ré-épingler via le bouton 📌 restauré. + popup._linkedIv = iv; // v4.3.0 : libérer le tooltip live (il redevient utilisable pour d'autres survols) bulleState.pinned = false; @@ -7228,14 +7315,29 @@ async function _refreshPinnedPopupIv(popup, iv) { } /** - * 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 - * la laisse visible. Elle disparait quand la souris sort. + * Désépinglage "mou" (v4.3.3) : transforme un popup épinglé en tooltip simple. + * + * v2026.5.34 : refonte — le popup ne disparaît PLUS au mouseleave. À la place, + * il redevient un tooltip normal avec ses boutons ↻ (Actualiser) et 📌 + * (Épingler) restaurés dans .tooltip-actions, et reste visible à l'écran + * comme un tooltip classique. L'user peut le re-cliquer sur 📌 pour le + * ré-épingler, ou cliquer ailleurs pour s'en débarrasser normalement. + * + * @param {HTMLElement} el - le popup à désépingler */ function _softUnpinPopup(el) { - // Retirer de la liste (pour le comptage Ctrl×2) mais garder le DOM + if (!el) { + console.warn("[softUnpin] elément null — abandon"); + return; + } + console.log("[softUnpin] désépinglage du popup", el.dataset.actionId || "(sans actionId)"); + + // 1. Retirer de la liste des popups épinglés (pour Ctrl×2 etc.) const idx = pinnedPopups.findIndex(p => p.el === el); - if (idx >= 0) pinnedPopups.splice(idx, 1); + if (idx >= 0) { + pinnedPopups.splice(idx, 1); + console.log(`[softUnpin] retiré de pinnedPopups (reste ${pinnedPopups.length})`); + } // v4.3.3 corr : basculer visuellement en tooltip normal (retirer tous les // attributs visuels du mode épinglé : bordure bleue, dragbar, bouton ×,