From 66b358d568a4d78c19bb0b108d1e792262872753 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Tue, 5 May 2026 14:31:00 -0400 Subject: [PATCH] refactor(ops/dispatch): single-color Lucide icons + tech-first resource filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues conflated in the same PR because they touch the same pixels: 1. **Resource filter no longer treats techs and materials as equals.** Was a 3-button inline toggle [Tous][👤 45][🔧 6] with all three visually similar — and the wrench glyph clashed with the wrench used for the filter-settings button. Now: • Default = 'human' (techs only). Materials are secondary resources; they don't deserve front-of-bar real estate. • Single chip [👥 45 ▾] in the toolbar. Click → dropdown: · Techs (45) ← active by default · Matériel (6) (only shown if materialCount > 0) · Tous (51) (only shown if materialCount > 0) • Defaults to localStorage 'sbv2-filterResType' if previously persisted, otherwise 'human' instead of '' (was ''). 2. **Mixed-style icons (emoji + Lucide SVG) replaced with consistent single-color Lucide-style strokes.** Each is a stroke-only inline with stroke="currentColor", so they inherit the surrounding text color (no green/red/yellow tinting). Added to the existing ICON set in useHelpers.js: user, users, package, sliders, chevDown, map, clipboard, sparkles, signal, rotateCw, alertTri, moreH, pause, play, externalLink, target, calendar Replaced in the dispatch top toolbar: ⚠️ → ICON.alertTri (overload alert) 📋 → ICON.clipboard (unscheduled jobs) 🗺 → ICON.map (map toggle) 🗓 → ICON.calendar (planning toggle) 👥 → ICON.users (team-jobs button + Ressources menu) 🔧 → ICON.sliders (filter-settings — was wrench, which collided with the materials filter) 👤/🔧 → ICON.users / .package (resource type dropdown) ↻ → ICON.rotateCw (refresh in ⋯ menu) ✨ → ICON.sparkles (AI in ⋯ menu) 📡 → ICON.signal (offers in ⋯ menu) ↗ → ICON.externalLink (ERPNext link in ⋯ menu) ⋯ → ICON.moreH (the ⋯ button itself) .sb-icon-svg gives them consistent sizing (14px in buttons, 15px in dropdown items, 16px in the ⋯ trigger) so they're crisp at all the spots they appear. Emojis still in place elsewhere (job-tile chips, status badges, etc.) will be migrated incrementally — out of scope for this pass which only targeted the user's visible header. --- apps/ops/src/composables/useHelpers.js | 20 ++++ apps/ops/src/composables/useResourceFilter.js | 5 +- apps/ops/src/pages/DispatchPage.vue | 92 +++++++++++++++---- apps/ops/src/pages/dispatch-styles.scss | 11 ++- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/apps/ops/src/composables/useHelpers.js b/apps/ops/src/composables/useHelpers.js index 0f27225..e135a26 100644 --- a/apps/ops/src/composables/useHelpers.js +++ b/apps/ops/src/composables/useHelpers.js @@ -212,6 +212,26 @@ export const ICON = { clock: _s(''), loader: _s(''), truck: _s(''), + // ── Single-color toolbar set (used in dispatch top bar). All inherit + // currentColor; CSS sizes them via .sb-icon-svg below. Strokes only, + // 2px stroke for crisp rendering at 14-16px display size. + user: _s(''), + users: _s(''), + package: _s(''), + sliders: _s(''), + chevDown: _s(''), + map: _s(''), + clipboard:_s(''), + sparkles: _s(''), + signal: _s(''), + rotateCw: _s(''), + alertTri: _s(''), + moreH: _s(''), + pause: _s(''), + play: _s(''), + externalLink: _s(''), + target: _s(''), + calendar: _s(''), } // Job type icon based on service/subject diff --git a/apps/ops/src/composables/useResourceFilter.js b/apps/ops/src/composables/useResourceFilter.js index 637f114..817e030 100644 --- a/apps/ops/src/composables/useResourceFilter.js +++ b/apps/ops/src/composables/useResourceFilter.js @@ -5,7 +5,10 @@ export function useResourceFilter (store, opts = {}) { const filterStatus = ref(localStorage.getItem('sbv2-filterStatus') || '') const filterGroup = ref(localStorage.getItem('sbv2-filterGroup') || '') const filterTags = ref(JSON.parse(localStorage.getItem('sbv2-filterTags') || '[]')) - const filterResourceType = ref(localStorage.getItem('sbv2-filterResType') || '') // '' | 'human' | 'material' + // Default 'human' = show only techs. Materials are secondary and + // accessed via the resource-type dropdown when needed. Once the user + // explicitly picks something else (incl. ''), it's persisted. + const filterResourceType = ref(localStorage.getItem('sbv2-filterResType') ?? 'human') // '' | 'human' | 'material' const searchQuery = ref('') const techSort = ref(localStorage.getItem('sbv2-techSort') || 'default') const manualOrder = ref(JSON.parse(localStorage.getItem('sbv2-techOrder') || '[]')) diff --git a/apps/ops/src/pages/DispatchPage.vue b/apps/ops/src/pages/DispatchPage.vue index eb5d709..034a7c8 100644 --- a/apps/ops/src/pages/DispatchPage.vue +++ b/apps/ops/src/pages/DispatchPage.vue @@ -401,8 +401,24 @@ const periodEndStr = computed(() => { return localDateStr(d) }) const onPublished = jobNames => store.publishJobsLocal(jobNames) -const moreMenuOpen = ref(false) // ⋯ dropdown in the top toolbar (right side) -const viewsMenuOpen = ref(false) // "Vue principale ▾" dropdown (left side) +const moreMenuOpen = ref(false) // ⋯ dropdown in the top toolbar (right side) +const viewsMenuOpen = ref(false) // "Vue principale ▾" dropdown (left side) +const resTypeMenuOpen = ref(false) // Resource type chip dropdown ([👤 45 ▾]) +const resTypeIcon = computed(() => + filterResourceType.value === 'material' ? ICON.package + : filterResourceType.value === 'human' ? ICON.users + : ICON.users // 'Tous' falls back to the people icon +) +const resTypeLabel = computed(() => + filterResourceType.value === 'material' ? 'Matériel' + : filterResourceType.value === 'human' ? 'Techs' + : 'Toutes les ressources' +) +const resTypeCount = computed(() => + filterResourceType.value === 'material' ? materialCount.value + : filterResourceType.value === 'human' ? humanCount.value + : (humanCount.value + materialCount.value) +) const gpsSettingsOpen = ref(false) const gpsShowInactive = ref(false) const gpsFilteredTechs = computed(() => @@ -968,6 +984,7 @@ function onKeyDown (e) { selectedJob.value = null; multiSelect.value = [] moreMenuOpen.value = false viewsMenuOpen.value = false + resTypeMenuOpen.value = false if (geoFixTech.value) cancelTechGeoFix() } if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) { @@ -1181,7 +1198,7 @@ onMounted(async () => { loadOffers() loadPresets() document.addEventListener('keydown', onKeyDown) - document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null; moreMenuOpen.value = false; viewsMenuOpen.value = false }) + document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null; moreMenuOpen.value = false; viewsMenuOpen.value = false; resTypeMenuOpen.value = false }) if (!document.getElementById('mapbox-css')) { const l = document.createElement('link'); l.id='mapbox-css'; l.rel='stylesheet' l.href='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css'; document.head.appendChild(l) @@ -1227,10 +1244,33 @@ onUnmounted(() => { 👥{{ p.name }} -
- - - + +
+ +
+ + +
+ +
+
@@ -1267,7 +1311,8 @@ onUnmounted(() => {
@@ -1276,12 +1321,17 @@ onUnmounted(() => { (badges, surcharge) ou qui sont des CTA principaux (Publier, + WO). Le reste descend dans le menu ⋯ pour libérer la largeur. --> - ⚠️ {{ overloadedTechs.length }} surchargé{{ overloadedTechs.length > 1 ? 's' : '' }} + + {{ overloadedTechs.length }} surchargé{{ overloadedTechs.length > 1 ? 's' : '' }} + - @@ -1292,25 +1342,27 @@ onUnmounted(() => { ressources/GPS, lien ERPNext. Tout ce qui n'est pas dans le hot path du dispatcher au quotidien. -->
- +
- Ouvrir ERPNext + Ouvrir ERPNext
diff --git a/apps/ops/src/pages/dispatch-styles.scss b/apps/ops/src/pages/dispatch-styles.scss index bc77b9b..da8891e 100644 --- a/apps/ops/src/pages/dispatch-styles.scss +++ b/apps/ops/src/pages/dispatch-styles.scss @@ -105,7 +105,16 @@ .sb-menu-dropdown-left { right:auto; left:0; min-width:180px; } /* Current-view button: shows the active tab name + a chevron. */ .sb-tab-current { display:inline-flex; align-items:center; gap:6px; } -.sb-tab-chev { font-size:0.6rem; opacity:0.65; } +.sb-tab-chev { font-size:0.6rem; opacity:0.65; display:inline-flex; align-items:center; } +.sb-tab-chev svg { width:10px; height:10px; } +/* Single-color Lucide-style icon container — sizes the inline SVG and + keeps it baseline-aligned with neighboring text. Uses currentColor + on stroke so the icon inherits whatever color the parent applies. */ +.sb-icon-svg { display:inline-flex; align-items:center; line-height:0; } +.sb-icon-svg svg { width:14px; height:14px; flex-shrink:0; } +.sb-menu-item .sb-icon-svg svg { width:15px; height:15px; opacity:0.85; } +.sb-icon-btn .sb-icon-svg svg { width:13px; height:13px; } +.sb-menu-btn .sb-icon-svg svg { width:16px; height:16px; } @keyframes sb-menu-fade { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } } .sb-menu-item { display:flex; align-items:center; gap:10px; width:100%;