diff --git a/manifest.json b/manifest.json index ce7a7de..319a508 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planification", - "version": "5.0.3", + "version": "5.0.4", "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 ecf923e..20e80f3 100644 --- a/viewer.css +++ b/viewer.css @@ -2204,3 +2204,16 @@ header.topbar::before { font-size: 11px; padding: 4px 8px; } + +/* v5.0.4 : boutons preset matin / après-midi / journée dans modal absence */ +.modal-preset-row { + gap: 8px; + flex-wrap: wrap; +} +.modal-preset-btn { + flex: 1; + min-width: 100px; + padding: 8px 10px; + font-size: 13px; + cursor: pointer; +} diff --git a/viewer.js b/viewer.js index 8dfab6c..0bbd072 100644 --- a/viewer.js +++ b/viewer.js @@ -1424,6 +1424,38 @@ function showAbsenceModal() { endGroup.appendChild(endRow); card.appendChild(endGroup); + // v5.0.4 : presets rapides pour les horaires (matin / après-midi / journée) + const presetGroup = document.createElement("div"); + presetGroup.className = "modal-form-group"; + const presetLabel = document.createElement("label"); + presetLabel.className = "modal-form-label"; + presetLabel.textContent = "Presets rapides"; + presetGroup.appendChild(presetLabel); + const presetRow = document.createElement("div"); + presetRow.className = "modal-form-row modal-preset-row"; + const presets = [ + { label: "Matin", start: "08:00", end: "12:00" }, + { label: "Après-midi", start: "13:00", end: "18:00" }, + { label: "Toute la journée", start: "08:00", end: "18:00" } + ]; + for (const p of presets) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "btn btn-secondary modal-preset-btn"; + btn.textContent = p.label; + btn.addEventListener("click", () => { + startTime.value = p.start; + endTime.value = p.end; + // Synchroniser visuellement la mise à jour et déclencher + // endDateTouched si besoin (la date reste inchangée) + startTime.dispatchEvent(new Event("input", { bubbles: true })); + endTime.dispatchEvent(new Event("input", { bubbles: true })); + }); + presetRow.appendChild(btn); + } + presetGroup.appendChild(presetRow); + card.appendChild(presetGroup); + // v5.0.0 : la date de fin suit la date de début tant que l'user ne l'a // pas explicitement modifiée. 95% des absences sont d'un seul jour, donc // changer juste le start doit mettre à jour le end aussi. @@ -2139,24 +2171,18 @@ function actionNodeToIntervention(node) { if (refFromLabel) ref = refFromLabel[1]; } - // Détection du type "Réservation" : un coordinateur a bloqué un créneau. - // Dans le XML, action_type = "AL-Absence" pour ce genre de créneau, mais - // action_label contient le vrai pattern : - // action_label = "Xxxxx / Créé par : Nom, Prénom" - // Ex: "Ecrans / Créé par : Nom20, Prénom20" → Réservation (matériel) - // "Rollout / Créé par : Nom24, Prénom24" → Réservation - // "Congés / Créé par : ..." → Absence - // "Maladie / Créé par : ..." → Absence - // "Pompier / Créé par : ..." → Absence - // "Evènements spéciaux / Créé par : ..." → Absence - // "Réunion / Créé par : ..." → Absence - // "Déménagement / Créé par : ..." → Absence + // Détection du type "Réservation" vs "Absence". // - // v5.0.2 : pour éviter les faux positifs, on considère qu'une entrée est - // une Réservation UNIQUEMENT si son label correspond à une ressource - // matérielle (Ecrans, PC, MAC, Téléphones, UTP, Rollout). Tout le reste - // est une absence. Ça couvre les types de HOLIDAY_TYPES non-matériels. - const RESERVATION_LABELS = /^(ecran(s)?|pc|mac|t[ée]l[ée]phones?|utp|rollout)$/i; + // v5.0.3 (simplifiée) : le label suit le pattern "Nom / Créé par : X Y". + // + // - Congés / Maladie / Pompier → AL-Absence (tech réellement absent) + // - TOUT LE RESTE (Ecrans, PC, MAC, Rollout, Téléphones, UTP, Réunion, + // Déménagement, Evènements spéciaux, Formation, ...) + // → AL-Reservation (créneau bloqué, tech pas absent) + // + // Cette règle simple évite les cas "absence toute la journée" déclenchés + // par erreur pour des réservations de type événement / réunion. + const ABSENCE_LABELS = /^(cong[ée]s|maladie|pompier)$/i; let effectiveType = actionType; let reservationLabel = null; let reservationCreator = null; @@ -2164,15 +2190,14 @@ function actionNodeToIntervention(node) { if (reservationMatch) { const label1 = reservationMatch[1].trim(); const creator = reservationMatch[2].trim(); - if (RESERVATION_LABELS.test(label1)) { - // Ressource matérielle → Réservation + if (ABSENCE_LABELS.test(label1)) { + // Vraie absence du tech + effectiveType = "AL-Absence"; + } else { + // Réservation : créneau bloqué (matériel ou activité), tech pas absent effectiveType = "AL-Reservation"; reservationLabel = label1; reservationCreator = creator; - } else { - // Tout autre (Congés, Maladie, Pompier, Evènements spéciaux, Réunion, - // Déménagement, Formation, etc.) → Absence - effectiveType = "AL-Absence"; } } @@ -3912,24 +3937,23 @@ function buildCard(tech, isoDate) { note.textContent = "Absent toute la journée"; } body.appendChild(note); - // v5.0.1 : bouton 🗑 pour supprimer l'absence (seulement si actionId réel, - // pas les absences récurrentes type Pillonel vendredi qui n'existent pas - // dans EasyVista). + + // v5.0.4 : tooltip au hover sur toute la carte absent (pas juste un + // bouton visible). Contient : détail période + bouton supprimer si + // c'est une absence supprimable (actionId réel, pas pompier récurrent). if (ab.actionId && !ab.isPompier && !ab._recurring) { - const delWrap = document.createElement("div"); - delWrap.className = "absence-delete-wrap"; - const delBtn = document.createElement("button"); - delBtn.type = "button"; - delBtn.className = "tooltip-delete-btn"; - delBtn.textContent = "🗑 Supprimer l'absence"; - delBtn.dataset.actionId = ab.actionId; - delBtn.dataset.kind = "absence"; - delBtn.addEventListener("click", (e) => { - e.stopPropagation(); - _triggerDeleteItem(ab.actionId, "absence"); + // On attache le tooltip sur la CARD ENTIÈRE (cardEl) — comme ça + // survoler n'importe où sur la zone grisée "absent" le déclenche. + const ivCopy = { + ...ab, + type: "AL-Absence" // force pour buildTooltipHTML + }; + cardEl.addEventListener("mouseenter", (e) => { + showTooltip(e, ivCopy, cardEl); + }); + cardEl.addEventListener("mouseleave", () => { + hideTooltip(); }); - delWrap.appendChild(delBtn); - body.appendChild(delWrap); } }