ticket : afficher le technicien assigné EN TÊTE du panneau détail (IssueDetail)

Le panneau (IssueDetail dans DetailModal, ouvert depuis /tickets ou la fiche client) ne
montrait pas le tech assigné — seulement `_assign` (agents) + `assigned_tech` enfoui dans
l'arbre des tâches. Ajout d'une bannière « Technicien » en tête : chips (initiales + nom)
des techs des Dispatch Jobs liés (`assigned_tech`, déjà fetchés) + staff legacy
(`doc.assigned_staff`, ce que montrent les lignes de liste), dédupliqués ; « Non assigné »
si des jobs existent sans tech. Réutilise le `initials()` local. (1/4 de la standardisation
des détails ticket — reste en session dédiée, cf reference_ticket_detail_disparities.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-08 13:53:21 -04:00
parent 13039be301
commit 6224271e07

View File

@ -35,6 +35,16 @@
</q-select>
</div>
<!-- Technicien(s) assigné(s) surfacé EN TÊTE (avant : enfoui dans l'arbre des tâches / absent du panneau) -->
<div v-if="assignedPeople.length || dispatchJobs.length" class="tech-banner q-mb-sm">
<q-icon name="engineering" size="18px" :color="assignedPeople.length ? 'indigo-6' : 'grey-5'" />
<span class="tech-banner-lbl">Technicien</span>
<template v-if="assignedPeople.length">
<q-chip v-for="p in assignedPeople" :key="p" dense color="indigo-1" text-color="indigo-9"><q-avatar color="indigo-6" text-color="white" size="20px">{{ initials(p) }}</q-avatar>{{ p }}</q-chip>
</template>
<span v-else class="text-caption text-grey-6">Non assigné</span>
</div>
<div class="issue-field-grid">
<div class="if-row">
<span class="if-label">Statut</span>
@ -427,6 +437,14 @@ async function unlinkCustomer () {
const userOptions = ref([])
const loadingUsers = ref(false)
const assignees = ref([])
// Technicien(s) assigné(s) surfacé(s) EN TÊTE : tech des Dispatch Jobs liés (« assigné au job ») + staff legacy (ce que montrent les lignes de liste, absent du panneau jusqu'ici). Dédupliqué.
const assignedPeople = computed(() => {
const seen = new Set(); const out = []
const add = v => { const s = (v == null ? '' : String(v)).trim(); if (s && !seen.has(s.toLowerCase())) { seen.add(s.toLowerCase()); out.push(s) } }
for (const j of (props.dispatchJobs || [])) add(j.assigned_tech)
add(props.doc && props.doc.assigned_staff)
return out
})
function parseAssign (raw) {
if (!raw) return []
@ -863,6 +881,8 @@ async function sendTicketEmail () {
}
.cust-name { font-weight: 600; font-size: 0.9rem; color: #166534; cursor: pointer; }
.cust-name:hover { text-decoration: underline; }
.tech-banner { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.tech-banner-lbl { font-size: 12px; font-weight: 600; color: #607d8b; }
/* Fil unifié en bulles */
.thread-wrap { display: flex; flex-direction: column; gap: 10px; }