// viewer.js — Logique de la vue claire // // Étapes : // 1. Lire le HTML capturé depuis chrome.storage.local // 2. Parser les techniciens (emp_XXXXX dans le DOM) // 3. Parser les événements (g_arr_player[N] dans le JS inline) // 4. Calculer : pompier du jour, absents, interventions par tech // 5. Afficher tout ça // 6. Au survol / clic : charger la fiche détaillée via fetch() // ========================================================================== // Configuration // ========================================================================== // Règles fixes (techs avec horaires particuliers) const RULES = { // Pillonel, Olivier (ID 40944) est absent tous les vendredis "40944": { alwaysAbsentOn: [5], // 5 = vendredi (JS: 0=dim, 1=lun, ..., 5=ven, 6=sam) reason: "Absent fixe le vendredi" } }; // Cache des fiches détaillées (persiste pour la session) const detailsCache = new Map(); // Fetch en cours (pour éviter de lancer 2x la même requête) const detailsPromises = new Map(); // ========================================================================== // Init // ========================================================================== document.addEventListener("DOMContentLoaded", async () => { document.getElementById("btn-refresh").addEventListener("click", refresh); document.getElementById("btn-preload").addEventListener("click", preloadAll); await loadFromStorage(); }); async function loadFromStorage() { const data = await chrome.storage.local.get([ "planningHtml", "planningUrl", "planningCapturedAt", "planningError" ]); if (data.planningError) { showError(data.planningError); return; } if (!data.planningHtml) { showError( "Aucune donnée de planning disponible. " + "Va sur la page du planning des techniciens sur itsma.vd.ch puis clique sur l'icône de l'extension." ); return; } try { const parsed = parsePlanning(data.planningHtml, data.planningUrl); render(parsed, data.planningCapturedAt); } catch (err) { console.error(err); showError("Erreur lors du parsing du planning : " + err.message); } } async function refresh() { document.getElementById("subtitle").textContent = "Retour sur EasyVista pour actualiser…"; // Inviter l'utilisateur à revenir sur l'onglet EasyVista et re-cliquer sur l'icône // (on ne peut pas re-capturer automatiquement depuis le viewer) showError( "Pour actualiser : va sur l'onglet EasyVista, recharge le planning (F5), " + "puis clique à nouveau sur l'icône de l'extension." ); } // ========================================================================== // Parsing // ========================================================================== function parsePlanning(html, sourceUrl) { // Parser le HTML dans un DOM isolé pour pouvoir utiliser les sélecteurs CSS const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); // --- 1. Extraire les techniciens depuis
const techs = {}; // id -> { name, id } for (const el of doc.querySelectorAll('div.support_list[id^="emp_"]')) { const id = el.id.replace("emp_", ""); // Le nom est le texte direct du div (après le checkbox) const rawText = el.textContent.trim(); // Nettoyer : enlever les \u00a0 ( ) const name = rawText.replace(/\u00a0/g, " ").trim(); if (name && /^\d+$/.test(id)) { techs[id] = { id, name }; } } // --- 2. Extraire les événements depuis le JS inline (g_arr_player[N]) // On parse les scripts const events = {}; for (const script of doc.querySelectorAll("script")) { const src = script.textContent; if (!src.includes("g_arr_player")) continue; // Pattern 1 : new action_player("event_id", "label", ...) const pLabel = /g_arr_player\[(\d+)\]\s*=\s*new action_player\("(\d+)",\s*"([^"]*)"/g; let m; while ((m = pLabel.exec(src)) !== null) { const idx = m[1]; events[idx] = events[idx] || {}; events[idx].eventId = m[2]; events[idx].label = decodeText(m[3]); } // Pattern 2 : assign_informations(tech_id, "title", "type", ...) const pInfo = /g_arr_player\[(\d+)\]\.assign_informations\((\d+),\s*"([^"]*)",\s*"([^"]*)"/g; while ((m = pInfo.exec(src)) !== null) { const idx = m[1]; events[idx] = events[idx] || {}; events[idx].techId = m[2]; events[idx].title = decodeText(m[3]); events[idx].type = m[4]; } // Pattern 3 : assign_date_time_informations (plusieurs arguments) const pTime = /g_arr_player\[(\d+)\]\.assign_date_time_informations\(([^)]+)\)/g; while ((m = pTime.exec(src)) !== null) { const idx = m[1]; const args = m[2]; const parts = [...args.matchAll(/"([^"]*)"/g)].map(x => x[1]); if (parts.length >= 10) { events[idx] = events[idx] || {}; events[idx].dateStart = parts[0]; events[idx].dateEnd = parts[4]; events[idx].timeStart = parts[8]; events[idx].timeEnd = parts[9]; } } } // --- 3. Extraire les liens vers les fiches détaillées depuis les AffBulle // Les contenant target=XXXXXXXX donnent l'URL de la fiche const eventLinks = {}; // eventId -> full URL for (const a of doc.querySelectorAll('a[href*="target="]')) { const href = a.getAttribute("href"); const m = /target=(\d+)/.exec(href); if (m) { // Construire l'URL absolue à partir de sourceUrl try { const absoluteUrl = new URL(href, sourceUrl || "https://itsma.vd.ch/").href; eventLinks[m[1]] = absoluteUrl; } catch (e) { // ignore } } } // --- 4. Extraire les infobulles rapides pour chaque event // Format : AffBulle(this, '...texte échappé...') const eventBulles = {}; // eventId -> texte décodé // Recherche dans le HTML brut pour les AffBulle (ils ne sont pas dans