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 }