diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue
index 4d0c983..73893bb 100644
--- a/apps/ops/src/pages/PlanificationPage.vue
+++ b/apps/ops/src/pages/PlanificationPage.vue
@@ -31,7 +31,8 @@
-
Planification
+
+
Semaine = grille (vue d'ensemble) · Jour = disposition d'une journée · Tournées = trajets réels de la journée, 1 couleur/tech (OSRM)
{{ dirtyCount }} non publié(s)
{{ offShiftWeekCount }} hors quart{{ offShiftWeekCount }} job(s) assigné(s) cette période un jour où la ressource n'a AUCUN quart publié. Repère le ⚠ dans la grille → publier un quart ou réassigner.
@@ -44,8 +45,6 @@
{{ boardView === 'kanban' ? 'Jour affiché (mode Jour)' : 'Début de la fenêtre (mode Semaine)' }}
Étendue de la grille
-
Horaire :
-
Semaine = grille (vue d'ensemble) · Jour = disposition d'une journée · Tournées = trajets réels de la journée, 1 couleur par tech (OSRM)
Annuler (Ctrl+Z)
Rétablir (Ctrl+Shift+Z)
@@ -117,9 +116,20 @@
Appliquer le modèle par défaut (consciente des absences)
Répartition automatique des jobs du jour sélectionné (distance · compétence · priorité · taux d'occupation) — revue avant d'appliquer
-
Notifier les techs par SMS à la publication
-
-
Réassigne les tickets aux techniciens DANS le système legacy (osTicket) selon l'assignation Ops — aperçu avant d'écrire.
+
+
+
+
+
+ Notifier les techs par SMSà la publication
+
+
+
+
+ Publier au legacyréassigne les tickets dans osTicket (aperçu avant)
+
+
+
guard(loadWeek)" />
@@ -5188,8 +5198,9 @@ const anyCovers = (skills) => !((skills || []).filter(Boolean).length) || (visib
const showAllMoveTechs = ref(false)
function moveTechFilter (list, skills) {
if (showAllMoveTechs.value) return list
- const sel = list.filter(t => suggestDlg.techSel[t.id] && covers(t, skills))
- return sel.length ? sel : list
+ const cap = (t) => (t.capable != null) ? !!t.capable : covers(t._t || t, skills) // entrée = projection (.capable) ; groupe = tech complet (.skills)
+ const sel = list.filter(t => suggestDlg.techSel[t.id] && cap(t))
+ return sel.length ? sel : list // jamais vide : repli sur tous si aucun sélectionné-compétent
}
function capableTechsFirst (skills) { return moveTechFilter([...(visibleTechs.value || [])].sort((a, b) => (covers(b, skills) - covers(a, skills)) || String(a.name || '').localeCompare(String(b.name || ''))), skills) }
// Une entrée de la revue → objet job-like pour réutiliser techsForJob (classement proximité + occupation).
@@ -6253,6 +6264,9 @@ tr.res-hidden .hide-eye { opacity: 1; }
.jd-geolinks { padding-left: 22px; gap: 14px !important; }
.jd-geolink { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: #2563eb; text-decoration: none; font-weight: 500; }
.jd-geolink:hover { text-decoration: underline; }
+/* Sélecteur de vue (Semaine/Jour/Tournées) à gauche — mis en évidence */
+.view-switch { border: 1px solid #d7dce3; border-radius: 9px; overflow: hidden; }
+.view-switch :deep(.q-btn) { font-weight: 600; padding: 5px 13px; }
.jd-detail { margin-top: 10px; padding: 8px 10px; background: #f6f8fb; border-radius: 6px; white-space: pre-wrap; font-size: 12.5px; color: #333; }
.jd-team { margin-top: 12px; padding: 10px; border: 1px solid #e6e2f2; background: #faf9fe; border-radius: 8px; }
/* Suivi terrain (géofencing) : étapes horizontales façon suivi de colis. */