v2026.5.47 — Vue « échelle de temps exacte » : aide à la saisie, erreurs mises en évidence, corrections de fiabilité

- Retour visuel au survol d'un créneau libre (zone surlignée + heure choisie qui suit la souris ; bulle fixe plage libre + durée disponible, jamais par-dessus un rendez-vous).
- Rendez-vous en erreur (début = fin) affiché en rouge et visible en échelle exacte ; avertissement + blocage à la création par-dessus. Interventions courtes visibles.
- Zone du nom du technicien pleine hauteur ; barre des heures collée sous la banderole.
- Fiabilité : navigation rapide entre dates, technicien retiré, deadlock de synchro du dossier, jour lu depuis le dossier d'emblée, doublon d'absence, réservations multiples le même jour, barre de progression et message de reconnexion.
- Synchro : bouton « Tester la communication », panneau réorganisé, script du poste 2026.1.8 (verrou d'index, icône persistante).
This commit is contained in:
2026-07-18 23:17:58 +02:00
parent 975a907f2b
commit 5f6802a3ab
8 changed files with 870 additions and 183 deletions
+30
View File
@@ -9,6 +9,36 @@
---
## v2026.5.47 — Vue « échelle de temps exacte » : aide à la saisie, erreurs mises en évidence, corrections de fiabilité
> Cette version peaufine la vue horizontale « échelle de temps exacte » : un vrai retour visuel accompagne la pose d'un rendez-vous ou d'une absence, et les erreurs de saisie (rendez-vous dont le début est égal à la fin) sont désormais bien visibles et signalées. Elle corrige aussi plusieurs problèmes de navigation entre dates et de synchronisation du dossier partagé, réorganise le panneau de synchronisation, et ajoute un bouton de test de la communication avec le script du poste.
### Vue « échelle de temps exacte » — aide à la saisie
- **Retour visuel au survol d'un créneau libre** : une zone surlignée suit la souris et affiche **l'heure exacte** (début → fin) du rendez-vous ou de l'absence qu'un clic droit poserait. Une bulle fixe, en haut de la ligne, rappelle la **plage libre et la durée disponible** du créneau (« Libre 12:0018:00 / 6 h disponible »). Elle se place toujours sur un espace libre — jamais par-dessus un rendez-vous — et ne masque pas l'heure choisie.
- **Erreurs de saisie mises en évidence** : un rendez-vous dont le **début est égal à la fin** (ex. 10:00 → 10:00) est désormais affiché en **rouge** et reste **visible** (avant, il disparaissait sur l'échelle exacte). Survoler une telle zone pour en poser un autre affiche un **avertissement** et **empêche** la création tant que l'erreur n'est pas corrigée.
- **Interventions courtes visibles** : une intervention de 1 à 5 minutes garde une largeur minimale et reste visible.
- **Zone du nom** : le fond du nom du technicien remplit toute la hauteur de la ligne (nom centré).
- **Barre des heures** : reste collée en haut au défilement et passe sous la banderole de reconnexion (au lieu de disparaître derrière).
### Corrections de fiabilité
- **Navigation rapide entre dates** : le jour que l'on quitte ne peut plus écraser le jour qui vient de s'afficher.
- **Technicien retiré de l'équipe** : n'apparaît plus sur les jours relus depuis le dossier partagé.
- **Synchronisation du dossier partagé** : correction d'un blocage où l'écriture n'aboutissait jamais (conflit permanent entre l'index et le fichier relu) ; l'index se répare de lui-même.
- **Jour présent au dossier mais pas en local** : lu depuis le dossier d'emblée — plus de re-remplissage visible inutile.
- **Absences** : plus de doublon « absence récurrente » + « vraie absence » le même jour ; le fond des cartes d'absence remplit toute la carte en échelle exacte.
- **Réservations multiples le même jour** : les deux créneaux coexistent ; saisir le n° d'intervention dans le formulaire remplit automatiquement ville, rue et catégorie.
- **Barre de progression** et **message de reconnexion** : bien positionnés en vue horizontale (barre en haut de la sidebar, message centré dans le contenu).
### Synchronisation multi-postes
- **Nouveau bouton « Tester la communication »** (onglet Synchro) : diagnostic de bout en bout extension → script du poste → dossier partagé.
- **Panneau Synchro réorganisé** en sections claires : état de la synchro, dossier partagé, script sur le poste, journal d'activité.
- **Script du poste (native messaging host) 2026.1.8** : verrou dédié sur l'index du dossier partagé (protège l'écriture simultanée par plusieurs postes ou onglets) et icône de la zone de notification ré-affichée au redémarrage de l'explorateur Windows. Le bouton « Télécharger le script » récupère cette version.
---
## v2026.5.46 — Refonte de la couche données, vue « échelle de temps exacte », session fiabilisée, réservations enrichies
> Version majeure par son contenu : la couche données est entièrement refondue (store unifié par jour, un seul verdict par intervention, synchronisation du dossier partagé en sous-dossier `v2`), une nouvelle disposition « échelle de temps exacte » s'ajoute à la vue horizontale, la détection de session EasyVista est fiabilisée de bout en bout, et les réservations portent enfin leurs informations (n°, lieu, catégorie) sur tous les postes. Le script PowerShell du dossier partagé est désormais publié avec l'extension et téléchargeable depuis les Paramètres.
+1 -1
View File
@@ -9,7 +9,7 @@ Extension Chrome / Firefox pour visualiser de manière claire et rapide le plann
- **Auteur** : Quentin Rouiller (QRO), Technicien DGNSI — Canton de Vaud
- **Public cible** : coordinateurs DGNSI qui pilotent dans EasyVista (`itsma.etat-de-vaud.ch` / `itsma.vd.ch`) le planning de l'équipe technicienne
- **Démarrage projet** : jeudi 16 avril 2026
- **Version actuelle** : [`v2026.5.45`](https://gitea.netaplaid.ch/FroSteel/Planification/releases/tag/v2026.5.45) (latest)
- **Version actuelle** : [`v2026.5.47`](https://gitea.netaplaid.ch/FroSteel/Planification/releases/tag/v2026.5.47) (latest)
- **Contact** : voir [page wiki Contact](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Contact) ou [ouvrir une issue](https://gitea.netaplaid.ch/FroSteel/Planification/issues/new)
- **Manifest** : V3 (Chrome/Edge/Firefox 140+)
- **Format** : `.zip` (Chromium) + `.xpi` signé Mozilla (Firefox)
+6 -1
View File
@@ -2,10 +2,15 @@
"addons": {
"[email protected]": {
"updates": [
{
"version": "2026.5.47",
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.47/planification-v2026.5.47-firefox.xpi",
"update_hash": "sha256:e97883102af2f00fd468c9be62deac6c1d2546ef3e25afe90d79a390bbd1f0bb"
},
{
"version": "2026.5.46",
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.46/planification-v2026.5.46-firefox.xpi",
"update_hash": "sha256:052cac5d79a8ec9f1eeb7172e8491f2e2a82b5937622131905cfcaa3eba4737b"
"update_hash": "sha256:673dc0527073523750de348414ae1d3b5c0cb42cc8128a7839bbb09bffb6242c"
},
{
"version": "2026.5.45",
+27 -1
View File
@@ -30,7 +30,7 @@
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$HOST_VERSION = '2026.1.7'
$HOST_VERSION = '2026.1.8'
# v2026.1.7 - le host n'est plus persistant a vie : s'il ne recoit AUCUN message
# pendant ce delai, il se ferme proprement (le navigateur le relance a la
# prochaine activite de l'extension). Il lance aussi le tray a la demande.
@@ -155,6 +155,29 @@ function Read-Index([string]$share) {
function Update-Index([string]$share, [string]$date, [long]$savedAt) {
$idx = Join-Path $share $INDEX_NAME
# v2026.1.8 - verrou DEDIE a l'index. Plusieurs hotes (plusieurs onglets
# Planification ouverts, ou plusieurs postes) font un read-modify-write
# concurrent sur _index.json ; sans verrou, deux Move-Item se recouvrent et la
# maj d'un hote est perdue (lost update) -> l'index prend du retard sur les
# fichiers-jour -> conflits CAS a repetition ("refusion" en boucle). On
# serialise donc la sequence lecture->modif->ecriture. Vol du verrou perime
# (> 10 s) comme pour les fichiers-jour. Best-effort : si on n'obtient pas le
# verrou, on renonce (la prochaine ecriture reussie remettra l'index a jour).
$lock = $idx + '.lock'
$haveLock = $false
for ($i = 0; $i -lt 20 -and -not $haveLock; $i++) {
try { [System.IO.File]::Open($lock, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write).Close(); $haveLock = $true }
catch {
try {
$age = ((Get-Date) - (Get-Item -LiteralPath $lock).LastWriteTime).TotalSeconds
if ($age -gt 10) { Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue }
else { Start-Sleep -Milliseconds 50 }
} catch { Start-Sleep -Milliseconds 50 }
}
}
if (-not $haveLock) { Write-Activity 'DEBUG' "Index non mis a jour pour le $(Format-DateFR $date) (verrou index indisponible)"; return }
try {
# lecture DANS le verrou -> on part toujours de la derniere version.
$map = @{}
if (Test-Path -LiteralPath $idx) {
try {
@@ -167,6 +190,9 @@ function Update-Index([string]$share, [string]$date, [long]$savedAt) {
[System.IO.File]::WriteAllText($tmp, ($map | ConvertTo-Json -Compress -Depth 5), $Utf8NoBom)
Move-Item -LiteralPath $tmp -Destination $idx -Force
Write-Activity 'DEBUG' "Index du dossier mis a jour pour le $(Format-DateFR $date)"
} finally {
Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue
}
}
function Get-SavedAt([string]$share, [string]$date, [string]$file) {
+29
View File
@@ -160,6 +160,35 @@ $ni.Icon = Get-TrayIcon
$ni.Text = 'Planification - synchronisation active'
$ni.Visible = $true
# v2026.1.8 - "toujours present si actif" : le NotifyIcon disparait quand
# explorer.exe redemarre (Windows rediffuse alors le message "TaskbarCreated").
# Sans le traiter, l'icone ne revient JAMAIS tant que le tray tourne. On ecoute
# ce message via une petite fenetre native et on re-affiche l'icone.
try {
Add-Type -ReferencedAssemblies System.Windows.Forms -TypeDefinition @'
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class PlanifTrayWatcher : NativeWindow {
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);
public static readonly uint TaskbarCreated = RegisterWindowMessage("TaskbarCreated");
private NotifyIcon _icon;
public PlanifTrayWatcher(NotifyIcon icon) {
_icon = icon;
this.CreateHandle(new CreateParams());
}
protected override void WndProc(ref Message m) {
if (m.Msg == TaskbarCreated && _icon != null) {
try { _icon.Visible = false; _icon.Visible = true; } catch {}
}
base.WndProc(ref m);
}
}
'@ -ErrorAction Stop
$script:trayWatcher = New-Object PlanifTrayWatcher($ni)
} catch {}
$menu = New-Object System.Windows.Forms.ContextMenuStrip
$miLog = $menu.Items.Add('Voir le journal de synchronisation')
$miLog.add_Click({ Show-LogWindow })
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Planification",
"version": "2026.5.46",
"version": "2026.5.47",
"description": "Vue claire et rapide du planning des techniciens EasyVista. Développé par Quentin Rouiller — DGNSI, Canton de Vaud.",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/El4I9gxcZkp5bnheg4ZBCKxckRLt9xQ5x+i6phEPYlydl6NapWlkbOyycUaeTZcpoEh/tKQnpMpoEc5jY8zCoRTwh/vfejVBZE6ANDG/dc3iix/1Z8OhRI4zMaRYQ+DQTjS5Q6l6wkcwzbQB85HZNhke/k1qIW6RCuBjeJYmES5WVDwzfCNVvaF6uQvdOCpLjrA3+LI6vUBEwaMg1hM42mC3dU0z9p7qzAby/dH3XpZD9HTFXLJL9/1zryN9rXNDpvZwYWnJ0hLTu5tfmURkvgKOwquX1KJy9vupmhUALRtS08Fplx/e3kUH3LD+coS1ExLwoOZ+1ORBUE9JCB8wIDAQAB",
"permissions": [
+155 -14
View File
@@ -1130,6 +1130,18 @@ html.view-classic .error-box.error-box-centered {
box-shadow: var(--shadow);
text-align: center;
}
/* v2026.5.47 — message de reconnexion EasyVista (#session-needed /
#ev-unreachable) CENTRÉ dans la zone de contenu en vue horizontale (avant :
posé en haut, comme un item flex de main#main). main#main est
position:relative en horizontal ; on centre dans le contenu (à droite de la
sidebar) via left = 50% + --sidebar-w/2. */
html.view-horizontal .session-needed {
position: absolute !important;
top: 50% !important;
left: calc(50% + var(--sidebar-w, 200px) / 2) !important;
transform: translate(-50%, -50%) !important;
margin: 0 !important;
}
.session-needed h2 {
margin: 0 0 12px 0;
color: var(--text);
@@ -1549,6 +1561,35 @@ html.view-horizontal .timeline-noon {
background: #c44040 !important;
}
/* v2026.5.47 — intervention à DURÉE NULLE (début == fin, ex. 10:00→10:00 :
erreur de saisie EV). Même rouge plein que les conflits ci-dessus, pour la
repérer d'un coup d'oeil dans toutes les vues. */
.iv-mini-card.iv-zero-duration {
background: #b03030 !important;
color: #ffffff !important;
border-color: #7a1f1f !important;
}
.iv-mini-card.iv-zero-duration .iv-mini-card-bar,
.iv-mini-card.iv-zero-duration .iv-mini-time-vertical,
.iv-mini-card.iv-zero-duration .iv-mini-card-text {
color: #ffffff !important;
background: transparent !important;
}
/* Vue exacte : la bulle est très fine (min-width) → le bord gauche (poignée)
doit être rouge lui aussi, sinon on ne verrait que sa couleur de catégorie. */
html.view-horizontal .iv-exact-card.iv-zero-duration .iv-exact-handle {
background-color: #7a1f1f !important;
background-image: none !important;
}
/* v2026.5.47 — durée nulle PLUS VISIBLE en vue exacte : un peu plus large
que les interventions courtes (11 px vs 6 px) + hachures rouges pour attirer
l'oeil sur l'erreur. */
html.view-horizontal .iv-exact-card.iv-zero-duration {
min-width: 11px !important;
background-image: repeating-linear-gradient(
45deg, #b03030 0 4px, #7a1f1f 4px 8px) !important;
}
/* ==========================================================================
Interventions — layout v2 (heures verticales)
========================================================================== */
@@ -4352,6 +4393,16 @@ html.theme-dark .card.absence-cat-recurring {
background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--bg-elevated) 40%);
}
/* v2026.5.46 — vue horizontale : en échelle exacte, la carte d'un tech
absent est HAUTE (elle s'étire sur toute la frise) alors que son contenu est
court. Le dégradé « couleur → neutre à 40 % » (pensé pour les cartes courtes
de la vue classique) laissait tout le bas de la carte en neutre. En vue
horizontale on remplit donc TOUTE la carte de la couleur douce (theme-aware
via --c-*-soft) ; la vue classique garde le dégradé. */
html.view-horizontal .card.absence-cat-maladie { background: var(--c-maladie-soft); }
html.view-horizontal .card.absence-cat-conge,
html.view-horizontal .card.absence-cat-recurring { background: var(--c-conge-soft); }
/* Message "Absent le vendredi" (ou autre récurrence) en cyan.
Règle UNIQUE — les bordures haut/bas (qui venaient d'un 2e bloc plus
haut, v4.1.20) sont repliées ici ; le bloc dupliqué a été retiré. */
@@ -4521,6 +4572,14 @@ html.view-horizontal .card-header {
padding: 6px 10px !important;
gap: 3px !important;
flex: 0 0 auto;
/* v2026.5.47 — le fond de la zone nom remplit TOUTE la hauteur de la
ligne (nom toujours centré via justify-content:center). En mode normal le
parent .card est déjà align-items:stretch, mais en mode heure exacte il
passe à align-items:center (pour centrer les bulles du corps), ce qui
laissait le header en bandeau fixe plus court que la ligne, avec du fond de
carte visible au-dessus/en dessous. align-self:stretch force le header à
s'étirer dans les deux modes ; le corps garde son propre centrage. */
align-self: stretch !important;
}
/* v2026.5.40 : nom du tech complet (jusqu'à 3 lignes). Si vraiment
trop long, .card-tech-name-tight est appliqué dynamiquement par le JS
@@ -5199,23 +5258,29 @@ html.view-horizontal .horizontal-sidebar .date-custom {
justify-content: center !important;
}
/* 13. Barre de rafraîchissement en vue horizontale : overlay par-dessus
user-badge + titre (zone haut de sidebar). */
/* 13. Barre de rafraîchissement en vue horizontale.
v2026.5.47 — Quentin : la barre doit RESTER dans la barre de gauche,
AU-DESSUS des initiales — pas dans le contenu où elle couvrait
les heures du mode « échelle exacte ». Elle repasse donc en overlay sur la
SIDEBAR (left:0, width = --sidebar-w) ; top suit --stack-top (sous la
banderole de session/reco si présente). Pour NE PAS recouvrir les
initiales, le contenu de la sidebar est poussé vers le bas pendant le
rafraîchissement (classe .planif-refreshing sur <html>, cf. showProgressBar). */
html.view-horizontal #progress-bar {
position: fixed !important;
top: 0 !important;
top: var(--stack-top, 0px) !important;
left: 0 !important;
width: 200px !important; /* largeur de la sidebar */
width: var(--sidebar-w, 200px) !important;
right: auto !important;
z-index: 9999 !important;
border-radius: 0 !important;
margin: 0 !important;
pointer-events: none;
}
/* Sur breakpoint étroit (sidebar 170px) */
@media (max-width: 1400px) {
html.view-horizontal #progress-bar {
width: 170px !important;
}
/* Réserve la hauteur de la barre en haut de la sidebar pendant un
rafraîchissement → les initiales / le titre passent SOUS la barre. */
html.planif-refreshing.view-horizontal .horizontal-sidebar {
padding-top: 34px !important;
}
/* v2026.5.40 : banderole "En pompier du..." en vue HORIZONTALE
@@ -7017,6 +7082,76 @@ html.view-horizontal .iv-exact-lane + .iv-exact-lane {
outline-offset: -2px;
}
/* v2026.5.47 — surbrillance de survol : la ZONE (1 h par défaut) qu'un clic
droit poserait en rendez-vous/absence, calée sous la souris à l'heure snappée
(créneau libre, mode heure exacte). Plus marquée que le survol gris du trou,
pour qu'on VOIE clairement la zone visée. Purement visuelle. */
.iv-exact-selpreview {
position: absolute;
top: 0;
bottom: 0;
pointer-events: none;
z-index: 0;
box-sizing: border-box;
border-radius: 4px;
border: 2px solid var(--accent, #3b82f6);
background: rgba(94, 168, 232, 0.28);
background: color-mix(in srgb, var(--accent, #3b82f6) 28%, transparent);
}
/* v2026.5.47 — bulle « temps disponible » FIXE en haut de la bande (elle ne
suit PLUS la souris) : plage libre du créneau survolé, ou avertissement rouge
si erreur. Seule la zone de sélection (.iv-exact-selpreview) suit le curseur. */
.iv-exact-selinfo {
position: absolute;
top: 3px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
text-align: center;
line-height: 1.25;
font-size: calc(11px * var(--text-scale, 1));
padding: 3px 10px;
border-radius: 6px;
background: var(--bg-elevated, #21242b);
color: var(--text, #e8eaed);
border: 1px solid var(--accent, #3b82f6);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
pointer-events: none;
z-index: 3;
}
.iv-exact-selinfo-main { font-weight: 700; }
.iv-exact-selinfo-sub { font-size: 0.9em; opacity: 0.8; font-weight: 500; }
.iv-exact-selinfo.danger .iv-exact-selinfo-sub { opacity: 0.95; }
/* v2026.5.47 — heure choisie (débutfin) affichée DANS le rectangle de
sélection mobile ; suit le curseur, lisible sur le fond translucide.
v2026.5.47 — placée EN BAS du rectangle (la bulle fixe est en haut) → la
bulle ne cache jamais l'heure quand la souris passe dessous. */
.iv-exact-selpreview-hour {
position: absolute;
bottom: 3px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
font-size: calc(10.5px * var(--text-scale, 1));
font-weight: 700;
color: #ffffff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.75);
pointer-events: none;
}
/* v2026.5.47 — un rendez-vous en ERREUR (début == fin) tombe dans la zone
qu'on créerait : surbrillance ET bulle passent en rouge (le clic droit est
par ailleurs bloqué, cf. disabledReason). */
.iv-exact-selpreview.danger {
border-color: #b03030;
background: rgba(176, 48, 48, 0.30);
background: color-mix(in srgb, #b03030 32%, transparent);
}
.iv-exact-selinfo.danger {
background: #b03030;
color: #ffffff;
border-color: #7a1f1f;
}
/* ── Les cartes posées (héritent de .iv-mini-card : anatomie, conflits
rouges !important, liseré clos/résolu, tooltips, reschedule) ────────── */
html.view-horizontal .iv-exact-card {
@@ -7035,7 +7170,11 @@ html.view-horizontal .iv-exact-card {
height: auto !important;
pointer-events: auto;
flex: none;
min-width: 0;
/* v2026.5.47 — largeur MINIMALE visible : une intervention très courte
(1-5 min) ou à durée nulle (10:00→10:00) a une largeur proportionnelle
quasi nulle → elle disparaissait. Ce plancher la garde visible (très fine),
sans élargir les bulles normales (plus larges que ce plancher). */
min-width: 6px;
z-index: 1;
container-type: inline-size;
border-radius: 4px;
@@ -7155,10 +7294,12 @@ html.view-horizontal .iv-exact-card .iv-mini-card-text-mini .iv-mini-mini-ville
html.view-horizontal.horizontal-exact .exact-hours-ruler {
display: block;
position: sticky;
/* v2026.5.46 — top:0 : en vue horizontale la barre du haut est
masquée, donc --stack-top (calé sur elle) envoyait la règle hors écran.
La sidebar utilise déjà top:0 dans la même structure et tient. */
top: 0;
/* v2026.5.47 — top suit --stack-top : la règle des heures reste collée en
haut au scroll, ET passe SOUS la banderole de session/reconnexion quand il y
en a une (au lieu de disparaître derrière), 0 sinon. (--stack-top = topbar
masquée en horizontal + hauteur des banderoles visibles ; corrigé depuis
des ajustements précédents, il ne sort plus la règle de l'écran.) */
top: var(--stack-top, 0px);
z-index: 20;
background: var(--bg);
/* v2026.5.46 — l'alignement n'est PLUS un calcul théorique (faux au
+609 -153
View File
File diff suppressed because it is too large Load Diff