From 73691668d3b5ec3ec31d60b75fca0aa365124766 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Thu, 9 Apr 2026 08:26:26 -0400 Subject: [PATCH] feat: tech mobile view integrated into ops app at /j, unassign confirmation Tech mobile view (erp.gigafibre.ca/ops/#/j): - TechLayout with bottom nav tabs (tasks, scanner, diagnostic, more) - TechTasksPage: rich header with tech name/stats, job cards with priority dots, time, location, duration badges, bottom sheet detail with En route/Terminer buttons + scanner/detail access - TechJobDetailPage: editable fields, equipment list, GPS navigation - TechScanPage: device lookup by SN/MAC, create/link to job - TechDiagnosticPage: speed test + host reachability checks - Route /j replaces legacy dispatch-app tech view Dispatch unassign confirmation: - Dialog appears when unassigning published or in-progress jobs - Warns that tech has already received the task - Cancel/Confirm flow prevents accidental removal Co-Authored-By: Claude Opus 4.6 --- apps/field/src/pages/TasksPage.vue | 550 +++++++++++++++--- apps/ops/src/layouts/TechLayout.vue | 60 ++ .../modules/tech/pages/TechDiagnosticPage.vue | 77 +++ .../modules/tech/pages/TechJobDetailPage.vue | 186 ++++++ .../src/modules/tech/pages/TechMorePage.vue | 33 ++ .../src/modules/tech/pages/TechScanPage.vue | 138 +++++ .../src/modules/tech/pages/TechTasksPage.vue | 386 ++++++++++++ apps/ops/src/pages/DispatchPage.vue | 46 +- apps/ops/src/pages/dispatch-styles.scss | 24 + apps/ops/src/router/index.js | 13 + 10 files changed, 1423 insertions(+), 90 deletions(-) create mode 100644 apps/ops/src/layouts/TechLayout.vue create mode 100644 apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue create mode 100644 apps/ops/src/modules/tech/pages/TechJobDetailPage.vue create mode 100644 apps/ops/src/modules/tech/pages/TechMorePage.vue create mode 100644 apps/ops/src/modules/tech/pages/TechScanPage.vue create mode 100644 apps/ops/src/modules/tech/pages/TechTasksPage.vue diff --git a/apps/field/src/pages/TasksPage.vue b/apps/field/src/pages/TasksPage.vue index d4e9edd..0591f21 100644 --- a/apps/field/src/pages/TasksPage.vue +++ b/apps/field/src/pages/TasksPage.vue @@ -1,149 +1,521 @@ + + diff --git a/apps/ops/src/layouts/TechLayout.vue b/apps/ops/src/layouts/TechLayout.vue new file mode 100644 index 0000000..889531a --- /dev/null +++ b/apps/ops/src/layouts/TechLayout.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue b/apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue new file mode 100644 index 0000000..08a8748 --- /dev/null +++ b/apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/ops/src/modules/tech/pages/TechJobDetailPage.vue b/apps/ops/src/modules/tech/pages/TechJobDetailPage.vue new file mode 100644 index 0000000..22ce8ad --- /dev/null +++ b/apps/ops/src/modules/tech/pages/TechJobDetailPage.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/apps/ops/src/modules/tech/pages/TechMorePage.vue b/apps/ops/src/modules/tech/pages/TechMorePage.vue new file mode 100644 index 0000000..366e5fa --- /dev/null +++ b/apps/ops/src/modules/tech/pages/TechMorePage.vue @@ -0,0 +1,33 @@ + + + diff --git a/apps/ops/src/modules/tech/pages/TechScanPage.vue b/apps/ops/src/modules/tech/pages/TechScanPage.vue new file mode 100644 index 0000000..f7fa517 --- /dev/null +++ b/apps/ops/src/modules/tech/pages/TechScanPage.vue @@ -0,0 +1,138 @@ + + + diff --git a/apps/ops/src/modules/tech/pages/TechTasksPage.vue b/apps/ops/src/modules/tech/pages/TechTasksPage.vue new file mode 100644 index 0000000..16e6daf --- /dev/null +++ b/apps/ops/src/modules/tech/pages/TechTasksPage.vue @@ -0,0 +1,386 @@ + + + + + diff --git a/apps/ops/src/pages/DispatchPage.vue b/apps/ops/src/pages/DispatchPage.vue index 989493e..432bda6 100644 --- a/apps/ops/src/pages/DispatchPage.vue +++ b/apps/ops/src/pages/DispatchPage.vue @@ -243,13 +243,38 @@ const underutilizedTechs = computed(() => { const { pushUndo, performUndo } = useUndo(store, invalidateRoutes) const smartAssign = (job, newTechId, dateStr) => store.smartAssign(job.id, newTechId, dateStr) -function fullUnassign (job) { + +// Confirmation state for unassign +const confirmUnassignDialog = ref(false) +const pendingUnassignJob = ref(null) + +function _doUnassign (job) { pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...job.assistants] }) store.fullUnassign(job.id) if (selectedJob.value?.job?.id === job.id) selectedJob.value = null invalidateRoutes() } +function fullUnassign (job) { + // Require confirmation for published or in-progress jobs + if (job.published || job.status === 'in_progress' || job.status === 'In Progress' || job.status === 'assigned') { + pendingUnassignJob.value = job + confirmUnassignDialog.value = true + return + } + _doUnassign(job) +} + +function confirmUnassign () { + if (pendingUnassignJob.value) _doUnassign(pendingUnassignJob.value) + pendingUnassignJob.value = null + confirmUnassignDialog.value = false +} +function cancelUnassign () { + pendingUnassignJob.value = null + confirmUnassignDialog.value = false +} + const { ctxMenu, techCtx, assistCtx, assistNoteModal, openCtxMenu, closeCtxMenu, openTechCtx, openAssistCtx, @@ -1753,6 +1778,25 @@ onUnmounted(() => { + +
+
+
⚠️
+
Désaffecter ce job ?
+
+ {{ pendingUnassignJob?.subject || pendingUnassignJob?.id }}
+ Publié + En cours + Assigné +
Le technicien a déjà reçu cette tâche. Désaffecter la remettra dans le pool non-assigné. +
+
+ + +
+
+
+ import('src/layouts/TechLayout.vue'), + children: [ + { path: '', name: 'tech-tasks', component: () => import('src/modules/tech/pages/TechTasksPage.vue') }, + { path: 'job/:name', name: 'tech-job', component: () => import('src/modules/tech/pages/TechJobDetailPage.vue'), props: true }, + { path: 'scan', name: 'tech-scan', component: () => import('src/modules/tech/pages/TechScanPage.vue') }, + { path: 'diagnostic', name: 'tech-diag', component: () => import('src/modules/tech/pages/TechDiagnosticPage.vue') }, + { path: 'more', name: 'tech-more', component: () => import('src/modules/tech/pages/TechMorePage.vue') }, + ], + }, + // Ops staff desktop view { path: '/', component: () => import('src/layouts/MainLayout.vue'),