v2026.5.25 — Bouton ⚙ Paramètres dans popup user-badge (remplace 5 clics secrets sur le titre)

This commit is contained in:
Quentin Rouiller
2026-04-23 16:00:38 +02:00
parent b77f0a9caa
commit 10a1aef4c7
3 changed files with 196 additions and 24 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Planification", "name": "Planification",
"version": "2026.5.24", "version": "2026.5.25",
"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.", "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"], "permissions": ["activeTab", "scripting", "storage", "tabs", "alarms"],
"host_permissions": [ "host_permissions": [
+87
View File
@@ -3002,3 +3002,90 @@ body.popup-dragging .pinned-popup {
from { transform: scale(1); } from { transform: scale(1); }
to { transform: scale(1.05); } to { transform: scale(1.05); }
} }
/* ==========================================================================
v2026.5.25 : pastille dock enrichie (lieu + service + date) +
bouton Paramètres dans popup user-badge +
ref dans mini-menu pill
========================================================================== */
/* Pastille dock : 3 lignes */
.pinned-popup-dock-pill {
flex-direction: column !important;
align-items: flex-start !important;
padding: 6px 14px !important;
gap: 2px !important;
line-height: 1.2 !important;
text-align: left;
min-width: 200px;
max-width: 300px;
}
.pinned-popup-dock-pill-lieu {
display: block;
font-size: 13px;
font-weight: 700;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pinned-popup-dock-pill-service {
display: block;
font-size: 11px;
font-weight: 500;
opacity: 0.85;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pinned-popup-dock-pill-date {
display: block;
font-size: 10px;
font-weight: 500;
opacity: 0.75;
font-family: var(--mono, monospace);
}
/* Ref dans le mini-menu hover de la pastille */
.pill-hover-menu-ref {
padding: 6px 12px;
text-align: center;
font-family: var(--mono, monospace);
font-size: 13px;
font-weight: 700;
color: var(--text);
border-bottom: 1px solid var(--border);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Bouton Paramètres dans popup user-badge */
.user-name-popup-settings {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
padding: 6px 10px;
background: var(--bg-muted);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
font-family: inherit;
font-size: 13px;
font-weight: 500;
cursor: pointer;
width: 100%;
justify-content: center;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.user-name-popup-settings:hover {
background: var(--accent, #3b82f6);
color: white;
border-color: var(--accent, #3b82f6);
}
.user-name-popup-settings .settings-ico {
font-size: 15px;
}
+108 -23
View File
@@ -416,6 +416,21 @@ function toggleUserNamePopup() {
_renderUserPopupSessionLine(sessEl); _renderUserPopupSessionLine(sessEl);
popup.appendChild(sessEl); popup.appendChild(sessEl);
// v2026.5.25 : bouton Paramètres (remplace les 5 clics sur le titre)
const settingsBtn = document.createElement("button");
settingsBtn.type = "button";
settingsBtn.className = "user-name-popup-settings";
settingsBtn.innerHTML = '<span class="settings-ico">⚙</span> Paramètres';
settingsBtn.title = "Ouvrir les paramètres d'administration";
settingsBtn.addEventListener("click", (e) => {
e.stopPropagation();
hideUserNamePopup();
if (typeof showAdminPanel === "function") {
showAdminPanel();
}
});
popup.appendChild(settingsBtn);
popup.classList.remove("hidden"); popup.classList.remove("hidden");
badge.classList.add("open"); badge.classList.add("open");
// Positionne juste en dessous de la pastille // Positionne juste en dessous de la pastille
@@ -1002,25 +1017,14 @@ function updateNowLine() {
}); });
} }
// v5.0.0 : menu admin caché. 5 clics consécutifs sur le titre "Planification" // v5.0.0 : menu admin caché via 5 clics sur le titre "Planification".
// (avec max 2 secondes entre chaque clic) ouvrent le panneau admin. // v2026.5.25 : SUPPRIMÉ — l'accès au panneau admin se fait désormais via le
// bouton "⚙ Paramètres" du popup user-badge (clic sur les initiales).
function initAdminMenu() { function initAdminMenu() {
const title = document.getElementById("app-title"); const title = document.getElementById("app-title");
if (!title) return; if (!title) return;
let clicks = 0;
let resetTimer = null;
title.addEventListener("click", () => {
clicks++;
if (resetTimer) clearTimeout(resetTimer);
resetTimer = setTimeout(() => { clicks = 0; }, 2000);
if (clicks >= 5) {
clicks = 0;
clearTimeout(resetTimer);
showAdminPanel();
}
});
// Cursor pointer pour indiquer (discrètement) qu'il est cliquable
title.style.cursor = "default"; title.style.cursor = "default";
// Plus de handler de clic : les 5 clics n'ouvrent plus rien.
} }
// ============================================================================ // ============================================================================
@@ -5623,6 +5627,21 @@ function extractContacts(raw) {
*/ */
function splitOneContact(raw) { function splitOneContact(raw) {
if (!raw) return { name: null, phone: null }; if (!raw) return { name: null, phone: null };
// v2026.5.25 : avant d'extraire les numéros, on REMPLACE les séquences qui
// sont des identifiants de matériel (LETTRES_CHIFFRES) par des espaces.
// Exemples : XXXX_NNNNNNNNNNN, XNNNNNN, XNNNNNN, XNNNNNN.
// Sans ça, XXXX_NNNNNNNNNNN laisse des "NNNN NNN NN NN" qui se font prendre
// pour un numéro de téléphone par le regex qui greedy sur [0-9\s.\-].
// On remplace par des espaces de même longueur pour préserver les offsets
// (important pour le calcul de position du nom avant le 1er numéro).
raw = String(raw);
raw = raw.replace(/\b[A-Z]{1,6}_\d+/g, (m) => " ".repeat(m.length));
// Idem pour les identifiants sans underscore style XNNNNNN, XNNNNNN, XNNNNNN
// (1-2 lettres majuscules suivies de 5+ chiffres collés). On garde assez
// permissif pour matcher les variantes sans enlever des vrais mots.
raw = raw.replace(/\b[A-Z]{1,3}\d{5,}\b/g, (m) => " ".repeat(m.length));
// v4.1.20 : regex plus permissives pour tolérer les erreurs humaines : // v4.1.20 : regex plus permissives pour tolérer les erreurs humaines :
// - pas d'espace après le numéro (ex: "021555555Textecoller") // - pas d'espace après le numéro (ex: "021555555Textecoller")
// - pas d'espace/parenthèse avant un court numéro // - pas d'espace/parenthèse avant un court numéro
@@ -6606,6 +6625,35 @@ function pinTooltip() {
// v2026.5.19 : mémoriser aussi la date pour l'afficher sur la pastille dock // v2026.5.19 : mémoriser aussi la date pour l'afficher sur la pastille dock
popup.dataset.originDate = state.currentDate || ""; 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"
const serviceRaw = (info.service) || iv.categoryLine || "";
if (serviceRaw) {
const parts = serviceRaw.split("/").map(s => s.trim()).filter(Boolean);
const last2 = parts.slice(-2).join("/");
popup.dataset.service = last2;
} 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 // 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) // popup a sa propre topbar avec le bouton "désépingler" 📍 explicite)
const oldPin = popup.querySelector('.tooltip-pinbtn[data-action="pin"]'); const oldPin = popup.querySelector('.tooltip-pinbtn[data-action="pin"]');
@@ -6985,24 +7033,34 @@ function _reducePinnedPopup(popup) {
// Créer la pastille dock // Créer la pastille dock
// v2026.5.18 : le fond de la pastille prend la couleur de catégorie // 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.25 : pastille à 3 lignes — lieu (fort), service (petit), date (petit)
// v2026.5.19 : pastille à 2 lignes — ref (gras) + date origine (petit) // La référence est affichée dans le mini-menu au survol.
const pill = document.createElement("button"); const pill = document.createElement("button");
pill.type = "button"; pill.type = "button";
pill.className = "pinned-popup-dock-pill color-" + colorKey; pill.className = "pinned-popup-dock-pill color-" + colorKey;
pill.title = "Cliquer pour agrandir"; pill.title = "Cliquer pour agrandir";
const pillRef = document.createElement("span"); // Ligne 1 : lieu (ou ref si pas de lieu)
pillRef.className = "pinned-popup-dock-pill-ref"; const pillLieu = document.createElement("span");
pillRef.textContent = label; pillLieu.className = "pinned-popup-dock-pill-lieu";
pill.appendChild(pillRef); pillLieu.textContent = popup.dataset.lieu || label || "—";
pill.appendChild(pillLieu);
// Date d'origine (ex: "21.04") // Ligne 2 : service (2 dernières parties)
const serviceTxt = popup.dataset.service || "";
if (serviceTxt) {
const pillService = document.createElement("span");
pillService.className = "pinned-popup-dock-pill-service";
pillService.textContent = serviceTxt;
pill.appendChild(pillService);
}
// Ligne 3 : date "Mardi 22.04"
const originDate = popup.dataset.originDate || ""; const originDate = popup.dataset.originDate || "";
if (originDate) { if (originDate) {
const pillDate = document.createElement("span"); const pillDate = document.createElement("span");
pillDate.className = "pinned-popup-dock-pill-date"; pillDate.className = "pinned-popup-dock-pill-date";
pillDate.textContent = _formatDateShort(originDate); pillDate.textContent = _formatDateForPill(originDate);
pill.appendChild(pillDate); pill.appendChild(pillDate);
} }
@@ -7078,6 +7136,15 @@ function _showPillHoverMenu(pill, popup) {
menu._linkedPill = pill; menu._linkedPill = pill;
menu._linkedPopup = popup; menu._linkedPopup = popup;
// v2026.5.25 : REF en haut du menu (info seulement, centrée)
const refText = popup.dataset.ref || "";
if (refText) {
const refLabel = document.createElement("div");
refLabel.className = "pill-hover-menu-ref";
refLabel.textContent = refText;
menu.appendChild(refLabel);
}
const restoreBtn = document.createElement("button"); const restoreBtn = document.createElement("button");
restoreBtn.type = "button"; restoreBtn.type = "button";
restoreBtn.className = "pill-hover-menu-btn"; restoreBtn.className = "pill-hover-menu-btn";
@@ -7419,6 +7486,24 @@ function _formatDateShort(iso) {
return `${m[3]}.${m[2]}`; return `${m[3]}.${m[2]}`;
} }
/**
* v2026.5.25 : formatte une date ISO en "Mardi 22.04" pour la pastille dock.
*/
function _formatDateForPill(iso) {
if (!iso) return "";
const m = String(iso).match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!m) return iso;
try {
const d = new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));
const dayName = (typeof DAY_NAMES_FULL !== "undefined" && DAY_NAMES_FULL[d.getDay()])
? DAY_NAMES_FULL[d.getDay()]
: "";
return (dayName ? dayName + " " : "") + m[3] + "." + m[2];
} catch (e) {
return m[3] + "." + m[2];
}
}
/** /**
* v2026.5.18 : ajoute (ou met à jour) le bouton "Fermer tous" dans le dock * 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). * quand au moins 2 popups épinglés existent (réduits OU affichés).