refactor(ops/dispatch): consolidate top toolbar with overflow ⋯ menu
The header right-side was getting noisy — 8 buttons + 2 indicators all competing for screen width, with two visually-similar 📡 icons (offer pool vs GPS settings) that confused dispatchers. On narrower laptops the bar wrapped or icons overflowed. New layout: [⚠ overload] [📋 unassigned + count] [🗺 Carte] [Publier + count] [+ WO] [⋯] Everything else dropped into the ⋯ dropdown: • ↻ Actualiser • ✨ Assistant IA • 📡 Offres aux techs (with green count badge) • 👥 Ressources & GPS ← was 📡 in the bar; this is also where the tech-management UI (rename, deactivate, home location, Traccar device link) lives • ↗ Ouvrir ERPNext (with the inline status dot) The ⋯ menu closes on Escape, on click outside, and after picking an item. Same close-handler chain that already serves the job/tech context menus. The kept-up-front buttons all have either a status badge (counts, overload alert) or are the primary CTAs (Publier, + WO) — so the dispatcher's eye stays on workflow signal, not on chrome.
This commit is contained in:
parent
c96092e9e8
commit
96a84c3e48
|
|
@ -396,6 +396,7 @@ const periodEndStr = computed(() => {
|
|||
return localDateStr(d)
|
||||
})
|
||||
const onPublished = jobNames => store.publishJobsLocal(jobNames)
|
||||
const moreMenuOpen = ref(false) // ⋯ dropdown in the top toolbar (right side)
|
||||
const gpsSettingsOpen = ref(false)
|
||||
const gpsShowInactive = ref(false)
|
||||
const gpsFilteredTechs = computed(() =>
|
||||
|
|
@ -959,6 +960,7 @@ function onKeyDown (e) {
|
|||
dispatchCriteriaModal.value = false; bookingOverlay.value = null
|
||||
filterPanelOpen.value = false; projectsPanelOpen.value = false
|
||||
selectedJob.value = null; multiSelect.value = []
|
||||
moreMenuOpen.value = false
|
||||
if (geoFixTech.value) cancelTechGeoFix()
|
||||
}
|
||||
if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
||||
|
|
@ -1172,7 +1174,7 @@ onMounted(async () => {
|
|||
loadOffers()
|
||||
loadPresets()
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null })
|
||||
document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null; moreMenuOpen.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)
|
||||
|
|
@ -1248,27 +1250,50 @@ onUnmounted(() => {
|
|||
</button>
|
||||
</div>
|
||||
<div class="sb-header-right">
|
||||
<!-- Overload alert -->
|
||||
<!-- ─────────────────── PRIMAIRE — workflow quotidien ───────────────────
|
||||
Sont gardés en première ligne ceux qui ont un statut visuel important
|
||||
(badges, surcharge) ou qui sont des CTA principaux (Publier, + WO).
|
||||
Le reste descend dans le menu ⋯ pour libérer la largeur. -->
|
||||
<span v-if="overloadedTechs.length" class="sb-overload-alert" :title="overloadedTechs.map(o => o.tech.fullName + ' ' + o.pct + '%').join(', ')">
|
||||
⚠️ {{ overloadedTechs.length }} surchargé{{ overloadedTechs.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
<button class="sb-icon-btn" :class="{ active: bottomPanelOpen }" @click="bottomPanelOpen=!bottomPanelOpen" title="Jobs non assignées">
|
||||
📋 <span v-if="unscheduledJobs.length" class="sbs-count" style="position:relative;top:-2px;right:auto">{{ unscheduledJobs.length }}</span>
|
||||
</button>
|
||||
<!-- Offer pool -->
|
||||
<button class="sb-icon-btn" :class="{ active: showOfferPool }" @click="showOfferPool=!showOfferPool; if(showOfferPool) loadOffers()" title="Offres de travail">
|
||||
📡 <span v-if="activeOfferCount" class="sbs-count" style="position:relative;top:-2px;right:auto;background:#4ade80;color:#000">{{ activeOfferCount }}</span>
|
||||
</button>
|
||||
<button v-if="currentView==='day'" class="sb-icon-btn" :class="{ active: mapVisible }" @click="mapVisible=!mapVisible" title="Carte">🗺 Carte</button>
|
||||
<button class="sb-icon-btn" @click="refreshData()" title="Actualiser">↻</button>
|
||||
<button class="sb-icon-btn" :class="{ active: nlpVisible }" @click="nlpVisible=!nlpVisible" title="Assistant IA">✨</button>
|
||||
<button class="sb-icon-btn" @click="gpsSettingsOpen=true" title="GPS Tracking">📡</button>
|
||||
<button class="sb-wo-btn" style="background:#7c3aed" @click="publishModalOpen=true" title="Publier & envoyer l'horaire">
|
||||
Publier <span v-if="draftCount" class="sbs-count" style="position:relative;top:-2px;right:auto;background:#ef4444">{{ draftCount }}</span>
|
||||
</button>
|
||||
<button class="sb-wo-btn" @click="openWoModal()" title="Nouveau work order">+ WO</button>
|
||||
<a class="sb-erp-link" :href="erpUrl + '/desk'" target="_blank" title="Ouvrir ERPNext">ERP</a>
|
||||
<div class="sb-erp-dot" :class="{ ok: store.erpStatus==='ok' }" :title="{ ok:'ERPNext ✓', error:'Hors ligne', loading:'Connexion…' }[store.erpStatus]||'ERPNext'"></div>
|
||||
|
||||
<!-- ─────────────────── MENU ⋯ — secondaire / admin ─────────────────────
|
||||
Plus rarement utilisé : refresh, assistant IA, offres aux techs,
|
||||
ressources/GPS, lien ERPNext. Tout ce qui n'est pas dans le hot
|
||||
path du dispatcher au quotidien. -->
|
||||
<div class="sb-menu-wrap">
|
||||
<button class="sb-icon-btn sb-menu-btn" :class="{ active: moreMenuOpen }" @click.stop="moreMenuOpen = !moreMenuOpen" title="Plus d'options">⋯</button>
|
||||
<div v-if="moreMenuOpen" class="sb-menu-dropdown" @click.stop>
|
||||
<button class="sb-menu-item" @click="refreshData(); moreMenuOpen=false">
|
||||
<span class="sb-menu-icon">↻</span> Actualiser
|
||||
</button>
|
||||
<button class="sb-menu-item" :class="{ active: nlpVisible }" @click="nlpVisible=!nlpVisible; moreMenuOpen=false">
|
||||
<span class="sb-menu-icon">✨</span> Assistant IA
|
||||
</button>
|
||||
<button class="sb-menu-item" :class="{ active: showOfferPool }" @click="showOfferPool=!showOfferPool; if(showOfferPool) loadOffers(); moreMenuOpen=false">
|
||||
<span class="sb-menu-icon">📡</span> Offres aux techs
|
||||
<span v-if="activeOfferCount" class="sbs-count" style="margin-left:auto;background:#4ade80;color:#000">{{ activeOfferCount }}</span>
|
||||
</button>
|
||||
<div class="sb-menu-sep"></div>
|
||||
<button class="sb-menu-item" @click="gpsSettingsOpen=true; moreMenuOpen=false">
|
||||
<span class="sb-menu-icon">👥</span> Ressources & GPS
|
||||
</button>
|
||||
<div class="sb-menu-sep"></div>
|
||||
<a class="sb-menu-item" :href="erpUrl + '/desk'" target="_blank" @click="moreMenuOpen=false">
|
||||
<span class="sb-menu-icon">↗</span> Ouvrir ERPNext
|
||||
<span class="sb-erp-dot" :class="{ ok: store.erpStatus==='ok' }" style="margin-left:auto" :title="{ ok:'ERPNext ✓', error:'Hors ligne', loading:'Connexion…' }[store.erpStatus]||'ERPNext'"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,28 @@
|
|||
.sb-logout-btn:hover { opacity:1; color:var(--sb-red); }
|
||||
.sb-erp-dot { width:7px; height:7px; border-radius:50%; background:var(--sb-red); transition:background 0.3s; }
|
||||
.sb-erp-dot.ok { background:var(--sb-green); }
|
||||
|
||||
/* ⋯ overflow menu in the top toolbar — secondary/admin actions live here. */
|
||||
.sb-menu-wrap { position:relative; display:inline-block; }
|
||||
.sb-menu-btn { font-size:0.95rem; line-height:0.85rem; padding:0.05rem 0.5rem 0.18rem; }
|
||||
.sb-menu-dropdown {
|
||||
position:absolute; top:calc(100% + 4px); right:0; z-index:200;
|
||||
background:var(--sb-card); border:1px solid var(--sb-border);
|
||||
border-radius:6px; box-shadow:0 6px 22px rgba(0,0,0,0.35);
|
||||
min-width:220px; padding:4px 0; animation:sb-menu-fade 0.12s ease-out;
|
||||
}
|
||||
@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%;
|
||||
background:none; border:none; color:var(--sb-text);
|
||||
font-size:0.78rem; font-weight:500; padding:0.45rem 0.85rem;
|
||||
cursor:pointer; text-align:left; text-decoration:none;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.sb-menu-item:hover, .sb-menu-item.active { background:var(--sb-sidebar); color:var(--sb-text); }
|
||||
.sb-menu-item.active { color:var(--sb-accent); font-weight:700; }
|
||||
.sb-menu-icon { display:inline-block; width:18px; text-align:center; font-size:0.95rem; }
|
||||
.sb-menu-sep { height:1px; background:var(--sb-border); margin:3px 0; }
|
||||
.sb-wo-btn { background:var(--sb-acc); border:none; border-radius:6px; color:var(--sb-text); font-size:0.7rem; font-weight:800; padding:0.22rem 0.65rem; cursor:pointer; white-space:nowrap; }
|
||||
.sb-wo-btn:hover { filter:brightness(1.15); }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user