<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, updateTech } from 'src/api/dispatch'

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 UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'
import PublishScheduleModal from 'src/modules/dispatch/components/PublishScheduleModal.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'

import {
  localDateStr, timeToH, hToTime, 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'
import { useAddressSearch } from 'src/composables/useAddressSearch'

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

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

const {
  selectedResIds, filterStatus, filterTags, filterResourceType, searchQuery, techSort, manualOrder,
  filteredResources, groupedResources, availableGroups, filterGroup,
  showInactive, inactiveCount, humanCount, materialCount, availableCategories,
  resSelectorOpen, tempSelectedIds, dragReorderTech,
  openResSelector, applyResSelector, toggleTempRes, clearFilters,
  onTechReorderStart, onTechReorderDrop,
} = useResourceFilter(store)

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

const { addrResults, addrLoading, searchAddr, selectAddr } = useAddressSearch()

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

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'))

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()
}

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) {
  timeModal.value = { job, techId, time: getJobTime(job.id) || '08:00', hasPin: !!getJobTime(job.id) }
}
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 }

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))

function jobColor (job) { return _jobColorBase(job, TECH_COLORS, store) }

const PX_PER_HR = ref(80)
const ROW_H     = 68
const pxPerHr = computed(() => currentView.value === 'week' ? PX_PER_HR.value * 0.55 : currentView.value === 'month' ? 0 : PX_PER_HR.value)
const dayW = computed(() => currentView.value === 'month' ? 110 : (H_END - H_START) * pxPerHr.value)
const totalW = computed(() => dayW.value * periodDays.value)

const {
  H_START, H_END, routeLegs, routeGeometry,
  techAllJobsForDate, techDayJobsWithTravel,
  periodLoadH, techPeriodCapacityH, techDayEndH,
} = 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)

const { pushUndo, performUndo } = useUndo(store, invalidateRoutes)

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()
}

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

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 })

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)
}

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,
})

function startAbsenceResize (e, seg, tech, side) {
  e.preventDefault()
  const startX = e.clientX
  const block = e.target.closest('.sb-block-absence')
  const startW = block.offsetWidth
  const startL = parseFloat(block.style.left)
  const SNAP_PX = pxPerHr.value / 4 // 15-minute snap

  function snapPx (px) { return Math.round(px / SNAP_PX) * SNAP_PX }

  function onMove (ev) {
    const dx = ev.clientX - startX
    if (side === 'right') {
      block.style.width = Math.max(SNAP_PX, snapPx(startW + dx)) + 'px'
    } else {
      const newL = snapPx(startL + dx)
      const newW = startW + (startL - newL)
      if (newW >= SNAP_PX && newL >= 0) {
        block.style.left = newL + 'px'
        block.style.width = newW + 'px'
      }
    }
    // Update label
    const curL = parseFloat(block.style.left)
    const curW = parseFloat(block.style.width)
    const sH = H_START + curL / pxPerHr.value
    const eH = sH + curW / pxPerHr.value
    const lbl = block.querySelector('.sb-absence-label')
    if (lbl) lbl.textContent = `${hToTime(sH)} → ${hToTime(eH)}`
  }

  function onUp () {
    document.removeEventListener('mousemove', onMove)
    document.removeEventListener('mouseup', onUp)
    const curL = parseFloat(block.style.left)
    const curW = parseFloat(block.style.width)
    const newStartH = H_START + curL / pxPerHr.value
    const newEndH = newStartH + curW / pxPerHr.value
    const startTime = hToTime(newStartH)
    const endTime = hToTime(newEndH)
    // Update local state
    tech.absenceStartTime = startTime
    tech.absenceEndTime = endTime
    // Persist to ERPNext
    updateTech(tech.name || tech.id, {
      absence_start_time: startTime,
      absence_end_time: endTime,
    }).catch(() => {})
  }

  document.addEventListener('mousemove', onMove)
  document.addEventListener('mouseup', onUp)
}

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

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() }
  },
)

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()
}

const bookingOverlay = ref(null)
const woModalOpen = ref(false)
const woModalCtx = ref({})
const publishModalOpen = ref(false)
const draftCount = computed(() => store.jobs.filter(j => !j.published && j.status !== 'completed' && j.status !== 'cancelled').length)
const periodEndStr = computed(() => {
  const ps = periodStart.value
  if (!ps) return ''
  const d = new Date(ps + 'T12:00:00')
  d.setDate(d.getDate() + (periodDays.value || 7) - 1)
  return d.toISOString().slice(0, 10)
})
function onPublished (jobNames) {
  store.publishJobsLocal(jobNames)
}
const gpsSettingsOpen = ref(false)
const gpsShowInactive = ref(false)
const gpsFilteredTechs = computed(() =>
  gpsShowInactive.value ? store.technicians : store.technicians.filter(t => t.active !== false)
)

const {
  editingTech, newTechName, newTechPhone, newTechDevice, addingTech,
  absenceModalOpen, absenceModalTech, absenceForm, absenceProcessing,
  saveTechField, addTech,
  openAbsenceModal, confirmAbsence, endAbsence,
  deactivateTech, reactivateTech, removeTech, saveTraccarLink, saveWeeklySchedule,
  ABSENCE_REASONS,
} = useTechManagement(store, invalidateRoutes)

const newTechGroup = ref('')

// ── Schedule editor modal ──
import { WEEK_DAYS, DAY_LABELS, DEFAULT_WEEKLY_SCHEDULE, SCHEDULE_PRESETS } from 'src/composables/useHelpers'
const scheduleModalTech = ref(null)
const scheduleForm = ref({})
function openScheduleModal (tech) {
  scheduleModalTech.value = tech
  scheduleForm.value = {}
  WEEK_DAYS.forEach(d => {
    const day = tech.weeklySchedule?.[d]
    scheduleForm.value[d] = day ? { on: true, start: day.start || '08:00', end: day.end || '16:00' } : { on: false, start: '08:00', end: '16:00' }
  })
}
function applySchedulePreset (preset) {
  WEEK_DAYS.forEach(d => {
    const day = preset.schedule[d]
    scheduleForm.value[d] = day ? { on: true, start: day.start, end: day.end } : { on: false, start: '08:00', end: '16:00' }
  })
}
function confirmSchedule () {
  const sched = {}
  WEEK_DAYS.forEach(d => {
    const f = scheduleForm.value[d]
    sched[d] = f.on ? { start: f.start, end: f.end } : null
  })
  saveWeeklySchedule(scheduleModalTech.value, sched)
  scheduleModalTech.value = null
}

// Group technicians for the resource selector modal
const resSelectorGroupFilter = ref('')
const resSelectorSearch = ref('')
const savedPresets = ref(JSON.parse(localStorage.getItem('sbv2-resPresets') || '[]'))
const presetNameInput = ref('')
const showPresetSave = ref(false)

function savePreset () {
  const name = presetNameInput.value.trim()
  if (!name || !tempSelectedIds.value.length) return
  const existing = savedPresets.value.findIndex(p => p.name === name)
  const preset = { name, ids: [...tempSelectedIds.value], created: new Date().toISOString() }
  if (existing >= 0) savedPresets.value.splice(existing, 1, preset)
  else savedPresets.value.push(preset)
  localStorage.setItem('sbv2-resPresets', JSON.stringify(savedPresets.value))
  presetNameInput.value = ''
  showPresetSave.value = false
}

function loadPreset (preset) {
  tempSelectedIds.value = [...preset.ids]
}

function deletePreset (idx) {
  savedPresets.value.splice(idx, 1)
  localStorage.setItem('sbv2-resPresets', JSON.stringify(savedPresets.value))
}

const activePresetName = computed(() => {
  if (!selectedResIds.value.length) return null
  const ids = selectedResIds.value
  return savedPresets.value.find(p => p.ids.length === ids.length && p.ids.every(id => ids.includes(id)))?.name || null
})

const resSelectorGroupsFiltered = computed(() => {
  const grouper = (techs) => {
    const groups = new Map()
    for (const t of techs) {
      const g = t.group || ''
      if (!groups.has(g)) groups.set(g, [])
      groups.get(g).push(t)
    }
    const sorted = [...groups.entries()].sort((a, b) => {
      if (!a[0] && b[0]) return 1
      if (a[0] && !b[0]) return -1
      return a[0].localeCompare(b[0])
    })
    return sorted.map(([name, techs]) => ({ name, label: name || 'Sans groupe', techs }))
  }
  let techs = store.technicians.filter(t => t.status !== 'inactive')
  // Apply group filter within selector
  if (resSelectorGroupFilter.value) techs = techs.filter(t => t.group === resSelectorGroupFilter.value)
  // Apply search filter
  if (resSelectorSearch.value) {
    const q = resSelectorSearch.value.toLowerCase()
    techs = techs.filter(t => t.fullName.toLowerCase().includes(q))
  }
  return {
    available: grouper(techs.filter(x => !tempSelectedIds.value.includes(x.id))),
    selected: grouper(store.technicians.filter(x => tempSelectedIds.value.includes(x.id))),
  }
})

const RES_ICONS = { 'Véhicule': '🚛', 'Outil': '🔧', 'Salle': '🏢', 'Équipement': '📦', 'Nacelle': '🏗️', 'Grue': '🏗️', 'Fusionneuse': '🔧', 'OTDR': '📡' }
function resIcon (t) {
  if (t.resourceType !== 'material') return ''
  return RES_ICONS[t.resourceCategory] || RES_ICONS[t.fullName] || '🔧'
}

function openResSelectorFull () {
  resSelectorGroupFilter.value = filterGroup.value
  resSelectorSearch.value = ''
  openResSelector()
}

function applyGroupFilter () {
  filterGroup.value = resSelectorGroupFilter.value
  resSelectorOpen.value = false
}

async function onTechStatusChange (tech, value) {
  tech.status = value
  tech.active = value !== 'inactive'
  saveTechField(tech, 'status', value)
}

async function saveTechGroup (tech, value) {
  const trimmed = (value || '').trim()
  if (trimmed === tech.group) return
  tech.group = trimmed
  try { await import('src/api/dispatch').then(m => m.updateTech(tech.name || tech.id, { tech_group: trimmed })) }
  catch {}
}

function openWoModal (prefillDate = null, prefillTech = null) {
  woModalCtx.value = {
    scheduled_date: prefillDate || todayStr,
    assigned_tech: prefillTech || null,
  }
  woModalOpen.value = true
}
async function confirmWo (formData) {
  const job = await store.createJob({
    subject: formData.subject,
    address: formData.address,
    duration_h: formData.duration_h,
    priority: formData.priority,
    assigned_tech: formData.assigned_tech || null,
    scheduled_date: formData.scheduled_date || null,
    latitude: formData._latitude || null,
    longitude: formData._longitude || null,
    note: formData.description || '',
    tags: (formData.tags || []).map(t => typeof t === 'string' ? { tag: t } : t),
    depends_on: formData.depends_on || '',
  })
  return job
}

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)
}

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
}

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

function onDropUnassign (e) {
  e.preventDefault()
  if (dragJob.value) { fullUnassign(dragJob.value); dragJob.value = null; dragSrc.value = null }
  unassignDropActive.value = false
}

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
  }
}

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; woModalOpen.value = false; 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()
  }
}

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('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('techPeriodCapacityH', techPeriodCapacityH)
provide('techDayEndH', techDayEndH)
provide('isJobMultiSelected', isJobMultiSelected)
provide('btColW', btColW)
provide('startColResize', startColResize)
provide('searchAddr', searchAddr)
provide('addrResults', addrResults)
provide('selectAddr', selectAddr)

// ── SSE for real-time tech absence updates ──
const HUB_SSE_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca'
let dispatchSse = null

function connectDispatchSSE () {
  if (dispatchSse) dispatchSse.close()
  dispatchSse = new EventSource(`${HUB_SSE_URL}/sse?topics=dispatch`)

  dispatchSse.addEventListener('tech-absence', (e) => {
    try {
      const data = JSON.parse(e.data)
      const tech = store.technicians.find(t => t.name === data.techName || t.id === data.techName)
      if (!tech) return
      if (data.action === 'set') {
        tech.status = 'off'
        tech.absenceFrom = data.from || null
        tech.absenceUntil = data.until || null
        tech.absenceStartTime = data.startTime || null
        tech.absenceEndTime = data.endTime || null
        tech.absenceReason = data.reason || 'personal'
      } else if (data.action === 'clear') {
        tech.status = 'available'
        tech.absenceFrom = null
        tech.absenceUntil = null
        tech.absenceStartTime = null
        tech.absenceEndTime = null
        tech.absenceReason = ''
      }
    } catch {}
  })
}

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()
  connectDispatchSSE()
})
onUnmounted(() => { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('click', closeCtxMenu); destroyMap(); store.stopGpsTracking(); if (dispatchSse) dispatchSse.close() })
</script>

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

  <header class="sb-header">
    <div class="sb-header-left">
      <div class="sb-search-bar" @click="openResSelectorFull">
        <span class="sb-search-icon">🔍</span>
        <span v-if="filterGroup" class="sb-search-chip" @click.stop="filterGroup=''">{{ filterGroup }} ✕</span>
        <span v-if="activePresetName" class="sb-search-chip" @click.stop="selectedResIds=[]">{{ activePresetName }} ✕</span>
        <span v-else-if="selectedResIds.length" class="sb-search-chip sb-search-chip-count" @click.stop="selectedResIds=[]">{{ selectedResIds.length }} ressource{{ selectedResIds.length>1?'s':'' }} ✕</span>
        <span v-if="!filterGroup && !selectedResIds.length" class="sb-search-placeholder">Ressources…</span>
      </div>
      <div v-if="materialCount > 0" class="sb-res-type-toggle">
        <button :class="{ active: !filterResourceType }" @click="filterResourceType=''">Tous <span class="sbf-count">{{ humanCount + materialCount }}</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>
      </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>
      </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||filterGroup||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 v-for="v in [['day','Jour'],['week','Semaine'],['month','Mois']]" :key="v[0]" :class="{ active: currentView===v[0] }" @click="currentView=v[0]">{{ v[1] }}</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" 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>
    </div>
  </header>

  <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="openResSelectorFull">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">Groupe</label>
          <select class="sbf-select" v-model="filterGroup">
            <option value="">Tous les groupes</option>
            <option v-for="g in availableGroups" :key="g" :value="g">{{ g }}</option>
          </select>
          <label class="sbf-lbl">Statut</label>
          <select class="sbf-select" v-model="filterStatus">
            <option value="">Tous (actifs)</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><option value="inactive">Inactifs</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="min-width:120px">
          <div class="sbf-title">Légende</div>
          <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
            <span style="display:inline-block;width:18px;height:12px;border-radius:3px;background:#6366f1"></span>
            <span style="font-size:0.7rem;color:#c4c8e4">Publié</span>
          </div>
          <div style="display:flex;align-items:center;gap:6px">
            <span style="display:inline-block;width:18px;height:12px;border-radius:3px;border:1px dashed rgba(255,255,255,0.3);background:repeating-linear-gradient(-45deg,transparent,transparent 3px,rgba(255,255,255,0.08) 3px,rgba(255,255,255,0.08) 6px),#6366f1"></span>
            <span style="font-size:0.7rem;color:#c4c8e4">Brouillon</span>
          </div>
        </div>
        <div style="display:flex;align-items:flex-end;margin-left:auto">
          <button v-if="filterStatus||filterGroup||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>

  <div class="sb-body">

    <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 v-for="f in [['Titre',bookingOverlay.job?.subject],['Adresse',bookingOverlay.job?.address],['Durée',fmtDur(bookingOverlay.job?.duration)],['Technicien',bookingOverlay.tech?.fullName||'—'],['Statut',bookingOverlay.job?.status]]" :key="f[0]" class="sb-rp-field">
          <span class="sb-rp-lbl">{{ f[0] }}</span>{{ f[1] }}
        </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">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">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>

    <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>

        <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"
          @open-absence="openAbsenceModal" @end-absence="endAbsence"
          @open-schedule="openScheduleModal" />

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

        <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"
               @absence-resize="startAbsenceResize"
               @open-absence="openAbsenceModal" @end-absence="endAbsence"
               @open-schedule="openScheduleModal" />
        </div>
      </div>

      <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>

    <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>

    <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>

  <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>

  <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>

  <div v-if="techTagModal" class="sb-overlay" @click.self="techTagModal=null">
    <div class="sb-modal sb-modal-tags">
      <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" style="overflow:visible;min-height:320px">
        <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>Ressources & Groupes</span><button class="sb-rp-close" @click="resSelectorOpen=false">✕</button></div>
      <div class="sb-modal-body">
        <!-- Saved presets -->
        <div v-if="savedPresets.length" class="sb-rsel-groups">
          <div class="sb-rsel-section-title">Sélections sauvegardées</div>
          <div class="sb-rsel-chips">
            <button v-for="(p, idx) in savedPresets" :key="p.name" class="sb-rsel-chip sb-rsel-preset"
              :class="{ active: tempSelectedIds.length && p.ids.length === tempSelectedIds.length && p.ids.every(id => tempSelectedIds.includes(id)) }"
              @click="loadPreset(p)">
              {{ p.name }} <span class="sb-rsel-preset-count">{{ p.ids.length }}</span>
              <span class="sb-rsel-preset-del" @click.stop="deletePreset(idx)" title="Supprimer">✕</span>
            </button>
          </div>
        </div>

        <!-- Group chips -->
        <div class="sb-rsel-groups">
          <div class="sb-rsel-section-title">Groupes</div>
          <div class="sb-rsel-chips">
            <button class="sb-rsel-chip" :class="{ active: !resSelectorGroupFilter }" @click="resSelectorGroupFilter=''">Tous</button>
            <button v-for="g in availableGroups" :key="g" class="sb-rsel-chip"
              :class="{ active: resSelectorGroupFilter === g }" @click="resSelectorGroupFilter = resSelectorGroupFilter === g ? '' : g">{{ g }}</button>
          </div>
          <div class="sb-rsel-group-actions">
            <button v-if="resSelectorGroupFilter" class="sb-rsel-apply-group" @click="applyGroupFilter">
              Afficher seulement « {{ resSelectorGroupFilter }} »
            </button>
          </div>
        </div>

        <!-- Search -->
        <div class="sb-rsel-search-row">
          <input v-model="resSelectorSearch" class="sb-rsel-search" placeholder="🔍 Rechercher une ressource…" />
        </div>

        <!-- Resource list -->
        <div class="sb-res-sel-wrap">
          <div class="sb-res-sel-col">
            <div class="sb-res-sel-hdr">Disponibles</div>
            <template v-for="group in resSelectorGroupsFiltered.available" :key="'avail-'+group.name">
              <div v-if="group.techs.length" class="sb-res-sel-group-hdr">{{ group.label }} <span class="sbf-count">{{ group.techs.length }}</span></div>
              <div v-for="t in group.techs" :key="t.id" class="sb-res-sel-item" @click="toggleTempRes(t.id)">
                <div v-if="t.resourceType==='material'" class="sb-avatar-xs sb-avatar-material">{{ resIcon(t) }}</div>
                <div v-else class="sb-avatar-xs" :style="'background:'+TECH_COLORS[t.colorIdx]">{{ t.fullName.split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}</div>
                <span class="sb-res-sel-name">{{ t.fullName }}</span>
                <span v-if="t.resourceCategory" class="sb-res-sel-cat-tag">{{ t.resourceCategory }}</span>
                <span v-else-if="t.group" class="sb-res-sel-grp-tag">{{ t.group }}</span>
              </div>
            </template>
            <div v-if="!resSelectorGroupsFiltered.available.flatMap(g=>g.techs).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>
            <template v-for="group in resSelectorGroupsFiltered.selected" :key="'sel-'+group.name">
              <div v-if="group.techs.length" class="sb-res-sel-group-hdr">{{ group.label }}</div>
              <div v-for="t in group.techs" :key="t.id" class="sb-res-sel-item sb-res-sel-active" @click="toggleTempRes(t.id)">
                <div v-if="t.resourceType==='material'" class="sb-avatar-xs sb-avatar-material">{{ resIcon(t) }}</div>
                <div v-else class="sb-avatar-xs" :style="'background:'+TECH_COLORS[t.colorIdx]">{{ t.fullName.split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) }}</div>
                <span class="sb-res-sel-name">{{ t.fullName }}</span><span class="sb-res-sel-rm">✕</span>
              </div>
            </template>
            <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>
        <template v-if="tempSelectedIds.length">
          <button v-if="!showPresetSave" class="sb-rp-btn" @click="showPresetSave=true">💾 Sauvegarder</button>
          <div v-else class="sb-rsel-save-row">
            <input v-model="presetNameInput" class="sb-rsel-save-input" placeholder="Nom du groupe…"
              @keyup.enter="savePreset" @keyup.escape="showPresetSave=false" />
            <button class="sb-rsel-save-btn" @click="savePreset" :disabled="!presetNameInput.trim()">✓</button>
            <button class="sb-rp-btn" style="padding:0.2rem 0.4rem" @click="showPresetSave=false">✕</button>
          </div>
          <button class="sb-rp-btn" @click="tempSelectedIds=[]">Tout désélectionner</button>
        </template>
        <button class="sb-rp-btn" @click="resSelectorOpen=false">Annuler</button>
      </div>
    </div>
  </div>

  <UnifiedCreateModal v-model="woModalOpen" mode="work-order"
    :context="woModalCtx"
    :technicians="store.technicians"
    :external-tags="store.allTags"
    :external-get-color="getTagColor"
    :submit-handler="confirmWo" />
  <JobEditModal v-model="editModal" @confirm="confirmEdit" />
  <PublishScheduleModal v-model="publishModalOpen"
    :jobs="store.jobs" :technicians="filteredResources"
    :period-start="periodStart" :period-end="periodEndStr"
    @published="onPublished" />

  <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>

  <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 ressources dispatch, associer les devices Traccar et suivre le GPS en temps réel.</p>
        <div class="sb-gps-toggle-row">
          <label class="sb-gps-toggle-label">
            <input type="checkbox" v-model="gpsShowInactive" />
            Afficher les inactifs
            <span v-if="inactiveCount" class="sb-gps-inactive-count">({{ inactiveCount }})</span>
          </label>
        </div>
        <table class="sb-gps-table">
          <thead><tr><th>Technicien</th><th>Groupe</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 gpsFilteredTechs" :key="tech.id" :class="{ 'sb-gps-inactive-row': !tech.active }">
              <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" :value="tech.group" placeholder="Ex: Terrain, Support..." style="width:100px"
                  @blur="saveTechGroup(tech, $event.target.value)" @keydown.enter="$event.target.blur()" list="tech-groups" />
              </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="onTechStatusChange(tech, $event.target.value)" class="sb-gps-select sb-gps-status-sel">
                  <option v-for="s in [['available','Disponible'],['busy','Occupé'],['off','Absent'],['inactive','Inactif']]" :key="s[0]" :value="s[0]">{{ s[1] }}</option>
                </select>
                <div v-if="tech.absenceReason && !tech.active" class="sb-gps-absence-info">
                  {{ ABSENCE_REASONS.find(r => r.value === tech.absenceReason)?.icon || '📋' }}
                  {{ ABSENCE_REASONS.find(r => r.value === tech.absenceReason)?.label || tech.absenceReason }}
                  <span v-if="tech.absenceUntil" class="sb-gps-absence-date">→ {{ tech.absenceUntil }}</span>
                </div>
              </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 class="sb-gps-actions">
                <button v-if="tech.active" class="sb-gps-absence-btn" @click="openAbsenceModal(tech)" title="Mettre en absence">⏸</button>
                <button v-else class="sb-gps-react-btn" @click="endAbsence(tech)" title="Réactiver">▶</button>
              </td>
            </tr>
            <tr class="sb-gps-add-row">
              <td><input v-model="newTechName" class="sb-gps-input" placeholder="Nom complet" @keydown.enter="addTech({ tech_group: newTechGroup.trim() || '' }); newTechGroup = ''" /></td>
              <td><input v-model="newTechGroup" class="sb-gps-input" placeholder="Groupe..." list="tech-groups" style="width:100px" /></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({ tech_group: newTechGroup.trim() || '' }); newTechGroup = ''" :disabled="!newTechName.trim() || addingTech">{{ addingTech ? '...' : '+ Ajouter' }}</button>
              </td>
            </tr>
          </tbody>
        </table>
        <datalist id="tech-groups">
          <option v-for="g in availableGroups" :key="g" :value="g" />
        </datalist>
        <div class="sb-gps-footer">
          <span class="sb-gps-info">{{ store.traccarDevices.length }} devices Traccar · {{ store.technicians.length }} techniciens · {{ availableGroups.length }} groupes</span>
        </div>
      </div>
    </div>
  </div>

  <!-- Absence Modal (z-index above GPS modal) -->
  <div v-if="absenceModalOpen" class="sb-overlay sb-overlay-top" @click.self="absenceModalOpen = false">
    <div class="sb-modal sb-absence-modal">
      <div class="sb-modal-hdr">
        <span>⏸ Mettre en absence — {{ absenceModalTech?.fullName }}</span>
        <button class="sb-rp-close" @click="absenceModalOpen = false">✕</button>
      </div>
      <div class="sb-modal-body">
        <div class="sb-absence-form">
          <label class="sb-absence-lbl">Raison</label>
          <div class="sb-absence-reasons">
            <button v-for="r in ABSENCE_REASONS" :key="r.value"
              class="sb-absence-reason-btn" :class="{ active: absenceForm.reason === r.value }"
              @click="absenceForm.reason = r.value">
              {{ r.icon }} {{ r.label }}
            </button>
          </div>

          <div class="sb-absence-dates">
            <div>
              <label class="sb-absence-lbl">Du</label>
              <input type="date" class="sb-form-input" v-model="absenceForm.from" />
            </div>
            <div>
              <label class="sb-absence-lbl">Jusqu'au <span class="sb-absence-opt">(optionnel)</span></label>
              <input type="date" class="sb-form-input" v-model="absenceForm.until" />
            </div>
          </div>

          <div v-if="store.jobs.filter(j => j.assignedTech === absenceModalTech?.id).length" class="sb-absence-jobs">
            <label class="sb-absence-lbl">
              {{ store.jobs.filter(j => j.assignedTech === absenceModalTech?.id).length }} job(s) assigné(s)
            </label>
            <div class="sb-absence-job-actions">
              <label class="sb-absence-radio">
                <input type="radio" v-model="absenceForm.jobAction" value="unassign" />
                Désassigner (retour dans le pool non assigné)
              </label>
              <label class="sb-absence-radio">
                <input type="radio" v-model="absenceForm.jobAction" value="keep" />
                Garder assignés (réassigner manuellement après)
              </label>
            </div>
            <div class="sb-absence-job-list">
              <div v-for="j in store.jobs.filter(j => j.assignedTech === absenceModalTech?.id)" :key="j.id" class="sb-absence-job-item">
                <span class="sb-absence-job-dot" :style="{ background: j.priority === 'urgent' ? '#ef4444' : j.priority === 'high' ? '#f59e0b' : '#6366f1' }"></span>
                {{ j.subject }} <span class="sb-absence-job-date">{{ j.scheduledDate || '—' }}</span>
              </div>
            </div>
          </div>
          <div v-else class="sb-absence-no-jobs">Aucun job assigné actuellement.</div>
        </div>
      </div>
      <div class="sb-modal-ftr">
        <button class="sbf-primary-btn" @click="confirmAbsence" :disabled="absenceProcessing">
          {{ absenceProcessing ? 'En cours...' : '⏸ Confirmer l\'absence' }}
        </button>
        <button class="sb-rp-btn" @click="absenceModalOpen = false">Annuler</button>
      </div>
    </div>
  </div>

  <!-- Schedule editor modal -->
  <div v-if="scheduleModalTech" class="sb-overlay sb-overlay-top" @click.self="scheduleModalTech = null">
    <div class="sb-modal sb-schedule-modal">
      <div class="sb-modal-hdr">
        <span>🗓 Horaire — {{ scheduleModalTech.fullName }}</span>
        <button class="sb-rp-close" @click="scheduleModalTech = null">✕</button>
      </div>
      <div class="sb-schedule-presets">
        <button v-for="p in SCHEDULE_PRESETS" :key="p.key" class="sb-preset-btn" @click="applySchedulePreset(p)">{{ p.label }}</button>
      </div>
      <div class="sb-schedule-grid">
        <div v-for="d in WEEK_DAYS" :key="d" class="sb-schedule-day" :class="{ 'sb-schedule-off': !scheduleForm[d]?.on }">
          <label class="sb-schedule-toggle">
            <input type="checkbox" v-model="scheduleForm[d].on" />
            <span class="sb-schedule-day-label">{{ DAY_LABELS[d] }}</span>
          </label>
          <template v-if="scheduleForm[d]?.on">
            <input type="time" v-model="scheduleForm[d].start" class="sb-schedule-time" />
            <span class="sb-schedule-sep">→</span>
            <input type="time" v-model="scheduleForm[d].end" class="sb-schedule-time" />
            <span class="sb-schedule-hours">{{ ((timeToH(scheduleForm[d].end) - timeToH(scheduleForm[d].start)) || 0).toFixed(1) }}h</span>
          </template>
          <span v-else class="sb-schedule-off-label">Repos</span>
        </div>
      </div>
      <div class="sb-modal-footer">
        <button class="sb-rp-btn" @click="scheduleModalTech = null">Annuler</button>
        <button class="sb-rp-btn sb-rp-primary" @click="confirmSchedule">Enregistrer</button>
      </div>
    </div>
  </div>

</div>
</template>

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