forked from FroSteel/Planification
v2026.5.44 — Refonte topbar, personnalisation Apparence, onboarding équipe, fix #1
Refresh / cache / verdicts ghost : - Rafraîchissement séquentiel (1 fiche à la fois) avec arrêt instantané via AbortController. - Re-fetch checksum frais (basicAutoComplete + redirectHeader). - Cache merge robuste avec fallback cachedByRef ; cache écrit toutes les 5 fiches (incrémental). - Verdicts ghost unifiés : ✓✓ clos/résolu, ✓ Fait (pending), ✓ jaune Suspendu, retrait silencieux pour cancelled. - Statuts EV configurables depuis Paramètres → EasyVista (matching insensible à la casse, accents, conjugaisons). - Mode diagnostic optionnel (Diagnostics) qui logge tout sans rien retirer. Topbar (vue classique) : - Sélecteur de date du planning ancré au centre absolu (ne se décale plus quand le bouton Arrêter apparaît). - Bouton Aujourd'hui en toutes lettres. - Horloge contextuelle réduite à côté. Personnalisation (Paramètres → Apparence) : - Couleur de la topbar : 12 presets cliquables + picker custom + champ hex. Texte topbar adapté automatiquement (luminance) pour rester lisible. - Police de l'application : 28 choix (Arial, Helvetica, Verdana, Tahoma, Trebuchet, Calibri, Segoe UI, Times New Roman, Georgia, Cambria, Garamond, Palatino, Courier, Consolas, Comic Sans, Impact, …) appliquée à toute la page (cards, popups, panel admin) avec preview live. - Export / import du cache et de admin_config. Vue horizontale : - Bloc Aujourd'hui + horloge empilé verticalement dans la sidebar. - Date sélectionnée mise en avant (taille augmentée, gras), date du jour + heure réduites à la même petite taille. - Barre verticale verte à droite des mini-cards clos/résolu (✓✓), avec décalage du ✓✓ pour ne pas chevaucher. - Sidebar adopte la couleur de topbar custom (titre, horloge, today-block, date sélectionnée, boutons, theme-toggle, séparateurs translucides cohérents via color-mix). Stats globales : - Nouveau compteur 'X faits / Y clos' entre (matin · après-midi) et tech. dispo. - Vue classique : séparateur '//' après clos. - Vue horizontale (sidebar) : barre horizontale 1px de séparation. Onboarding équipe : - Carte centrée propre (icône, titre, description, bouton 'Ouvrir paramètres') quand aucun technicien n'est sélectionné. Bouton ouvre directement la section Équipe du panel admin. Bugfix : - Issue #1 (Pompier + Absence) : les deux badges s'affichent désormais avec '/' au lieu de masquer l'absence. - Absences récurrentes restaurées au switch de groupe (étaient invisibles alors qu'en storage). - Barre de progression / bannière session expirée suivent la hauteur dynamique de la topbar (--topbar-height via ResizeObserver). - STATUS_FR regex limite 30 → 200 chars. - Description action décodée proprement (\u0022, <br>, HTML strippé) ; préfixe 'login:' retiré du commentaire technicien. - Flèche '↗' retirée des références cliquables.
This commit is contained in:
+69
-7
@@ -299,14 +299,39 @@ async function fetchPlanningXml(origin, phpsessid, unixDate) {
|
||||
* @param {string} origin - origine EasyVista (pour construire le Referer)
|
||||
* @param {object} [opts] - options fetch (method, body, headers supplémentaires)
|
||||
*/
|
||||
// registre global des AbortController des fetchs EV en vol. Permet
|
||||
// au foreground (viewer.js) d'envoyer un message "abortAllFetches" pour
|
||||
// tuer instantanément les requêtes en cours quand l'user clique "Arrêter".
|
||||
const _evFetchControllers = new Set();
|
||||
function _abortAllEvFetches() {
|
||||
for (const c of _evFetchControllers) {
|
||||
try { c.abort(); } catch (e) { /* ignore */ }
|
||||
}
|
||||
_evFetchControllers.clear();
|
||||
}
|
||||
|
||||
async function evFetch(url, origin, opts = {}) {
|
||||
const defaultHeaders = {
|
||||
"Referer": `${origin}/index.php?eventName=HelpDesk_PlanningItem`,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
};
|
||||
const headers = Object.assign({}, defaultHeaders, opts.headers || {});
|
||||
const fetchOpts = Object.assign({ credentials: "include" }, opts, { headers });
|
||||
return await fetch(url, fetchOpts);
|
||||
// on ne remplace pas un signal explicitement passé par l'appelant.
|
||||
let controller = null;
|
||||
if (!opts.signal) {
|
||||
controller = new AbortController();
|
||||
_evFetchControllers.add(controller);
|
||||
}
|
||||
const fetchOpts = Object.assign(
|
||||
{ credentials: "include" },
|
||||
opts,
|
||||
{ headers, signal: opts.signal || (controller && controller.signal) }
|
||||
);
|
||||
try {
|
||||
return await fetch(url, fetchOpts);
|
||||
} finally {
|
||||
if (controller) _evFetchControllers.delete(controller);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,10 +401,10 @@ async function fetchFicheHtml(origin, phpsessid, formLink) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sinon : on retourne ce qu'on a
|
||||
return html;
|
||||
// on signale au foreground si la dernière réponse est tronquée pour
|
||||
// qu'il puisse afficher un ⚠ et probe la session.
|
||||
return { html, truncated: html.length < MIN_VALID_SIZE, size: html.length };
|
||||
}
|
||||
// Ne devrait pas arriver (la boucle fait return avant)
|
||||
throw new Error("fetchFicheHtml: max retries reached");
|
||||
}
|
||||
|
||||
@@ -1225,6 +1250,13 @@ async function detectTeamFromEV(origin, phpsessid, groupIdArg, supportIdsArg) {
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
(async () => {
|
||||
try {
|
||||
// abort de toutes les requêtes EV en vol (clic sur "Arrêter").
|
||||
if (msg.type === "abortAllFetches") {
|
||||
_abortAllEvFetches();
|
||||
sendResponse({ ok: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "getSession") {
|
||||
const session = await findEasyVistaSession();
|
||||
sendResponse({ ok: true, session });
|
||||
@@ -1282,12 +1314,14 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const html = await fetchFicheHtml(session.origin, session.phpsessid, msg.formLink);
|
||||
// fetchFicheHtml renvoie maintenant { html, truncated, size }.
|
||||
const result = await fetchFicheHtml(session.origin, session.phpsessid, msg.formLink);
|
||||
const html = result.html;
|
||||
if (looksLikeLoginPage(html)) {
|
||||
sendResponse({ ok: false, error: "session_expired" });
|
||||
return;
|
||||
}
|
||||
sendResponse({ ok: true, html, session });
|
||||
sendResponse({ ok: true, html, session, truncated: !!result.truncated, size: result.size });
|
||||
} catch (err) {
|
||||
sendResponse({
|
||||
ok: false,
|
||||
@@ -1299,6 +1333,34 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// probe rapide de session — fetch un endpoint léger pour vérifier
|
||||
// que PHPSESSID est toujours valide. Renvoie ok=false/error=session_expired
|
||||
// si la session est morte.
|
||||
if (msg.type === "checkSession") {
|
||||
const session = await findEasyVistaSession();
|
||||
if (!session) {
|
||||
sendResponse({ ok: false, error: "no_session" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const url = `${session.origin}/index.php?eventName=HelpDesk_PlanningItem&PHPSESSID=${encodeURIComponent(session.phpsessid)}`;
|
||||
const r = await evFetch(url, session.origin);
|
||||
if (!r.ok) {
|
||||
sendResponse({ ok: false, error: classifyHttpStatus(r.status), httpStatus: r.status });
|
||||
return;
|
||||
}
|
||||
const txt = await r.text();
|
||||
if (looksLikeLoginPage(txt) || txt.length < 5000) {
|
||||
sendResponse({ ok: false, error: "session_expired" });
|
||||
return;
|
||||
}
|
||||
sendResponse({ ok: true });
|
||||
} catch (err) {
|
||||
sendResponse({ ok: false, error: "fetch_failed", detail: err.message || String(err) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "fetchTimelineApi") {
|
||||
const session = await findEasyVistaSession();
|
||||
if (!session) {
|
||||
|
||||
Reference in New Issue
Block a user