@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/assets/css/primitives.css +1113 -0
  2. package/assets/css/screens.css +1097 -0
  3. package/assets/css/shell.css +258 -0
  4. package/assets/css/tokens.css +236 -0
  5. package/components/AcceptDialog.vue +17 -18
  6. package/components/AccessCard/AvailableStats.vue +24 -16
  7. package/components/AccessCardAddForm.vue +42 -42
  8. package/components/AccessCardAssignToUnitForm.vue +13 -21
  9. package/components/AccessCardDeleteDialog.vue +41 -39
  10. package/components/AccessCardDetailsDialog.vue +125 -79
  11. package/components/AccessCardPreviewDialog.vue +58 -84
  12. package/components/AccessCardQrTagging.vue +60 -63
  13. package/components/AccessCardReplaceForm.vue +27 -23
  14. package/components/AccessManagement.vue +86 -85
  15. package/components/AddEqupmentForm.vue +18 -22
  16. package/components/AddPassKeyToVisitor.vue +95 -73
  17. package/components/AppButton.vue +62 -0
  18. package/components/AppCard.vue +20 -0
  19. package/components/AppDateField.vue +49 -0
  20. package/components/AppField.vue +76 -0
  21. package/components/AppKpiTile.vue +65 -0
  22. package/components/AppSegmented.vue +38 -0
  23. package/components/AppSelect.vue +247 -0
  24. package/components/AreaChecklistHistoryLogs.vue +51 -44
  25. package/components/AreaChecklistHistoryMain.vue +14 -7
  26. package/components/AreaFormDialog.vue +71 -59
  27. package/components/AreaMain.vue +73 -107
  28. package/components/AttachmentsViewer.vue +84 -22
  29. package/components/AttendanceCheckInOutDialog.vue +85 -88
  30. package/components/AttendanceDetailsDialog.vue +57 -63
  31. package/components/AttendanceMain.vue +4 -3
  32. package/components/AttendanceMapSearchDialog.vue +42 -44
  33. package/components/AttendanceSettingsDialog.vue +79 -63
  34. package/components/Avatar/Main.vue +54 -2
  35. package/components/BillingMain.vue +26 -30
  36. package/components/BtnUploadFile.vue +53 -11
  37. package/components/BuildingForm.vue +34 -27
  38. package/components/BuildingManagement/buildings.vue +39 -39
  39. package/components/BuildingManagement/units.vue +26 -42
  40. package/components/BuildingUnitFormAdd.vue +20 -23
  41. package/components/BuildingUnitFormEdit.vue +8 -4
  42. package/components/BulletinBoardForm.vue +19 -24
  43. package/components/BulletinBoardManagement.vue +49 -57
  44. package/components/BulletinBoardView.vue +9 -6
  45. package/components/BulletinExpirationChip.vue +13 -4
  46. package/components/Button/Close.vue +2 -2
  47. package/components/CameraForm.vue +21 -22
  48. package/components/CameraMain.vue +50 -49
  49. package/components/Card/DeleteConfirmation.vue +24 -22
  50. package/components/Card/MemberInfoSummary.vue +28 -3
  51. package/components/Card/Toggle.vue +15 -3
  52. package/components/CardToolbar.vue +37 -0
  53. package/components/Carousel.vue +56 -36
  54. package/components/Chat/Bubbles.vue +43 -14
  55. package/components/Chat/Information.vue +49 -32
  56. package/components/Chat/ListCard.vue +38 -9
  57. package/components/Chat/Message.vue +41 -15
  58. package/components/Chat/Navigation.vue +27 -15
  59. package/components/Chat/SkeletonLoader.vue +9 -2
  60. package/components/ChecklistItemRow.vue +25 -1
  61. package/components/CleaningScheduleMain.vue +44 -49
  62. package/components/ClientDetailForm.vue +81 -56
  63. package/components/ClientMain.vue +92 -82
  64. package/components/ConfirmDialog.vue +14 -8
  65. package/components/Container/Standard.vue +9 -7
  66. package/components/CreateSubsciptionPlan.vue +148 -89
  67. package/components/DashboardEmptyState.vue +48 -3
  68. package/components/DashboardMain.vue +924 -676
  69. package/components/DashboardPanel.vue +84 -21
  70. package/components/DeliveryCompany.vue +34 -20
  71. package/components/Dialog/DeleteConfirmation.vue +20 -21
  72. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  73. package/components/Dialog/ReusablePrompt.vue +30 -29
  74. package/components/Dialog/UpdateMoreAction.vue +62 -59
  75. package/components/DocumentForm.vue +14 -21
  76. package/components/DocumentManagement.vue +162 -108
  77. package/components/DocumentViewer.vue +29 -12
  78. package/components/DrawImage.vue +29 -21
  79. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  80. package/components/EntryPassInformation.vue +40 -10
  81. package/components/EntryPassMain.vue +49 -27
  82. package/components/EquipmentItemMain.vue +84 -97
  83. package/components/EquipmentManagementMain.vue +81 -103
  84. package/components/Facility/BookingSetup.vue +43 -52
  85. package/components/Feedback/Form.vue +26 -12
  86. package/components/FeedbackMain.vue +35 -49
  87. package/components/FileInput.vue +75 -58
  88. package/components/FileInputWithList.vue +59 -12
  89. package/components/FormDialog.vue +60 -21
  90. package/components/HidAccessLogDashboard.vue +512 -490
  91. package/components/HidCameraCaptureDialog.vue +216 -0
  92. package/components/HidEnabledGate.vue +67 -9
  93. package/components/HidIntercomManagement.vue +1322 -653
  94. package/components/HidQrCodeConfiguration.vue +1030 -258
  95. package/components/HidReaderForm.vue +142 -59
  96. package/components/HidReaderManagement.vue +211 -323
  97. package/components/HidServiceSettingsPanel.vue +44 -13
  98. package/components/HidUserEnrollment.vue +1185 -412
  99. package/components/HygieneUpdateMoreAction.vue +44 -44
  100. package/components/IncidentReport/Authorities.vue +30 -18
  101. package/components/IncidentReport/IncidentInformation.vue +10 -10
  102. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  103. package/components/IncidentReport/affectedEntities.vue +41 -20
  104. package/components/Input/Date.vue +3 -0
  105. package/components/Input/DatePicker.vue +1 -2
  106. package/components/Input/DateTimePicker.vue +1 -1
  107. package/components/Input/File.vue +55 -10
  108. package/components/Input/FileV2.vue +39 -6
  109. package/components/Input/InputPhoneNumberV2.vue +2 -1
  110. package/components/Input/ListGroupSelection.vue +56 -8
  111. package/components/Input/NRICNumber.vue +1 -0
  112. package/components/Input/NewDate.vue +1 -0
  113. package/components/Input/Number.vue +1 -0
  114. package/components/Input/Password.vue +1 -0
  115. package/components/Input/PhoneNumber.vue +21 -1
  116. package/components/Input/VehicleNumber.vue +1 -0
  117. package/components/InputLabel.vue +2 -2
  118. package/components/InvitationClientForm.vue +21 -19
  119. package/components/InvitationForm.vue +15 -21
  120. package/components/InvitationMain.vue +46 -61
  121. package/components/Layout/Header.vue +263 -36
  122. package/components/Layout/NavigationDrawer.vue +300 -30
  123. package/components/ListItem.vue +33 -3
  124. package/components/LocalPagination.vue +27 -3
  125. package/components/ManageChecklistMain.vue +136 -86
  126. package/components/MemberInformation.vue +32 -16
  127. package/components/MemberMain.vue +113 -93
  128. package/components/MyAttendanceMain.vue +3 -5
  129. package/components/NavigationItem.vue +83 -3
  130. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  131. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  132. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  133. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  134. package/components/Nfc/NFCTagForm.vue +22 -8
  135. package/components/Nfc/NFCTagMain.vue +41 -43
  136. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  137. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  139. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  140. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  141. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  142. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  143. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  144. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  145. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  146. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  147. package/components/NotificationSettings.vue +373 -0
  148. package/components/NumberSettingField.vue +9 -2
  149. package/components/OnlineFormConfigurationForm.vue +725 -237
  150. package/components/OnlineFormFill.vue +213 -106
  151. package/components/OnlineFormRenderPreview.vue +25 -30
  152. package/components/OnlineFormsConfiguration.vue +113 -109
  153. package/components/OrgViewDialog.vue +34 -16
  154. package/components/OvernightParkingAvailability.vue +49 -26
  155. package/components/OvernightParkingManagement.vue +78 -18
  156. package/components/PageHeader.vue +43 -0
  157. package/components/PassInformation.vue +109 -53
  158. package/components/PasswordConfirmation.vue +44 -26
  159. package/components/PeopleForm.vue +41 -20
  160. package/components/PhotoUpload.vue +110 -48
  161. package/components/PlaceholderComponent.vue +35 -19
  162. package/components/PlanActionsMenu.vue +12 -2
  163. package/components/PlanStatusBadge.vue +7 -8
  164. package/components/PlateNumberDisplay.vue +9 -6
  165. package/components/QrTemplate/PrintDialog.vue +66 -43
  166. package/components/RolePermissionFormCreate.vue +104 -79
  167. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  168. package/components/RolePermissionMain.vue +183 -81
  169. package/components/ScanVisitorQRCode.vue +99 -35
  170. package/components/ScheduleAreaMain.vue +34 -59
  171. package/components/ScheduleTaskForm.vue +61 -42
  172. package/components/ScheduleTaskMain.vue +56 -69
  173. package/components/SearchVehicleNumberUser.vue +39 -37
  174. package/components/ServiceProviderFormCreate.vue +29 -20
  175. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  176. package/components/ServiceProviderMain.vue +601 -432
  177. package/components/Signature.vue +40 -34
  178. package/components/SignaturePad.vue +5 -29
  179. package/components/SiteSettings.vue +41 -11
  180. package/components/SiteSettingsWorkOrder.vue +61 -30
  181. package/components/SlideCardGroup.vue +35 -11
  182. package/components/Snackbar.vue +36 -3
  183. package/components/SpecificAttr.vue +18 -8
  184. package/components/StatusChip.vue +156 -0
  185. package/components/StockCard.vue +17 -12
  186. package/components/SubscriptionPlanTable.vue +207 -140
  187. package/components/SwitchContext.vue +201 -9
  188. package/components/TableHygiene.vue +36 -46
  189. package/components/TableList.vue +10 -10
  190. package/components/TableListSecondary.vue +105 -54
  191. package/components/TableMain.vue +251 -78
  192. package/components/TableV3.vue +61 -70
  193. package/components/TiptapEditor.vue +33 -40
  194. package/components/TiptapEditorQr.vue +5 -2
  195. package/components/Tooltip/Info.vue +69 -24
  196. package/components/UnitMain.vue +40 -60
  197. package/components/UnitPersonCard.vue +42 -13
  198. package/components/VehicleAddSelection.vue +48 -32
  199. package/components/VehicleForm.vue +112 -129
  200. package/components/VehicleManagement.vue +26 -12
  201. package/components/VehicleUpdateMoreAction.vue +59 -46
  202. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  203. package/components/VisitorForm.vue +774 -210
  204. package/components/VisitorFormSelection.vue +40 -37
  205. package/components/VisitorManagement.vue +239 -82
  206. package/components/VisitorPassKeyQRScanner.vue +65 -18
  207. package/components/VisitorSearchResultSummary.vue +60 -16
  208. package/components/VisitorSocketPopUp.vue +22 -14
  209. package/components/VisitorsReportPreview.vue +44 -29
  210. package/components/WorkOrder/Create.vue +26 -17
  211. package/components/WorkOrder/ListView.vue +38 -7
  212. package/components/WorkOrder/Main.vue +62 -59
  213. package/composables/useFeedback.ts +3 -3
  214. package/composables/useHidAmico.ts +269 -0
  215. package/composables/useHidNavigation.ts +2 -9
  216. package/composables/useMember.ts +3 -3
  217. package/composables/useNFCPatrolDailyReport.ts +13 -0
  218. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  219. package/composables/useNFCPatrolReport.ts +13 -0
  220. package/composables/useNotificationPreference.ts +87 -0
  221. package/composables/useOnlineFormPages.ts +2 -0
  222. package/composables/useRole.ts +22 -0
  223. package/composables/useServiceProvider.ts +3 -3
  224. package/composables/useServiceProviderInvitation.ts +145 -0
  225. package/composables/useSipWebPhone.ts +336 -0
  226. package/composables/useSubscriptionPlan.ts +62 -0
  227. package/composables/useThemePreference.ts +63 -0
  228. package/composables/useVisitor.ts +6 -1
  229. package/composables/useWebUsb.ts +127 -37
  230. package/composables/useWorkOrder.ts +17 -3
  231. package/error.vue +30 -6
  232. package/nuxt.config.ts +36 -0
  233. package/package.json +25 -4
  234. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  235. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  237. package/pages/notification-settings.vue +19 -0
  238. package/pages/payment-method-linked.vue +34 -10
  239. package/pages/require-customer.vue +48 -10
  240. package/pages/require-organization-membership.vue +32 -8
  241. package/pages/unauthorized.vue +29 -4
  242. package/plugins/secure-member.client.ts +21 -56
  243. package/plugins/vuetify.ts +69 -2
  244. package/types/nfc-patrol-report.ts +3 -2
  245. package/types/site.d.ts +71 -0
  246. package/types/subscription.ts +18 -11
  247. package/utils/breadcrumb.test.ts +108 -0
  248. package/utils/breadcrumb.ts +82 -0
  249. package/utils/dashboard.test.ts +118 -0
  250. package/utils/dashboard.ts +53 -0
  251. package/utils/data.test.ts +95 -0
  252. package/utils/data.ts +40 -2
  253. package/utils/hid-discover.test.ts +102 -0
  254. package/utils/list-page.test.ts +60 -0
  255. package/utils/list-page.ts +38 -0
  256. package/utils/materialSymbols.ts +363 -0
  257. package/utils/nav-sections.test.ts +190 -0
  258. package/utils/nav-sections.ts +151 -0
  259. package/utils/nfc-patrol-report.test.ts +130 -0
  260. package/utils/nfc-patrol-report.ts +101 -0
  261. package/utils/role.test.ts +69 -0
  262. package/utils/role.ts +10 -0
  263. package/utils/route-name.test.ts +85 -0
  264. package/utils/route-name.ts +24 -0
  265. package/utils/status.test.ts +110 -0
  266. package/utils/status.ts +135 -0
  267. package/utils/table-card.test.ts +84 -0
  268. package/utils/theme-aa-ledger.ts +148 -0
  269. package/utils/theme.test.ts +485 -7
  270. package/utils/theme.ts +295 -110
  271. package/.changeset/README.md +0 -8
  272. package/.changeset/config.json +0 -11
  273. package/.editorconfig +0 -12
  274. package/.github/workflows/main.yml +0 -17
  275. package/.github/workflows/publish-staging.yml +0 -47
  276. package/.github/workflows/publish.yml +0 -39
  277. package/PUBLISHING.md +0 -269
  278. package/components/HidIdentityMapping.vue +0 -975
  279. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  280. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  281. package/components/ScheduleTastTicketMain.vue +0 -182
  282. package/components/TableWithButton.vue +0 -94
  283. package/test/visitor-socket.test.mjs +0 -36
@@ -1,27 +1,48 @@
1
+ <!--
2
+ The summary a visitor search draws under its result. Every one of the four
3
+ cells was `text-subtitle-1` - Vuetify's 16px/400 Roboto - so the LABEL that
4
+ names a value ("Name:", "NRIC:") was drawn at exactly the same size, weight
5
+ and ink as the value itself, and neither was the product font.
6
+
7
+ DESIGN GAP, STATED: the handoff draws no visitor-search panel. What it does
8
+ draw is a label sitting next to the value it names, at
9
+ `iService365 Redesign.dc.html:659` - `color:var(--muted);font-size:12.5px` -
10
+ inside a row whose own type is 13.5px/600 with the name at 700 (line 658).
11
+ Those are the values below: a muted 12.5px label over a 13.5px/700 value, the
12
+ same label-quieter-than-value relationship `ListItem.vue` already uses.
13
+
14
+ The trailing colons are dropped: the design separates a label from its value
15
+ by weight and ink, not by punctuation.
16
+ -->
1
17
  <template>
2
18
  <div class="w-100 py-2">
3
- <div class="d-flex align-center mb-3 justify-space-between ga-4 w-100">
4
- <span class="text-subtitle-1" style="min-width: 50%">
5
- <div>Name:</div>
6
- <div>{{ displayName }}</div>
7
- </span>
8
- <span class="text-subtitle-1" style="min-width: 50%">
9
- <div>NRIC:</div>
10
- <div>{{ item?.nric || "N/A" }}</div>
11
- </span>
19
+ <div class="d-flex flex-wrap align-center mb-3 justify-space-between ga-4 w-100">
20
+ <!-- These were `<span>` wrappers with `<div>` children - a block inside
21
+ an inline element, which the parser closes early. Now both are
22
+ blocks, which is also what makes the 50% min-width behave. -->
23
+ <div class="visitor-summary__cell">
24
+ <div class="visitor-summary__label">Name</div>
25
+ <div class="visitor-summary__value">{{ displayName }}</div>
26
+ </div>
27
+ <div class="visitor-summary__cell">
28
+ <div class="visitor-summary__label">NRIC</div>
29
+ <div class="visitor-summary__value">{{ item?.nric || "N/A" }}</div>
30
+ </div>
12
31
  </div>
13
32
 
14
33
  <div
15
34
  v-if="contacts.length < 2 && plateNumbers.length < 2"
16
- class="d-flex align-center justify-space-between ga-4 w-100"
35
+ class="d-flex flex-wrap align-center justify-space-between ga-4 w-100"
17
36
  >
18
- <div class="text-subtitle-1" style="min-width: 50%">
19
- <div>Contact:</div>
20
- <div>{{ contacts[0] || "N/A" }}</div>
37
+ <div class="visitor-summary__cell">
38
+ <div class="visitor-summary__label">Contact</div>
39
+ <div class="visitor-summary__value">{{ contacts[0] || "N/A" }}</div>
21
40
  </div>
22
- <div class="text-subtitle-1" style="min-width: 50%">
23
- <div>Plate:</div>
24
- <div class="text-uppercase">{{ plateNumbers[0] || "N/A" }}</div>
41
+ <div class="visitor-summary__cell">
42
+ <div class="visitor-summary__label">Plate</div>
43
+ <div class="visitor-summary__value text-uppercase">
44
+ {{ plateNumbers[0] || "N/A" }}
45
+ </div>
25
46
  </div>
26
47
  </div>
27
48
  </div>
@@ -84,3 +105,26 @@ const plateNumbers = computed(() => {
84
105
  );
85
106
  });
86
107
  </script>
108
+
109
+ <style scoped>
110
+ .visitor-summary__cell {
111
+ min-width: 50%;
112
+ }
113
+
114
+ .visitor-summary__label {
115
+ font-family: var(--font-sans);
116
+ font-size: 12.5px;
117
+ font-weight: var(--fw-cell);
118
+ letter-spacing: 0;
119
+ color: var(--muted);
120
+ margin-bottom: 2px;
121
+ }
122
+
123
+ .visitor-summary__value {
124
+ font-family: var(--font-sans);
125
+ font-size: var(--fs-cell);
126
+ font-weight: var(--fw-btn);
127
+ letter-spacing: 0;
128
+ color: var(--text);
129
+ }
130
+ </style>
@@ -49,7 +49,7 @@
49
49
  v-if="snackBarMessage.isUnregistered"
50
50
  rounded="md"
51
51
  variant="text"
52
- color="baseButton"
52
+ color="primary"
53
53
  class="text-truncate text-decoration-underline px-0"
54
54
  @click="goToUnregistered"
55
55
  >
@@ -90,19 +90,14 @@
90
90
  </v-snackbar>
91
91
 
92
92
  <v-dialog v-model="permanentMessageDialog.modal" max-width="480" persistent>
93
- <v-card>
94
- <v-card-title class="text-h6">Notice</v-card-title>
95
- <v-card-text>{{ permanentMessageDialog.text }}</v-card-text>
96
- <v-card-actions>
97
- <v-spacer />
98
- <v-btn
99
- color="primary"
100
- variant="elevated"
101
- @click="permanentMessageDialog.modal = false"
102
- >
103
- Close
104
- </v-btn>
105
- </v-card-actions>
93
+ <v-card class="screen-modal socket-notice">
94
+ <v-card-title>Notice</v-card-title>
95
+ <div class="screen-modal__body socket-notice__body">
96
+ {{ permanentMessageDialog.text }}
97
+ </div>
98
+ <div class="socket-notice__footer">
99
+ <AppButton @click="permanentMessageDialog.modal = false">Close</AppButton>
100
+ </div>
106
101
  </v-card>
107
102
  </v-dialog>
108
103
  </div>
@@ -372,3 +367,16 @@ onUnmounted(() => {
372
367
 
373
368
 
374
369
  </script>
370
+
371
+ <style scoped>
372
+ .socket-notice__body {
373
+ padding: 4px 20px 0;
374
+ color: var(--text2);
375
+ }
376
+
377
+ .socket-notice__footer {
378
+ display: flex;
379
+ justify-content: flex-end;
380
+ padding: 16px 20px 20px;
381
+ }
382
+ </style>
@@ -1,22 +1,25 @@
1
1
  <template>
2
- <v-card height="100%" width="100%" flat class="pa-2 pa-sm-5">
2
+ <AppCard class="pa-2 pa-sm-5">
3
3
  <v-row no-gutters>
4
4
  <v-col cols="12">
5
5
  <v-row no-gutters class="d-flex ga-2 justify-space-between align-start mb-3 mb-sm-5 flex-wrap">
6
6
 
7
7
  <div class="d-flex flex-column" style="min-width: 0;">
8
- <span class="text-subtitle-1 font-weight-bold" style="word-break: break-word;">Visitor
8
+ <span class="screen-card-title" style="word-break: break-word;">Visitor
9
9
  Management - Visitor ({{ items.length }} visitors)</span>
10
- <span style="font-size: clamp(11px, 1.1vw, 13px); word-break: break-word;">Date Range: {{
10
+ <span class="report-meta" style="word-break: break-word;">Date Range: {{
11
11
  formatDateDDMMYYYY(dateFrom) }} - {{ formatDateDDMMYYYY(dateTo) ?? "" }}</span>
12
- <span class="text-subtitle-1" style="word-break: break-word;">Site Name: {{ activeSiteName
12
+ <span class="report-meta" style="word-break: break-word;">Site Name: {{ activeSiteName
13
13
  }}</span>
14
14
  </div>
15
15
 
16
16
  </v-row>
17
- <div style="overflow-x: auto; max-width: 100%;">
17
+ <!-- `text-black` forced every cell AND the empty-state line to
18
+ literal black, which on the dark theme's card measured
19
+ 1.26:1. The table takes the theme's own ink now. -->
20
+ <div class="table-card" style="overflow-x: auto; max-width: 100%;">
18
21
  <v-data-table :headers="headers" :items="items" show-header :items-per-page="100"
19
- hide-default-footer class="text-black" no-data-text="No entries to display">
22
+ hide-default-footer no-data-text="No entries to display">
20
23
 
21
24
  <template v-slot:item.typeCompany="{ item }">
22
25
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
@@ -77,13 +80,13 @@
77
80
  <template v-slot:item.checkInOutImage="{ item }">
78
81
  <div class="d-flex flex-column ga-2">
79
82
  <div v-if="item?.snapshotEntryImage" class="d-flex flex-column">
80
- <span class="text-caption font-weight-bold text-blue-darken-1">Check-In</span>
83
+ <span class="snapshot-label snapshot-label--in">Check-In</span>
81
84
  <v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotEntryImage}`)"
82
85
  style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
83
86
  class="border" />
84
87
  </div>
85
88
  <div v-if="item?.snapshotExitImage" class="d-flex flex-column">
86
- <span class="text-caption font-weight-bold text-red-darken-1">Check-Out</span>
89
+ <span class="snapshot-label snapshot-label--out">Check-Out</span>
87
90
  <v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotExitImage}`)"
88
91
  style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
89
92
  class="border" />
@@ -101,7 +104,7 @@
101
104
  </v-col>
102
105
 
103
106
  </v-row>
104
- </v-card>
107
+ </AppCard>
105
108
  </template>
106
109
 
107
110
  <script setup lang="ts">
@@ -311,44 +314,56 @@ onMounted(() => {
311
314
  box-sizing: border-box;
312
315
  }
313
316
 
314
- :deep(.v-card) {
315
- overflow-x: auto;
317
+ /* The handoff names no type for a report's meta lines or a snapshot caption,
318
+ so both take the design's own secondary sizes. The captions were
319
+ `text-blue-darken-1` / `text-red-darken-1` - fixed Vuetify hues that do not
320
+ follow the theme. */
321
+ .report-meta {
322
+ font-family: var(--font-sans);
323
+ font-size: var(--fs-breadcrumb);
324
+ color: var(--text2);
316
325
  }
317
326
 
318
- :deep(.v-data-table-header__content) {
319
- background-color: #1976d2 !important;
320
- color: white !important;
321
- font-weight: bold;
327
+ .snapshot-label {
328
+ font-family: var(--font-sans);
329
+ font-size: var(--fs-chip);
330
+ font-weight: var(--fw-chip);
322
331
  }
323
332
 
324
- :deep(.v-data-table thead tr th) {
325
- background-color: #1976d2 !important;
326
- color: white !important;
327
- border-right: 1px solid rgba(255, 255, 255, 0.2) !important;
328
- font-size: clamp(11px, 1.2vw, 14px) !important;
329
- padding: clamp(8px, 1vw, 12px) !important;
330
- word-break: break-word;
333
+ .snapshot-label--in {
334
+ color: var(--info);
331
335
  }
332
336
 
333
- :deep(.v-data-table thead tr th:last-child) {
334
- border-right: none !important;
337
+ .snapshot-label--out {
338
+ /* Was `var(--error)`, which is not a token here - the name is `--err`. An
339
+ undefined custom property invalidates the declaration silently, so the
340
+ label inherited instead of turning red. */
341
+ color: var(--err);
342
+ }
343
+
344
+ :deep(.v-card) {
345
+ overflow-x: auto;
335
346
  }
336
347
 
348
+ /* The header row was a fixed `#1976d2` fill with white labels and the cell
349
+ rules were a fixed `#e0e0e0`, none of which follows the theme and none of
350
+ which is in the handoff - the design's table head is 11px/800 uppercase in
351
+ `--muted` on `--thead`. Deleted so the shared `.table-card` rule applies;
352
+ only the column rule this report actually needs is kept, on `--border`. */
353
+ :deep(.v-data-table thead tr th),
337
354
  :deep(.v-data-table tbody tr td) {
338
- border-right: 1px solid #e0e0e0 !important;
339
- font-size: clamp(10px, 1.1vw, 13px) !important;
340
- padding: clamp(6px, 0.8vw, 10px) !important;
355
+ border-right: 1px solid var(--border) !important;
341
356
  word-break: break-word;
342
- word-wrap: break-word;
343
357
  overflow-wrap: break-word;
344
358
  }
345
359
 
360
+ :deep(.v-data-table thead tr th:last-child),
346
361
  :deep(.v-data-table tbody tr td:last-child) {
347
362
  border-right: none !important;
348
363
  }
349
364
 
350
365
  :deep(.v-data-table tbody tr:last-child td) {
351
- border-bottom: 1px solid #e0e0e0 !important;
366
+ border-bottom: 1px solid var(--border) !important;
352
367
  }
353
368
 
354
369
  :deep(.v-img) {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <FormDialog v-model="dialog" @close="closeDialog">
3
3
  <template #title>
4
- <span class="text-h6 font-weight-medium pt-1 text-capitalize">
4
+ <span class="screen-card-title pt-1 text-capitalize">
5
5
  {{ isEditMode ? "Edit Work Order" : "Create Work Order" }}
6
6
  </span>
7
7
  </template>
@@ -24,8 +24,7 @@
24
24
  item-props
25
25
  label="Subject"
26
26
  variant="outlined"
27
- density="compact"
28
- class="mb-2"
27
+ class="filter-field mb-2"
29
28
  :clearable="
30
29
  props.createdFrom === 'feedback' && workOrder.subject !== ''
31
30
  ? false
@@ -39,7 +38,7 @@
39
38
  :custom-filter="customFilter"
40
39
  :rules="[requiredRule]"
41
40
  >
42
- <template #label> Subject <span class="text-red">*</span> </template>
41
+ <template #label> Subject <span class="form-required">*</span> </template>
43
42
  </v-autocomplete>
44
43
 
45
44
  <v-autocomplete
@@ -50,8 +49,7 @@
50
49
  item-props
51
50
  label="Category"
52
51
  variant="outlined"
53
- density="compact"
54
- class="mb-2"
52
+ class="filter-field mb-2"
55
53
  :clearable="
56
54
  props.createdFrom === 'feedback' && workOrder.category !== ''
57
55
  ? false
@@ -64,12 +62,12 @@
64
62
  "
65
63
  :rules="[requiredRule]"
66
64
  >
67
- <template #label> Category <span class="text-red">*</span> </template>
65
+ <template #label> Category <span class="form-required">*</span> </template>
68
66
  </v-autocomplete>
69
67
 
70
68
  <v-checkbox v-model="workOrder.highPriority" density="compact" class="ma-0">
71
69
  <template #label>
72
- <span class="text-caption">
70
+ <span class="form-check-label">
73
71
  Set work order as High Priority (Optional)
74
72
  </span>
75
73
  </template>
@@ -98,14 +96,13 @@
98
96
  <v-autocomplete
99
97
  label="Unit"
100
98
  variant="outlined"
101
- density="compact"
99
+ class="filter-field mt-1"
102
100
  v-model="workOrder.unit"
103
101
  :items="units"
104
- class="mt-1"
105
102
  clearable
106
103
  :rules="[requiredRule]"
107
104
  >
108
- <template #label> Unit <span class="text-red">*</span> </template>
105
+ <template #label> Unit <span class="form-required">*</span> </template>
109
106
  </v-autocomplete>
110
107
 
111
108
  <!-- <v-text-field
@@ -125,23 +122,21 @@
125
122
  <v-textarea
126
123
  label="Work Order Description"
127
124
  variant="outlined"
128
- density="compact"
125
+ class="filter-field mt-1"
129
126
  v-model="workOrder.description"
130
- class="mt-1"
131
127
  rows="4"
132
128
  clearable
133
129
  auto-grow
134
130
  :rules="[requiredRule]"
135
131
  >
136
- <template #label> Description <span class="text-red">*</span> </template>
132
+ <template #label> Description <span class="form-required">*</span> </template>
137
133
  </v-textarea>
138
134
 
139
135
  <template #footer>
140
136
  <v-btn
141
- color="primary-button"
142
- class="text-capitalize mb-4"
137
+ class="screen-btn-primary mb-4"
138
+ variant="flat"
143
139
  block
144
- height="40"
145
140
  :loading="loading"
146
141
  :disabled="loading || !isFormValid"
147
142
  @click="submitWorkOrder"
@@ -282,3 +277,17 @@ const customFilter = (value: string, query: string, item: any) => {
282
277
  return title.includes(search) || subtitle.includes(search);
283
278
  };
284
279
  </script>
280
+
281
+ <style scoped>
282
+ /* The required star was `text-red`, a Vuetify palette word rather than the
283
+ design's error token. */
284
+ .form-required {
285
+ color: var(--err);
286
+ }
287
+
288
+ .form-check-label {
289
+ font-family: var(--font-sans);
290
+ font-size: var(--fs-cell);
291
+ color: var(--text);
292
+ }
293
+ </style>
@@ -13,14 +13,22 @@
13
13
  @click:create="emit('click:create')"
14
14
  :length="pages"
15
15
  >
16
+ <!--
17
+ A failed request is not a site with no work orders. Without this the
18
+ table drew its neutral "No data available" for a 500, which reads as
19
+ "there is nothing outstanding" - a statement the server never made.
20
+ -->
21
+ <template v-if="failed" #no-data>
22
+ <DashboardEmptyState error />
23
+ </template>
24
+
16
25
  <template #title>
17
- <span class="text-h6 font-weight-regular">Work Order</span>
26
+ <span class="screen-card-title">Work Order</span>
18
27
  </template>
19
28
  <template #action-button>
20
29
  <v-btn
21
- color="primary-button"
22
30
  variant="flat"
23
- class="ml-4 text-capitalize"
31
+ class="screen-btn-primary ml-4"
24
32
  @click="emit('click:create')"
25
33
  >
26
34
  Work Order
@@ -53,6 +61,8 @@
53
61
  </template>
54
62
 
55
63
  <script lang="ts" setup>
64
+ import { readListPage, NO_PAGE_RANGE } from "../../utils/list-page";
65
+
56
66
  const emit = defineEmits(["click:create", "update:pagination"]);
57
67
 
58
68
  const { page, pages, pageRange, workOrders, getWorkOrders } = useWorkOrder();
@@ -62,6 +72,7 @@ const message = ref("");
62
72
  const messageColor = ref("");
63
73
  const messageSnackbar = ref(false);
64
74
  const loading = ref(false);
75
+ const failed = ref(false);
65
76
  const height = ref("500px");
66
77
  const selected = ref<string[]>([]);
67
78
 
@@ -73,20 +84,40 @@ const headers = ref([
73
84
  { title: "Date", value: "createdAt" },
74
85
  ]);
75
86
 
87
+ // `getWorkOrders` RETURNS the reply; nothing was ever reading it. `workOrders`,
88
+ // `pages` and `pageRange` are `useState` cells that no code in this layer or in
89
+ // any of the eleven apps ever assigned, so this table drew zero rows and
90
+ // "-- - -- of --" no matter what the server sent. The reply is now bound
91
+ // through the one helper that knows how a paginated reply is shaped, so the
92
+ // count on screen is the server's count of the whole list - never the number
93
+ // of rows on this page.
76
94
  async function _getWorkOrders() {
77
95
  loading.value = true;
96
+ failed.value = false;
78
97
 
79
98
  try {
80
- await getWorkOrders({
81
- page: page.value,
82
- });
99
+ const listPage = readListPage<TWorkOrder>(
100
+ await getWorkOrders({
101
+ page: page.value,
102
+ })
103
+ );
104
+ workOrders.value = listPage.items;
105
+ pages.value = listPage.pages;
106
+ pageRange.value = listPage.pageRange;
83
107
 
84
108
  emit("update:pagination", {
85
109
  page: page.value,
86
110
  pages: pages.value,
87
111
  });
88
112
  } catch (error) {
89
- console.error("Error fetching feedbacks:", error);
113
+ // Say the request failed rather than presenting it as an empty list. The
114
+ // count goes back to the placeholder for the same reason: a failure is not
115
+ // a zero.
116
+ failed.value = true;
117
+ workOrders.value = [];
118
+ pages.value = 0;
119
+ pageRange.value = NO_PAGE_RANGE;
120
+ console.error("Error fetching work orders:", error);
90
121
  } finally {
91
122
  loading.value = false;
92
123
  }
@@ -1,58 +1,47 @@
1
1
  <template>
2
- <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters align="center" justify="space-between">
5
- <v-btn
6
- class="text-none text-capitalize"
7
- rounded="pill"
8
- variant="tonal"
9
- size="large"
10
- @click="(showCreateDialog = true), (isEditMode = false)"
11
- v-if="canCreateWorkOrder"
12
- >
2
+ <div>
3
+ <!--
4
+ The design's page scaffold. The screen had no heading, its Create button
5
+ sat top-left and the search floated top-right outside the card, with
6
+ refresh and the pager stacked on their own bar above the table. Same
7
+ button, same dialog, same permission, same debounced search, same pager.
8
+ -->
9
+ <PageHeader title="Work Orders">
10
+ <template v-if="canCreateWorkOrder" #actions>
11
+ <AppButton @click="(showCreateDialog = true), (isEditMode = false)">
13
12
  Create Work Order
14
- </v-btn>
15
-
16
- <v-text-field
17
- v-model="searchText"
18
- placeholder="Search work order..."
19
- variant="outlined"
20
- density="comfortable"
21
- clearable
22
- hide-details
23
- class="ml-2"
24
- style="max-width: 250px"
25
- />
26
- </v-row>
27
- </v-col>
13
+ </AppButton>
14
+ </template>
15
+ </PageHeader>
16
+
17
+ <v-row no-gutters>
28
18
  <v-col cols="12">
29
- <v-card
30
- width="100%"
31
- variant="outlined"
32
- border="thin"
33
- rounded="lg"
34
- :loading="loading"
35
- >
36
- <v-toolbar density="compact" color="grey-lighten-4">
37
- <template #prepend>
38
- <v-btn fab icon density="comfortable" @click="updatePage(1)">
39
- <v-icon>mdi-refresh</v-icon>
40
- </v-btn>
19
+ <AppCard class="table-card">
20
+ <CardToolbar :count="pageRange">
21
+ <template #left>
22
+ <AppButton
23
+ variant="icon"
24
+ icon="mdi-refresh"
25
+ aria-label="Refresh"
26
+ @click="updatePage(1)"
27
+ />
41
28
  </template>
42
29
 
43
- <template #append>
44
- <v-row no-gutters justify="end" align="center">
45
- <span class="mr-2 text-caption text-fontgray">
46
- {{ pageRange }}
47
- </span>
48
- <local-pagination
49
- v-model="page"
50
- :length="pages"
51
- @update:value="updatePage"
52
- />
53
- </v-row>
30
+ <template #right>
31
+ <AppField
32
+ v-model="searchText"
33
+ search
34
+ compact
35
+ placeholder="Search work order..."
36
+ klass="app-toolbar__search"
37
+ />
38
+ <local-pagination
39
+ v-model="page"
40
+ :length="pages"
41
+ @update:value="updatePage"
42
+ />
54
43
  </template>
55
- </v-toolbar>
44
+ </CardToolbar>
56
45
 
57
46
  <v-data-table
58
47
  :headers="headers"
@@ -62,6 +51,7 @@
62
51
  fixed-header
63
52
  hide-default-footer
64
53
  :hide-default-header="false"
54
+ :loading="loading"
65
55
  @click:row="tableRowClickHandler"
66
56
  style="max-height: calc(100vh - (200px))"
67
57
  >
@@ -69,9 +59,10 @@
69
59
  {{ formatDateDDMMYYYYLocal(item.createdAt) }}
70
60
  </template>
71
61
  </v-data-table>
72
- </v-card>
62
+ </AppCard>
73
63
  </v-col>
74
- </v-row>
64
+ </v-row>
65
+ </div>
75
66
 
76
67
  <WorkOrderCreate
77
68
  v-model="showCreateDialog"
@@ -217,6 +208,9 @@
217
208
 
218
209
  <script setup lang="ts">
219
210
  import { useTheme } from "vuetify";
211
+ // Relative import, never Nuxt auto-import: this layer's `utils/` are auto-imported
212
+ // into the consuming apps but not reliably into the layer's own components.
213
+ import { pickRouteName } from "../../utils/route-name";
220
214
  const emit = defineEmits(["click:create", "update:pagination"]);
221
215
 
222
216
  const props = defineProps({
@@ -372,14 +366,23 @@ function handleRowClick(row: any) {
372
366
  const org = useRoute().params.org || "defaultOrg";
373
367
  const site = useRoute().params.site || "defaultSite";
374
368
  const id = row._id;
375
- useRouter().push({
376
- name: "work-order-details",
377
- params: {
378
- org,
379
- site,
380
- id,
381
- },
382
- });
369
+ // `work-order-details` is not a route in any consuming app; the detail screen
370
+ // is `pages/[org]/[site]/work-orders/[id].vue` -> `org-site-work-orders-id`.
371
+ const details = pickRouteName(
372
+ useRouter(),
373
+ "org-site-work-orders-id",
374
+ "work-orders-id"
375
+ );
376
+ if (details) {
377
+ useRouter().push({
378
+ name: details,
379
+ params: {
380
+ org,
381
+ site,
382
+ id,
383
+ },
384
+ });
385
+ }
383
386
  }
384
387
 
385
388
  function resetWorkOrderForm() {
@@ -51,9 +51,9 @@ export default function useFeedback() {
51
51
 
52
52
  const feedback = useState("feedback", () => new MFeedback());
53
53
  const feedbacks = useState("feedbacks", (): TFeedback[] => []);
54
- const page = useState("page", () => 1);
55
- const pages = useState("pages", () => 0);
56
- const pageRange = useState("pageRange", () => "-- - -- of --");
54
+ const page = useState("feedbackPage", () => 1);
55
+ const pages = useState("feedbackPages", () => 0);
56
+ const pageRange = useState("feedbackPageRange", () => "-- - -- of --");
57
57
 
58
58
  function setFeedback(data?: TFeedback) {
59
59
  feedback.value = data || new MFeedback();