@@ -1815,6 +1805,7 @@ import JobReplyBox from 'src/components/shared/detail-sections/modules/JobReplyB
import LeaveDialog from 'src/components/planif/LeaveDialog.vue' // Congés & disponibilités (extrait — décomposition #4)
import TechSyncDialog from 'src/components/planif/TechSyncDialog.vue' // Synchroniser les techniciens (extrait — décomposition #4)
import ShiftTypesDialog from 'src/components/planif/ShiftTypesDialog.vue' // Types de shift (extrait — décomposition #4)
+import WeekTemplatesDialog from 'src/components/planif/WeekTemplatesDialog.vue' // Modèles de semaine (ex-sous-menu Outils → dialogue, règle « pas de menu-dans-menu »)
import TicketStatusControl from 'src/components/shared/TicketStatusControl.vue'
import HelpHint from 'src/components/shared/HelpHint.vue'
import { skillIcon, skillSym, markerIcon } from 'src/composables/useSkillIcons' // SOURCE UNIQUE des icônes de compétences (réutilisée fiche client)
@@ -1822,6 +1813,7 @@ import TagEditor from 'src/components/shared/TagEditor.vue' // module de tags/co
import SkillCadenceTable from 'src/components/planif/SkillCadenceTable.vue' // éditeur compétences+cadence PARTAGÉ (volet tech + outil Équipe)
import RouteMap from 'src/components/shared/RouteMap.vue' // carte de tournées réutilisable (revue dispatch auto + onglet Tournées) : OSRM réel, arrêts numérotés, fitTo
import OccupancyStrip from 'src/components/shared/OccupancyStrip.vue' // bande d'occupation réutilisable (sélecteur de jour Tournées + tableau de bord)
+import UserAvatar from 'src/components/shared/UserAvatar.vue' // avatar CANONIQUE identité-aware (photo + nom résolu + carte + renommage) — remplace les monogrammes bruts du board
import OverflowMenu from 'src/components/shared/OverflowMenu.vue' // « ⋮ » standardisé pour les actions secondaires (désencombrer la barre)
import JobPool from 'src/components/planif/JobPool.vue' // pool « Jobs à assigner » UNIFIÉ (flottant · kanban · mobile) — remplace les 3 réimplémentations
import { useJobPool } from 'src/composables/useJobPool' // cerveau filtre/tri/groupe/badges partagé par les 3 surfaces
@@ -1946,13 +1938,14 @@ const manualGarde = ref({}) // overrides manuels de garde : 'techId|iso' → 'on
const newGardeRule = reactive({ dept: '', shift: '', shiftWeekend: '', weekdays: [], anchor: '', steps: [] })
const GARDE_DOW = [{ v: 1, l: 'L' }, { v: 2, l: 'M' }, { v: 3, l: 'M' }, { v: 4, l: 'J' }, { v: 5, l: 'V' }, { v: 6, l: 'S' }, { v: 0, l: 'D' }]
const history = ref([]); const future = ref([])
-const search = ref(''); const groupFilter = ref(null); const maxHours = ref(40); const skillFilter = ref([])
+const search = ref(''); const groupFilter = ref(null); const skillFilter = ref([]) // (maxHours retiré — champ orphelin jamais consommé)
// Ressources masquées (hors front-line : compta, etc.) — masquées ET ignorées du calcul ; localStorage.
const hiddenTechs = ref([]); const showHidden = ref(false)
function isHidden (id) { return hiddenTechs.value.includes(id) }
function toggleHidden (id) { const i = hiddenTechs.value.indexOf(id); if (i >= 0) hiddenTechs.value.splice(i, 1); else hiddenTechs.value.push(id); localStorage.setItem('roster-hidden-techs-v1', JSON.stringify(hiddenTechs.value)) }
const hiddenCount = computed(() => techs.value.filter(t => isHidden(t.id)).length)
const showShiftEditor = ref(false) // v-model de (editTpls/newTpl/fonctions déplacés dans le composant)
+const showWeekTemplates = ref(false) // v-model de — l'état weekTemplates + mutations restent ICI (localStorage)
const showTeamEditor = ref(false); const editTechs = ref([]); const teamCost = reactive({})
const notifySms = ref(false)
// ── Notifier les techs de leur tournée (SMS/Email + lien token) : aperçu d'abord (compose sans envoi), puis envoi. ──
@@ -2745,7 +2738,10 @@ function skillEffColor (t, sk) { const e = t.skill_eff && t.skill_eff[sk]; retur
// Icône Material (offline, jeu Quasar par défaut) représentant une compétence/type — par mots-clés, repli 'bolt'.
// Icône de compétence pour q-icon (accepte les SYMBOLES SVG importés) : échelle=installation, casque=support,
// outils=réparation ; sinon ligature classique unique (skillIcon). NB: les pins carte (HTML brut) gardent skillIcon.
-function tSkills (t) { return [...(t.skills || [])].sort((a, b) => a.localeCompare(b)) } // compétences triées du tech (pour l'affichage 1 + « +N »)
+// Compétences du tech pour l'affichage (icône principale + « +N ») : on RESPECTE l'ordre de priorité
+// défini par glisser-déposer dans l'éditeur de compétences (t.skills) — la 1re = fonction principale.
+// (ex. une personne au support voit son casque en 1er ; réparation ≠ support, chacun garde son rang.)
+function tSkills (t) { return [...(t.skills || [])] }
// Icône d'un bloc de tournée (vue JOUR et SEMAINE) : renfort → group ; sinon icône de COMPÉTENCE
// (skill/required_skill, ou dept pour les tickets legacy → ex. « Install/Reparation Télé » = TV).
// Le ticket générique n'est utilisé qu'en dernier recours (legacy sans skill ni dept).
@@ -6022,6 +6018,8 @@ registerActions('planif', [
{ id: 'planif-create', label: 'Créer une tâche / intervention', icon: 'add', group: 'Planification', keywords: 'creer job tache intervention soumission', run: () => { createChooser.value = true } },
{ id: 'planif-assign', label: 'Jobs à assigner', icon: 'assignment_ind', group: 'Planification', keywords: 'assigner pool repartir', run: openAssignPanel },
{ id: 'planif-leave', label: 'Congés / absences', icon: 'beach_access', group: 'Planification', keywords: 'conges absences vacances', run: () => { showLeave.value = true } },
+ { id: 'planif-templates', label: 'Modèles de semaine', icon: 'bookmark', group: 'Planification', keywords: 'modeles semaine patron template horaire', run: () => { showWeekTemplates.value = true } },
+ { id: 'planif-apply-default', label: 'Appliquer le modèle par défaut (★)', icon: 'star', group: 'Planification', keywords: 'modele defaut appliquer patron star', run: applyDefault },
])
onUnmounted(() => unregisterActions('planif'))
onBeforeRouteLeave(async () => { await autosaveDraft() }) // #2 — flush le brouillon (attendu) en partant ; plus jamais d'« abandonner ? »
@@ -6471,6 +6469,7 @@ tfoot .sum .tech-col { background: #fafafa; }
.pm-more { width: 100%; border: none; background: none; color: #6366f1; font-size: 12px; font-weight: 600; padding: 9px; cursor: pointer; }
.pm-tech-idle { opacity: .55; }
.pm-ava { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; background: #e0e7ff; color: #4338ca; font-size: 10px; font-weight: 800; display: flex; align-items: center; justify-content: center; }
+.pm-ava-uav { flex: 0 0 auto; } /* avatar canonique dans la liste techs mobile (garde l'espacement via le gap du parent) */
.pm-tech.assignable { background: #f5f3ff; outline: 1px dashed #c4b5fd; }
.pm-tech.assignable:active { background: #ede9fe; }
.pm-assign-banner { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; background: #6366f1; color: #fff; font-size: 12px; font-weight: 600; padding: 8px 10px; border-radius: 8px; margin-bottom: 8px; }
diff --git a/docs/UX_REFACTORING_PROMPT.md b/docs/UX_REFACTORING_PROMPT.md
index 6469d13..a0ea5b5 100644
--- a/docs/UX_REFACTORING_PROMPT.md
+++ b/docs/UX_REFACTORING_PROMPT.md
@@ -78,12 +78,12 @@ File layout: template L1–1768 · script L1770–6033 (~4,264 lines — the rea
| ID | Anti-pattern | Anchor | Status | Problem |
|---|---|---|---|---|
-| PL1 | WALL_OF_OPTIONS | desktop header rows (comment « Rangée 1 : actions principales ») ~L32-154 | open | 20+ interactive controls in one toolbar |
-| PL2 | WALL_OF_OPTIONS | `label="Outils"` dropdown ~L71-125 | open | 13 items in 3 sections + nested submenu |
+| PL1 | WALL_OF_OPTIONS | desktop header rows (comment « Rangée 1 : actions principales ») ~L32-154 | **largely fixed 2026-07-14** | Was 20+ controls. Now: view switch · nav · date · journal + 5 primary actions (À assigner, Créer, Outils, Suggérer, Publier) + grouped ⋮ (★ défaut relocated there + ⌘K). HelpHint removed (explainer lives in the Suggérer dialog banner) |
+| PL2 | WALL_OF_OPTIONS | `label="Outils"` dropdown ~L71-125 | **fixed 2026-07-14** | Was 13 items + nested submenu. Now 11 flat items; the 2 entries duplicating always-visible controls (À assigner, vue Mois) removed |
| PL3 | WALL_OF_OPTIONS | q-dialog `jobSheet.open` ~L281-356 | open | ~15 interactive zones on a phone screen. Reuse `detail-sections/modules/JobThread.vue` + `JobReplyBox.vue` instead of the hand-rolled pm-postbox composer |
-| PL4 | HIDDEN_PRIMARY | `label` contains « Publier », split q-btn-dropdown ~L131-147 | **partially fixed** | Publier is now a unified split q-btn-dropdown (color=positive, dirty-count, outline-when-clean). Remaining: the row still has ~10 sibling controls (see PL1) |
-| PL5 | MENU_IN_MENU | « Modèles de semaine » ~L85-97 | open | Submenu inside a dropdown |
-| PL6 | ORPHAN_FIELD | `label="Max h/sem"` ~L159 | open | Floating numeric input with no context |
+| PL4 | HIDDEN_PRIMARY | `label` contains « Publier », split q-btn-dropdown ~L131-147 | **fixed** | Publier is a unified split q-btn-dropdown (color=positive, dirty-count, outline-when-clean); sibling-control count resolved with PL1 |
+| PL5 | MENU_IN_MENU | « Modèles de semaine » ~L85-97 | **fixed 2026-07-14** | Extracted to `components/planif/WeekTemplatesDialog.vue` (props data / emits save·apply·set-default·delete; state stays in the page). Reachable via Outils, ⋮ and ⌘K |
+| PL6 | ORPHAN_FIELD | `label="Max h/sem"` ~L159 | **fixed 2026-07-14** | Deleted — the input was dead code (ref never consumed by any filter/solver) |
| PL7 | STATUS_MYSTERY | autosave journal ~L48-61 | **largely fixed** | Icon now swaps to cloud_sync + primary color while saving (persistent feedback); popover holds the change journal + per-change revert by design. Verify before changing |
| PL8 | MONOLITH | entire file | open | 6,559 lines = unmaintainable (see decomposition plan) |