<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick, provide } from 'vue'
import { useDispatchStore } from 'src/stores/dispatch'
import { useAuthStore } from 'src/stores/auth'
import { TECH_COLORS, MAPBOX_TOKEN, BASE_URL } from 'src/config/erpnext'
import { fetchOpenRequests } from 'src/api/service-request'
import { updateJob } from 'src/api/dispatch'

// ── Components ──────────────────────────────────────────────────────────────────
import TagEditor from 'src/components/shared/TagEditor.vue'
import TimelineRow from 'src/modules/dispatch/components/TimelineRow.vue'
import BottomPanel from 'src/modules/dispatch/components/BottomPanel.vue'
import JobEditModal from 'src/modules/dispatch/components/JobEditModal.vue'
import WoCreateModal from 'src/modules/dispatch/components/WoCreateModal.vue'
import WeekCalendar from 'src/modules/dispatch/components/WeekCalendar.vue'
import MonthCalendar from 'src/modules/dispatch/components/MonthCalendar.vue'
import RightPanel from 'src/modules/dispatch/components/RightPanel.vue'

// ── Composables ─────────────────────────────────────────────────────────────────
import {
  localDateStr, timeToH, fmtDur,
  SVC_COLORS, prioLabel, prioClass, serializeAssistants,
  jobColor as _jobColorBase, ICON, prioColor,
} from 'src/composables/useHelpers'
import { useScheduler } from 'src/composables/useScheduler'
import { useUndo } from 'src/composables/useUndo'
import { useMap } from 'src/composables/useMap'
import { useBottomPanel } from 'src/composables/useBottomPanel'
import { useDragDrop } from 'src/composables/useDragDrop'
import { useSelection } from 'src/composables/useSelection'
import { useAutoDispatch } from 'src/composables/useAutoDispatch'
import { usePeriodNavigation } from 'src/composables/usePeriodNavigation'
import { useResourceFilter } from 'src/composables/useResourceFilter'
import { useTagManagement } from 'src/composables/useTagManagement'
import { useContextMenus } from 'src/composables/useContextMenus'
import { useTechManagement } from 'src/composables/useTechManagement'

// ─── Store ────────────────────────────────────────────────────────────────────
const store = useDispatchStore()
const auth = useAuthStore()
const erpUrl = BASE_URL || window.location.origin

// ─── Period Navigation ───────────────────────────────────────────────────────
const {
  currentView, anchorDate, periodStart, periodDays, dayColumns, periodLabel, todayStr,
  prevPeriod, nextPeriod, goToToday, goToDay,
} = usePeriodNavigation()

// ─── Resource Filter ─────────────────────────────────────────────────────────
const {
  selectedResIds, filterStatus, filterTags, searchQuery, techSort, manualOrder,
  filteredResources, resSelectorOpen, tempSelectedIds, dragReorderTech,
  openResSelector, applyResSelector, toggleTempRes, clearFilters,
  onTechReorderStart, onTechReorderDrop,
} = useResourceFilter(store)

// ─── Tags ────────────────────────────────────────────────────────────────────
const techTagModal = ref(null)
const {
  getTagColor, onCreateTag, onUpdateTag, onRenameTag, onDeleteTag,
  _serializeTags, persistJobTags, persistTechTags,
} = useTagManagement(store)

// ─── Address autocomplete ────────────────────────────────────────────────────
const addrResults = ref([])
const addrLoading = ref(false)
let addrDebounce = null
function searchAddr (q) {
  clearTimeout(addrDebounce)
  if (!q || q.length < 3) { addrResults.value = []; return }
  addrLoading.value = true
  addrDebounce = setTimeout(async () => {
    try {
      const res = await fetch(`/api/method/search_address?q=${encodeURIComponent(q)}`, { credentials: 'include' })
      const data = await res.json()
      addrResults.value = data.results || data.message?.results || []
    } catch { addrResults.value = [] }
    addrLoading.value = false
  }, 300)
}
function selectAddr (addr, target) {
  if (!target) { addrResults.value = []; return }
  target.address = addr.address_full
  if (addr.latitude) target.latitude = parseFloat(addr.latitude)
  if (addr.longitude) target.longitude = parseFloat(addr.longitude)
  if (addr.ville) target.ville = addr.ville
  if (addr.code_postal) target.code_postal = addr.code_postal
  addrResults.value = []
}

function setEndDate (job, endDate) {
  job.endDate = endDate || null
  updateJob(job.name || job.id, { end_date: endDate || '' }).catch(() => {})
}

// ─── Layout state ─────────────────────────────────────────────────────────────
const filterPanelOpen   = ref(false)
const projectsPanelOpen = ref(false)
const mapVisible  = ref(localStorage.getItem('sbv2-map') === 'true')
const rightPanel  = ref(null)

watch(mapVisible, v => localStorage.setItem('sbv2-map', v ? 'true' : 'false'))

// ─── Edit modal (double-click) ───────────────────────────────────────────────
const editModal = ref(null)
function openEditModal (job) {
  editModal.value = {
    job, subject: job.subject || '', address: job.address || '',
    note: job.note || '', duration: job.duration || 1,
    priority: job.priority || 'low', tags: [...(job.tags || [])],
    latitude: job.latitude || null, longitude: job.longitude || null,
  }
}
function confirmEdit () {
  if (!editModal.value) return
  const { job, subject, address, note, duration, priority, tags, latitude, longitude } = editModal.value
  Object.assign(job, { subject, address, note, duration: parseFloat(duration) || 1, priority, tags: [...tags] })
  if (latitude) job.latitude = latitude
  if (longitude) job.longitude = longitude
  updateJob(job.name || job.id, {
    subject, address, note: note || '', duration_h: job.duration, priority,
    tags: tags.map(t => ({ tag: t })),
    latitude: latitude || null, longitude: longitude || null,
  }).catch(() => {})
  editModal.value = null
  invalidateRoutes()
}

// ─── Job schedule helpers ─────────────────────────────────────────────────────
function getJobDate (jobId) { return store.jobs.find(j => j.id === jobId)?.scheduledDate || null }
function getJobTime (jobId) { return store.jobs.find(j => j.id === jobId)?.startTime || null }
function setJobTime (jobId, time) {
  const job = store.jobs.find(j => j.id === jobId)
  if (!job) return
  store.setJobSchedule(jobId, job.scheduledDate, time || null)
  job.startHour = time ? timeToH(time) : null
}

const timeModal = ref(null)
function openTimeModal (job, techId) {
  const cur = getJobTime(job.id)
  timeModal.value = { job, techId, time: cur || '08:00', hasPin: !!cur }
}
function confirmTime () { if (!timeModal.value) return; setJobTime(timeModal.value.job.id, timeModal.value.time); timeModal.value = null }
function clearTime () { if (!timeModal.value) return; setJobTime(timeModal.value.job.id, null); timeModal.value = null }

// ─── Unscheduled / Pending ────────────────────────────────────────────────────
const pendingReqs    = ref([])
const pendingLoading = ref(false)
async function loadPendingReqs () {
  pendingLoading.value = true
  try { pendingReqs.value = await fetchOpenRequests() } catch { pendingReqs.value = [] }
  pendingLoading.value = false
}
const unscheduledJobs = computed(() => store.jobs.filter(j => !j.assignedTech))
const teamJobs = computed(() => store.jobs.filter(j => j.assistants?.length > 0))

// ─── Job color ────────────────────────────────────────────────────────────────
function jobColor (job) { return _jobColorBase(job, TECH_COLORS, store) }

// ─── Timeline geometry ────────────────────────────────────────────────────────
const PX_PER_HR = ref(80)
const ROW_H     = 68
const pxPerHr = computed(() => {
  if (currentView.value === 'week')  return PX_PER_HR.value * 0.55
  if (currentView.value === 'month') return 0
  return PX_PER_HR.value
})
const dayW = computed(() => currentView.value === 'month' ? 110 : (H_END - H_START) * pxPerHr.value)
const totalW = computed(() => dayW.value * periodDays.value)

// ─── Scheduler composable ────────────────────────────────────────────────────
const {
  H_START, H_END, routeLegs, routeGeometry,
  techAllJobsForDate, techDayJobsWithTravel,
  periodLoadH,
} = useScheduler(store, currentView, periodStart, periodDays, dayColumns, pxPerHr, getJobDate, getJobTime, jobColor)

const hourTicks = computed(() => {
  if (currentView.value === 'month') return []
  const ticks = []
  dayColumns.value.forEach((day, di) => {
    for (let h = H_START; h <= H_END; h++) {
      const x = di * dayW.value + (h - H_START) * pxPerHr.value
      ticks.push({ x, label: h===H_START||h%2===0 ? h+':00' : null, isMajor: true, isDay: h===H_START, day, h })
    }
  })
  return ticks
})

const isCalView = computed(() => currentView.value === 'week')
const unassignDropActive = ref(false)

// ─── Undo composable ─────────────────────────────────────────────────────────
const { pushUndo, performUndo } = useUndo(store, invalidateRoutes)

// ─── Smart assign & full unassign (delegated to store) ───────────────────────
function smartAssign (job, newTechId, dateStr) { store.smartAssign(job.id, newTechId, dateStr) }
function fullUnassign (job) {
  pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...job.assistants] })
  store.fullUnassign(job.id)
  if (selectedJob.value?.job?.id === job.id) selectedJob.value = null
  invalidateRoutes()
}

// ─── Context menus ────────────────────────────────────────────────────────────
const {
  ctxMenu, techCtx, assistCtx, assistNoteModal,
  openCtxMenu, closeCtxMenu, openTechCtx, openAssistCtx,
  assistCtxTogglePin, assistCtxRemove, assistCtxNote, confirmAssistNote,
  ctxDetails, ctxMove, ctxUnschedule,
} = useContextMenus({ store, rightPanel, invalidateRoutes, fullUnassign, openMoveModal, openEditModal })

// ─── Bottom panel composable ──────────────────────────────────────────────────
const {
  bottomPanelOpen, bottomPanelH, unassignedGrouped, startBottomResize,
  bottomSelected, toggleBottomSelect, selectAllBottom, clearBottomSelect, batchAssignBottom,
  dispatchCriteria, dispatchCriteriaModal, saveDispatchCriteria, moveCriterion,
  btColWidths, btColW, startColResize,
} = useBottomPanel(store, todayStr, unscheduledJobs, { pushUndo, smartAssign, invalidateRoutes, periodStart })

// ─── Selection composable ────────────────────────────────────────────────────
const {
  hoveredJobId, selectedJob, multiSelect,
  selectJob: _selectJob, isJobMultiSelected, batchUnassign, batchMoveTo,
  lasso, boardScroll, lassoStyle, startLasso, moveLasso, endLasso,
  techHasLinkedJob, techIsHovered,
} = useSelection({ store, periodStart, smartAssign, invalidateRoutes, fullUnassign })

function selectJob (job, techId, isAssist = false, assistTechId = null, event = null) {
  _selectJob(job, techId, isAssist, assistTechId, event, rightPanel)
}

// ─── Drag & Drop composable ──────────────────────────────────────────────────
const {
  dragJob, dragSrc, dragIsAssist, dragBatchIds, dropGhost,
  onJobDragStart, onTimelineDragOver, onTimelineDragLeave,
  onTechDragStart,
  onTimelineDrop, onCalDrop,
  startBlockMove, startResize,
} = useDragDrop({
  store, pxPerHr, dayW, periodStart, periodDays, H_START,
  getJobDate, bottomSelected, multiSelect,
  pushUndo, smartAssign, invalidateRoutes,
})

// ─── Map composable ──────────────────────────────────────────────────────────
let computeDayRoute = () => {}, drawMapMarkers = () => {}, drawSelectedRoute = () => {}, getMap = () => null
const _map = useMap({
  store, MAPBOX_TOKEN, TECH_COLORS,
  currentView, periodStart, filteredResources, mapVisible,
  routeLegs, routeGeometry,
  getJobDate, jobColor, pushUndo, smartAssign, invalidateRoutes,
  dragJob, dragIsAssist, rightPanel, openCtxMenu,
})
const { mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, mapDragJob,
  startGeoFix, cancelGeoFix, startMapResize, initMap, selectTechOnBoard, destroyMap, loadMapboxCss } = _map
computeDayRoute = _map.computeDayRoute
drawMapMarkers = _map.drawMapMarkers
drawSelectedRoute = _map.drawSelectedRoute
getMap = _map.getMap

// ─── Route invalidation ──────────────────────────────────────────────────────
function invalidateRoutes () {
  routeLegs.value = {}; routeGeometry.value = {}
  if (currentView.value === 'day') {
    const ds = localDateStr(periodStart.value)
    filteredResources.value.forEach(tech => computeDayRoute(tech, ds))
  }
  nextTick(() => { if (getMap()) { drawMapMarkers(); drawSelectedRoute() } })
}

watch(
  [currentView, () => anchorDate.value.getTime(), filteredResources],
  () => {
    if (currentView.value !== 'day') return
    routeLegs.value = {}; routeGeometry.value = {}
    const ds = localDateStr(periodStart.value)
    filteredResources.value.forEach(tech => computeDayRoute(tech, ds))
    if (getMap()) { drawMapMarkers(); drawSelectedRoute() }
  },
)

// ─── Move modal ───────────────────────────────────────────────────────────────
const moveModalOpen = ref(false)
const moveForm = ref(null)
function openMoveModal (job, srcTechId) {
  moveForm.value = { job, srcTechId, newTechId: srcTechId, newDate: getJobDate(job.id) || todayStr }
  moveModalOpen.value = true
}
async function confirmMove () {
  if (!moveForm.value) return
  const { job, srcTechId, newTechId, newDate } = moveForm.value
  pushUndo({ type: 'unassignJob', jobId: job.id, techId: srcTechId, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants||[])] })
  if (newTechId !== srcTechId) smartAssign(job, newTechId, newDate)
  else store.setJobSchedule(job.id, newDate)
  moveModalOpen.value = false; bookingOverlay.value = null; invalidateRoutes()
}

// ─── Booking overlay ──────────────────────────────────────────────────────────
const bookingOverlay = ref(null)

// ─── WO creation modal ────────────────────────────────────────────────────────
const woModal = ref(null)
const gpsSettingsOpen = ref(false)

// ─── Tech management (GPS modal) ─────────────────────────────────────────────
const {
  editingTech, newTechName, newTechPhone, newTechDevice, addingTech,
  saveTechField, addTech, removeTech, saveTraccarLink,
} = useTechManagement(store, invalidateRoutes)

function openWoModal (prefillDate = null, prefillTech = null) {
  woModal.value = { subject: '', address: '', latitude: null, longitude: null, duration_h: 1, priority: 'low', note: '', tags: [], techId: prefillTech || '', date: prefillDate || todayStr }
}
async function confirmWo () {
  if (!woModal.value || !woModal.value.subject.trim()) return
  const { subject, address, duration_h, priority, techId, date, latitude, longitude, note, tags } = woModal.value
  await store.createJob({ subject, address, duration_h, priority, assigned_tech: techId || null, scheduled_date: date || null, latitude: latitude || null, longitude: longitude || null, note: note || '', tags: tags.map(t => ({ tag: t })) })
  woModal.value = null
}

// ─── Auto-dispatch composable ────────────────────────────────────────────────
const { autoDistribute, optimizeRoute: _optimizeRoute } = useAutoDispatch({
  store, MAPBOX_TOKEN, filteredResources, periodStart, getJobDate, unscheduledJobs,
  bottomSelected, dispatchCriteria, pushUndo, invalidateRoutes,
})
function optimizeRoute () {
  if (!techCtx.value) return
  const tech = techCtx.value.tech; techCtx.value = null
  _optimizeRoute(tech)
}

// ─── Criteria drag-and-drop ───────────────────────────────────────────────────
const critDragIdx  = ref(null)
const critDragOver = ref(null)
function dropCriterion (toIdx) {
  const fromIdx = critDragIdx.value
  if (fromIdx == null || fromIdx === toIdx) { critDragIdx.value = null; critDragOver.value = null; return }
  const arr = dispatchCriteria.value
  const [item] = arr.splice(fromIdx, 1)
  arr.splice(toIdx, 0, item)
  critDragIdx.value = null; critDragOver.value = null
}

// ─── Board tabs ───────────────────────────────────────────────────────────────
const boardTabs = ref(['Vue principale','Par région'])
const activeTab = ref('Vue principale')

// ─── Unassign drop handler ────────────────────────────────────────────────────
function onDropUnassign (e) {
  e.preventDefault()
  if (dragJob.value) { fullUnassign(dragJob.value); dragJob.value = null; dragSrc.value = null }
  unassignDropActive.value = false
}

// ─── Click empty space = deselect all ─────────────────────────────────────────
let _lassoJustEnded = false
function onRootClick (e) {
  if (_lassoJustEnded) { _lassoJustEnded = false; return }
  const interactive = e.target.closest('.sb-block, .sb-chip, .sb-bottom-row, .sb-bottom-hdr, button, input, select, a, .sb-ctx-menu, .sb-right-panel, .sb-wo-modal, .sb-edit-modal, .sb-criteria-modal, .sb-gps-modal, .sb-modal-overlay, .sb-multi-bar, .sb-toolbar-panel, .sb-header, .sb-bt-checkbox, .sb-res-cell, .sb-bottom-date-sep')
  if (interactive) return
  if (selectedJob.value || multiSelect.value.length || bottomSelected.size || rightPanel.value) {
    selectedJob.value = null
    multiSelect.value = []
    clearBottomSelect()
    rightPanel.value = null
  }
}

// ─── Keyboard ─────────────────────────────────────────────────────────────────
function onKeyDown (e) {
  if (e.key === 'Escape') {
    closeCtxMenu(); assistCtx.value = null; techCtx.value = null; assistNoteModal.value = null
    moveModalOpen.value = false; resSelectorOpen.value = false; rightPanel.value = null
    timeModal.value = null; woModal.value = null; editModal.value = null
    dispatchCriteriaModal.value = false; bookingOverlay.value = null
    filterPanelOpen.value = false; projectsPanelOpen.value = false
    selectedJob.value = null; multiSelect.value = []
  }
  if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
    if (['INPUT','TEXTAREA','SELECT'].includes(document.activeElement?.tagName)) return
    e.preventDefault(); performUndo(); return
  }
  if ((e.key === 'Delete' || e.key === 'Backspace') && (selectedJob.value || multiSelect.value.length)) {
    if (['INPUT','TEXTAREA','SELECT'].includes(document.activeElement?.tagName)) return
    e.preventDefault()
    if (multiSelect.value.length) { batchUnassign(pushUndo); return }
    const { job, isAssist, assistTechId } = selectedJob.value
    if (isAssist && assistTechId) {
      const assist = job.assistants.find(a => a.techId === assistTechId)
      pushUndo({ type: 'removeAssistant', jobId: job.id, techId: assistTechId, duration: assist?.duration || 0, note: assist?.note || '' })
      store.removeAssistant(job.id, assistTechId)
    } else { fullUnassign(job) }
    selectedJob.value = null; invalidateRoutes()
  }
}

// ─── Refresh ──────────────────────────────────────────────────────────────────
async function refreshData () {
  const prevTechId = selectedTechId.value
  await store.loadAll()
  const savedCoords = JSON.parse(localStorage.getItem('dispatch-job-coords') || '{}')
  Object.entries(savedCoords).forEach(([jobId, coords]) => { const job = store.jobs.find(j => j.id === jobId); if (job) job.coords = coords })
  routeLegs.value = {}; routeGeometry.value = {}
  const _ds = localDateStr(periodStart.value)
  filteredResources.value.forEach(tech => computeDayRoute(tech, _ds))
  const pj = bookingOverlay.value?.job?.id, pt = bookingOverlay.value?.tech?.id
  if (pj) { const j = store.jobs.find(j => j.id === pj); const t = pt ? store.technicians.find(t => t.id === pt) : null; if (j) bookingOverlay.value = { job: j, tech: t } }
  selectedTechId.value = prevTechId
  if (getMap()) { drawMapMarkers(); drawSelectedRoute() }
  await loadPendingReqs()
}

// ─── Provide for child components ─────────────────────────────────────────────
provide('store', store)
provide('TECH_COLORS', TECH_COLORS)
provide('MAPBOX_TOKEN', MAPBOX_TOKEN)
provide('jobColor', jobColor)
provide('getTagColor', getTagColor)
provide('onCreateTag', onCreateTag)
provide('onUpdateTag', onUpdateTag)
provide('onRenameTag', onRenameTag)
provide('onDeleteTag', onDeleteTag)
provide('selectedJob', selectedJob)
provide('hoveredJobId', hoveredJobId)
provide('periodLoadH', periodLoadH)
provide('isJobMultiSelected', isJobMultiSelected)
provide('btColW', btColW)
provide('startColResize', startColResize)
provide('searchAddr', searchAddr)
provide('addrResults', addrResults)
provide('selectAddr', selectAddr)

// ─── Lifecycle ────────────────────────────────────────────────────────────────
onMounted(async () => {
  if (!store.technicians.length) await store.loadAll()
  const savedCoords = JSON.parse(localStorage.getItem('dispatch-job-coords') || '{}')
  Object.entries(savedCoords).forEach(([jobId, coords]) => { const job = store.jobs.find(j => j.id === jobId); if (job) job.coords = coords })
  routeLegs.value = {}; routeGeometry.value = {}
  const _ds = localDateStr(periodStart.value)
  filteredResources.value.forEach(tech => computeDayRoute(tech, _ds))
  await loadPendingReqs()
  document.addEventListener('keydown', onKeyDown)
  document.addEventListener('click', () => { closeCtxMenu(); assistCtx.value = null; techCtx.value = null })
  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)
  }
  store.startGpsTracking()
})
onUnmounted(() => { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('click', closeCtxMenu); destroyMap(); store.stopGpsTracking() })
</script>

<template>
<div class="sb-root" @click="onRootClick">

  <!-- ══ DISPATCH TOOLBAR (replaces old header + sidebar) ══ -->
  <header class="sb-header">
    <div class="sb-header-left">
      <input class="sb-search" v-model="searchQuery" placeholder="🔍 Ressource…" />
      <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>
      </div>
      <button class="sb-icon-btn" :class="{ active: filterPanelOpen }" @click="filterPanelOpen=!filterPanelOpen" title="Filtres & Ressources">
        <span v-html="ICON.wrench"></span>
        <span v-if="filterStatus||selectedResIds.length||filterTags.length" class="sbs-badge" style="position:relative;top:-4px;right:2px"></span>
      </button>
      <button v-if="teamJobs.length" class="sb-icon-btn" :class="{ active: projectsPanelOpen }" @click="projectsPanelOpen=!projectsPanelOpen" title="Projets">
        👥 <span class="sbs-count" style="position:relative;top:-2px;right:auto">{{ teamJobs.length }}</span>
      </button>
    </div>
    <div class="sb-header-center">
      <button class="sb-hbtn sb-today-btn" @click="goToToday">Aujourd'hui</button>
      <button class="sb-hbtn" @click="prevPeriod">‹</button>
      <span class="sb-period-label">{{ periodLabel }}</span>
      <button class="sb-hbtn" @click="nextPeriod">›</button>
      <div class="sb-view-sw">
        <button :class="{ active: currentView==='day' }"   @click="currentView='day'">Jour</button>
        <button :class="{ active: currentView==='week' }"  @click="currentView='week'">Semaine</button>
        <button :class="{ active: currentView==='month' }" @click="currentView='month'">Mois</button>
      </div>
    </div>
    <div class="sb-header-right">
      <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>
      <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" @click="gpsSettingsOpen=true" title="GPS Tracking">📡</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>
    </div>
  </header>

  <!-- ══ FILTER / PROJECTS PANELS (dropdown from toolbar) ══ -->
  <transition name="sb-slide-down">
  <div v-if="filterPanelOpen" class="sb-toolbar-panel" @click.stop>
    <div class="sb-toolbar-panel-inner">
      <div class="sbf-section" style="border-bottom:none;display:flex;gap:1rem;flex-wrap:wrap;padding:0.5rem 0.75rem">
        <div style="min-width:180px">
          <div class="sbf-title">Ressources</div>
          <input class="sbs-search-full" v-model="searchQuery" placeholder="🔍 Rechercher une ressource…" />
          <button class="sbf-primary-btn" style="margin-top:0.3rem" @click="openResSelector">Sélectionner les ressources</button>
          <label class="sbf-lbl" style="margin-top:0.3rem">Tri</label>
          <select class="sbf-select" v-model="techSort">
            <option value="default">Par défaut</option><option value="alpha">Alphabétique (nom)</option><option value="manual">Manuel (drag)</option>
          </select>
          <div v-if="selectedResIds.length" class="sbf-chip">{{ selectedResIds.length }} sélectionnée{{ selectedResIds.length>1?'s':'' }}<button @click="selectedResIds=[]">✕</button></div>
        </div>
        <div style="min-width:180px">
          <div class="sbf-title">Filtres</div>
          <label class="sbf-lbl">Statut</label>
          <select class="sbf-select" v-model="filterStatus">
            <option value="">Tous</option><option value="available">Disponible</option><option value="en-route">En route</option><option value="busy">En cours</option><option value="off">Hors shift</option>
          </select>
          <label class="sbf-lbl">Tags</label>
          <TagEditor :model-value="filterTags" @update:model-value="v => { filterTags = v; localStorage.setItem('sbv2-filterTags', JSON.stringify(v)) }"
                    :all-tags="store.allTags" :get-color="getTagColor" :can-create="false" :can-edit="false" placeholder="Filtrer par tag…" />
        </div>
        <div style="display:flex;align-items:flex-end;margin-left:auto">
          <button v-if="filterStatus||selectedResIds.length||searchQuery||filterTags.length" class="sbf-clear-btn" style="width:auto;padding:0.22rem 0.6rem" @click="clearFilters">✕ Réinitialiser</button>
          <button class="sb-icon-btn" style="margin-left:0.3rem" @click="filterPanelOpen=false" title="Fermer">✕</button>
        </div>
      </div>
    </div>
  </div>
  </transition>

  <transition name="sb-slide-down">
  <div v-if="projectsPanelOpen" class="sb-toolbar-panel" @click.stop>
    <div class="sb-toolbar-panel-inner" style="max-height:300px;overflow-y:auto">
      <div style="display:flex;align-items:center;padding:0.4rem 0.75rem;gap:0.5rem">
        <div class="sbf-title" style="margin:0">Projets <span class="sbf-count">{{ teamJobs.length }}</span></div>
        <div style="flex:1"></div>
        <button class="sb-icon-btn" @click="projectsPanelOpen=false" title="Fermer">✕</button>
      </div>
      <div style="display:flex;flex-wrap:wrap;gap:0.4rem;padding:0 0.75rem 0.5rem">
        <div v-for="job in teamJobs" :key="'tj-'+job.id" class="sbf-card" style="width:220px"
             @click="rightPanel={ mode:'details', data:{ job, tech: store.technicians.find(t=>t.id===job.assignedTech) } }">
          <div class="sbf-card-stripe" :style="'background:'+jobColor(job)"></div>
          <div class="sbf-card-body">
            <div class="sbf-card-title">{{ job.subject }}</div>
            <div class="sbf-card-meta">⏱ {{ fmtDur(job.duration) }} · {{ job.scheduledDate || '—' }}</div>
            <div class="sbf-team-badges">
              <span class="sb-assist-badge sb-assist-badge-lead" :style="'background:'+TECH_COLORS[store.technicians.find(t=>t.id===job.assignedTech)?.colorIdx||0]">
                {{ (store.technicians.find(t=>t.id===job.assignedTech)?.fullName||'?').split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}
              </span>
              <span v-for="a in job.assistants" :key="a.techId" class="sb-assist-badge" :style="'background:'+TECH_COLORS[store.technicians.find(t=>t.id===a.techId)?.colorIdx||0]" :title="a.techName">
                {{ (a.techName||'').split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}
              </span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  </transition>

  <!-- ══ BODY ══ -->
  <div class="sb-body">

    <!-- Booking overlay (left) -->
    <div v-if="bookingOverlay" class="sb-overlay-backdrop" @click.self="bookingOverlay=null"></div>
    <transition name="sb-slide-left">
    <div v-if="bookingOverlay" class="sb-left-overlay" @click.stop="()=>{}">
      <div class="sb-rp-hdr"><span class="sb-rp-title">Réservation</span><button class="sb-rp-close" @click="bookingOverlay=null">✕</button></div>
      <div class="sb-rp-body">
        <div class="sb-rp-color-bar" :style="'background:'+jobColor(bookingOverlay.job||{})"></div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Titre</span><strong>{{ bookingOverlay.job?.subject }}</strong></div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Adresse</span>{{ bookingOverlay.job?.address }}</div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Durée</span>{{ fmtDur(bookingOverlay.job?.duration) }}</div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Priorité</span><span :class="prioClass(bookingOverlay.job?.priority)">{{ prioLabel(bookingOverlay.job?.priority) }}</span></div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Technicien</span>{{ bookingOverlay.tech?.fullName || '—' }}</div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Date planifiée</span>{{ bookingOverlay.job?.scheduledDate || '—' }}<span v-if="bookingOverlay.job?.endDate"> → {{ bookingOverlay.job.endDate }}</span></div>
        <div v-if="bookingOverlay.job?.assignedTech" class="sb-rp-field">
          <span class="sb-rp-lbl">Date de fin</span>
          <input type="date" class="sb-form-input" :value="bookingOverlay.job?.endDate || ''" @change="setEndDate(bookingOverlay.job, $event.target.value)" style="margin-top:2px" />
        </div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Statut</span>{{ bookingOverlay.job?.status }}</div>
        <div class="sb-rp-field"><span class="sb-rp-lbl">Tags</span>
          <TagEditor v-if="bookingOverlay.job" :model-value="bookingOverlay.job.tagsWithLevel || bookingOverlay.job.tags || []"
                    @update:model-value="v => { bookingOverlay.job.tagsWithLevel = v; bookingOverlay.job.tags = v.map(x => typeof x === 'string' ? x : x.tag); persistJobTags(bookingOverlay.job) }"
                    :all-tags="store.allTags" :get-color="getTagColor" :show-required="true" :show-level="true"
                    level-label="Niveau min." level-hint="1 = basique · 5 = expert requis"
                    @create="onCreateTag" @update-tag="onUpdateTag" @rename-tag="onRenameTag" @delete-tag="onDeleteTag" />
        </div>
        <div v-if="bookingOverlay.job?.assistants?.length" class="sb-rp-field">
          <span class="sb-rp-lbl">Assistants</span>
          <div v-for="a in bookingOverlay.job.assistants" :key="a.techId" style="display:flex;align-items:center;gap:6px;margin-top:3px">
            <span class="sb-assist-badge" :style="'background:'+TECH_COLORS[store.technicians.find(t=>t.id===a.techId)?.colorIdx||0]">{{ (a.techName||'').split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}</span>
            <span style="font-size:0.72rem">{{ a.techName }} · {{ fmtDur(a.duration) }}{{ a.note ? ' · '+a.note : '' }}</span>
            <button style="margin-left:auto;background:none;border:none;color:#ef4444;cursor:pointer;font-size:0.7rem" @click="store.removeAssistant(bookingOverlay.job.id, a.techId); invalidateRoutes()">✕</button>
          </div>
        </div>
      </div>
      <div class="sb-rp-actions">
        <button class="sb-rp-primary" @click="openMoveModal(bookingOverlay.job, bookingOverlay.tech?.id)">↔ Déplacer / Réassigner</button>
        <button class="sb-rp-btn" @click="startGeoFix(bookingOverlay.job)">📍 Géofixer sur la carte</button>
        <button class="sb-rp-btn" @click="ctxMenu={job:bookingOverlay.job,techId:bookingOverlay.tech?.id};ctxUnschedule();bookingOverlay=null">✕ Désaffecter</button>
      </div>
    </div>
    </transition>

    <!-- Center column -->
    <div class="sb-center-col">
      <div class="sb-board" ref="boardScroll" @mousedown="startLasso($event)" @mousemove="moveLasso($event)" @mouseup="if(lasso) _lassoJustEnded = true; endLasso($event)">
        <div v-if="lasso" class="sb-lasso" :style="lassoStyle"></div>

        <!-- Week view -->
        <WeekCalendar v-if="isCalView"
          :filtered-resources="filteredResources" :day-columns="dayColumns"
          :selected-tech-id="selectedTechId" :drop-ghost="dropGhost" :today-str="todayStr"
          @go-to-day="goToDay" @select-tech="selectTechOnBoard" @ctx-tech="openTechCtx"
          @tech-reorder-start="onTechReorderStart" @tech-reorder-drop="onTechReorderDrop"
          @cal-drop="onCalDrop" @job-dragstart="onJobDragStart"
          @job-click="selectJob" @job-dblclick="openEditModal" @job-ctx="openCtxMenu"
          @clear-filters="clearFilters" />

        <!-- Month view -->
        <MonthCalendar v-else-if="currentView==='month'"
          :anchor-date="anchorDate" :filtered-resources="filteredResources" :today-str="todayStr"
          @go-to-day="goToDay" @select-tech="selectTechOnBoard" />

        <!-- Day view -->
        <div v-else class="sb-grid" :style="'min-width:'+(220+totalW)+'px'">
          <div class="sb-grid-hdr">
            <div class="sb-res-hdr">Ressources <span class="sbf-count">{{ filteredResources.length }}</span></div>
            <div class="sb-time-hdr-wrap" :style="'width:'+totalW+'px;position:relative;height:100%'">
              <div v-for="tick in hourTicks" :key="'dht-'+tick.x" class="sb-htick" :style="'left:'+tick.x+'px'">
                <span v-if="tick.label" class="sb-htick-lbl">{{ tick.label }}</span>
              </div>
            </div>
          </div>
          <div v-if="store.loading" class="sb-loading-row">Chargement…</div>
          <div v-else-if="!filteredResources.length" class="sb-empty-row">
            Aucune ressource. <button class="sbf-primary-btn" style="display:inline-block;margin-left:0.75rem" @click="clearFilters">Réinitialiser</button>
          </div>
          <TimelineRow v-for="tech in filteredResources" :key="tech.id"
               :tech="tech" :segments="techDayJobsWithTravel(tech)"
               :hour-ticks="hourTicks" :total-w="totalW" :px-per-hr="pxPerHr"
               :h-start="H_START" :h-end="H_END" :row-h="ROW_H"
               :is-selected="selectedTechId===tech.id"
               :is-elevated="techHasLinkedJob(tech)||techIsHovered(tech)"
               :drop-ghost-x="dropGhost?.techId===tech.id ? dropGhost.x : null"
               @select-tech="selectTechOnBoard" @ctx-tech="openTechCtx" @open-tech-tags="t => techTagModal = t"
               @drag-tech-start="(e, tech) => { onTechReorderStart(e, tech); onTechDragStart(e, tech) }" @reorder-drop="onTechReorderDrop"
               @timeline-dragover="onTimelineDragOver" @timeline-dragleave="onTimelineDragLeave"
               @timeline-drop="onTimelineDrop"
               @job-dragstart="onJobDragStart" @job-click="selectJob"
               @job-dblclick="openEditModal" @job-ctx="openCtxMenu"
               @assist-ctx="openAssistCtx"
               @hover-job="id => hoveredJobId=id" @unhover-job="hoveredJobId=null"
               @block-move="startBlockMove" @block-resize="startResize" />
        </div>
      </div>

      <!-- Bottom panel -->
      <BottomPanel :open="bottomPanelOpen" :height="bottomPanelH"
                   :groups="unassignedGrouped" :unscheduled-count="unscheduledJobs.length"
                   :selected="bottomSelected" :drop-active="unassignDropActive"
                   @update:open="v => bottomPanelOpen = v" @resize-start="startBottomResize"
                   @toggle-select="toggleBottomSelect" @select-all="selectAllBottom" @clear-select="clearBottomSelect"
                   @lasso-select="ids => { const s = new Set(bottomSelected); ids.forEach(id => s.add(id)); bottomSelected = s }"
                   @deselect-all="() => { clearBottomSelect(); selectedJob = null; multiSelect = []; rightPanel = null }"
                   @batch-assign="batchAssignBottom" @auto-distribute="autoDistribute"
                   @open-criteria="dispatchCriteriaModal=true"
                   @row-click="(job, ev) => { if(ev?.shiftKey || ev?.ctrlKey || ev?.metaKey) { toggleBottomSelect(job.id, ev) } else { rightPanel={ mode:'details', data:{ job, tech:null } } } }"
                   @row-dblclick="openEditModal"
                   @row-dragstart="(e, job) => onJobDragStart(e, job, null)"
                   @drop-unassign="(e, type) => { if(type==='over') unassignDropActive=!!dragJob; else if(type==='leave') unassignDropActive=false; else onDropUnassign(e) }" />
    </div>

    <!-- Map panel -->
    <div v-if="mapVisible" class="sb-map-backdrop" @click="mapVisible=false"></div>
    <div v-if="mapVisible" class="sb-map-panel" @click.stop="()=>{}" :style="`width:${mapPanelW}px;min-width:${mapPanelW}px`">
      <div class="sb-map-resize-handle" @mousedown.prevent="startMapResize"></div>
      <div class="sb-map-bar" :class="{ 'sb-map-bar-geofix': geoFixJob }">
        <span class="sb-map-title">Carte</span>
        <template v-if="geoFixJob">
          <span class="sb-geofix-hint">📍 Cliquer sur la carte pour placer <strong>{{ geoFixJob.subject }}</strong></span>
          <button class="sb-geofix-cancel" @click="cancelGeoFix">✕ Annuler</button>
        </template>
        <template v-else>
          <span v-if="selectedTechId" class="sb-map-tech" :style="'color:'+TECH_COLORS[store.technicians.find(t=>t.id===selectedTechId)?.colorIdx||0]">
            ● {{ store.technicians.find(t=>t.id===selectedTechId)?.fullName }}
            <span class="sb-map-route-hint">· Glisser une job sur le trajet</span>
          </span>
          <span v-else class="sb-map-hint">Cliquer un technicien pour voir son trajet</span>
          <button class="sb-map-close" @click="mapVisible=false">✕</button>
        </template>
      </div>
      <div class="sb-map-legend">
        <div v-for="(col, lbl) in SVC_COLORS" :key="lbl" class="sb-legend-item">
          <span class="sb-legend-dot" :style="'background:'+col"></span>{{ lbl }}
        </div>
      </div>
      <div ref="mapContainer" class="sb-map"></div>
    </div>

    <!-- Right panel -->
    <RightPanel :panel="rightPanel"
      @close="rightPanel=null; selectedJob=null"
      @edit="openEditModal" @move="openMoveModal" @geofix="startGeoFix"
      @unassign="job => { fullUnassign(job); rightPanel=null }"
      @set-end-date="setEndDate"
      @remove-assistant="(jobId, techId) => { store.removeAssistant(jobId, techId); invalidateRoutes() }"
      @assign-pending="() => rightPanel=null"
      @update-tags="(job, v) => { job.tags = v; persistJobTags(job) }" />

  </div>

  <!-- ══ CONTEXT MENUS ══ -->
  <div v-if="ctxMenu" class="sb-ctx" :style="'left:'+ctxMenu.x+'px;top:'+ctxMenu.y+'px'" @click.stop="()=>{}">
    <button class="sb-ctx-item" @click="ctxDetails()">📄 Voir détails</button>
    <button class="sb-ctx-item" @click="ctxMove()">↔ Déplacer / Réassigner</button>
    <button class="sb-ctx-item" @click="openTimeModal(ctxMenu.job, ctxMenu.techId); closeCtxMenu()">🕐 Fixer l'heure</button>
    <button class="sb-ctx-item" @click="startGeoFix(ctxMenu.job); closeCtxMenu()">📍 Géofixer sur la carte</button>
    <div class="sb-ctx-sep"></div>
    <button class="sb-ctx-item sb-ctx-warn" @click="ctxUnschedule()">✕ Désaffecter</button>
  </div>

  <div v-if="techCtx" class="sb-ctx" :style="'left:'+techCtx.x+'px;top:'+techCtx.y+'px'" @click.stop="()=>{}">
    <button class="sb-ctx-item" @click="selectTechOnBoard(techCtx.tech); techCtx=null">🗺 Voir sur la carte</button>
    <button class="sb-ctx-item" @click="optimizeRoute()">🔀 Optimiser la route</button>
    <button class="sb-ctx-item" @click="techTagModal=techCtx.tech; techCtx=null">🏷 Skills / Tags</button>
    <div class="sb-ctx-sep"></div>
    <button class="sb-ctx-item" @click="window.open('/desk/Dispatch Technician/'+techCtx.tech.name,'_blank'); techCtx=null">↗ Ouvrir dans ERPNext</button>
  </div>

  <div v-if="assistCtx" class="sb-ctx" :style="'left:'+assistCtx.x+'px;top:'+assistCtx.y+'px'" @click.stop="()=>{}">
    <button class="sb-ctx-item" @click="assistCtxTogglePin()">
      {{ assistCtx?.job?.assistants?.find(a=>a.techId===assistCtx?.techId)?.pinned ? '↕ Rendre flottant' : '📌 Prioriser dans le timeline' }}
    </button>
    <button class="sb-ctx-item" @click="assistCtxNote()">📝 Modifier la note</button>
    <button class="sb-ctx-item" @click="bookingOverlay={job:assistCtx.job, tech:store.technicians.find(t=>t.id===assistCtx.job.assignedTech)}; assistCtx=null">📄 Voir le job parent</button>
    <div class="sb-ctx-sep"></div>
    <button class="sb-ctx-item sb-ctx-warn" @click="assistCtxRemove()">✕ Retirer l'assistant</button>
  </div>

  <!-- ══ MULTI-SELECT TOOLBAR ══ -->
  <transition name="sb-slide-up">
  <div v-if="multiSelect.length" class="sb-multi-bar">
    <span class="sb-multi-count">{{ multiSelect.length }} sélectionné{{ multiSelect.length>1?'s':'' }}</span>
    <button class="sb-multi-btn" @click="batchUnassign(pushUndo)">✕ Désaffecter</button>
    <span class="sb-multi-sep">|</span>
    <span class="sb-multi-lbl">Déplacer vers :</span>
    <button v-for="t in store.technicians" :key="t.id" class="sb-multi-tech" :style="'border-color:'+TECH_COLORS[t.colorIdx]" @click="batchMoveTo(t.id, localDateStr(periodStart), pushUndo)">
      {{ t.fullName.split(' ').map(n=>n[0]).join('').toUpperCase() }}
    </button>
    <button class="sb-multi-btn sb-multi-clear" @click="multiSelect=[]; selectedJob=null">Annuler</button>
  </div>
  </transition>

  <!-- ══ MODALS ══ -->
  <div v-if="techTagModal" class="sb-overlay" @click.self="techTagModal=null">
    <div class="sb-modal">
      <div class="sb-modal-hdr"><span>🏷 Tags — {{ techTagModal.fullName }}</span><button class="sb-rp-close" @click="techTagModal=null">✕</button></div>
      <div class="sb-modal-body">
        <TagEditor :model-value="techTagModal.tagsWithLevel || []" @update:model-value="v => { techTagModal.tagsWithLevel = v; techTagModal.tags = v.map(x => typeof x === 'string' ? x : x.tag); persistTechTags(techTagModal) }"
                  :all-tags="store.allTags" :get-color="getTagColor" :show-level="true"
                  level-label="Compétence" level-hint="1 = base · 5 = expert"
                  @create="onCreateTag" @update-tag="onUpdateTag" @rename-tag="onRenameTag" @delete-tag="onDeleteTag" />
      </div>
      <div class="sb-modal-ftr"><button class="sb-rp-primary" @click="techTagModal=null">Fermer</button></div>
    </div>
  </div>

  <div v-if="assistNoteModal" class="sb-overlay" @click.self="assistNoteModal=null">
    <div class="sb-modal">
      <div class="sb-modal-hdr"><span>📝 Note assistant</span><button class="sb-rp-close" @click="assistNoteModal=null">✕</button></div>
      <div class="sb-modal-body">
        <label class="sb-form-lbl">Titre / note pour cette tâche</label>
        <input type="text" class="sb-form-input" v-model="assistNoteModal.note" placeholder="Ex: Livraison outil, Support câblage..." @keyup.enter="confirmAssistNote" />
        <p style="font-size:0.6rem;color:#7b80a0;margin-top:0.3rem">Job parent : {{ assistNoteModal.job?.subject }}</p>
      </div>
      <div class="sb-modal-footer"><button class="sb-rp-primary" @click="confirmAssistNote">Enregistrer</button><button class="sb-rp-btn" @click="assistNoteModal=null">Annuler</button></div>
    </div>
  </div>

  <div v-if="timeModal" class="sb-overlay" @click.self="timeModal=null">
    <div class="sb-modal">
      <div class="sb-modal-hdr"><span>🕐 Heure de début fixe</span><button class="sb-rp-close" @click="timeModal=null">✕</button></div>
      <div class="sb-modal-body">
        <div class="sb-form-row"><label class="sb-form-lbl">Job</label><div class="sb-form-val">{{ timeModal.job?.subject }}</div></div>
        <div class="sb-form-row"><label class="sb-form-lbl">Heure fixe</label><input type="time" class="sb-form-input" v-model="timeModal.time" /></div>
        <div v-if="timeModal.hasPin" style="font-size:0.68rem;color:#f59e0b;margin-top:0.4rem">⚠ Heure actuellement fixée — modifier ou supprimer ci-dessous.</div>
      </div>
      <div class="sb-modal-ftr">
        <button class="sbf-primary-btn" @click="confirmTime">📌 Fixer</button>
        <button v-if="timeModal.hasPin" class="sb-rp-btn" style="color:#ef4444" @click="clearTime">✕ Supprimer</button>
        <button class="sb-rp-btn" @click="timeModal=null">Annuler</button>
      </div>
    </div>
  </div>

  <div v-if="moveModalOpen" class="sb-overlay" @click.self="moveModalOpen=false">
    <div class="sb-modal">
      <div class="sb-modal-hdr"><span>Déplacer la réservation</span><button class="sb-rp-close" @click="moveModalOpen=false">✕</button></div>
      <div class="sb-modal-body" v-if="moveForm">
        <div class="sb-form-row"><label class="sb-form-lbl">Ticket</label><div class="sb-form-val">{{ moveForm.job?.subject }}</div></div>
        <div class="sb-form-row"><label class="sb-form-lbl">Technicien actuel</label><div class="sb-form-val">{{ store.technicians.find(t=>t.id===moveForm.srcTechId)?.fullName || '—' }}</div></div>
        <div class="sb-form-row"><label class="sb-form-lbl">Nouveau technicien</label>
          <select class="sb-form-sel" v-model="moveForm.newTechId"><option v-for="t in store.technicians" :key="t.id" :value="t.id">{{ t.fullName }}</option></select>
        </div>
        <div class="sb-form-row"><label class="sb-form-lbl">Nouvelle date</label><input type="date" class="sb-form-input" v-model="moveForm.newDate" /></div>
      </div>
      <div class="sb-modal-ftr"><button class="sbf-primary-btn" @click="confirmMove">✓ Confirmer</button><button class="sb-rp-btn" @click="moveModalOpen=false">Annuler</button></div>
    </div>
  </div>

  <div v-if="resSelectorOpen" class="sb-overlay" @click.self="resSelectorOpen=false">
    <div class="sb-modal sb-modal-wide">
      <div class="sb-modal-hdr"><span>Sélectionner les ressources à afficher</span><button class="sb-rp-close" @click="resSelectorOpen=false">✕</button></div>
      <div class="sb-modal-body">
        <div class="sb-res-sel-wrap">
          <div class="sb-res-sel-col">
            <div class="sb-res-sel-hdr">Disponibles</div>
            <div v-for="t in store.technicians.filter(x=>!tempSelectedIds.includes(x.id))" :key="t.id" class="sb-res-sel-item" @click="toggleTempRes(t.id)">
              <div class="sb-avatar-xs" :style="'background:'+TECH_COLORS[t.colorIdx]">{{ t.fullName.split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}</div>{{ t.fullName }}
            </div>
            <div v-if="!store.technicians.filter(x=>!tempSelectedIds.includes(x.id)).length" class="sbf-empty">Toutes sélectionnées</div>
          </div>
          <div class="sb-res-sel-arrow">→</div>
          <div class="sb-res-sel-col">
            <div class="sb-res-sel-hdr">Sélectionnées <span class="sbf-count">{{ tempSelectedIds.length || 'Toutes' }}</span></div>
            <div v-for="t in store.technicians.filter(x=>tempSelectedIds.includes(x.id))" :key="t.id" class="sb-res-sel-item sb-res-sel-active" @click="toggleTempRes(t.id)">
              <div class="sb-avatar-xs" :style="'background:'+TECH_COLORS[t.colorIdx]">{{ t.fullName.split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}</div>{{ t.fullName }}<span class="sb-res-sel-rm">✕</span>
            </div>
            <div v-if="!tempSelectedIds.length" class="sbf-empty">Toutes affichées</div>
          </div>
        </div>
      </div>
      <div class="sb-modal-ftr"><button class="sbf-primary-btn" @click="applyResSelector">Appliquer</button><button class="sb-rp-btn" @click="resSelectorOpen=false">Annuler</button></div>
    </div>
  </div>

  <WoCreateModal v-model="woModal" @confirm="confirmWo" />
  <JobEditModal v-model="editModal" @confirm="confirmEdit" />

  <div v-if="dispatchCriteriaModal" class="sb-overlay" @click.self="dispatchCriteriaModal=false">
    <div class="sb-modal">
      <div class="sb-modal-hdr"><span>⚙ Critères de dispatch automatique</span><button class="sb-rp-close" @click="dispatchCriteriaModal=false">✕</button></div>
      <div class="sb-modal-body">
        <p style="font-size:0.65rem;color:var(--sb-muted);margin:0 0 0.5rem">Glissez pour réordonner. Les critères du haut ont plus de poids.</p>
        <div v-for="(c, i) in dispatchCriteria" :key="c.id" class="sb-crit-row"
             draggable="true"
             :class="{ 'sb-crit-drag-over': critDragOver === i }"
             @dragstart="critDragIdx = i; $event.dataTransfer.effectAllowed = 'move'"
             @dragend="critDragIdx = null; critDragOver = null"
             @dragover.prevent="critDragOver = i"
             @dragleave="critDragOver === i && (critDragOver = null)"
             @drop.prevent="dropCriterion(i)">
          <span class="sb-crit-handle" title="Glisser">⠿</span>
          <span class="sb-crit-order">{{ i + 1 }}</span>
          <label class="sb-crit-label"><input type="checkbox" v-model="c.enabled" />{{ c.label }}</label>
          <div class="sb-crit-arrows">
            <button :disabled="i===0" @click="moveCriterion(i,-1)">▲</button>
            <button :disabled="i===dispatchCriteria.length-1" @click="moveCriterion(i,1)">▼</button>
          </div>
        </div>
      </div>
      <div class="sb-modal-ftr"><button class="sbf-primary-btn" @click="saveDispatchCriteria">✓ Enregistrer</button><button class="sb-rp-btn" @click="dispatchCriteriaModal=false">Annuler</button></div>
    </div>
  </div>

  <!-- ══ GPS Settings Modal ══ -->
  <div v-if="gpsSettingsOpen" class="sb-modal-overlay" @click.self="gpsSettingsOpen=false">
    <div class="sb-gps-modal">
      <div class="sb-gps-modal-hdr">
        <h3>📡 GPS Tracking — Traccar</h3>
        <button class="sb-gps-close" @click="gpsSettingsOpen=false">&times;</button>
      </div>
      <div class="sb-gps-modal-body">
        <p class="sb-gps-desc">Gérer les techniciens, associer les devices Traccar et suivre le GPS en temps réel.</p>
        <table class="sb-gps-table">
          <thead>
            <tr>
              <th>Technicien</th>
              <th>Téléphone</th>
              <th>Statut</th>
              <th>Device GPS</th>
              <th>GPS</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="tech in store.technicians" :key="tech.id">
              <td>
                <input v-if="editingTech === tech.id" class="sb-gps-input sb-gps-edit-name" :value="tech.fullName"
                  @blur="saveTechField(tech, 'full_name', $event.target.value); editingTech = null"
                  @keydown.enter="$event.target.blur()" @keydown.escape="editingTech = null" />
                <strong v-else @dblclick="editingTech = tech.id" class="sb-gps-editable" title="Double-clic pour modifier">{{ tech.fullName }}</strong>
              </td>
              <td>
                <input class="sb-gps-input sb-gps-phone" :value="tech.phone" placeholder="514..."
                  @blur="saveTechField(tech, 'phone', $event.target.value)"
                  @keydown.enter="$event.target.blur()" />
              </td>
              <td>
                <select :value="tech.status || 'available'" @change="saveTechField(tech, 'status', $event.target.value)" class="sb-gps-select sb-gps-status-sel">
                  <option value="available">Disponible</option>
                  <option value="busy">Occupé</option>
                  <option value="off">Absent</option>
                </select>
              </td>
              <td>
                <select :value="tech.traccarDeviceId || ''" @change="saveTraccarLink(tech, $event.target.value)" class="sb-gps-select">
                  <option value="">— Non lié —</option>
                  <option v-for="d in store.traccarDevices" :key="d.id" :value="String(d.id)">
                    {{ d.name }} {{ d.status === 'online' ? '🟢' : '⚫' }}
                  </option>
                </select>
              </td>
              <td>
                <span v-if="tech.gpsCoords" class="sb-gps-badge sb-gps-online" :title="tech.gpsCoords[1].toFixed(4)+', '+tech.gpsCoords[0].toFixed(4)">
                  En ligne<span v-if="tech.gpsSpeed > 1"> · {{ (tech.gpsSpeed * 1.852).toFixed(0) }}km/h</span>
                </span>
                <span v-else-if="tech.traccarDeviceId" class="sb-gps-badge sb-gps-offline">Hors ligne</span>
                <span v-else class="sb-gps-badge sb-gps-none">—</span>
              </td>
              <td><button class="sb-gps-del-btn" @click="removeTech(tech)" title="Supprimer">&times;</button></td>
            </tr>
            <!-- Add technician row -->
            <tr class="sb-gps-add-row">
              <td><input v-model="newTechName" class="sb-gps-input" placeholder="Nom complet" @keydown.enter="addTech" /></td>
              <td><input v-model="newTechPhone" class="sb-gps-input sb-gps-phone" placeholder="514..." /></td>
              <td></td>
              <td>
                <select v-model="newTechDevice" class="sb-gps-select">
                  <option value="">— Aucun —</option>
                  <option v-for="d in store.traccarDevices" :key="d.id" :value="String(d.id)">
                    {{ d.name }} {{ d.status === 'online' ? '🟢' : '⚫' }}
                  </option>
                </select>
              </td>
              <td colspan="2">
                <button class="sb-gps-add-btn" @click="addTech" :disabled="!newTechName.trim() || addingTech">
                  {{ addingTech ? '...' : '+ Ajouter' }}
                </button>
              </td>
            </tr>
          </tbody>
        </table>
        <div class="sb-gps-footer">
          <span class="sb-gps-info">{{ store.traccarDevices.length }} devices Traccar · {{ store.technicians.length }} techniciens</span>
        </div>
      </div>
    </div>
  </div>

</div>
</template>

<style src="./dispatch-styles.scss"></style>
