refactor(ui): T7 — extraire CustomerSatisfaction de ClientDetailPage (faible couplage)

Premier découpage de la fiche (2102 l) : bloc « Satisfaction » (étoiles/commentaire + « Demander une évaluation »)
→ components/customer/CustomerSatisfaction.vue (props latest/count, émet 'draft'). Vérifié sur la fiche (rend « Aucune
évaluation » à l'identique, 0 erreur). Extraction DÉLIBÉRÉMENT petite + vérifiable ; les gros blocs couplés
(LocationCard/PaymentSection/DeviceStrip — 10+ méthodes parentes : getDevice/combinedStatus/isOnline… + données ACS live
à vérifier) restent à faire en passe dédiée. Build vert, leak 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-04 20:22:52 -04:00
parent 936152d452
commit 68063a9f69
2 changed files with 43 additions and 31 deletions

View File

@ -0,0 +1,40 @@
<template>
<!-- Bloc « Satisfaction » de la fiche client (extrait de ClientDetailPage, T7). Faible couplage :
props latest/count + émet 'draft' (canal). Réutilisable dans le customer-360. -->
<div v-if="latest" class="ops-card q-pa-md q-mb-sm" style="border-radius:10px">
<div class="row items-center no-wrap">
<q-icon name="star" color="warning" size="20px" class="q-mr-xs" />
<span class="section-title" style="font-size:1rem">Satisfaction</span>
<span class="q-ml-md" :style="{ color: latest.stars >= 5 ? '#00C853' : (latest.stars <= 2 ? '#e53935' : '#f5b50a'), letterSpacing: '2px', fontSize: '1.1rem' }">
<span>{{ '★'.repeat(latest.stars || 0) }}</span><span style="color:#e2e8f0">{{ '☆'.repeat(5 - (latest.stars || 0)) }}</span>
</span>
<span class="text-caption text-grey-6 q-ml-sm">{{ latest.stars }}/5</span>
<q-space />
<span class="text-caption text-grey-5">{{ formatDateTime(latest.stars_ts || latest.comment_ts || latest.created) }}</span>
<q-btn v-if="count > 1" dense flat size="sm" no-caps color="primary" :label="`${count} évaluations`" class="q-ml-sm" @click="$router.push('/evaluations')" />
</div>
<div v-if="latest.comment" class="q-mt-xs" style="white-space:pre-wrap;color:#334155">« {{ latest.comment }} »</div>
</div>
<!-- Pas encore d'évaluation : proposer d'en demander une (brouillon courriel/texto pré-rempli, langue du client) -->
<div v-else class="ops-card q-px-md q-py-sm q-mb-sm row items-center" style="border-radius:10px">
<q-icon name="star_outline" color="warning" size="20px" class="q-mr-sm" />
<span class="text-grey-7">Aucune évaluation pour ce client.</span>
<q-space />
<q-btn dense unelevated no-caps color="warning" text-color="white" icon="reviews" label="Demander une évaluation">
<q-tooltip>Ouvrir un brouillon d'invitation (coordonnées + message pré-remplis)</q-tooltip>
<q-menu auto-close>
<q-list dense style="min-width:210px">
<q-item-label header class="q-py-xs">Brouillon d'invitation (langue du client)</q-item-label>
<q-item clickable @click="$emit('draft', 'email')"><q-item-section avatar><q-icon name="mail" color="primary" /></q-item-section><q-item-section>Par courriel</q-item-section></q-item>
<q-item clickable @click="$emit('draft', 'sms')"><q-item-section avatar><q-icon name="sms" color="positive" /></q-item-section><q-item-section>Par texto</q-item-section></q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</template>
<script setup>
import { formatDateTime } from 'src/composables/useFormatters'
defineProps({ latest: { type: Object, default: null }, count: { type: Number, default: 0 } })
defineEmits(['draft'])
</script>

View File

@ -47,37 +47,8 @@
</div>
</div>
<!-- Satisfaction : dernière évaluation laissée par CE client (étoiles + commentaire) -->
<div v-if="latestRating" class="ops-card q-pa-md q-mb-sm" style="border-radius:10px">
<div class="row items-center no-wrap">
<q-icon name="star" color="warning" size="20px" class="q-mr-xs" />
<span class="section-title" style="font-size:1rem">Satisfaction</span>
<span class="q-ml-md" :style="{ color: latestRating.stars >= 5 ? '#00C853' : (latestRating.stars <= 2 ? '#e53935' : '#f5b50a'), letterSpacing: '2px', fontSize: '1.1rem' }">
<span>{{ '★'.repeat(latestRating.stars || 0) }}</span><span style="color:#e2e8f0">{{ '☆'.repeat(5 - (latestRating.stars || 0)) }}</span>
</span>
<span class="text-caption text-grey-6 q-ml-sm">{{ latestRating.stars }}/5</span>
<q-space />
<span class="text-caption text-grey-5">{{ formatDateTime(latestRating.stars_ts || latestRating.comment_ts || latestRating.created) }}</span>
<q-btn v-if="custRatings.length > 1" dense flat size="sm" no-caps color="primary" :label="`${custRatings.length} évaluations`" class="q-ml-sm" @click="$router.push('/evaluations')" />
</div>
<div v-if="latestRating.comment" class="q-mt-xs" style="white-space:pre-wrap;color:#334155">« {{ latestRating.comment }} »</div>
</div>
<!-- Pas encore d'évaluation : proposer d'en demander une (brouillon courriel/texto pré-rempli, langue du client) -->
<div v-else class="ops-card q-px-md q-py-sm q-mb-sm row items-center" style="border-radius:10px">
<q-icon name="star_outline" color="warning" size="20px" class="q-mr-sm" />
<span class="text-grey-7">Aucune évaluation pour ce client.</span>
<q-space />
<q-btn dense unelevated no-caps color="warning" text-color="white" icon="reviews" label="Demander une évaluation">
<q-tooltip>Ouvrir un brouillon d'invitation (coordonnées + message pré-remplis)</q-tooltip>
<q-menu auto-close>
<q-list dense style="min-width:210px">
<q-item-label header class="q-py-xs">Brouillon d'invitation (langue du client)</q-item-label>
<q-item clickable @click="draftRatingInvite('email')"><q-item-section avatar><q-icon name="mail" color="primary" /></q-item-section><q-item-section>Par courriel</q-item-section></q-item>
<q-item clickable @click="draftRatingInvite('sms')"><q-item-section avatar><q-icon name="sms" color="positive" /></q-item-section><q-item-section>Par texto</q-item-section></q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- Satisfaction (extrait CustomerSatisfaction, T7) -->
<CustomerSatisfaction :latest="latestRating" :count="custRatings.length" @draft="draftRatingInvite" />
<!-- Communications regroupées : courriels/SMS = panneau « Messagerie » (droite) ; les appels & comms loggées restent visibles dans le résumé « Activité récente ». Le Journal séparé (ex-ChatterPanel) a été retiré pour éviter le doublon. `callLog` est conservé : il alimente l'Activité récente. -->
@ -1179,6 +1150,7 @@ import DataTable from 'src/components/shared/DataTable.vue'
import CustomerHeader from 'src/components/customer/CustomerHeader.vue'
import ContactCard from 'src/components/customer/ContactCard.vue'
import CustomerInfoCard from 'src/components/customer/CustomerInfoCard.vue'
import CustomerSatisfaction from 'src/components/customer/CustomerSatisfaction.vue'
import InlineField from 'src/components/shared/InlineField.vue'
import { useSoftphone } from 'src/composables/useSoftphone'
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'