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:
parent
936152d452
commit
68063a9f69
40
apps/ops/src/components/customer/CustomerSatisfaction.vue
Normal file
40
apps/ops/src/components/customer/CustomerSatisfaction.vue
Normal 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>
|
||||||
|
|
@ -47,37 +47,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Satisfaction : dernière évaluation laissée par CE client (étoiles + commentaire) -->
|
<!-- Satisfaction (extrait → CustomerSatisfaction, T7) -->
|
||||||
<div v-if="latestRating" class="ops-card q-pa-md q-mb-sm" style="border-radius:10px">
|
<CustomerSatisfaction :latest="latestRating" :count="custRatings.length" @draft="draftRatingInvite" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 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. -->
|
<!-- 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 CustomerHeader from 'src/components/customer/CustomerHeader.vue'
|
||||||
import ContactCard from 'src/components/customer/ContactCard.vue'
|
import ContactCard from 'src/components/customer/ContactCard.vue'
|
||||||
import CustomerInfoCard from 'src/components/customer/CustomerInfoCard.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 InlineField from 'src/components/shared/InlineField.vue'
|
||||||
import { useSoftphone } from 'src/composables/useSoftphone'
|
import { useSoftphone } from 'src/composables/useSoftphone'
|
||||||
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'
|
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user