<template>
  <q-page padding>
    <!-- Real-time outage alerts -->
    <OutageAlertsPanel class="q-mb-md" @open-ticket="id => $router.push('/tickets')" />

    <!-- KPI cards (cliquables → page liée · sous-ligne = tendance/contexte) -->
    <div class="row q-col-gutter-md q-mb-lg">
      <div class="col-6 col-md" v-for="stat in stats" :key="stat.label">
        <div class="ops-card ops-stat" :class="{ 'ops-stat-clk': stat.to }" @click="stat.to && $router.push(stat.to)">
          <div class="row items-center no-wrap q-gutter-x-sm">
            <q-icon :name="stat.icon" :style="{ color: stat.color }" size="22px" />
            <div class="col">
              <div class="ops-stat-value" :style="{ color: stat.color }">{{ stat.value }}</div>
              <div class="ops-stat-label">{{ stat.label }}</div>
              <div v-if="stat.sub" class="ops-stat-sub" :class="stat.subClass">{{ stat.sub }}</div>
            </div>
            <q-icon v-if="stat.to" name="chevron_right" size="18px" class="ops-stat-go" />
          </div>
        </div>
      </div>
    </div>

    <!-- Charge 2 semaines : 2 bandes d'occupation (courante + prochaine), jour courant marqué (liseré indigo). Clic jour → Planification (jour ciblé). -->
    <div class="ops-card q-mb-lg">
      <div class="row items-center q-mb-sm">
        <div class="text-subtitle1 text-weight-bold">Charge <span class="text-caption text-grey-6 q-ml-xs">2 semaines</span></div>
        <q-space />
        <!-- Filtre compétence : le dénominateur ne compte que les techs qualifiés (« libre » = pour CE type de job). -->
        <q-chip v-for="s in CAP_SKILLS" :key="s" clickable dense size="sm"
                :color="capSkill === s ? 'teal' : 'grey-3'" :text-color="capSkill === s ? 'white' : 'grey-8'"
                @click="capSkill = (capSkill === s ? '' : s); loadWeek()">{{ s }}</q-chip>
        <q-btn flat dense no-caps size="sm" color="primary" icon-right="chevron_right" label="Planification" @click="$router.push('/planification')" />
      </div>
      <div v-if="capSkill" class="text-caption text-teal-7 q-mb-xs">Disponibilité pour « {{ capSkill }} » — seuls les techs qualifiés comptent.</div>
      <template v-if="weekLoad.length">
        <div class="dash-wk-lbl">Cette semaine</div>
        <OccupancyStrip :days="weekLoad.slice(0, 7)" :selected="todayIso" @select="iso => $router.push({ path: '/planification', query: { day: iso } })" />
        <div class="dash-wk-lbl q-mt-sm">Semaine prochaine</div>
        <OccupancyStrip :days="weekLoad.slice(7, 14)" :selected="todayIso" @select="iso => $router.push({ path: '/planification', query: { day: iso } })" />
      </template>
      <div v-else class="text-caption text-grey-6 q-py-sm">Chargement de la charge…</div>
      <div class="text-caption text-grey-6 q-mt-sm row items-center q-gutter-x-sm">
        <span><span class="os-leg-dot" style="background:#22c55e"></span>libre</span>
        <span><span class="os-leg-dot" style="background:#f59e0b"></span>chargé</span>
        <span><span class="os-leg-dot" style="background:#ef4444"></span>saturé</span>
        <span v-if="weekUnplaced" class="text-warning">· <b>{{ weekUnplaced }}h</b> à répartir</span>
      </div>
    </div>

    <!-- Administration : contrôles secondaires (planificateur, facturation) repliés par défaut → la vue « coup d'œil » (KPI + charge + activité) reste épurée. -->
    <DisclosureSection title="Administration" icon="admin_panel_settings" persist-key="dash.admin" class="q-mb-lg">
      <template #summary>Planificateur {{ schedulerEnabled ? 'actif' : 'désactivé' }}<template v-if="draftInvoiceCount"> · {{ draftInvoiceCount }} brouillon(s)</template></template>
    <div class="row q-col-gutter-md">
      <div class="col-12 col-md-6">
        <div class="ops-card">
          <div class="text-subtitle1 text-weight-bold q-mb-sm">Planificateur</div>
          <div class="row items-center q-gutter-md">
            <q-chip :color="schedulerEnabled ? 'positive' : 'negative'" text-color="white" icon="schedule">
              {{ schedulerEnabled ? 'Actif' : 'Désactivé' }}
            </q-chip>
            <q-btn
              :label="schedulerEnabled ? 'Désactiver' : 'Activer'"
              :color="schedulerEnabled ? 'negative' : 'positive'"
              :icon="schedulerEnabled ? 'pause' : 'play_arrow'"
              :loading="togglingScheduler"
              dense no-caps
              @click="toggleScheduler"
            />
          </div>
        </div>
      </div>
      <div class="col-12 col-md-6">
        <div class="ops-card">
          <div class="text-subtitle1 text-weight-bold q-mb-sm">Facturation récurrente</div>

          <!-- Step indicators -->
          <div class="row items-center q-gutter-x-sm q-mb-sm">
            <q-chip :color="billingStep >= 1 ? 'primary' : 'grey-4'" text-color="white" dense size="sm" icon="receipt_long">1. Générer</q-chip>
            <q-icon name="chevron_right" size="16px" color="grey-5" />
            <q-chip :color="billingStep >= 2 ? 'amber-8' : 'grey-4'" text-color="white" dense size="sm" icon="publish">2. Soumettre</q-chip>
            <q-icon name="chevron_right" size="16px" color="grey-5" />
            <q-chip :color="billingStep >= 3 ? 'teal-7' : 'grey-4'" text-color="white" dense size="sm" icon="credit_score">3. PPA</q-chip>
          </div>

          <!-- Draft count indicator -->
          <div v-if="draftInvoiceCount > 0" class="q-mb-sm">
            <q-banner dense rounded class="bg-amber-1 text-warning" style="font-size:0.8rem">
              <template #avatar><q-icon name="pending_actions" color="warning" /></template>
              {{ draftInvoiceCount }} facture(s) en brouillon à soumettre
            </q-banner>
          </div>

          <div class="row items-center q-gutter-sm">
            <q-btn
              label="Générer"
              color="primary"
              icon="receipt_long"
              :loading="runningBilling"
              dense no-caps
              @click="runBilling"
            >
              <q-tooltip>Génère les factures récurrentes pour les abonnements actifs</q-tooltip>
            </q-btn>
            <q-btn
              label="Soumettre"
              color="warning"
              icon="publish"
              :loading="submittingDrafts"
              :disable="draftInvoiceCount === 0"
              dense no-caps
              @click="submitDrafts"
            >
              <q-tooltip>Soumet les {{ draftInvoiceCount }} facture(s) en brouillon</q-tooltip>
            </q-btn>
            <q-space />
            <!-- Action sensible (le PPA prélève immédiatement — cf. incident double-charge) : reléguée dans un ⋮
                 pour éviter le clic accidentel, sans la retirer. -->
            <OverflowMenu tooltip="Actions sensibles" :items="[
              { icon: 'credit_score', label: runningPPA ? 'PPA en cours…' : 'PPA — prélever les cartes', caption: 'Prélèvement immédiat · confirmation requise', color: 'positive', disabled: runningPPA, action: confirmPPA },
            ]" />
          </div>
          <div class="q-mt-xs q-gutter-xs" style="min-height:20px">
            <span v-if="billingResult" class="text-caption" :class="billingResult.ok ? 'text-positive' : 'text-negative'">
              {{ billingResult.message }}
            </span>
            <span v-if="submitResult" class="text-caption" :class="submitResult.ok ? 'text-positive' : 'text-negative'">
              {{ submitResult.message }}
            </span>
            <span v-if="ppaResult" class="text-caption" :class="ppaResult.ok ? 'text-positive' : 'text-negative'">
              {{ ppaResult.message }}
            </span>
          </div>
        </div>
      </div>
    </div>
    </DisclosureSection>

    <!-- Recent activity -->
    <div class="row q-col-gutter-md">
      <div class="col-12 col-md-6">
        <div class="ops-card">
          <div class="text-subtitle1 text-weight-bold q-mb-sm">Tickets ouverts</div>
          <q-list separator>
            <q-item v-for="t in openTickets" :key="t.name" clickable @click="$router.push('/tickets')">
              <q-item-section avatar style="min-width:32px">
                <q-icon name="confirmation_number" size="20px"
                  :color="t.priority === 'Urgent' ? 'red' : t.priority === 'High' ? 'orange-8' : 'grey-5'" />
              </q-item-section>
              <q-item-section>
                <q-item-label>{{ t.subject }}</q-item-label>
                <q-item-label caption><router-link v-if="t.customer" :to="'/clients/' + encodeURIComponent(t.customer)" class="erp-link" @click.stop>{{ t.customer_name || t.customer }}</router-link><template v-else>{{ t.customer_name }}</template></q-item-label>
              </q-item-section>
              <q-item-section side>
                <span class="ops-badge open">{{ t.priority }}</span>
              </q-item-section>
            </q-item>
            <q-item v-if="!openTickets.length">
              <q-item-section>
                <q-item-label caption>Aucun ticket ouvert</q-item-label>
              </q-item-section>
            </q-item>
          </q-list>
        </div>
      </div>
      <div class="col-12 col-md-6">
        <div class="ops-card">
          <div class="text-subtitle1 text-weight-bold q-mb-sm">
            Dispatch aujourd'hui
            <q-badge v-if="todayJobs.length" :label="todayJobs.length" color="primary" class="q-ml-sm" />
          </div>
          <q-list separator>
            <q-item v-for="j in todayJobs" :key="j.name" clickable @click="$router.push('/planification')">
              <q-item-section avatar style="min-width:32px">
                <q-icon :name="j.status === 'completed' ? 'check_circle' : j.assigned_tech ? 'person' : 'radio_button_unchecked'"
                  :color="j.status === 'completed' ? 'green-6' : j.assigned_tech ? 'blue-6' : 'grey-5'" size="20px" />
              </q-item-section>
              <q-item-section>
                <q-item-label>{{ j.subject || j.name }}</q-item-label>
                <q-item-label caption>
                  <span v-if="j.assigned_tech">{{ j.assigned_tech }}</span>
                  <template v-if="j.customer"> &middot; <router-link :to="'/clients/' + encodeURIComponent(j.customer)" class="erp-link" @click.stop>{{ j.customer }}</router-link></template>
                </q-item-label>
              </q-item-section>
              <q-item-section side>
                <span class="ops-badge" :class="j.status === 'completed' ? 'active' : j.status === 'assigned' ? 'draft' : 'closed'">{{ j.status }}</span>
              </q-item-section>
            </q-item>
            <q-item v-if="!todayJobs.length">
              <q-item-section>
                <q-item-label caption>Aucune tâche planifiée aujourd'hui</q-item-label>
              </q-item-section>
            </q-item>
          </q-list>
        </div>
      </div>
    </div>
  </q-page>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useQuasar } from 'quasar'
import { useRouter } from 'vue-router'
import { listDocs, countDocs } from 'src/api/erp'
import { authFetch } from 'src/api/auth'
import { getCapacity } from 'src/api/roster'
import { BASE_URL } from 'src/config/erpnext'
import { HUB_SSE_URL } from 'src/config/dispatch'
import { startOfWeek, localDateStr } from 'src/composables/useHelpers'
import OutageAlertsPanel from 'src/components/shared/OutageAlertsPanel.vue'
import OccupancyStrip from 'src/components/shared/OccupancyStrip.vue'
import DisclosureSection from 'src/components/shared/DisclosureSection.vue'
import OverflowMenu from 'src/components/shared/OverflowMenu.vue'
import { useCommandPalette } from 'src/composables/useCommandPalette'
import { DEPARTMENTS } from 'src/config/departments'

const $q = useQuasar()
const router = useRouter()

const stats = ref([
  { label: 'Abonnés', value: '...', color: 'var(--ops-accent)', icon: 'people', to: '/clients', sub: '', subClass: '' },
  { label: 'Clients', value: '...', color: 'var(--ops-primary)', icon: 'business', to: '/clients', sub: '', subClass: '' },
  { label: 'Rev. mensuel', value: '...', color: 'var(--ops-success)', icon: 'attach_money', to: '/rapports/revenus', sub: 'récurrent', subClass: 'text-grey-6' },
  { label: 'Tickets ouverts', value: '...', color: 'var(--ops-warning)', icon: 'confirmation_number', to: '/tickets', sub: '', subClass: '' },
  { label: 'Dispatch aujourd\'hui', value: '...', color: 'var(--ops-accent)', icon: 'local_shipping', to: '/planification', sub: '', subClass: '' },
])

const openTickets = ref([])
const todayJobs = ref([])

// ── Charge de la semaine (bande d'occupation, réutilise /roster/capacity) ──
const weekLoad = ref([])
const weekUnplaced = ref(0)
// Compétences terrain sélectionnables (SOURCE UNIQUE departments.js) → dénominateur filtré.
const CAP_SKILLS = [...new Set(DEPARTMENTS.flatMap(d => d.skills || []))]
const capSkill = ref('')
const todayIso = localDateStr(new Date())
const FR_DOW = ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam']

async function loadWeek () {
  try {
    const mon = startOfWeek(new Date()) // lundi de la semaine courante → 2 semaines (courante + prochaine)
    const { capacity } = await getCapacity(localDateStr(mon), 14, capSkill.value)
    const days = []; let unplaced = 0
    for (let i = 0; i < 14; i++) {
      const dt = new Date(mon); dt.setDate(mon.getDate() + i)
      const iso = localDateStr(dt)
      const c = capacity[iso] || {}
      const h = +c.due_h || 0; const cap = +c.cap_h || 0
      unplaced += +c.unplaced_h || 0
      days.push({
        iso, dow: FR_DOW[dt.getDay()], dnum: dt.getDate() + '/' + String(dt.getMonth() + 1).padStart(2, '0'),
        h, cap, ratio: cap > 0 ? h / cap : (h > 0 ? 1 : 0), over: !!c.overbooked,
        weekend: dt.getDay() === 0 || dt.getDay() === 6, noShift: cap === 0 && h > 0, today: iso === todayIso,
      })
    }
    weekLoad.value = days
    weekUnplaced.value = Math.round(unplaced)
  } catch { /* strip masqué si le hub ne répond pas */ }
}

// Scheduler controls
const schedulerEnabled = ref(false)
const togglingScheduler = ref(false)

async function fetchSchedulerStatus () {
  try {
    const res = await authFetch(BASE_URL + '/api/method/scheduler_status')
    if (res.ok) {
      const data = await res.json()
      schedulerEnabled.value = data.message?.status === 'enabled'
    }
  } catch {}
}

async function toggleScheduler () {
  togglingScheduler.value = true
  try {
    const res = await authFetch(BASE_URL + '/api/method/toggle_scheduler', { method: 'POST' })
    if (res.ok) {
      const data = await res.json()
      schedulerEnabled.value = data.message?.status === 'enabled'
    }
  } catch {}
  togglingScheduler.value = false
}

// ── Billing pipeline: Generate → Submit → PPA ──────────────────
const billingStep = ref(0) // 0=idle, 1=generated, 2=submitted, 3=ppa done
const runningBilling = ref(false)
const billingResult = ref(null)
const submittingDrafts = ref(false)
const submitResult = ref(null)
const runningPPA = ref(false)
const ppaResult = ref(null)
const draftInvoiceCount = ref(0)

async function refreshDraftCount () {
  try {
    const res = await authFetch(BASE_URL + '/api/method/frappe.client.get_count?doctype=Sales+Invoice&filters=' +
      encodeURIComponent(JSON.stringify({ docstatus: 0, is_return: 0 })))
    if (res.ok) {
      const data = await res.json()
      draftInvoiceCount.value = data.message || 0
    }
  } catch {}
}

async function runBilling () {
  runningBilling.value = true
  billingResult.value = null
  submitResult.value = null
  ppaResult.value = null
  billingStep.value = 0
  try {
    // Create a Process Subscription doc (submitting it enqueues the background job)
    const today = new Date().toISOString().slice(0, 10)
    const res = await authFetch(BASE_URL + '/api/resource/Process Subscription', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ posting_date: today }),
    })
    if (!res.ok) throw new Error('Erreur création: ' + res.status)
    const doc = (await res.json()).data

    // Submit it to trigger the background job (must pass full doc with modified timestamp)
    const submitRes = await authFetch(BASE_URL + '/api/method/frappe.client.submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ doc: doc }),
    })
    if (!submitRes.ok) throw new Error('Erreur soumission: ' + submitRes.status)

    // Poll background jobs until complete (max 60s)
    billingResult.value = { ok: true, message: 'Génération en cours...' }
    const startDrafts = draftInvoiceCount.value
    let attempts = 0
    while (attempts < 30) {
      await new Promise(r => setTimeout(r, 2000))
      await refreshDraftCount()
      attempts++
      // Check if background job finished by looking at job queue
      const jobRes = await authFetch(BASE_URL + '/api/method/frappe.client.get_list?' + new URLSearchParams({
        doctype: 'RQ Job',
        filters: JSON.stringify({ job_name: ['like', '%process_all%'], status: ['in', ['queued', 'started']] }),
        limit_page_length: 1,
      }))
      if (jobRes.ok) {
        const jobData = await jobRes.json()
        const jobs = jobData.data || jobData.message || []
        if (!jobs.length) break // no more running jobs
      } else {
        break // can't check, assume done
      }
    }

    await refreshDraftCount()
    const newDrafts = draftInvoiceCount.value - startDrafts
    billingResult.value = { ok: true, message: `Génération terminée. ${newDrafts > 0 ? newDrafts + ' nouvelle(s) facture(s)' : 'Aucune nouvelle facture'}.` }
    billingStep.value = 1
  } catch (e) {
    billingResult.value = { ok: false, message: e.message }
  }
  runningBilling.value = false
}

async function submitDrafts () {
  submittingDrafts.value = true
  submitResult.value = null
  try {
    // Get all draft invoices (docstatus=0, not credit notes)
    const draftsRes = await authFetch(BASE_URL + '/api/method/frappe.client.get_list?' + new URLSearchParams({
      doctype: 'Sales Invoice',
      filters: JSON.stringify({ docstatus: 0, is_return: 0 }),
      fields: JSON.stringify(['name']),
      limit_page_length: 500,
    }))
    if (!draftsRes.ok) throw new Error('Erreur lecture drafts: ' + draftsRes.status)
    const drafts = (await draftsRes.json()).message || []

    if (!drafts.length) {
      submitResult.value = { ok: true, message: 'Aucune facture en brouillon.' }
      submittingDrafts.value = false
      return
    }

    // Submit each draft (must fetch full doc for modified timestamp)
    let submitted = 0
    let errors = 0
    for (const d of drafts) {
      try {
        const docRes = await authFetch(BASE_URL + '/api/resource/Sales%20Invoice/' + encodeURIComponent(d.name))
        if (!docRes.ok) { errors++; continue }
        const fullDoc = (await docRes.json()).data
        const r = await authFetch(BASE_URL + '/api/method/frappe.client.submit', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ doc: fullDoc }),
        })
        if (r.ok) submitted++
        else errors++
      } catch {
        errors++
      }
    }

    await refreshDraftCount()
    submitResult.value = {
      ok: errors === 0,
      message: `${submitted} facture(s) soumise(s)${errors ? ', ' + errors + ' erreur(s)' : ''}.`,
    }
    billingStep.value = 2
  } catch (e) {
    submitResult.value = { ok: false, message: e.message }
  }
  submittingDrafts.value = false
}

// Garde-fou : le PPA prélève IMMÉDIATEMENT les cartes enregistrées. Un double lancement peut
// charger deux fois (cf. incident 2026-06) → confirmation explicite obligatoire avant d'exécuter.
function confirmPPA () {
  $q.dialog({
    title: 'Prélèvement automatique (PPA)',
    message: 'Cette action prélève <b>immédiatement</b> les cartes enregistrées des clients éligibles. À n\'exécuter <b>qu\'une seule fois</b> par cycle — un double lancement peut charger deux fois. Continuer ?',
    html: true,
    ok: { label: 'Prélever maintenant', color: 'positive', noCaps: true, unelevated: true },
    cancel: { label: 'Annuler', flat: true, noCaps: true },
    persistent: true,
  }).onOk(() => { runPPA() })
}

async function runPPA () {
  runningPPA.value = true
  ppaResult.value = null
  try {
    const res = await fetch(HUB_SSE_URL + '/payments/ppa-run', { method: 'POST' })
    if (res.ok) {
      const data = await res.json()
      const charged = data.charged?.length || 0
      const failed = data.failed?.length || 0
      const skipped = data.skipped?.length || 0
      ppaResult.value = {
        ok: true,
        message: `PPA: ${charged} prélevé(s), ${failed} échoué(s), ${skipped} ignoré(s)`,
      }
      billingStep.value = 3
    } else {
      const err = await res.json().catch(() => ({}))
      ppaResult.value = { ok: false, message: err.error || 'Erreur ' + res.status }
    }
  } catch (e) {
    ppaResult.value = { ok: false, message: e.message }
  }
  runningPPA.value = false
}

// Actions d'administration → joignables via la palette (⌘K), même repliées dans « Administration ».
const { registerActions, unregisterActions } = useCommandPalette()
registerActions('dashboard', [
  { id: 'sched-toggle', label: 'Basculer le planificateur', icon: 'schedule', group: 'Administration', keywords: 'scheduler activer desactiver', run: toggleScheduler },
  { id: 'billing-generate', label: 'Facturation — Générer', icon: 'receipt_long', group: 'Administration', keywords: 'facture recurrente billing generer', run: runBilling },
  { id: 'billing-submit', label: 'Facturation — Soumettre les brouillons', icon: 'publish', group: 'Administration', keywords: 'soumettre draft facture', run: submitDrafts },
  { id: 'billing-ppa', label: 'Facturation — Lancer le PPA', icon: 'credit_score', group: 'Administration', keywords: 'ppa prelevement carte', run: confirmPPA },
])
onUnmounted(() => unregisterActions('dashboard'))

onMounted(async () => {
  fetchSchedulerStatus()
  refreshDraftCount()
  loadWeek()

  const today = new Date().toISOString().slice(0, 10)
  const d7 = localDateStr(new Date(Date.now() - 7 * 86400000)) // il y a 7 jours (tendance tickets)

  const [clients, tickets, todayDispatch, openTix, newTickets7d] = await Promise.all([
    countDocs('Customer', { disabled: 0 }),
    countDocs('Issue', { status: 'Open' }),
    listDocs('Dispatch Job', {
      filters: { scheduled_date: today },
      fields: ['name', 'subject', 'status', 'assigned_tech', 'customer', 'priority', 'source_issue'],
      limit: 50, orderBy: 'start_time asc',
    }).catch(() => []),
    listDocs('Issue', {
      filters: { status: 'Open' },
      fields: ['name', 'subject', 'customer', 'customer_name', 'priority', 'opening_date'],
      limit: 10, orderBy: 'opening_date desc',
    }),
    countDocs('Issue', { opening_date: ['>=', d7] }).catch(() => 0),
  ])

  // Abonnés = unique customers with active subscriptions (via server script)
  let abonnes = 0
  let monthlyRev = 0
  try {
    const [subRes, revRes] = await Promise.all([
      authFetch(BASE_URL + '/api/method/subscriber_count').then(r => r.ok ? r.json() : null).catch(() => null),
      listDocs('Service Subscription', {
        filters: { status: 'Actif' },
        fields: ['actual_price'],
        limit: 0,
      }).catch(() => []),
    ])
    abonnes = subRes?.message?.count || clients
    monthlyRev = revRes.reduce((sum, s) => sum + parseFloat(s.actual_price || 0), 0)
  } catch {
    abonnes = clients
  }

  stats.value[0].value = abonnes.toLocaleString()
  stats.value[1].value = clients.toLocaleString()
  stats.value[2].value = monthlyRev ? (Math.round(monthlyRev).toLocaleString() + ' $') : '...'
  stats.value[3].value = tickets.toLocaleString()
  stats.value[4].value = todayDispatch.length.toLocaleString()

  // Tendance / contexte des KPI (à partir des données déjà chargées → 0 requête de plus, sauf le compte 7 j)
  const urgents = openTix.filter(t => t.priority === 'Urgent' || t.priority === 'High').length
  const tkParts = []
  if (urgents) tkParts.push(urgents + ' urgents')
  if (newTickets7d) tkParts.push('+' + newTickets7d + ' (7j)')
  stats.value[3].sub = tkParts.join(' · ')
  stats.value[3].subClass = urgents ? 'text-negative' : 'text-grey-6'
  const unassigned = todayDispatch.filter(j => !j.assigned_tech).length
  stats.value[4].sub = todayDispatch.length ? (unassigned ? unassigned + ' non assignés' : 'tous assignés ✓') : ''
  stats.value[4].subClass = unassigned ? 'text-warning' : 'text-positive'

  openTickets.value = openTix
  todayJobs.value = todayDispatch
})
</script>

<style scoped>
/* KPI cliquable : affordance au survol + sous-ligne tendance/contexte */
.ops-stat-clk { cursor: pointer; transition: transform .12s, box-shadow .12s; }
.ops-stat-clk:hover { transform: translateY(-1px); box-shadow: 0 4px 14px rgba(2, 6, 23, .08); }
.ops-stat-clk .ops-stat-go { color: var(--ops-text-secondary, #94a3b8); opacity: 0; transition: opacity .12s; }
.ops-stat-clk:hover .ops-stat-go { opacity: 1; }
.ops-stat-sub { font-size: 11px; line-height: 1.2; margin-top: 1px; }
/* Légende de la bande d'occupation */
.os-leg-dot { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 3px; vertical-align: middle; }
/* Libellé de semaine au-dessus de chaque bande (« Cette semaine » / « Semaine prochaine ») */
.dash-wk-lbl { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; color: var(--ops-text-secondary, #94a3b8); margin-bottom: 4px; }
</style>
