@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,92 +1,197 @@
1
+ <!--
2
+ PHASE 3 - THE STANDARD TABLE CARD.
3
+
4
+ The redesign is, more than anything else, a table card: toolbar row (refresh,
5
+ optional in-card search, pagination) -> uppercase header row -> rows. This is
6
+ the shared one, so this file is where that pattern is drawn once.
7
+
8
+ VISUAL ONLY, and that matters more here than anywhere. Every column, filter,
9
+ sort, action, slot, prop, emit and page still behaves exactly as it did:
10
+ the headers are the caller's, the items are the caller's, every slot is still
11
+ forwarded to `v-data-table` untouched, and no prop's default has changed. The
12
+ empty state is the same wording `v-data-table` already showed, with the icon
13
+ the design draws above it - and it is only supplied when the caller has NOT
14
+ passed its own `no-data` slot.
15
+ -->
1
16
  <template>
2
- <v-row no-gutters>
3
- <!-- Top Actions -->
4
- <v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
5
- <v-row no-gutters>
6
- <slot name="actions">
7
- <v-col cols="12">
8
- <v-row no-gutters class="ga-2">
9
- <v-btn
10
- v-if="canCreate"
11
- class="text-none"
12
- rounded="pill"
13
- variant="tonal"
14
- size="large"
15
- @click="emits('create')"
16
- >
17
- {{ createLabel }}
18
- </v-btn>
19
- <v-text-field
20
- v-if="canSearch"
21
- v-model="searchInput"
22
- density="compact"
23
- placeholder="Search"
24
- clearable
25
- max-width="300"
26
- append-inner-icon="mdi-magnify"
27
- hide-details
28
- />
29
- <v-btn
30
- v-if="canScanVisitorQRCode"
31
- text="Scan QR Code"
32
- class="text-none ml-2"
33
- rounded="pill"
34
- variant="tonal"
35
- size="large"
36
- @click="emits('scan')"
37
- />
38
- </v-row>
39
- </v-col>
40
- </slot>
41
- </v-row>
17
+ <v-row no-gutters class="table-card__shrink">
18
+ <!--
19
+ THE DESIGN'S PAGE TITLE (Phase 9).
20
+
21
+ The design opens every screen with a 22px/800 heading. This shared table
22
+ card is mounted by 17 module screens, and none of them had one - four of
23
+ them (Units, Areas, Equipment Item, Equipment Management) were already
24
+ PASSING `:title` to this component, which had no `title` prop, so the
25
+ string was landing on the root element as a plain HTML `title` attribute:
26
+ a browser tooltip nobody asked for. It now draws the heading it was
27
+ always meant to draw.
28
+
29
+ Placement only. No caller changes, no slot moves, no behaviour: a screen
30
+ that passes no title renders exactly what it rendered before.
31
+ -->
32
+ <!--
33
+ The actions used to be drawn on a SECOND row underneath that heading,
34
+ which is not where the design puts them - the design's title row is the
35
+ page name on the left and the screen's actions on the right of the SAME
36
+ line, which is why every screen converted so far had to bypass this
37
+ component and draw its own `PageHeader` above it. They are on the title
38
+ row now, in `PageHeader`'s own `actions` slot.
39
+
40
+ Same buttons, same slot name, same content, same events - one row instead
41
+ of two. A screen with no title still gets its actions at the left, where
42
+ they have always been, because the title block is not drawn at all.
43
+ -->
44
+ <v-col cols="12" v-if="title || canCreate || $slots.actions">
45
+ <PageHeader :title="title">
46
+ <template v-if="canCreate || $slots.actions" #actions>
47
+ <slot name="actions">
48
+ <!--
49
+ The design's primary button: the accent fill that may carry a
50
+ white label (`--accent-strong`; white on `--accent` is 3.23:1),
51
+ 40px, 10px radius, leading icon. Same click, same label.
52
+ -->
53
+ <v-btn
54
+ v-if="canCreate"
55
+ class="text-none table-card__primary"
56
+ variant="flat"
57
+ prepend-icon="mdi-plus"
58
+ @click="emits('create')"
59
+ >
60
+ {{ createLabel }}
61
+ </v-btn>
62
+ <v-text-field
63
+ v-if="canSearch"
64
+ v-model="searchInput"
65
+ density="compact"
66
+ placeholder="Search"
67
+ clearable
68
+ max-width="300"
69
+ append-inner-icon="mdi-magnify"
70
+ hide-details
71
+ class="table-card__search"
72
+ />
73
+ <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
74
+ <v-btn
75
+ v-if="canScanVisitorQRCode"
76
+ text="Scan QR Code"
77
+ class="text-none table-card__ghost"
78
+ variant="outlined"
79
+ @click="emits('scan')"
80
+ />
81
+ </slot>
82
+ </template>
83
+ </PageHeader>
42
84
  </v-col>
43
85
 
44
86
  <!-- Table Card -->
45
- <v-col cols="12">
46
- <v-card
47
- width="100%"
48
- variant="outlined"
49
- border="thin"
50
- rounded="lg"
51
- :loading="loading"
52
- >
87
+ <!--
88
+ `table-card__shrink` is the fix for the wide-table blowout: this column is a
89
+ flex item, and a flex item's automatic minimum size is its CONTENT's, so
90
+ a table with enough columns set a floor the column would not shrink below
91
+ and the whole page scrolled sideways at 360 and 768. The table scrolls
92
+ inside the card instead - see `.table-card__shrink` in `screens.css`.
93
+ -->
94
+ <v-col cols="12" class="table-card__shrink">
95
+ <!--
96
+ THE DESIGN'S CARD AND ITS IN-CARD TOP ROW (Phase 9).
97
+
98
+ Was a `v-card` + `v-toolbar`. `v-card` brings its own radius, elevation
99
+ and surface at a specificity a class has to fight, and `v-toolbar` puts
100
+ its `extension` on a SECOND stacked row. `AppCard` + `CardToolbar` are
101
+ the primitives the design is drawn with, already proven on
102
+ `ServiceProviderMain` / `MemberMain`.
103
+
104
+ Placement only. The refresh button, the `prepend-additional` slot, the
105
+ page range, the pager, the `extension` slot and the `footer` slot are
106
+ the same controls, emitting the same events, in the same order. The
107
+ `loading` bar `v-card` used to draw is drawn explicitly so a loading
108
+ screen still says so.
109
+ -->
110
+ <AppCard class="table-card">
111
+ <v-progress-linear
112
+ v-if="loading"
113
+ indeterminate
114
+ height="2"
115
+ class="table-card__loading"
116
+ />
117
+
53
118
  <!-- Toolbar -->
54
- <v-toolbar
55
- density="compact"
56
- color="grey-lighten-4"
57
- :extension-height="extensionHeight"
58
- >
59
- <template #prepend>
60
- <v-btn fab icon density="comfortable" @click="emits('refresh')">
119
+ <CardToolbar :count="pageRange">
120
+ <!--
121
+ The design draws a screen's tabs ON the toolbar row, not on a
122
+ stacked strip above the table. `CardToolbar` has always had the
123
+ slot; `TableMain` simply never forwarded it, so callers put their
124
+ `v-tabs` in `extension` and got a second row. Purely additive: a
125
+ caller that passes no `tabs` renders exactly what it rendered.
126
+ -->
127
+ <template v-if="$slots.tabs" #tabs>
128
+ <slot name="tabs" />
129
+ </template>
130
+
131
+ <template #left>
132
+ <!--
133
+ THE REFRESH BUTTON HAD NO BUSY STATE.
134
+
135
+ It emitted `refresh` and then looked identical for the whole
136
+ request. When the reload returned the same rows, NOTHING on the
137
+ screen changed - so there was no way to tell whether the click
138
+ had registered, which is exactly what QA reported on Work Orders
139
+ and on the Duty Officer Book (both fire the request; neither
140
+ showed it). `loading` is the caller's own in-flight flag, the
141
+ same one the bar above already reads: while it is true the button
142
+ is disabled, so the click is acknowledged and a second one cannot
143
+ stack another request on the first.
144
+
145
+ A caller that passes no `loading` renders exactly what it renders
146
+ today (the prop defaults to false).
147
+ -->
148
+ <v-btn
149
+ fab
150
+ icon
151
+ density="comfortable"
152
+ variant="text"
153
+ :disabled="loading"
154
+ @click="emits('refresh')"
155
+ >
61
156
  <v-icon>mdi-refresh</v-icon>
62
157
  </v-btn>
63
158
  <slot name="prepend-additional" />
64
159
  </template>
65
160
 
66
- <template #append>
67
- <v-row no-gutters justify="end" align="center">
68
- <span class="mr-2 text-caption text-fontgray">
69
- {{ pageRange }}
70
- </span>
71
- <local-pagination
72
- v-model="internalPage"
73
- :length="pages"
74
- @update:value="emits('update:page', internalPage)"
75
- />
76
- </v-row>
161
+ <template #right>
162
+ <local-pagination
163
+ v-model="internalPage"
164
+ :length="pages"
165
+ @update:value="emits('update:page', internalPage)"
166
+ />
77
167
  </template>
168
+ </CardToolbar>
78
169
 
79
- <template v-if="$slots.extension" #extension>
80
- <slot name="extension" />
81
- </template>
82
- </v-toolbar>
170
+ <!--
171
+ The callers' `extension` is a FILTER row (selects, date ranges), not
172
+ tabs - it is full-width by construction, so it keeps its own line
173
+ under the toolbar rather than being folded into it, which would push
174
+ the pager off the end of the row.
175
+ -->
176
+ <div v-if="$slots.extension" class="table-card__extension">
177
+ <slot name="extension" />
178
+ </div>
83
179
 
84
180
  <!-- Data Table -->
181
+ <!--
182
+ `tableHeaders`, not `headers`, and the two are the SAME ARRAY unless a
183
+ caller has opted into fixed proportions by putting `weight` on a
184
+ column. See `columnsAreWeighted` below.
185
+ -->
85
186
  <v-data-table
187
+ v-model="selectedItems"
86
188
  :headers="headers"
189
+ :class="columnsAreWeighted ? 'table-card__fixed' : undefined"
190
+ :headers="tableHeaders"
87
191
  :items="items"
88
192
  :item-value="itemValue"
89
193
  :items-per-page="itemsPerPage"
194
+ :show-select="showSelect"
90
195
  fixed-header
91
196
  hide-default-footer
92
197
  :hide-default-header="!showHeader"
@@ -94,17 +199,40 @@
94
199
  :style="`max-height: calc(100vh - (${offset}px))`"
95
200
  >
96
201
  <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
97
- <slot :name="slotName" v-bind="slotProps" />
202
+ <!--
203
+ Vuetify invokes argument-less slots such as `no-data` with null
204
+ props. Vue's dynamic `renderSlot` expects an object and otherwise
205
+ crashes while reading `props.key`, leaving the previous route on
206
+ screen even though Vue Router has already updated the URL.
207
+ -->
208
+ <slot :name="slotName" v-bind="slotProps ?? {}" />
209
+ </template>
210
+
211
+ <!--
212
+ The design's empty state. Same sentence the table already showed;
213
+ only supplied when the caller has not passed its own, so nobody's
214
+ existing empty state is replaced.
215
+ -->
216
+ <template v-if="!$slots['no-data']" #no-data>
217
+ <div class="table-card__empty">
218
+ <v-icon icon="mdi-magnify-close" size="32" />
219
+ <span>No data available</span>
220
+ </div>
98
221
  </template>
99
222
  </v-data-table>
100
223
  <slot name="footer" />
101
- </v-card>
224
+ </AppCard>
102
225
  </v-col>
103
226
  </v-row>
104
227
  </template>
105
228
 
106
229
  <script lang="ts" setup>
107
230
  const props = defineProps({
231
+ // The design's page heading. Empty = no heading, i.e. exactly today's markup.
232
+ title: {
233
+ type: String,
234
+ default: "",
235
+ },
108
236
  headers: {
109
237
  type: Array as PropType<Array<Record<string, any>>>,
110
238
  required: true,
@@ -157,6 +285,10 @@ const props = defineProps({
157
285
  type: Boolean,
158
286
  default: false,
159
287
  },
288
+ showSelect: {
289
+ type: Boolean,
290
+ default: false,
291
+ },
160
292
  extensionHeight: {
161
293
  type: Number,
162
294
  default: 50,
@@ -167,6 +299,49 @@ const props = defineProps({
167
299
  },
168
300
  });
169
301
 
302
+ /**
303
+ * THE DESIGN'S FIXED COLUMN PROPORTIONS, AND WHY THEY ARE OPT-IN.
304
+ *
305
+ * Every table in the prototype is a CSS grid with an explicit, per-table
306
+ * `grid-template-columns` - `0.9fr 1.5fr 1.7fr 0.8fr 1fr 1.1fr` on Feedbacks,
307
+ * `2fr 1fr 1fr` on Units, `1.6fr 1fr 1.3fr 1.6fr 48px` on Pass & Key. Those
308
+ * ratios are a per-screen design decision. Nothing in a caller's `headers`
309
+ * array carries that information, so this shared component cannot derive them,
310
+ * and inventing one set of proportions for 21 components and 18 app pages would
311
+ * be worse than the automatic layout they have now.
312
+ *
313
+ * So it is stated, not guessed: a header may carry `weight: 1.5` and this turns
314
+ * the weights into percentage widths and switches the table to
315
+ * `table-layout: fixed`, which is what makes a width binding rather than a hint.
316
+ * A column with NO weight is left exactly as the caller wrote it, so an
317
+ * existing `width` (an action column pinned at 48px, say) survives untouched
318
+ * and only the weighted columns share out what is left.
319
+ *
320
+ * NO caller changes. With no weights anywhere, `tableHeaders` IS `props.headers`
321
+ * - the same array reference - and the table renders exactly what it renders
322
+ * today, automatic widths and all.
323
+ */
324
+ const columnsAreWeighted = computed(() =>
325
+ props.headers.some((h) => Number(h?.weight) > 0)
326
+ );
327
+
328
+ const tableHeaders = computed(() => {
329
+ if (!columnsAreWeighted.value) return props.headers;
330
+
331
+ // Columns with no weight keep whatever width they already had (often none,
332
+ // e.g. a 48px action column), and only the weighted ones share what is left.
333
+ const total = props.headers.reduce(
334
+ (sum, h) => sum + (Number(h?.weight) > 0 ? Number(h.weight) : 0),
335
+ 0
336
+ );
337
+
338
+ return props.headers.map((h) =>
339
+ Number(h?.weight) > 0
340
+ ? { ...h, width: `${((Number(h.weight) / total) * 100).toFixed(4)}%` }
341
+ : h
342
+ );
343
+ });
344
+
170
345
  const emits = defineEmits([
171
346
  "create",
172
347
  "scan",
@@ -176,6 +351,10 @@ const emits = defineEmits([
176
351
  ]);
177
352
 
178
353
  const searchInput = defineModel("search", { default: "" });
354
+ const selectedItems = defineModel("selected", {
355
+ type: Array as PropType<any[]>,
356
+ default: () => [],
357
+ });
179
358
 
180
359
  const internalPage = ref(props.page);
181
360
  watch(
@@ -185,9 +364,3 @@ watch(
185
364
  }
186
365
  );
187
366
  </script>
188
-
189
- <style scoped>
190
- .v-data-table :deep(thead th) {
191
- font-weight: bold !important;
192
- }
193
- </style>
@@ -5,43 +5,39 @@
5
5
  border="thin"
6
6
  rounded="lg"
7
7
  :loading="loading"
8
- class="table-v3"
8
+ class="table-v3 table-card"
9
9
  >
10
- <v-toolbar density="compact" color="grey-lighten-5">
11
- <template #prepend>
10
+ <div class="app-toolbar app-toolbar--plain">
11
+ <v-btn
12
+ icon
13
+ variant="text"
14
+ density="comfortable"
15
+ class="table-v3__refresh-btn"
16
+ @click="emit('refresh')"
17
+ >
18
+ <v-icon size="24">mdi-history</v-icon>
19
+ </v-btn>
20
+
21
+ <div class="app-toolbar__spacer" />
22
+
23
+ <div class="app-toolbar__right">
24
+ <span class="table-card__range">{{ pageRange }}</span>
12
25
  <v-btn
13
- icon
26
+ icon="mdi-chevron-left"
14
27
  variant="text"
15
28
  density="comfortable"
16
- class="table-v3__refresh-btn"
17
- @click="emit('refresh')"
18
- >
19
- <v-icon size="28" color="black">mdi-history</v-icon>
20
- </v-btn>
21
- </template>
22
-
23
- <template #append>
24
- <v-row no-gutters justify="end" align="center">
25
- <span class="mr-2 text-caption text-fontgray">
26
- {{ pageRange }}
27
- </span>
28
- <v-btn
29
- icon="mdi-chevron-left"
30
- variant="text"
31
- density="comfortable"
32
- :disabled="!canGoPrev"
33
- @click="goPrev"
34
- />
35
- <v-btn
36
- icon="mdi-chevron-right"
37
- variant="text"
38
- density="comfortable"
39
- :disabled="!canGoNext"
40
- @click="goNext"
41
- />
42
- </v-row>
43
- </template>
44
- </v-toolbar>
29
+ :disabled="!canGoPrev"
30
+ @click="goPrev"
31
+ />
32
+ <v-btn
33
+ icon="mdi-chevron-right"
34
+ variant="text"
35
+ density="comfortable"
36
+ :disabled="!canGoNext"
37
+ @click="goNext"
38
+ />
39
+ </div>
40
+ </div>
45
41
 
46
42
  <div v-if="$slots.breadcrumb" class="table-v3__breadcrumb">
47
43
  <slot name="breadcrumb" />
@@ -62,29 +58,27 @@
62
58
  </template>
63
59
  </v-data-table>
64
60
 
65
- <v-toolbar density="compact" color="grey-lighten-5" class="table-v3__footer">
66
- <template #append>
67
- <v-row no-gutters justify="end" align="center">
68
- <span class="mr-2 text-caption text-fontgray">
69
- {{ pageRange }}
70
- </span>
71
- <v-btn
72
- icon="mdi-chevron-left"
73
- variant="text"
74
- density="comfortable"
75
- :disabled="!canGoPrev"
76
- @click="goPrev"
77
- />
78
- <v-btn
79
- icon="mdi-chevron-right"
80
- variant="text"
81
- density="comfortable"
82
- :disabled="!canGoNext"
83
- @click="goNext"
84
- />
85
- </v-row>
86
- </template>
87
- </v-toolbar>
61
+ <div class="app-toolbar app-toolbar--plain table-v3__footer">
62
+ <div class="app-toolbar__spacer" />
63
+
64
+ <div class="app-toolbar__right">
65
+ <span class="table-card__range">{{ pageRange }}</span>
66
+ <v-btn
67
+ icon="mdi-chevron-left"
68
+ variant="text"
69
+ density="comfortable"
70
+ :disabled="!canGoPrev"
71
+ @click="goPrev"
72
+ />
73
+ <v-btn
74
+ icon="mdi-chevron-right"
75
+ variant="text"
76
+ density="comfortable"
77
+ :disabled="!canGoNext"
78
+ @click="goNext"
79
+ />
80
+ </div>
81
+ </div>
88
82
  </v-card>
89
83
  </template>
90
84
 
@@ -161,19 +155,16 @@ watch(
161
155
  </script>
162
156
 
163
157
  <style scoped>
164
- .table-v3 :deep(thead th) {
165
- background-color: #f5f5f5;
166
- color: #6b7280;
167
- font-weight: 600 !important;
168
- font-size: 12px;
169
- }
170
-
171
- .table-v3 :deep(tbody td) {
172
- height: 54px;
173
- }
174
-
158
+ /*
159
+ * The header row is no longer drawn here. It was `#f5f5f5` on `#6b7280`,
160
+ * both literal, so in the dark theme the fixed-header rule won the background
161
+ * and the header read 3.45:1. `.table-card` on the root gives this table the
162
+ * same tokenised header, cell and hover treatment as every other table in the
163
+ * product, which measures 4.77:1 light / 4.71:1 dark.
164
+ */
175
165
  .table-v3__footer {
176
- border-top: 1px solid rgba(0, 0, 0, 0.08);
166
+ border-top: 1px solid var(--border);
167
+ border-bottom: 0;
177
168
  }
178
169
 
179
170
  .table-v3__refresh-btn {
@@ -182,8 +173,8 @@ watch(
182
173
  }
183
174
 
184
175
  .table-v3__breadcrumb {
185
- border-bottom: 1px solid rgba(0, 0, 0, 0.08);
186
- background-color: #fff;
176
+ border-bottom: 1px solid var(--border);
177
+ background-color: var(--card);
187
178
  padding: 6px 12px;
188
179
  }
189
180
  </style>
@@ -1,8 +1,10 @@
1
1
  <template>
2
2
  <div>
3
- <VCard class="">
3
+ <!-- AppCard, not VCard: the design's 14px radius and 1px rule, without
4
+ Vuetify's own elevation and 4px corner fighting the class for it. -->
5
+ <AppCard>
4
6
  <VuetifyTiptap v-model="content" />
5
- </VCard>
7
+ </AppCard>
6
8
  </div>
7
9
  </template>
8
10
 
@@ -39,35 +41,37 @@ const updateContent = () => {
39
41
  </script>
40
42
 
41
43
  <style scoped>
42
- /deep/ .vuetify-pro-tiptap-editor__content {
44
+ /*
45
+ * The rich-text tables. Every colour here used to be a literal - #333 borders,
46
+ * #fff and #f8f8f8 cells, #f0f0f0 headers, #000 header text, #e6f3ff hover -
47
+ * so an editor opened in the dark theme drew a white table with black text on
48
+ * a dark page. They are the same tokens every other table in the product uses,
49
+ * so the editor content now follows the theme it is opened in.
50
+ *
51
+ * The selectors were also written twice, once with :deep() and again with the
52
+ * deprecated /deep/, matching the same two roots. One copy is enough.
53
+ */
54
+ :deep(.vuetify-pro-tiptap-editor__content) {
43
55
  padding: 15px;
44
56
  }
45
57
 
46
- /* Table styles with strong visible borders */
47
58
  :deep(.vuetify-pro-tiptap-editor__content table),
48
- :deep(.ProseMirror table),
49
- /deep/ .vuetify-pro-tiptap-editor__content table,
50
- /deep/ .ProseMirror table {
59
+ :deep(.ProseMirror table) {
51
60
  border-collapse: collapse;
52
61
  border-spacing: 0;
53
- border: 2px solid #333333 !important;
62
+ border: 2px solid var(--border) !important;
54
63
  width: 100%;
55
64
  margin: 16px 0;
56
- background-color: #ffffff;
65
+ background-color: var(--card);
57
66
  display: table !important;
58
- font-size: 14px;
67
+ font-size: var(--fs-cell);
59
68
  }
60
69
 
61
- /* Cell borders - very visible */
62
70
  :deep(.vuetify-pro-tiptap-editor__content table th),
63
71
  :deep(.vuetify-pro-tiptap-editor__content table td),
64
72
  :deep(.ProseMirror table th),
65
- :deep(.ProseMirror table td),
66
- /deep/ .vuetify-pro-tiptap-editor__content table th,
67
- /deep/ .vuetify-pro-tiptap-editor__content table td,
68
- /deep/ .ProseMirror table th,
69
- /deep/ .ProseMirror table td {
70
- border: 1px solid #333333 !important;
73
+ :deep(.ProseMirror table td) {
74
+ border: 1px solid var(--border) !important;
71
75
  padding: 8px 12px;
72
76
  text-align: left;
73
77
  vertical-align: top;
@@ -75,38 +79,27 @@ const updateContent = () => {
75
79
  box-sizing: border-box;
76
80
  }
77
81
 
78
- /* Header styling */
79
82
  :deep(.vuetify-pro-tiptap-editor__content table th),
80
- :deep(.ProseMirror table th),
81
- /deep/ .vuetify-pro-tiptap-editor__content table th,
82
- /deep/ .ProseMirror table th {
83
- font-weight: 600;
84
- background-color: #f0f0f0;
85
- color: #000000;
86
- border-bottom: 2px solid #333333 !important;
83
+ :deep(.ProseMirror table th) {
84
+ font-weight: var(--fw-table-head);
85
+ background-color: var(--thead);
86
+ color: var(--text);
87
+ border-bottom: 2px solid var(--border) !important;
87
88
  }
88
89
 
89
- /* Regular cells */
90
90
  :deep(.vuetify-pro-tiptap-editor__content table td),
91
- :deep(.ProseMirror table td),
92
- /deep/ .vuetify-pro-tiptap-editor__content table td,
93
- /deep/ .ProseMirror table td {
94
- background-color: #ffffff;
91
+ :deep(.ProseMirror table td) {
92
+ background-color: var(--card);
93
+ color: var(--text);
95
94
  }
96
95
 
97
- /* Alternating row colors */
98
96
  :deep(.vuetify-pro-tiptap-editor__content table tr:nth-child(2n) td),
99
- :deep(.ProseMirror table tr:nth-child(2n) td),
100
- /deep/ .vuetify-pro-tiptap-editor__content table tr:nth-child(2n) td,
101
- /deep/ .ProseMirror table tr:nth-child(2n) td {
102
- background-color: #f8f8f8;
97
+ :deep(.ProseMirror table tr:nth-child(2n) td) {
98
+ background-color: var(--thead);
103
99
  }
104
100
 
105
- /* Hover effects */
106
101
  :deep(.vuetify-pro-tiptap-editor__content table tr:hover td),
107
- :deep(.ProseMirror table tr:hover td),
108
- /deep/ .vuetify-pro-tiptap-editor__content table tr:hover td,
109
- /deep/ .ProseMirror table tr:hover td {
110
- background-color: #e6f3ff !important;
102
+ :deep(.ProseMirror table tr:hover td) {
103
+ background-color: var(--hover) !important;
111
104
  }
112
105
  </style>