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))
|
localStorage.setItem('sbv2-mapW', String(mapPanelW.value))
|
||||||
mapVisible.value = true
|
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() }
|
if (map) { drawMapMarkers(); drawSelectedRoute() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -372,7 +372,12 @@ const bookingOverlay = ref(null)
|
||||||
const woModalOpen = ref(false)
|
const woModalOpen = ref(false)
|
||||||
const woModalCtx = ref({})
|
const woModalCtx = ref({})
|
||||||
const publishModalOpen = ref(false)
|
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)
|
const draftCount = computed(() => store.jobs.filter(j => !j.published && j.status !== 'completed' && j.status !== 'cancelled').length)
|
||||||
|
|
||||||
function onNlpAction (result) {
|
function onNlpAction (result) {
|
||||||
|
|
@ -397,6 +402,7 @@ const periodEndStr = computed(() => {
|
||||||
})
|
})
|
||||||
const onPublished = jobNames => store.publishJobsLocal(jobNames)
|
const onPublished = jobNames => store.publishJobsLocal(jobNames)
|
||||||
const moreMenuOpen = ref(false) // ⋯ dropdown in the top toolbar (right side)
|
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 gpsSettingsOpen = ref(false)
|
||||||
const gpsShowInactive = ref(false)
|
const gpsShowInactive = ref(false)
|
||||||
const gpsFilteredTechs = computed(() =>
|
const gpsFilteredTechs = computed(() =>
|
||||||
|
|
@ -961,6 +967,7 @@ function onKeyDown (e) {
|
||||||
filterPanelOpen.value = false; projectsPanelOpen.value = false
|
filterPanelOpen.value = false; projectsPanelOpen.value = false
|
||||||
selectedJob.value = null; multiSelect.value = []
|
selectedJob.value = null; multiSelect.value = []
|
||||||
moreMenuOpen.value = false
|
moreMenuOpen.value = false
|
||||||
|
viewsMenuOpen.value = false
|
||||||
if (geoFixTech.value) cancelTechGeoFix()
|
if (geoFixTech.value) cancelTechGeoFix()
|
||||||
}
|
}
|
||||||
if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
||||||
|
|
@ -1174,7 +1181,7 @@ onMounted(async () => {
|
||||||
loadOffers()
|
loadOffers()
|
||||||
loadPresets()
|
loadPresets()
|
||||||
document.addEventListener('keydown', onKeyDown)
|
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')) {
|
if (!document.getElementById('mapbox-css')) {
|
||||||
const l = document.createElement('link'); l.id='mapbox-css'; l.rel='stylesheet'
|
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)
|
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==='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>
|
<button :class="{ active: filterResourceType==='material' }" @click="filterResourceType='material'">🔧 <span class="sbf-count">{{ materialCount }}</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-tabs">
|
<!-- Board view selector — a single dropdown instead of all tabs
|
||||||
<button v-for="tab in boardTabs" :key="tab" class="sb-tab" :class="{ active: activeTab===tab }" @click="activeTab=tab">{{ tab }}</button>
|
inline. Saves header width on narrow laptops; the chevron
|
||||||
<button class="sb-tab sb-tab-add" title="Nouvelle vue">+</button>
|
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>
|
</div>
|
||||||
<button class="sb-icon-btn" :class="{ active: filterPanelOpen }" @click="filterPanelOpen=!filterPanelOpen" title="Filtres & Ressources">
|
<button class="sb-icon-btn" :class="{ active: filterPanelOpen }" @click="filterPanelOpen=!filterPanelOpen" title="Filtres & Ressources">
|
||||||
<span v-html="ICON.wrench"></span>
|
<span v-html="ICON.wrench"></span>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Header ── */
|
/* ── 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-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-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; }
|
.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 { width:7px; height:7px; border-radius:50%; background:var(--sb-red); transition:background 0.3s; }
|
||||||
.sb-erp-dot.ok { background:var(--sb-green); }
|
.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-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-btn { font-size:0.95rem; line-height:0.85rem; padding:0.05rem 0.5rem 0.18rem; }
|
||||||
.sb-menu-dropdown {
|
.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);
|
background:var(--sb-card); border:1px solid var(--sb-border);
|
||||||
border-radius:6px; box-shadow:0 6px 22px rgba(0,0,0,0.35);
|
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); } }
|
@keyframes sb-menu-fade { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }
|
||||||
.sb-menu-item {
|
.sb-menu-item {
|
||||||
display:flex; align-items:center; gap:10px; width:100%;
|
display:flex; align-items:center; gap:10px; width:100%;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user