<script setup>
/**
 * JobPool — liste UNIFIÉE « Jobs à assigner » (source unique, remplace les 3 réimplémentations divergentes).
 *   variant='floating' → panneau flottant (desktop) : cases à cocher, menu d'assignation, niveau, fil, badges.
 *   variant='docked'   → colonne « Jour »/kanban : recherche + tri, cartes glissables (drag-only).
 *   variant='mobile'   → liste tactile : chips compétence, tri, swipe = sélectionner, tap = feuille.
 *
 * Le composant est PRÉSENTATIONNEL : l'état/dérivés viennent du prop `pool` (instance useJobPool, détenue par la page,
 * → la carte du panneau garde sa source de vérité) et TOUS les handlers/formatters de `inject('jobPoolCtx')` (fournis
 * une seule fois par la page → selectedJobs/draggingSet gardent la MÊME identité, donc le bulk-assign reste cohérent).
 */
import { reactive, computed, inject } from 'vue'
import { skillSym } from 'src/composables/useSkillIcons' // icône de compétence (source unique)

const props = defineProps({
  variant: { type: String, default: 'floating' }, // floating | docked | mobile
  pool: { type: Object, required: true }, // instance useJobPool
  selectedDay: { type: String, default: '' }, // mobile : jour sélectionné (en-têtes secteur + tri « pertinence »)
})

const ctx = inject('jobPoolCtx') // handlers + état partagé + formatters (fournis par la page)

const MOBILE_SORTS = [{ value: 'smart', label: 'Pertinence' }, { value: 'sector', label: 'Secteur' }, { value: 'date', label: 'Date prévue' }, { value: 'prio', label: 'Priorité' }, { value: 'dur', label: 'Durée' }]
const DOCKED_SORTS = [{ label: 'Priorité', value: 'prio' }, { label: 'Ville', value: 'city' }, { label: 'Distance (dépôt)', value: 'dist' }, { label: 'Compétence', value: 'skill' }, { label: 'Durée', value: 'dur' }]

// `pool` est un objet PLAIN de refs (instance useJobPool) → les refs imbriquées ne se déballent PAS dans le template.
// On les ré-expose ici en bindings locaux (déballés + réactifs) ; les tris/recherche/filtres sont writables (get/set).
const P = props.pool
const filteredJobs = computed(() => P.filteredJobs.value)
const groups = computed(() => P.groups.value)
const skillChips = computed(() => P.skillChips.value)
const dateChips = computed(() => P.dateChips.value)
const allCollapsed = computed(() => P.allCollapsed.value)
const collapsed = computed(() => P.collapsed.value)
const skillFilter = computed(() => P.skillFilter.value)
const dateFilter = computed(() => P.dateFilter.value)
const sort = computed({ get: () => P.sort.value, set: v => { P.sort.value = v } })
const sortDir = computed({ get: () => P.sortDir.value, set: v => { P.sortDir.value = v } })
const search = computed({ get: () => P.search.value, set: v => { P.search.value = v } })

// Couleur de bordure d'une carte kanban : compétence, sinon département legacy, sinon gris.
function kbColor (j) { return j.skill ? ctx.getTagColor(j.skill) : (j.required_skill ? ctx.getTagColor(j.required_skill) : (ctx.legacyDeptColor(j.legacy_dept) || '#90a4ae')) }

// ── Swipe mobile (pointer events) : tap = ouvrir la feuille (onJobClick) · glissé horizontal franc = sélectionner ──
const swipe = reactive({ name: null, x0: 0, y0: 0, dx: 0, active: false })
let swipeGuard = false
function swipeStart (e, j) { if (e.pointerType === 'mouse' && e.button !== 0) return; swipe.name = j.name; swipe.x0 = e.clientX; swipe.y0 = e.clientY; swipe.dx = 0; swipe.active = false }
function swipeMove (e) {
  if (swipe.name == null) return
  const dx = e.clientX - swipe.x0; const dy = e.clientY - swipe.y0
  if (!swipe.active) {
    if (Math.abs(dx) > 10 && Math.abs(dx) > Math.abs(dy) * 1.4) { swipe.active = true; try { e.currentTarget.setPointerCapture(e.pointerId) } catch (_) {} }
    else if (Math.abs(dy) > 10) { swipe.name = null; return }
  }
  if (swipe.active) { e.preventDefault(); swipe.dx = Math.max(-120, Math.min(120, dx)) }
}
function swipeEnd (e, j) {
  if (swipe.name == null) return
  const active = swipe.active; const dx = swipe.dx
  swipe.name = null; swipe.active = false; swipe.dx = 0
  if (!active) return
  swipeGuard = true; setTimeout(() => { swipeGuard = false }, 380)
  if (Math.abs(dx) > 55) ctx.toggleSel(j)
}
function swipeReset () { swipe.name = null; swipe.active = false; swipe.dx = 0 }
function onJobClick (j) { if (swipeGuard) { swipeGuard = false; return } ctx.openJobSheet(j) }
function swipeStyle (j) { return (swipe.active && swipe.name === j.name) ? { transform: 'translateX(' + swipe.dx + 'px)' } : {} }
</script>

<template>
  <template v-if="variant === 'mobile'">
    <div class="pm-pool-hd"><q-icon name="inbox" size="16px" class="q-mr-xs" />À assigner <span class="pm-count">{{ filteredJobs.length }}</span><q-space /><span class="text-grey-5 q-mr-sm" style="font-size:10px;display:inline-flex;align-items:center;gap:1px">prio<i class="pm-leg" style="background:#ef4444"></i><i class="pm-leg" style="background:#f59e0b"></i><i class="pm-leg" style="background:#9e9e9e"></i></span>
      <q-select :model-value="sort" @update:model-value="v => sort = v" :options="MOBILE_SORTS" dense options-dense borderless emit-value map-options class="pm-sort"><template #prepend><q-icon name="sort" size="15px" color="grey-6" /></template></q-select>
    </div>
    <div v-if="skillChips.length" class="pm-chips">
      <button v-for="s in skillChips" :key="s.k" type="button" class="pm-chip" :class="{ on: skillFilter.includes(s.k) }" :style="skillFilter.includes(s.k) ? { background: ctx.getTagColor(s.k), borderColor: ctx.getTagColor(s.k), color: '#fff' } : {}" @click="pool.toggleSkill(s.k)">{{ s.k }} {{ s.n }}</button>
      <button v-if="skillFilter.length" type="button" class="pm-chip pm-chip-x" @click="pool.clearSkills()">✕ tout</button>
    </div>
    <div class="pm-pool-list">
      <template v-for="grp in groups" :key="grp.key">
        <div v-if="grp.label" class="pm-sector-hd" @click="ctx.selectAllSector(grp.key)"><q-icon name="place" size="13px" color="green-5" />{{ grp.key }}<q-icon name="done_all" size="14px" color="primary" class="q-ml-xs"><q-tooltip class="bg-grey-9">Sélectionner / désélectionner tout ce secteur (puis « Assigner »)</q-tooltip></q-icon><span style="margin-left:auto;font-weight:400;color:#94a3b8">{{ grp.n }} · {{ ctx.fmtMin(grp.mins || 0) }}</span></div>
        <div v-for="j in grp.jobs" :key="j.name" class="pm-swipe">
          <div class="pm-swipe-bg"><span class="pm-swipe-hint l"><q-icon name="check_circle" size="17px" /> Sélectionner</span><span class="pm-swipe-hint r">Sélectionner <q-icon name="check_circle" size="17px" /></span></div>
          <button type="button" class="pm-job" :class="{ sel: ctx.selectedJobs[j.name], hold: j.status === 'On Hold', swiping: swipe.active && swipe.name === j.name }" :style="{ borderLeft: '4px solid ' + ctx.prioColor(j.priority), ...swipeStyle(j) }"
                  @click="onJobClick(j)" @pointerdown="swipeStart($event, j)" @pointermove="swipeMove($event)" @pointerup="swipeEnd($event, j)" @pointercancel="swipeReset">
            <q-icon :name="ctx.selectedJobs[j.name] ? 'check_circle' : (ctx.jobIsOnsite(j) ? 'home_repair_service' : 'cloud')" size="16px" :color="ctx.selectedJobs[j.name] ? 'primary' : (ctx.jobIsOnsite(j) ? 'teal' : 'grey-5')" class="q-mr-xs" />
            <span class="pm-job-main">
              <span class="pm-job-t ellipsis">{{ j.subject || j.service_type || j.name }}</span>
              <span class="pm-job-s ellipsis">{{ j.required_skill || '—' }}<template v-if="j.customer_name"> · {{ j.customer_name }}</template></span>
              <span v-if="pool.jobAddrBadge(j)" class="pm-addr" :class="{ multi: pool.jobAddrBadge(j).multi }"><q-icon :name="pool.jobAddrBadge(j).multi ? 'workspaces' : 'place'" size="11px" />{{ pool.jobAddrBadge(j).n }} jobs<q-tooltip class="bg-grey-9">{{ pool.jobAddrBadge(j).n }} job(s) à cette adresse<template v-if="pool.jobAddrBadge(j).multi"> — nécessite un tech avec <b>{{ pool.jobAddrBadge(j).skills.join(' + ') }}</b></template>.</q-tooltip></span>
              <span v-if="j.scheduled_date || j.creation" class="pm-job-d">
                <span v-if="j.scheduled_date" :class="ctx.isOverdue(j.scheduled_date) ? 'text-warning text-weight-medium' : 'text-grey-6'">📅 {{ ctx.fmtDueLabel(j.scheduled_date) }}</span>
                <span v-if="j.creation" class="text-grey-5"> · créé {{ String(j.creation).slice(0, 10) }}</span>
              </span>
              <span v-if="j.notes" class="pm-job-note ellipsis"><q-icon name="sticky_note_2" size="12px" color="warning" /> {{ j.notes }}</span>
            </span>
            <span class="pm-job-icons">
              <q-icon :name="j.priority === 'high' ? 'star' : 'star_border'" :color="j.priority === 'high' ? 'amber-7' : 'grey-5'" size="21px" @click.stop="ctx.toggleUrgent(j)"><q-tooltip>{{ j.priority === 'high' ? 'Urgent — toucher pour retirer' : 'Marquer urgent (priorité, heure fixée / SLA)' }}</q-tooltip></q-icon>
              <q-icon :name="j.notes ? 'sticky_note_2' : 'note_add'" :color="j.notes ? 'deep-orange-6' : 'grey-4'" size="19px" @click.stop="ctx.openJobSheet(j, { note: true })"><q-tooltip>Note importante / détails</q-tooltip></q-icon>
              <q-icon name="more_vert" color="grey-6" size="19px" @click.stop="ctx.openJobSheet(j)"><q-tooltip>Options (priorité, statut, compétence, report)</q-tooltip></q-icon>
            </span>
            <span class="pm-job-h">{{ j.est_min ? ctx.fmtMin(j.est_min) : ((j.duration_h || 1) + 'h') }}</span>
          </button>
        </div>
      </template>
      <div v-if="!filteredJobs.length" class="pm-empty">Rien à assigner 🎉</div>
    </div>
  </template>
  <template v-else-if="variant === 'docked'">
    <div class="kbb-pool-hd"><q-icon name="inbox" size="15px" />&nbsp;À assigner <span class="kb-count">{{ filteredJobs.length }}</span></div>
    <div class="kbb-pool-tools">
      <q-input dense outlined :model-value="search" @update:model-value="v => search = v || ''" placeholder="Rechercher…" clearable><template #prepend><q-icon name="search" size="16px" /></template></q-input>
      <q-select dense outlined :model-value="sort" @update:model-value="v => sort = v" emit-value map-options :options="DOCKED_SORTS"><template #prepend><q-icon name="sort" size="16px" /></template></q-select>
      <div v-if="skillChips.length > 1" class="kb-chips">
        <button v-for="s in skillChips" :key="s.k" type="button" class="kb-chip" :class="{ on: skillFilter.includes(s.k) }" :style="skillFilter.includes(s.k) ? { background: ctx.getTagColor(s.k), borderColor: ctx.getTagColor(s.k), color: '#fff' } : {}" @click="pool.toggleSkill(s.k)">{{ s.k }} {{ s.n }}</button>
        <button v-if="skillFilter.length" type="button" class="kb-chip kb-chip-x" @click="pool.clearSkills()">✕</button>
      </div>
    </div>
    <div class="kbb-pool-body">
      <template v-for="grp in groups" :key="grp.key">
        <div v-if="grp.label" class="kb-grp-hd">{{ grp.label }} <span style="opacity:.6">({{ grp.n }})</span></div>
        <div v-for="j in grp.jobs" :key="j.name" class="kb-card" :class="{ hold: j.status === 'On Hold', dragging: ctx.draggingSet.has(j.name) }" :style="{ borderLeftColor: kbColor(j) }" :draggable="j.status !== 'On Hold'" @dragstart="ctx.onJobDragStart($event, j)" @dragend="ctx.onJobDragEnd">
          <div class="kb-card-t"><q-icon :name="skillSym(j.required_skill)" size="13px" :style="{ color: kbColor(j) }" />&nbsp;{{ j.subject || j.service_type || j.name }}</div>
          <div class="kb-card-m"><span class="kb-dot" :style="{ background: ctx.prioColor(j.priority) }"></span>{{ j.est_min ? ctx.fmtMin(j.est_min) : ((j.duration_h || 1) + 'h') }}<template v-if="ctx.jobCity(j)"> · {{ ctx.jobCity(j) }}</template><span v-if="pool.jobAddrBadge(j)" class="kb-addr" :class="{ multi: pool.jobAddrBadge(j).multi }"><q-icon :name="pool.jobAddrBadge(j).multi ? 'workspaces' : 'place'" size="10px" />{{ pool.jobAddrBadge(j).n }}</span></div>
          <q-tooltip class="bg-grey-9" :delay="350" style="font-size:11px">{{ j.subject || j.service_type }}<template v-if="j.customer_name"><br>{{ j.customer_name }}</template><template v-if="j.address"><br>📍 {{ j.address }}</template><template v-if="j.required_skill"><br>🧩 {{ j.required_skill }}</template><template v-if="pool.jobAddrBadge(j) && pool.jobAddrBadge(j).multi"><br>👥 {{ pool.jobAddrBadge(j).n }} jobs à cette adresse — tech avec {{ pool.jobAddrBadge(j).skills.join(' + ') }}</template><template v-if="j.status === 'On Hold'"><br>🔒 en attente — non assignable</template></q-tooltip>
        </div>
      </template>
      <div v-if="!filteredJobs.length" class="kb-empty">— rien à assigner —</div>
    </div>
  </template>
  <template v-else>
    <div class="assign-sortbar" @mousedown.stop>
      <span>Trier :</span>
      <select :value="sort" @change="e => sort = e.target.value" @mousedown.stop>
        <option value="group">Groupe (parent-enfant)</option>
        <option value="skill">Compétence</option>
        <option value="date">Date</option>
        <option value="city">Ville</option>
        <option value="priority">Priorité</option>
      </select>
      <q-btn dense flat size="sm" :icon="sortDir === 'asc' ? 'arrow_upward' : 'arrow_downward'" @click="sortDir = sortDir === 'asc' ? 'desc' : 'asc'" @mousedown.stop><q-tooltip>{{ sortDir === 'asc' ? 'Croissant (ancien → récent)' : 'Décroissant (récent → ancien)' }}</q-tooltip></q-btn>
      <q-btn dense flat size="sm" :icon="allCollapsed ? 'unfold_more' : 'unfold_less'" no-caps @click="pool.toggleCollapseAll()" @mousedown.stop><q-tooltip>{{ allCollapsed ? 'Tout déplier' : 'Tout replier (voir un seul groupe)' }}</q-tooltip></q-btn>
      <q-space />
      <q-btn v-if="ctx.selectedNames.value.length" dense flat size="sm" icon="deselect" :label="'✕ ' + ctx.selectedNames.value.length" no-caps color="grey-7" @click="ctx.clearSel()" @mousedown.stop><q-tooltip>Tout désélectionner</q-tooltip></q-btn>
    </div>
    <div v-if="skillChips.length > 1" class="assign-chips" @mousedown.stop>
      <span v-for="t in skillChips" :key="t.k" class="assign-chip-f" :style="skillFilter.includes(t.k) ? { background: ctx.getTagColor(t.k), color: '#fff', borderColor: ctx.getTagColor(t.k) } : { borderColor: ctx.getTagColor(t.k) }" @click="pool.toggleSkill(t.k)">{{ t.k }} <b>{{ t.n }}</b></span>
      <span v-if="skillFilter.length" class="assign-chip-f clear" @click="pool.clearSkills()"><q-tooltip>Tout afficher</q-tooltip>✕</span>
    </div>
    <div v-if="dateChips.length > 1" class="assign-chips" @mousedown.stop>
      <q-icon name="event" size="14px" color="grey-6" class="q-mr-xs" />
      <span v-for="d in dateChips" :key="d.iso" class="assign-chip-f" :style="dateFilter.includes(d.iso) ? { background: d.overdue ? '#f59e0b' : '#6366f1', color: '#fff', borderColor: d.overdue ? '#f59e0b' : '#6366f1' } : (d.overdue ? { borderColor: '#f59e0b', color: '#b45309' } : {})" @click="pool.toggleDate(d.iso)">{{ d.label }} <b>{{ d.n }}</b></span>
      <span v-if="dateFilter.length" class="assign-chip-f clear" @click="pool.clearDates()"><q-tooltip>Toutes les dates</q-tooltip>✕</span>
    </div>
    <div class="assign-body">
      <div v-if="!filteredJobs.length" class="text-grey-6 q-pa-md text-center">Aucun job à assigner 🎉</div>
      <div v-for="grp in groups" :key="grp.key" class="assign-grp" :class="{ 'grp-hl': ctx.groupSelected(grp) }">
        <div v-if="grp.label" class="assign-grp-lbl" style="cursor:pointer;display:flex;align-items:center;gap:3px" @click="pool.toggleCollapse(grp.key)"><q-icon :name="collapsed.has(grp.key) ? 'chevron_right' : 'expand_more'" size="15px" /><span>{{ grp.label }} <span style="opacity:.6">({{ grp.jobs.length }})</span></span><q-space /><q-icon name="done_all" size="16px" :color="ctx.groupSelected(grp) ? 'primary' : 'grey-5'" @click.stop="ctx.toggleGroupSelAll(grp)"><q-tooltip>Sélectionner / désélectionner tout ce groupe</q-tooltip></q-icon></div>
        <template v-if="!(grp.label && collapsed.has(grp.key))">
          <div v-if="sort === 'group' && grp.jobs.length > 1" class="assign-grp-hdr" @click="ctx.toggleGroupSel(grp)"><q-icon name="account_tree" size="12px" /> Groupe ({{ grp.jobs.length }}) — tout sélectionner (terrain)</div>
          <div v-for="(j, idx) in grp.jobs" :key="j.name" :data-jobname="j.name" class="assign-job" :class="{ blocked: j.status === 'On Hold', child: sort === 'group' && grp.jobs.length > 1 && idx > 0, sel: !!ctx.selectedJobs[j.name], dragging: ctx.draggingSet.has(j.name), 'job-flash': ctx.flashJob.value === j.name, assoc: pool.jobAssocOnly(j) }" :style="{ borderLeft: '5px solid ' + ctx.panelJobColor(j) }" draggable="true" @dragstart="ctx.onJobDragStart($event, j)" @dragend="ctx.onJobDragEnd">
            <div class="row items-center no-wrap">
              <q-checkbox dense size="xs" :model-value="!!ctx.selectedJobs[j.name]" @update:model-value="v => { if (v) ctx.selectedJobs[j.name] = true; else delete ctx.selectedJobs[j.name] }" @click.stop @mousedown.stop class="q-mr-xs" />
              <q-icon :name="ctx.jobIsOnsite(j) ? 'home_repair_service' : 'cloud'" size="13px" :color="ctx.jobIsOnsite(j) ? 'teal' : 'grey-5'" class="q-mr-xs"><q-tooltip>{{ ctx.jobIsOnsite(j) ? 'Sur site (terrain)' : 'À distance / netadmin — pas pour un tech terrain' }}</q-tooltip></q-icon>
              <span v-if="ctx.groupLabel(j)" class="assign-grp-badge q-mr-xs"><q-tooltip class="bg-grey-9">Même adresse de service (groupe {{ ctx.groupLabel(j)[0] }}) — simple repère de proximité</q-tooltip>{{ ctx.groupLabel(j) }}</span>
              <q-badge v-if="j.step_order" color="indigo" class="q-mr-xs">{{ j.step_order }}</q-badge>
              <span class="ellipsis text-weight-medium" :style="ctx.showMap.value ? 'cursor:pointer' : ''" @click="ctx.showMap.value && ctx.focusAssignJob(j)">{{ j.subject || j.service_type || j.name }}<q-tooltip v-if="j.legacy_detail" max-width="380px" class="bg-grey-9" style="white-space:pre-wrap;font-size:11px">{{ j.legacy_detail }}</q-tooltip></span>
              <span v-if="ctx.jobSlaBadge(j)" class="assign-sla" :class="'sla-' + ctx.jobSlaBadge(j).state"><q-icon :name="ctx.jobSlaBadge(j).icon" size="11px" />{{ ctx.jobSlaBadge(j).short }}<q-tooltip class="bg-grey-9">{{ ctx.jobSlaBadge(j).label }}</q-tooltip></span>
              <q-space />
              <q-btn v-if="j.status !== 'On Hold'" flat dense round size="sm" icon="person_add" color="primary" class="q-ml-xs" @click.stop @mousedown.stop>
                <q-tooltip>Assigner à un technicien (classés par pertinence : compétence · dispo · distance · charge)</q-tooltip>
                <q-menu anchor="bottom right" self="top right" @click.stop>
                  <div class="assign-pick-hd">Assigner « {{ (j.subject || j.name).slice(0, 30) }} » · 📅 {{ ctx.fmtDueLabel(ctx.jobTargetDay(j)) }}</div>
                  <q-list dense style="min-width:270px;max-height:44vh;overflow:auto">
                    <q-item v-for="tk in ctx.techsForJob(j)" :key="tk.id" clickable v-close-popup @click="ctx.quickAssign(j, tk)" :class="{ 'assign-pick-incap': !tk.capable }">
                      <q-item-section avatar style="min-width:32px"><q-avatar size="26px" :color="tk.capable ? 'blue-grey-1' : 'grey-3'" text-color="blue-grey-8" style="font-size:10px;font-weight:700">{{ ctx.initials(tk.name) }}</q-avatar></q-item-section>
                      <q-item-section>
                        <q-item-label>{{ tk.name }}<q-icon v-if="!tk.capable && j.required_skill" name="warning" size="12px" color="orange" class="q-ml-xs"><q-tooltip>Compétence « {{ j.required_skill }} » absente</q-tooltip></q-icon></q-item-label>
                        <q-item-label caption><span v-if="tk.distKm != null">📍 {{ Math.round(tk.distKm) }} km</span><span v-else class="text-grey-5">dist. ?</span> · {{ tk.h }}/{{ tk.cap }}h<span v-if="tk.noShift" class="text-warning"> · ▲ sans quart</span><span v-else-if="tk.over" class="text-negative"> · surchargé</span></q-item-label>
                      </q-item-section>
                      <q-item-section side><q-icon name="chevron_right" size="16px" color="grey-5" /></q-item-section>
                    </q-item>
                    <q-item v-if="!ctx.techsForJob(j).length"><q-item-section class="text-grey-6 text-caption">Aucun technicien visible.</q-item-section></q-item>
                  </q-list>
                </q-menu>
              </q-btn>
              <q-icon v-if="j.legacy_ticket_id" name="forum" size="14px" :color="j._showThread ? 'indigo' : 'grey-5'" class="q-ml-xs" style="cursor:pointer" @click.stop="ctx.toggleAssignThread(j)" @mousedown.stop><q-tooltip>Voir le fil du ticket #{{ j.legacy_ticket_id }}</q-tooltip></q-icon>
              <q-icon v-if="j.status === 'On Hold'" name="lock" size="13px" color="orange"><q-tooltip>En attente de {{ j.depends_on || 'la tâche précédente' }}</q-tooltip></q-icon>
            </div>
            <div class="assign-sub">
              <span v-if="j.required_skill" class="assign-skill" :style="{ background: ctx.getTagColor(j.required_skill) }">{{ j.required_skill }}</span>
              <span v-if="pool.jobAssocOnly(j)" class="assign-linked"><q-icon name="link" size="11px" />lié<q-tooltip class="bg-grey-9">Affiché car un autre job filtré est à la MÊME adresse — à dispatcher avec lui (même tech).</q-tooltip></span>
              <span v-if="pool.jobAddrBadge(j)" class="assign-addr" :class="{ multi: pool.jobAddrBadge(j).multi }"><q-icon :name="pool.jobAddrBadge(j).multi ? 'workspaces' : 'place'" size="11px" />{{ pool.jobAddrBadge(j).n }} jobs<q-tooltip class="bg-grey-9">{{ pool.jobAddrBadge(j).n }} job(s) à cette adresse<template v-if="pool.jobAddrBadge(j).multi"> — nécessite un tech avec <b>{{ pool.jobAddrBadge(j).skills.join(' + ') }}</b></template>.</q-tooltip></span>
              <span v-if="j.required_skill" class="assign-lvl" :class="{ set: j.required_level > 1 }" @click.stop @mousedown.stop><q-icon name="military_tech" size="11px" />niv&nbsp;{{ j.required_level || 1 }}<q-tooltip class="bg-grey-9">Niveau min requis pour ce job (persistant · mode « juste ce qu'il faut » → réserve les experts)</q-tooltip><q-menu anchor="bottom left" self="top left" @click.stop><q-list dense style="min-width:150px"><q-item v-for="n in [1,2,3,4,5]" :key="n" clickable v-close-popup @click="ctx.setJobReqLevel(j, n)" :active="(j.required_level||1)===n"><q-item-section>Niveau {{ n }}{{ n===1 ? ' — de base' : (n===5 ? ' — expert' : '') }}</q-item-section></q-item></q-list></q-menu></span>
              {{ j.customer_name || j.location_label || j.service_location || '' }}<span v-if="j.depends_on"> · après {{ j.depends_on }}</span> · <span class="text-positive text-weight-medium">≈ {{ j.est_min ? ctx.fmtMin(j.est_min) : (j.duration_h || 1) + 'h' }}<q-tooltip v-if="j.est_labels && j.est_labels.length" class="bg-grey-9" style="font-size:11px">Estimé (auto) : {{ j.est_labels.join(' + ') }}</q-tooltip></span><span v-if="j.scheduled_date"> · 📅 <span :class="ctx.isOverdue(j.scheduled_date) ? 'text-warning text-weight-bold' : 'text-grey-7'">{{ ctx.fmtDueLabel(j.scheduled_date) }}</span><q-icon name="edit_calendar" size="14px" :color="ctx.isOverdue(j.scheduled_date) ? 'warning' : 'grey-5'" class="q-ml-xs" style="cursor:pointer" @click.stop @mousedown.stop><q-tooltip>Replanifier (aujourd'hui / demain / date)</q-tooltip><q-menu anchor="bottom left" self="top left" @click.stop><q-list dense style="min-width:160px"><q-item clickable v-close-popup @click="ctx.setJobDate(j, ctx.todayISO())"><q-item-section avatar style="min-width:28px"><q-icon name="today" size="18px" color="primary" /></q-item-section><q-item-section>Aujourd'hui</q-item-section></q-item><q-item clickable v-close-popup @click="ctx.setJobDate(j, ctx.tomorrowISO())"><q-item-section avatar style="min-width:28px"><q-icon name="event" size="18px" color="primary" /></q-item-section><q-item-section>Demain</q-item-section></q-item><q-separator /><q-date :model-value="j.scheduled_date" @update:model-value="v => ctx.setJobDate(j, v)" mask="YYYY-MM-DD" minimal today-btn /></q-list></q-menu></q-icon></span>
            </div>
            <div v-if="j._showThread" class="assign-thread" @mousedown.stop @click.stop>
              <div v-if="j._thread && j._thread.loading" class="text-grey-6" style="font-size:11px"><q-spinner size="12px" class="q-mr-xs" />Chargement du fil…</div>
              <template v-else-if="j._thread && j._thread.messages && j._thread.messages.length">
                <div v-for="(m, mi) in j._thread.messages" :key="mi" class="assign-msg"><b>{{ m.author }}</b><span v-if="m.at" class="text-grey-5"> · {{ ctx.fmtDT(m.at) }}</span><div class="assign-msg-txt">{{ m.text }}</div></div>
              </template>
              <div v-else class="text-grey-6" style="font-size:11px;white-space:pre-wrap">{{ j.legacy_detail || 'Aucun fil pour ce ticket.' }}</div>
              <div class="text-right q-mt-xs"><q-btn dense flat size="sm" color="negative" icon="block" label="Fermer ce ticket (legacy)" @click="ctx.closeLegacyTicket(j)"><q-tooltip>Ticket inutile → status closed dans osTicket + sort du dispatch</q-tooltip></q-btn></div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </template>
</template>

<!-- Styles NON scopés (préfixes assign-/kb-/pm- = pas de collision) — le composant est monté DANS la page dont le <style scoped> ne l'atteint pas. -->
<style>
/* ── FLOATING ── */
.assign-sortbar { display: flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 11px; color: #555; background: #f3f0fa; border-bottom: 1px solid #e0e0e0; }
.assign-sortbar select { font-size: 11px; border: 1px solid #cfc4e8; border-radius: 5px; padding: 1px 4px; background: #fff; color: #333; flex: 1; }
.assign-body { overflow: auto; padding: 5px; flex: 1 1 auto; min-height: 60px; }
.assign-chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 6px; border-bottom: 1px solid #eee; flex: 0 0 auto; }
.assign-chip-f { font-size: 10px; border: 1.5px solid #cfd8dc; border-radius: 11px; padding: 1px 8px; cursor: pointer; user-select: none; line-height: 1.6; background: #fff; color: #455a64; }
.assign-chip-f.clear { border-color: #b0bec5; color: #607d8b; font-weight: 700; }
.assign-chip-f:hover { filter: brightness(0.95); }
.assign-grp-badge { display: inline-block; background: #cfd8dc; color: #455a64; font-weight: 700; border-radius: 4px; padding: 0 5px; font-size: 10px; line-height: 16px; }
.assign-grp { margin-bottom: 6px; border-radius: 7px; padding: 2px; }
.assign-grp-lbl { font-size: 11px; font-weight: 700; color: #37474f; padding: 3px 6px 2px; border-bottom: 1px solid #eee; margin-bottom: 2px; position: sticky; top: 0; background: #fff; z-index: 1; }
.assign-grp.grp-hl { background: #ede7f6; box-shadow: inset 0 0 0 1px #b39ddb; }
.assign-grp-hdr { font-size: 10px; font-weight: 700; color: #5e35b1; padding: 2px 6px; cursor: pointer; display: flex; align-items: center; gap: 3px; }
.assign-grp-hdr:hover { text-decoration: underline; }
.assign-job { border: 1px solid #e0e0e0; border-radius: 6px; padding: 3px 7px; margin: 3px 0; cursor: grab; background: #fafafa; font-size: 12px; }
.assign-job:hover { border-color: #5e35b1; background: #f3e9fb; }
.assign-job:active { cursor: grabbing; }
.assign-job.sel { border-color: #00897b; background: #e0f2f1; box-shadow: inset 0 0 0 1px #00897b; }
.assign-job.child { margin-left: 14px; border-left: 3px solid #b39ddb; }
.assign-job.blocked { opacity: .65; }
.assign-job.assoc { background: #fbfbfd; border-style: dashed; }
.assign-linked { display: inline-flex; align-items: center; gap: 1px; color: #6d4c9f; background: #ede7f6; border-radius: 6px; padding: 0 4px; font-size: 9px; font-weight: 700; margin-right: 3px; }
.assign-addr { display: inline-flex; align-items: center; gap: 1px; color: #37474f; background: #eceff1; border-radius: 6px; padding: 0 4px; font-size: 9px; font-weight: 700; margin-right: 3px; }
.assign-addr.multi { color: #fff; background: #7e57c2; }
.assign-job.job-flash { animation: jobflash 1.7s ease-out; }
@keyframes jobflash { 0% { background: #fff59d; box-shadow: inset 0 0 0 2px #fbc02d; } 100% { background: #fafafa; box-shadow: none; } }
.assign-sub { font-size: 10px; color: #888; margin-top: 1px; }
.assign-pick-hd { font-size: 11px; font-weight: 700; color: #455a64; padding: 6px 10px; border-bottom: 1px solid #eef2f7; background: #f7f5fc; }
.assign-pick-incap { opacity: 0.62; }
.assign-skill { display: inline-block; color: #fff; border-radius: 6px; padding: 0 5px; font-size: 9px; font-weight: 600; margin-right: 3px; }
.assign-lvl { display: inline-flex; align-items: center; gap: 1px; border: 1px solid #cfd8dc; border-radius: 6px; padding: 0 5px; font-size: 9px; font-weight: 600; color: #607d8b; margin-right: 3px; cursor: pointer; }
.assign-lvl.set { border-color: #6366f1; color: #4338ca; background: #eef2ff; }
.assign-sla { display: inline-flex; align-items: center; gap: 2px; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 8px; white-space: nowrap; margin-left: 4px; }
.assign-sla.sla-breached { background: #fee2e2; color: #b91c1c; }
.assign-sla.sla-at_risk { background: #ffedd5; color: #c2410c; }
.assign-thread { background: #f5f5f5; border-radius: 5px; padding: 4px 7px; margin-top: 4px; max-height: 220px; overflow: auto; }
.assign-msg { font-size: 11px; border-top: 1px solid #e0e0e0; padding: 3px 0; }
.assign-msg:first-child { border-top: none; }
.assign-msg-txt { white-space: pre-wrap; color: #37474f; }
/* ── DOCKED (kanban) ── */
.kbb-pool-hd { display: flex; align-items: center; font-size: 12px; font-weight: 700; color: #2b3445; padding: 7px 9px 4px; }
.kbb-pool-tools { display: flex; flex-direction: column; gap: 5px; padding: 0 7px 7px; border-bottom: 1px solid #e0e6ef; }
.kbb-pool-body { flex: 1 1 auto; overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 5px; }
.kb-count { margin-left: auto; background: #d7deea; color: #3a4658; border-radius: 9px; padding: 0 7px; font-size: 11px; }
.kb-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.kb-chip { font-size: 10px; border: 1px solid #cbd5e1; background: #fff; color: #475569; border-radius: 999px; padding: 1px 8px; cursor: pointer; white-space: nowrap; }
.kb-chip.on { color: #fff; border-color: transparent; }
.kb-chip-x { border-color: #fca5a5; color: #dc2626; font-weight: 700; }
.kb-grp-hd { font-size: 10.5px; font-weight: 700; color: #475569; padding: 3px 4px 1px; position: sticky; top: 0; background: #eef2f7; z-index: 1; }
.kb-card { background: #fff; border: 1px solid #e6e9ef; border-left: 4px solid #90a4ae; border-radius: 6px; padding: 5px 7px; cursor: grab; box-shadow: 0 1px 2px rgba(0,0,0,.06); }
.kb-card:hover { box-shadow: 0 2px 6px rgba(0,0,0,.13); }
.kb-card:active { cursor: grabbing; }
.kb-card.dragging { opacity: .4; }
.kb-card.hold { opacity: .62; cursor: not-allowed; background: repeating-linear-gradient(45deg, #fafafa 0 6px, #f0f0f0 6px 12px); }
.kb-card-t { font-size: 12px; font-weight: 600; color: #222; line-height: 1.25; display: flex; align-items: center; }
.kb-card-m { font-size: 10.5px; color: #67707e; display: flex; align-items: center; gap: 4px; margin-top: 2px; }
.kb-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.kb-addr { display: inline-flex; align-items: center; gap: 1px; color: #37474f; background: #eceff1; border-radius: 5px; padding: 0 3px; font-size: 9px; font-weight: 700; }
.kb-addr.multi { color: #fff; background: #7e57c2; }
.kb-empty { font-size: 11px; color: #9aa3b0; text-align: center; padding: 10px 4px; border: 1px dashed #d7deea; border-radius: 6px; }
/* ── MOBILE ── */
.pm-pool-hd { display: flex; align-items: center; font-size: 12px; font-weight: 700; color: #475569; margin-bottom: 4px; }
.pm-count { margin-left: 6px; background: #e2e8f0; color: #475569; border-radius: 9px; font-size: 11px; padding: 0 7px; font-weight: 700; }
.pm-leg { width: 7px; height: 7px; border-radius: 2px; display: inline-block; margin-left: 2px; }
.pm-sort { font-size: 11px; min-height: 0; max-width: 120px; }
.pm-sort .q-field__control { min-height: 26px; }
.pm-sort .q-field__native { font-size: 11px; font-weight: 600; color: #475569; padding: 0; min-height: 26px; }
.pm-sort .q-field__prepend { padding-right: 2px; height: 26px; }
.pm-sort .q-field__marginal { height: 26px; }
.pm-chips { display: flex; gap: 5px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 2px; }
.pm-chips::-webkit-scrollbar { height: 0; }
.pm-chip { flex: 0 0 auto; border: 1px solid #cbd5e1; background: #fff; color: #475569; border-radius: 999px; font-size: 11px; font-weight: 600; padding: 3px 10px; cursor: pointer; white-space: nowrap; }
.pm-chip.on { color: #fff; border-color: transparent; }
.pm-chip-x { border-color: #fca5a5; color: #dc2626; }
.pm-pool-list { max-height: 46vh; overflow-y: auto; }
.pm-sector-hd { display: flex; align-items: center; gap: 4px; padding: 5px 8px; margin: 6px 0 2px; background: #eef2f7; border-radius: 6px; font-size: 11px; font-weight: 600; color: #475569; position: sticky; top: 0; z-index: 2; cursor: pointer; }
.pm-swipe { position: relative; margin-bottom: 6px; border-radius: 8px; overflow: hidden; }
.pm-swipe-bg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: space-between; border-radius: 8px; background: linear-gradient(90deg, #bbf7d0 0 50%, #c7d2fe 50% 100%); }
.pm-swipe-hint { display: flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 800; padding: 0 14px; }
.pm-swipe-hint.l { color: #166534; }
.pm-swipe-hint.r { color: #3730a3; }
.pm-swipe .pm-job { margin-bottom: 0; position: relative; touch-action: pan-y; transition: transform .18s ease; }
.pm-job { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; border: 1px solid #e2e8f0; background: #fff; border-radius: 8px; padding: 8px; margin-bottom: 6px; cursor: pointer; }
.pm-job.sel { border-color: #6366f1; background: #eef2ff; box-shadow: 0 0 0 2px rgba(99, 102, 241, .18); }
.pm-job.hold { background: repeating-linear-gradient(45deg, #f8fafc 0 7px, #eef2f7 7px 14px); cursor: not-allowed; }
.pm-job.hold .pm-job-t { color: #64748b; }
.pm-job.swiping { transition: none; }
.pm-job-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pm-job-t { font-size: 13px; font-weight: 600; color: #1e293b; }
.pm-job-s { font-size: 11px; color: #64748b; }
.pm-job-d { font-size: 10.5px; margin-top: 1px; }
.pm-job-note { font-size: 10.5px; margin-top: 1px; color: #c2410c; font-style: italic; max-width: 100%; }
.pm-job-icons { flex: 0 0 auto; display: flex; align-items: center; gap: 1px; }
.pm-job-icons .q-icon { padding: 2px; cursor: pointer; }
.pm-job-h { flex: 0 0 auto; font-size: 12px; font-weight: 700; color: #0d9488; min-width: 30px; text-align: right; }
.pm-addr { display: inline-flex; align-items: center; gap: 1px; align-self: flex-start; color: #37474f; background: #eceff1; border-radius: 5px; padding: 0 4px; font-size: 9px; font-weight: 700; margin-top: 1px; }
.pm-addr.multi { color: #fff; background: #7e57c2; }
.pm-empty { color: #94a3b8; font-size: 13px; text-align: center; padding: 12px; }
</style>
