gigafibre-fsm/apps/ops/src/composables/useSla.js
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

77 lines
3.9 KiB
JavaScript

import { ref } from 'vue'
import { HUB_URL } from 'src/config/hub'
// ─────────────────────────────────────────────────────────────────────────────
// SLA côté Ops : charge les politiques (par FILE=issue_type + PRIORITÉ) depuis le hub,
// et calcule l'état d'un ticket (réponse d'abord tant que non répondu, puis résolution).
// const { slaFor, savePolicies, policies, defaults } = useSla()
// slaFor(ticket) → { state:'ok'|'at_risk'|'breached'|'met'|'none', kind, label, remainMins }
// ─────────────────────────────────────────────────────────────────────────────
let _policies = null
let _defaults = {}
let _loadP = null
const policies = ref(null)
const defaults = ref({})
function loadPolicies () {
if (_policies) return Promise.resolve(_policies)
if (!_loadP) {
_loadP = fetch(`${HUB_URL}/sla/policies`).then(r => r.ok ? r.json() : {})
.then(d => { _policies = d.policies || {}; _defaults = d.defaults || {}; policies.value = _policies; defaults.value = _defaults; return _policies })
.catch(() => { _policies = {}; policies.value = {}; return {} })
}
return _loadP
}
async function savePolicies (pol) {
_policies = pol; policies.value = pol
const r = await fetch(`${HUB_URL}/sla/policies`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ policies: pol }) })
return r.ok
}
function policyFor (issueType, priority) {
const p = policies.value || _policies || {} // policies.value = ref réactif → recalcule quand les politiques se chargent
return (p[issueType] && p[issueType][priority]) || (p._default && p._default[priority]) || null
}
function fmt (mins) {
const a = Math.abs(Math.round(mins))
if (a < 60) return a + ' min'
if (a < 1440) { const h = Math.floor(a / 60); const m = a % 60; return h + ' h' + (m ? ' ' + m : '') }
const j = Math.floor(a / 1440); const h = Math.floor((a % 1440) / 60); return j + ' j' + (h ? ' ' + h + ' h' : '')
}
export function useSla () {
loadPolicies()
function slaFor (t) {
if (!t) return { state: 'none' }
const pol = policyFor(t.issue_type, t.priority)
if (!pol) return { state: 'none' }
const od = t.opening_date || t.creation
if (!od) return { state: 'none' }
const opening = new Date(String(od).length <= 10 ? od + 'T00:00:00' : od)
if (isNaN(opening.getTime())) return { state: 'none' }
if (['Resolved', 'Closed'].includes(t.status)) return { state: 'met', label: 'SLA clos' }
const now = Date.now()
const band = (remain, target, kind, verb) => {
if (remain < 0) return { state: 'breached', kind, label: verb + ' en retard ' + fmt(remain), remainMins: remain }
if (remain < target * 0.25) return { state: 'at_risk', kind, label: verb + ' dans ' + fmt(remain), remainMins: remain }
return { state: 'ok', kind, label: verb + ' dans ' + fmt(remain), remainMins: remain }
}
// Réponse (1ʳᵉ réponse) prioritaire tant que pas encore répondu
if (!t.first_responded_on && pol.response) return band((opening.getTime() + pol.response * 60000 - now) / 60000, pol.response, 'response', 'Réponse')
// Sinon résolution
if (pol.resolution) return band((opening.getTime() + pol.resolution * 60000 - now) / 60000, pol.resolution, 'resolution', 'Résolution')
return { state: 'none' }
}
return { slaFor, loadPolicies, savePolicies, policies, defaults }
}
// Couleurs de pastille par état (réutilisable).
export const SLA_BADGE = {
breached: { color: 'red-2', text: 'red-9', icon: 'error' },
at_risk: { color: 'orange-2', text: 'orange-9', icon: 'schedule' },
ok: { color: 'green-2', text: 'green-9', icon: 'schedule' },
met: { color: 'grey-3', text: 'grey-7', icon: 'check' },
}