fix(ops/dispatch): top bar polish — visible ⋯ menu, collapsed AI, fly-to tech, views dropdown
Four fixes around the dispatch header following dispatcher feedback:
1. **⋯ overflow menu was invisible**: .sb-header had `overflow:hidden`,
which clipped the absolutely-positioned dropdown right at the
header's bottom edge. Switched the header to `overflow:visible`
(children all have flex-shrink:0 + a flex:1 center, so the layout
doesn't actually overflow horizontally). Bumped z-index to 5000
for safety on top of map/calendar layers.
2. **NLP/Assistant IA bar hidden by default**: was eagerly rendering
on every page load, with the long French placeholder polluting
the header below the toolbar. The user just wanted the icon. Now
`nlpVisible` defaults to false, persisted in localStorage so power
users who flip it on keep it open across sessions. Toggle still
lives in the ⋯ menu.
3. **Click a tech in the resource list now flies the map to them**:
selectTechOnBoard previously only opened the map panel. Now it
also `map.flyTo({ center })` using `tech.gpsCoords ?? tech.coords`
— live Traccar position wins when the tech is online; falls back
to the saved home base. Animated, deferred a tick so map.resize()
happens first, otherwise flyTo can land on garbage coords during
the panel's open transition.
4. **Board view tabs collapsed into a "Vue principale ▾" dropdown**:
was [Vue principale][Par région][+] inline. Now a single button
showing the active view; click reveals the others + the future
"+ Nouvelle vue" entry. Same dropdown component as the ⋯ menu
(shared CSS, click-outside + ESC close).
This commit is contained in:
parent
96a84c3e48
commit
16343b61e1
|
|
@ -411,6 +411,19 @@ export function useMap (deps) {
|
|||
localStorage.setItem('sbv2-mapW', String(mapPanelW.value))
|
||||
mapVisible.value = true
|
||||
}
|
||||
// Live GPS wins over the ERPNext-saved home base, so the rep
|
||||
// sees where the tech actually IS right now if Traccar reports
|
||||
// them online. Fall back to home coords when offline. We
|
||||
// flyTo (animated pan+zoom) so the dispatcher gets a clear
|
||||
// visual cue, instead of a hard jump.
|
||||
const pos = tech.gpsCoords || tech.coords
|
||||
if (pos && map && Number.isFinite(pos[0]) && Number.isFinite(pos[1])) {
|
||||
// Defer one tick so the map panel has time to be visible
|
||||
// and `map.resize()` has run before the camera animation.
|
||||
nextTick(() => {
|
||||
try { map.flyTo({ center: pos, zoom: Math.max(map.getZoom(), 12), speed: 1.2, essential: true }) } catch (_e) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (map) { drawMapMarkers(); drawSelectedRoute() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -372,7 +372,12 @@ const bookingOverlay = ref(null)
|
|||
const woModalOpen = ref(false)
|
||||
const woModalCtx = ref({})
|
||||
const publishModalOpen = ref(false)
|
||||
const nlpVisible = ref(true) // NLP bar always visible
|
||||
// NLP bar is hidden by default; toggled from the ⋯ menu (Assistant IA).
|
||||
// Showing it eagerly bloats the header on narrow laptops and the
|
||||
// example placeholder text added visual noise. Persist the user's
|
||||
// preference in localStorage so power users keep it open if they want.
|
||||
const nlpVisible = ref(localStorage.getItem('sbv2-nlp-visible') === '1')
|
||||
watch(nlpVisible, v => localStorage.setItem('sbv2-nlp-visible', v ? '1' : '0'))
|
||||
const draftCount = computed(() => store.jobs.filter(j => !j.published && j.status !== 'completed' && j.status !== 'cancelled').length)
|
||||
|
||||
function onNlpAction (result) {
|
||||
|
|
@ -397,6 +402,7 @@ const periodEndStr = computed(() => {
|
|||
})
|
||||
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 gpsSettingsOpen = ref(false)
|
||||
const gpsShowInactive = ref(false)
|
||||
const gpsFilteredTechs = computed(() =>
|
||||
|
|
@ -961,6 +967,7 @@ function onKeyDown (e) {
|
|||
filterPanelOpen.value = false; projectsPanelOpen.value = false
|
||||
selectedJob.value = null; multiSelect.value = []
|
||||
moreMenuOpen.value = false
|
||||
viewsMenuOpen.value = false
|
||||
if (geoFixTech.value) cancelTechGeoFix()
|
||||
}
|
||||
if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
||||
|
|
@ -1174,7 +1181,7 @@ onMounted(async () => {
|
|||
loadOffers()
|
||||
loadPresets()
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null; moreMenuOpen.value = false })
|
||||
document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null; moreMenuOpen.value = false; viewsMenuOpen.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)
|
||||
|
|
@ -1225,9 +1232,23 @@ onUnmounted(() => {
|
|||
<button :class="{ active: filterResourceType==='human' }" @click="filterResourceType='human'">👤 <span class="sbf-count">{{ humanCount }}</span></button>
|
||||
<button :class="{ active: filterResourceType==='material' }" @click="filterResourceType='material'">🔧 <span class="sbf-count">{{ materialCount }}</span></button>
|
||||
</div>
|
||||
<div class="sb-tabs">
|
||||
<button v-for="tab in boardTabs" :key="tab" class="sb-tab" :class="{ active: activeTab===tab }" @click="activeTab=tab">{{ tab }}</button>
|
||||
<button class="sb-tab sb-tab-add" title="Nouvelle vue">+</button>
|
||||
<!-- Board view selector — a single dropdown instead of all tabs
|
||||
inline. Saves header width on narrow laptops; the chevron
|
||||
hints there are more views available. -->
|
||||
<div class="sb-menu-wrap">
|
||||
<button class="sb-icon-btn sb-tab-current" :class="{ active: viewsMenuOpen }"
|
||||
@click.stop="viewsMenuOpen = !viewsMenuOpen" title="Changer de vue">
|
||||
{{ activeTab }} <span class="sb-tab-chev">▾</span>
|
||||
</button>
|
||||
<div v-if="viewsMenuOpen" class="sb-menu-dropdown sb-menu-dropdown-left" @click.stop>
|
||||
<button v-for="tab in boardTabs" :key="tab" class="sb-menu-item"
|
||||
:class="{ active: activeTab===tab }"
|
||||
@click="activeTab=tab; viewsMenuOpen=false">
|
||||
{{ tab }}
|
||||
</button>
|
||||
<div class="sb-menu-sep"></div>
|
||||
<button class="sb-menu-item" disabled title="Bientôt"><span class="sb-menu-icon">+</span> Nouvelle vue</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sb-icon-btn" :class="{ active: filterPanelOpen }" @click="filterPanelOpen=!filterPanelOpen" title="Filtres & Ressources">
|
||||
<span v-html="ICON.wrench"></span>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@
|
|||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.sb-header { display:flex; align-items:center; gap:0.5rem; padding:0 0.75rem; background:var(--sb-sidebar); border-bottom:1px solid var(--sb-border); box-shadow:0 1px 4px rgba(0,0,0,0.06); z-index:30; overflow:hidden; }
|
||||
/* `overflow:visible` (was hidden) so dropdowns rooted in the header
|
||||
can render below it. Children all have flex-shrink:0 with a flex:1
|
||||
center, so the layout doesn't visually overflow horizontally. */
|
||||
.sb-header { display:flex; align-items:center; gap:0.5rem; padding:0 0.75rem; background:var(--sb-sidebar); border-bottom:1px solid var(--sb-border); box-shadow:0 1px 4px rgba(0,0,0,0.06); z-index:30; overflow:visible; }
|
||||
.sb-header-left { display:flex; align-items:center; gap:0.4rem; flex-shrink:0; }
|
||||
.sb-header-center { display:flex; align-items:center; gap:0.35rem; flex:1; justify-content:center; }
|
||||
.sb-header-right { display:flex; align-items:center; gap:0.35rem; flex-shrink:0; margin-left:auto; }
|
||||
|
|
@ -86,15 +89,23 @@
|
|||
.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. */
|
||||
/* ⋯ overflow menu in the top toolbar — secondary/admin actions live here.
|
||||
Uses position:absolute now that .sb-header is overflow:visible.
|
||||
Right-side variant (default) anchors to the right of the wrapper;
|
||||
left-side variant (sb-menu-dropdown-left) anchors to the left edge. */
|
||||
.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;
|
||||
position:absolute; top:calc(100% + 6px); right:0; z-index:5000;
|
||||
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;
|
||||
min-width:230px; padding:4px 0; animation:sb-menu-fade 0.12s ease-out;
|
||||
}
|
||||
/* Left-anchored variant for the views dropdown ("Vue principale ▾") */
|
||||
.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; }
|
||||
@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%;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user