forked from FroSteel/Planification
v2026.5.16 — Passage au schéma de versionning ANNÉE.MAJEURE.PATCH + faux input date custom (Mardi 24.04.2026)
This commit is contained in:
@@ -246,6 +246,7 @@ async function init() {
|
||||
// Initialiser la date = aujourd'hui
|
||||
state.currentDate = todayISO();
|
||||
document.getElementById("date-picker").value = state.currentDate;
|
||||
updateDatePickerDayLabel(state.currentDate); // v2026.5.16 : label "Mardi"
|
||||
|
||||
// v5.0.11 : détecter le contexte réseau en arrière-plan (non bloquant)
|
||||
detectNetworkContextAsync();
|
||||
@@ -799,11 +800,37 @@ function initAppFooter() {
|
||||
function initAppClock() {
|
||||
const el = document.getElementById("app-clock");
|
||||
if (!el) return;
|
||||
const dateEl = document.getElementById("app-clock-date");
|
||||
const timeEl = document.getElementById("app-clock-time");
|
||||
|
||||
// v2026.5.16 : format "Mardi 21 avril 2026"
|
||||
const JOURS = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];
|
||||
const MOIS = [
|
||||
"janvier", "février", "mars", "avril", "mai", "juin",
|
||||
"juillet", "août", "septembre", "octobre", "novembre", "décembre"
|
||||
];
|
||||
|
||||
let lastDateStr = "";
|
||||
const tick = () => {
|
||||
const d = new Date();
|
||||
const h = String(d.getHours()).padStart(2, "0");
|
||||
const m = String(d.getMinutes()).padStart(2, "0");
|
||||
el.textContent = `${h}:${m}`;
|
||||
const timeStr = `${h}:${m}`;
|
||||
if (timeEl) timeEl.textContent = timeStr;
|
||||
else el.textContent = timeStr; // fallback si ancien markup
|
||||
|
||||
// Date complète : actualisée seulement si elle a changé (évite reflow inutile)
|
||||
if (dateEl) {
|
||||
const jour = JOURS[d.getDay()];
|
||||
const num = d.getDate();
|
||||
const mois = MOIS[d.getMonth()];
|
||||
const annee = d.getFullYear();
|
||||
const dateStr = `${jour} ${num} ${mois} ${annee}`;
|
||||
if (dateStr !== lastDateStr) {
|
||||
dateEl.textContent = dateStr;
|
||||
lastDateStr = dateStr;
|
||||
}
|
||||
}
|
||||
// v5.0.0 : profite du tick pour mettre à jour la ligne rouge "now"
|
||||
updateNowLine();
|
||||
};
|
||||
@@ -812,6 +839,21 @@ function initAppClock() {
|
||||
setInterval(tick, 30 * 1000);
|
||||
}
|
||||
|
||||
// v2026.5.16 : met à jour le label court du jour affiché à gauche du
|
||||
// date-picker (ex: "Mardi", "Lundi"). Appelé à chaque changement de date.
|
||||
const DAY_NAMES_FULL = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];
|
||||
function updateDatePickerDayLabel(isoDate) {
|
||||
const el = document.getElementById("date-picker-day");
|
||||
if (!el) return;
|
||||
if (!isoDate) { el.textContent = ""; return; }
|
||||
try {
|
||||
const d = isoToDate(isoDate);
|
||||
el.textContent = DAY_NAMES_FULL[d.getDay()];
|
||||
} catch (e) {
|
||||
el.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
// v5.0.0 : ligne verticale rouge "heure actuelle" sur la timeline, visible
|
||||
// UNIQUEMENT quand on affiche aujourd'hui. Appelée à chaque tick de l'horloge
|
||||
// + après chaque render (cf renderFromData).
|
||||
@@ -2155,6 +2197,7 @@ async function loadForDate(isoDate, opts = {}) {
|
||||
|
||||
state.currentDate = isoDate;
|
||||
document.getElementById("date-picker").value = isoDate;
|
||||
updateDatePickerDayLabel(isoDate); // v2026.5.16 : label "Mardi" à côté
|
||||
|
||||
if (!state.session) {
|
||||
// v4.2.5 : afficher le cache + bannière, plutôt que l'écran "session"
|
||||
@@ -5366,7 +5409,10 @@ function splitOneContact(raw) {
|
||||
// Prénom 41XXXXXXXXX → extraire 41XXXXXXXXX puis reformater en
|
||||
// +41 XX XXX XX XX). On exige exactement 11 chiffres collés pour
|
||||
// éviter de matcher des codes postaux ou autres nombres.
|
||||
const rxLong = /(\+41\s?\d(?:[\d\s.\-]*\d)?|\+33\s?\d(?:[\d\s.\-]*\d)?|0\d(?:[\d\s.\-]*\d)?|(?<!\d)41\d{9}(?!\d)|(?<!\d)33\d{9}(?!\d))/g;
|
||||
// v2026.5.16 : ne PAS matcher si le numéro est précédé d'une lettre ou
|
||||
// d'un underscore (identifiants style XXXX_NNNNNNNN, ABC123456,
|
||||
// SERIAL_0123456789). On ajoute un lookbehind négatif (?<![A-Za-z_]).
|
||||
const rxLong = /(?<![A-Za-z_])(\+41\s?\d(?:[\d\s.\-]*\d)?|\+33\s?\d(?:[\d\s.\-]*\d)?|0\d(?:[\d\s.\-]*\d)?|(?<!\d)41\d{9}(?!\d)|(?<!\d)33\d{9}(?!\d))/g;
|
||||
// SHORT : numéro interne court (5 chiffres).
|
||||
// - v4.1.20 : accepte "12345Texte" (pas de séparateur après)
|
||||
// - v4.2.3 : accepte aussi les formats AVEC ESPACES au sein du numéro,
|
||||
@@ -5415,6 +5461,26 @@ function splitOneContact(raw) {
|
||||
}
|
||||
|
||||
name = cleanContactName(name);
|
||||
|
||||
// v2026.5.16 : dernier garde-fou — rejeter les "noms" qui ressemblent
|
||||
// à des fragments de description technique plutôt qu'à des vrais contacts.
|
||||
// Exemples rejetés :
|
||||
// - "1x" (quantité isolée)
|
||||
// - "1x pc" (quantité + type matériel)
|
||||
// - "pc XNNNNNN" (type + numéro de série)
|
||||
// - "XXXX_NNNNNNNN" (identifiant matériel)
|
||||
// Critères d'un vrai nom : contient au moins un mot qui commence par une
|
||||
// majuscule ET n'est pas juste un identifiant technique.
|
||||
if (name) {
|
||||
const looksLikeIdentifier = /^[A-Z]{2,}[_\-]\d+$/.test(name); // XXXX_NNNNNNNN
|
||||
const startsWithQuantity = /^\d+x(\s|$)/i.test(name); // "1x" ou "1x pc"
|
||||
const noCapitalWord = !/\b[A-ZÉÈÀÂÎÔÛÇ][a-zéèàâîôûç]+/.test(name); // aucun mot "Xxxxx"
|
||||
const hasOnlyTechTokens = /^(\d+x|pc|mac|t[ée]l[ée]phone|ecran|docking|rollout)(\s+(\d+x|pc|mac|t[ée]l[ée]phone|ecran|docking|rollout|[A-Z]\d+))*\s*$/i.test(name);
|
||||
if (looksLikeIdentifier || startsWithQuantity || hasOnlyTechTokens || (noCapitalWord && !phone)) {
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
|
||||
return { name, phone };
|
||||
}
|
||||
|
||||
@@ -5513,22 +5579,34 @@ function splitLieu(raw) {
|
||||
// Retirer un / final (avec ou sans espaces)
|
||||
s = s.replace(/\s*\/\s*$/, "").trim();
|
||||
if (!s) return { ville: null, adresse: null };
|
||||
const idx = s.indexOf("/");
|
||||
|
||||
// v2026.5.16 : le format EasyVista peut avoir jusqu'à 3 parties séparées
|
||||
// par "/" : VILLE / ADRESSE / PRÉCISIONS (étage, bureau, indications).
|
||||
// Exemple : "LAUSANNE / Av. de Beaulieu 19 / 4eme en face de l'ascenseur"
|
||||
// On ne garde que VILLE + ADRESSE. Les précisions (3e partie et suivantes)
|
||||
// sont strippées — elles alourdissent la carte et sont disponibles dans
|
||||
// le tooltip détaillé.
|
||||
const parts = s.split("/").map(p => p.trim()).filter(Boolean);
|
||||
|
||||
let ville, adresse;
|
||||
if (idx < 0) {
|
||||
if (parts.length === 0) {
|
||||
return { ville: null, adresse: null };
|
||||
} else if (parts.length === 1) {
|
||||
// Pas de slash : tout est l'adresse
|
||||
ville = null;
|
||||
adresse = s;
|
||||
adresse = parts[0];
|
||||
} else {
|
||||
ville = s.substring(0, idx).trim();
|
||||
adresse = s.substring(idx + 1).trim();
|
||||
// 2+ parties : ville = 1ère, adresse = 2e, on ignore le reste
|
||||
ville = parts[0];
|
||||
adresse = parts[1];
|
||||
}
|
||||
|
||||
// Capitaliser la 1ère lettre des mots de voie (Rue, Chemin, Route, Avenue,
|
||||
// Boulevard, Place, Quai, Impasse + abréviations Av., Ch., Rte, Bd)
|
||||
if (adresse) {
|
||||
adresse = adresse.replace(
|
||||
/\b(rue|chemin|route|avenue|boulevard|place|quai|impasse|ruelle|allée|allee|passage|sentier|av\.?|ch\.?|rte\.?|bd\.?)\b/gi,
|
||||
(match) => {
|
||||
// Conserver la casse existante si déjà majuscule, sinon capitaliser
|
||||
if (/^[A-ZÉÈÀÂÎÔÛÇ]/.test(match)) return match;
|
||||
return match.charAt(0).toUpperCase() + match.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user