From dc28437d5f850d417c1be24caaf7d96c0fb71653 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sat, 4 Jul 2026 21:19:37 -0400 Subject: [PATCH] refactor(ops): extract DeviceStrip from ClientDetailPage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T7 découpage géants — sort le bandeau d'appareils (chips ONT/routeur + menu Ajouter) dans components/customer/DeviceStrip.vue. Consomme le composable singleton useDeviceStatus (couche données ACS/OLT partagée) ; les helpers de présentation propres aux appareils (signalColor, formatTimeAgo, doReboot, doRefreshParams) déménagent DANS le composant (utilisés nulle part ailleurs). Le parent émet open-device + add-*. ClientDetailPage -108 lignes ; imports/destructures device retirés. Vérifié en local sur hub LIVE (C-LPB4) : chip vert + point acs-online réel, tooltip ACS complet (SN TPLGC4160688, OLT oltRem03 1/3/0, En ligne il y a 4m, Fibre Up, 17 clients WiFi, FW/WAN IP/SSID), 0 warning console. Co-Authored-By: Claude Opus 4.8 --- .../src/components/customer/DeviceStrip.vue | 161 ++++++++++++++++++ apps/ops/src/pages/ClientDetailPage.vue | 134 ++------------- 2 files changed, 174 insertions(+), 121 deletions(-) create mode 100644 apps/ops/src/components/customer/DeviceStrip.vue diff --git a/apps/ops/src/components/customer/DeviceStrip.vue b/apps/ops/src/components/customer/DeviceStrip.vue new file mode 100644 index 0000000..80abc00 --- /dev/null +++ b/apps/ops/src/components/customer/DeviceStrip.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/apps/ops/src/pages/ClientDetailPage.vue b/apps/ops/src/pages/ClientDetailPage.vue index 7375c62..7027980 100644 --- a/apps/ops/src/pages/ClientDetailPage.vue +++ b/apps/ops/src/pages/ClientDetailPage.vue @@ -123,90 +123,14 @@ · -
-
- - - - - -
{{ eq.equipment_type }}{{ eq.brand ? ' — ' + eq.brand : '' }}{{ eq.model ? ' ' + eq.model : '' }}
-
SN: {{ eq.serial_number }}
- - - -
{{ eq.status }}
- -
- - - - - Redémarrer - - - - Rafraîchir params - - - -
-
- - Ajouter - - - - - - Nouvelle soumission - Wizard avec étapes dispatchables - - - - - - Forfait / Service (direct) - - - - Rabais / Crédit - - - - - Équipement - - - -
-
+
@@ -1137,14 +1061,13 @@ import { authFetch } from 'src/api/auth' import { BASE_URL, ERP_DESK_URL } from 'src/config/erpnext' import { HUB_URL } from 'src/config/hub' import { formatDate, formatDateTime, formatMoney, staffColor, staffInitials, noteAuthorName } from 'src/composables/useFormatters' -import { locStatusClass, ticketStatusClass, invStatusClass, deviceColorClass, priorityClass } from 'src/composables/useStatusClasses' +import { locStatusClass, ticketStatusClass, invStatusClass, priorityClass } from 'src/composables/useStatusClasses' import { useDetailModal } from 'src/composables/useDetailModal' import { useSubscriptionGroups, isRebate, subMainLabel, sectionTotal, annualPrice } from 'src/composables/useSubscriptionGroups' import { useSubscriptionActions } from 'src/composables/useSubscriptionActions' import { useCustomerNotes } from 'src/composables/useCustomerNotes' import { useConversations } from 'src/composables/useConversations' import { invoiceCols, paymentCols, ticketCols, voipCols, paymentMethodCols, arrangementCols, quotationCols, serviceContractCols } from 'src/config/table-columns' -import { deviceLucideIcon } from 'src/config/device-icons' import DetailModal from 'src/components/shared/DetailModal.vue' import DataTable from 'src/components/shared/DataTable.vue' import CustomerHeader from 'src/components/customer/CustomerHeader.vue' @@ -1166,6 +1089,7 @@ import { locInlineFields, equipTypeOptions, equipStatusOptions, defaultSectionsO import { useUserPrefs } from 'src/composables/useUserPrefs' import { erpPdfUrl } from 'src/utils/erp-pdf' import RewardDialog from 'src/components/customer/RewardDialog.vue' +import DeviceStrip from 'src/components/customer/DeviceStrip.vue' const $q = useQuasar() const { can } = usePermissions() @@ -1176,7 +1100,9 @@ const ticketsExpanded = ref(false) const invoicesExpanded = ref(false) const paymentsExpanded = ref(false) -const { fetchStatus, fetchOltStatus, getDevice, isOnline, combinedStatus, signalQuality, rebootDevice, refreshDeviceParams } = useDeviceStatus() +// Device status still needed at the page level to seed the shared map (fetchStatus/fetchOltStatus +// passed to useClientData); DeviceStrip consumes the same singleton composable for display. +const { fetchStatus, fetchOltStatus } = useDeviceStatus() const { modalOpen, modalLoading, modalDoctype, modalDocName, modalTitle, @@ -1509,40 +1435,6 @@ async function confirmDeleteSub (sub) { function onDispatchCreated (job) { modalDispatchJobs.value.push(job) } -function formatTimeAgo (dateStr) { - if (!dateStr) return '' - const diff = Date.now() - new Date(dateStr).getTime() - const mins = Math.floor(diff / 60000) - if (mins < 1) return 'à l\'instant' - if (mins < 60) return `il y a ${mins}m` - const hrs = Math.floor(mins / 60) - return hrs < 24 ? `il y a ${hrs}h` : `il y a ${Math.floor(hrs / 24)}j` -} - -function signalColor (serial) { - const q = signalQuality(serial) - return q === 'excellent' ? '#4ade80' : q === 'good' ? '#a3e635' : q === 'fair' ? '#fbbf24' : '#f87171' -} - -async function doReboot (eq) { - try { - await rebootDevice(eq.serial_number) - Notify.create({ type: 'positive', message: `Redémarrage envoyé: ${eq.serial_number}`, position: 'top' }) - } catch (e) { - Notify.create({ type: 'negative', message: `Erreur: ${e.message}`, position: 'top' }) - } -} - -async function doRefreshParams (eq) { - try { - await refreshDeviceParams(eq.serial_number) - Notify.create({ type: 'info', message: `Rafraîchissement lancé: ${eq.serial_number}`, position: 'top' }) - setTimeout(() => fetchStatus([eq]), 3000) - } catch (e) { - Notify.create({ type: 'negative', message: `Erreur: ${e.message}`, position: 'top' }) - } -} - const locCollapsed = reactive({}) const locHasSubs = (locName) => subscriptions.value.some(s => s.service_location === locName) const toggleLocCollapse = (locName) => { locCollapsed[locName] = locCollapsed[locName] === false }