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

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 +241 -79
  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,87 +1,77 @@
1
1
  <template>
2
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-row no-gutters class="ga-2">
6
- <v-btn
7
- class="text-none"
8
- rounded="pill"
9
- variant="tonal"
10
- size="large"
11
- @click="setCard()"
12
- v-if="canCreate && canCreateAccessCard"
13
- >
14
- Add Access Card
15
- </v-btn>
16
- <v-btn
17
- class="text-none"
18
- rounded="pill"
19
- variant="tonal"
20
- size="large"
21
- @click="assignDialog = true"
3
+ <!--
4
+ The design's title row. Was two `tonal` pills and a floating-label search
5
+ floating above the card with no page name at all; the search now sits on
6
+ the card's own toolbar row, where the design puts it. Same models, same
7
+ handlers, same permission gates.
8
+ -->
9
+ <v-col cols="12">
10
+ <!-- Nav calls this screen "Cards" (under Access Mgmt) - the title matches
11
+ the navigation, it is not new copy. -->
12
+ <PageHeader title="Cards">
13
+ <template #actions>
14
+ <AppButton
22
15
  v-if="canUpdateAccessCard && canAssignAccessCard"
16
+ variant="ghost"
17
+ @click="assignDialog = true"
23
18
  >
24
19
  Assign to Unit
25
- </v-btn>
26
- </v-row>
27
-
28
- <v-text-field
29
- v-model="searchText"
30
- placeholder="Search Card, Unit..."
31
- variant="outlined"
32
- density="comfortable"
33
- clearable
34
- hide-details
35
- class="ml-2"
36
- style="max-width: 250px"
37
- />
38
- </v-row>
20
+ </AppButton>
21
+ <AppButton
22
+ v-if="canCreate && canCreateAccessCard"
23
+ icon="mdi-plus"
24
+ @click="setCard()"
25
+ >
26
+ Add Access Card
27
+ </AppButton>
28
+ </template>
29
+ </PageHeader>
39
30
  </v-col>
40
31
  <!-- Available Card Stats -->
41
32
  <v-col cols="12" class="mb-2" v-if="canViewAllCards">
42
33
  <AccessCardAvailableStats ref="statsRef" :site-id="siteId" />
43
34
  </v-col>
44
35
 
45
- <v-col cols="12" v-if="canViewAllCards">
46
- <v-card
47
- width="100%"
48
- variant="outlined"
49
- border="thin"
50
- rounded="lg"
51
- :loading="loading"
52
- >
53
- <v-toolbar density="compact" color="grey-lighten-4">
54
- <template #prepend>
55
- <v-btn fab icon density="comfortable" @click="getCards">
56
- <v-icon>mdi-refresh</v-icon>
57
- </v-btn>
58
- </template>
59
-
60
- <template #append>
61
- <v-row no-gutters justify="end" align="center">
62
- <span class="mr-2 text-caption text-fontgray">
63
- {{ pageRange }}
64
- </span>
65
- <local-pagination
66
- v-model="page"
67
- :length="pages"
68
- @update:value="getCards"
69
- />
70
- </v-row>
71
- </template>
72
- </v-toolbar>
73
- <v-data-table
74
- :headers="headers"
75
- :items="items"
76
- item-value="_id"
77
- items-per-page="10"
78
- fixed-header
79
- hide-default-footer
80
- @click:row="tableRowClickHandler"
81
- style="max-height: calc(100vh - (200px))"
36
+ <!--
37
+ Was `v-card` + a `grey-lighten-4` `v-toolbar` (a white slab that stayed
38
+ white in dark mode) + a bare `v-data-table`. `TableMain` is the same three
39
+ things drawn as the design: same headers, items, page size, refresh,
40
+ page range, pager and row click.
41
+ -->
42
+ <TableMain
43
+ v-if="canViewAllCards"
44
+ :headers="headers"
45
+ :items="items"
46
+ :loading="loading"
47
+ :page="page"
48
+ :pages="pages"
49
+ :pageRange="pageRange"
50
+ :items-per-page="10"
51
+ show-header
52
+ @refresh="getCards"
53
+ @update:page="page = $event; getCards()"
54
+ @row-click="(data: any) => tableRowClickHandler(null, data)"
55
+ >
56
+ <template #prepend-additional>
57
+ <AppField
58
+ v-model="searchText"
59
+ search
60
+ compact
61
+ placeholder="Search Card, Unit..."
62
+ klass="app-toolbar__search"
82
63
  />
83
- </v-card>
84
- </v-col>
64
+ </template>
65
+
66
+ <!--
67
+ A failed request is not a site with no access cards. Without this the
68
+ table drew its neutral "No data available" for a 500, which reads as
69
+ "there are no cards here" - a statement the server never made.
70
+ -->
71
+ <template v-if="getCardError" #no-data>
72
+ <DashboardEmptyState error />
73
+ </template>
74
+ </TableMain>
85
75
 
86
76
  <!-- Create Dialog -->
87
77
  <v-dialog v-model="createDialog" width="650" persistent>
@@ -144,6 +134,10 @@
144
134
  </v-row>
145
135
  </template>
146
136
  <script setup lang="ts">
137
+ // Relative import, never Nuxt auto-import: this layer's `utils/` are auto-imported
138
+ // into the consuming apps but not reliably into the layer's own components.
139
+ import { readListPage } from "../utils/list-page";
140
+
147
141
  definePageMeta({
148
142
  middleware: ["01-auth", "02-org"],
149
143
  memberOnly: true,
@@ -281,6 +275,7 @@ const {
281
275
  data: getCardReq,
282
276
  refresh: getCards,
283
277
  status: getAllReqStatus,
278
+ error: getCardError,
284
279
  } = useLazyAsyncData(
285
280
  "get-all-cards",
286
281
  () =>
@@ -307,21 +302,27 @@ watch(searchText, (val) => {
307
302
 
308
303
  const loading = computed(() => getAllReqStatus.value === "pending");
309
304
 
305
+ // This endpoint answers with a NESTED envelope - `{ data: { items, pages,
306
+ // pageRange } }` - so the page totals are read off `reply.data`, through the
307
+ // one helper that knows how a paginated reply is shaped. The totals are the
308
+ // server's count of the WHOLE list; they are never re-derived from the rows on
309
+ // this page. Applying it unconditionally matters: when a later request fails,
310
+ // the reply goes null and the header falls back to the placeholder instead of
311
+ // keeping the previous page's total on screen as if it were still true.
310
312
  watchEffect(() => {
311
- if (getCardReq.value?.data) {
312
- items.value = getCardReq.value.data.items.map((item: any) => ({
313
- ...item,
314
- cardCounts: {
315
- ...item.cardCounts,
316
- available: {
317
- ...item.cardCounts?.available,
318
- physical: (item.available?.physical ?? []).filter((c: any) => !c.damage).length,
319
- },
313
+ const listPage = readListPage<Record<string, any>>(getCardReq.value?.data);
314
+ items.value = listPage.items.map((item: any) => ({
315
+ ...item,
316
+ cardCounts: {
317
+ ...item.cardCounts,
318
+ available: {
319
+ ...item.cardCounts?.available,
320
+ physical: (item.available?.physical ?? []).filter((c: any) => !c.damage).length,
320
321
  },
321
- }));
322
- pages.value = getCardReq.value.data.pages;
323
- pageRange.value = getCardReq.value.data.pageRange;
324
- }
322
+ },
323
+ }));
324
+ pages.value = listPage.pages;
325
+ pageRange.value = listPage.pageRange;
325
326
  });
326
327
 
327
328
  function setCard({ mode = "create", dialog = true, data = {} as TCard } = {}) {
@@ -1,15 +1,11 @@
1
1
  <template>
2
2
  <v-dialog v-model="showDialog" max-width="450">
3
- <v-card>
4
- <v-toolbar>
5
- <v-row no-gutters class="fill-height px-6" align="center">
6
- <span class="font-weight-bold text-h6 text-capitalize">
7
- {{ prop.mode === "edit" ? "Edit Equipment" : "Add New Equipment" }}
8
- </span>
9
- </v-row>
10
- </v-toolbar>
3
+ <v-card class="screen-modal">
4
+ <v-card-title class="text-capitalize">
5
+ {{ prop.mode === "edit" ? "Edit Equipment" : "Add New Equipment" }}
6
+ </v-card-title>
11
7
 
12
- <v-card-text class="pa-4">
8
+ <v-card-text class="screen-modal__body pa-4">
13
9
  <v-form ref="formRef" v-model="valid">
14
10
  <v-row no-gutters class="pa-0">
15
11
  <v-col cols="12" class="pa-0 mb-2">
@@ -39,7 +35,7 @@
39
35
  variant="outlined"
40
36
  placeholder="Enter equipment name"
41
37
  hide-details="auto"
42
- class="mb-0"
38
+ class="mb-0 filter-field"
43
39
  />
44
40
  </v-col>
45
41
 
@@ -58,35 +54,35 @@
58
54
  variant="outlined"
59
55
  placeholder="Select unitOfMeasurement"
60
56
  hide-details="auto"
61
- class="mb-0"
57
+ class="mb-0 filter-field"
62
58
  />
63
59
  </v-col>
64
60
  </v-row>
65
61
  </v-form>
66
62
  </v-card-text>
67
63
 
68
- <v-toolbar class="pa-0" density="compact">
64
+ <!-- Was a `v-toolbar` footer: a grey slab holding a bare-text Cancel and
65
+ a `rounded-0`, `color="black"` primary - a square black button that
66
+ ignored the theme. Same two buttons on the card's own surface, with
67
+ the design's radius and accent fill. -->
68
+ <v-card-actions class="screen-modal__footer">
69
69
  <v-row no-gutters>
70
- <v-col cols="6" class="pa-0">
70
+ <v-col cols="6" class="pr-2">
71
71
  <v-btn
72
72
  block
73
- variant="text"
74
- class="text-none"
75
- size="large"
73
+ variant="outlined"
74
+ class="text-none screen-btn-ghost"
76
75
  @click="close"
77
- height="48"
78
76
  >
79
77
  Cancel
80
78
  </v-btn>
81
79
  </v-col>
82
80
 
83
- <v-col cols="6" class="pa-0">
81
+ <v-col cols="6" class="pl-2">
84
82
  <v-btn
85
83
  block
86
84
  variant="flat"
87
- color="black"
88
- class="text-none font-weight-bold rounded-0"
89
- height="48"
85
+ class="text-none screen-btn-primary"
90
86
  :loading="submitting"
91
87
  @click="submit"
92
88
  >
@@ -94,7 +90,7 @@
94
90
  </v-btn>
95
91
  </v-col>
96
92
  </v-row>
97
- </v-toolbar>
93
+ </v-card-actions>
98
94
  </v-card>
99
95
  </v-dialog>
100
96
  </template>
@@ -1,24 +1,21 @@
1
1
  <template>
2
- <v-card width="100%" :loading="processing">
3
- <v-toolbar>
4
- <v-row
5
- no-gutters
6
- class="fill-height px-6 d-flex justify-space-between align-center"
7
- align="center"
8
- >
9
- <span class="font-weight-bold text-subtitle-1"
10
- >{{ isEditMode ? "Edit" : "Assign" }} Pass &amp; Keys</span
11
- >
12
- <ButtonClose @click="emit('close')" />
13
- </v-row>
14
- </v-toolbar>
15
-
16
- <v-card-text>
17
- <v-row no-gutters class="ga-1">
2
+ <v-card width="100%" class="screen-modal passkey" :loading="processing">
3
+ <div class="passkey__head">
4
+ <v-card-title>{{ isEditMode ? "Edit" : "Assign" }} Pass &amp; Keys</v-card-title>
5
+ <AppButton
6
+ variant="icon"
7
+ icon="mdi-close"
8
+ aria-label="Close"
9
+ @click="emit('close')"
10
+ />
11
+ </div>
12
+
13
+ <div class="screen-modal__body passkey__body">
14
+ <!-- `.filter-field` is the design's input, and it is a descendant
15
+ selector, so both autocompletes get it from this one class. -->
16
+ <v-row no-gutters class="ga-1 filter-field">
18
17
  <v-col cols="12">
19
- <span class="text-subtitle-2 text-medium-emphasis">
20
- Name: {{ prop.visitor.name }}
21
- </span>
18
+ <span class="passkey__name">Name: {{ prop.visitor.name }}</span>
22
19
  </v-col>
23
20
 
24
21
  <!-- Pass section -->
@@ -27,15 +24,11 @@
27
24
 
28
25
  <!-- Existing pass -->
29
26
  <div v-if="existingPass" class="d-flex align-center ga-2 mb-2">
30
- <v-chip
31
- prepend-icon="mdi-card-bulleted-outline"
32
- color="blue"
33
- variant="tonal"
34
- size="small"
35
- >
27
+ <StatusChip tone="info" :dot="false">
28
+ <v-icon icon="mdi-card-bulleted-outline" size="13" />
36
29
  {{ existingPass.prefixAndName }}
37
- </v-chip>
38
- <span class="text-caption text-medium-emphasis">(current)</span>
30
+ </StatusChip>
31
+ <span class="passkey__current">(current)</span>
39
32
  </div>
40
33
 
41
34
  <!-- New pass selector -->
@@ -79,21 +72,26 @@
79
72
  v-if="existingKeys.length > 0"
80
73
  class="d-flex flex-wrap ga-1 mb-2"
81
74
  >
82
- <v-chip
75
+ <!-- The design's tag chip, keeping the remove control the product
76
+ already had: the tone and the shape are the shared ones, only
77
+ the close button is drawn inside rather than by Vuetify. -->
78
+ <StatusChip
83
79
  v-for="key in existingKeys.filter((k) => k.status != 'Removed')"
84
80
  :key="key.keyId"
85
- prepend-icon="mdi-key"
86
- color="orange"
87
- variant="tonal"
88
- size="small"
89
- closable
90
- @click:close="removeExistingKey(key)"
81
+ tone="warn"
82
+ :dot="false"
91
83
  >
84
+ <v-icon icon="mdi-key" size="13" />
92
85
  {{ key.prefixAndName }}
93
- </v-chip>
94
- <span class="text-caption text-medium-emphasis align-self-center">
95
- (current)
96
- </span>
86
+ <button
87
+ class="passkey__remove"
88
+ :aria-label="`Remove ${key.prefixAndName}`"
89
+ @click="removeExistingKey(key)"
90
+ >
91
+ <v-icon icon="mdi-close-circle" size="14" />
92
+ </button>
93
+ </StatusChip>
94
+ <span class="passkey__current align-self-center">(current)</span>
97
95
  </div>
98
96
 
99
97
  <!-- New keys selector -->
@@ -132,41 +130,16 @@
132
130
  </v-row>
133
131
 
134
132
  <v-row v-if="errorMessage" no-gutters class="mt-2">
135
- <p class="text-error text-subtitle-2 w-100 text-center">
136
- {{ errorMessage }}
137
- </p>
138
- </v-row>
139
- </v-card-text>
140
-
141
- <v-toolbar density="compact">
142
- <v-row no-gutters>
143
- <v-col cols="6">
144
- <v-btn
145
- tile
146
- block
147
- variant="text"
148
- class="text-none"
149
- size="48"
150
- text="Cancel"
151
- @click="emit('close')"
152
- />
153
- </v-col>
154
- <v-col cols="6">
155
- <v-btn
156
- tile
157
- block
158
- variant="flat"
159
- color="black"
160
- class="text-none"
161
- size="48"
162
- :text="isEditMode ? 'Save' : 'Assign'"
163
- :disabled="!canSubmit"
164
- :loading="processing"
165
- @click="handleSubmit"
166
- />
167
- </v-col>
133
+ <p class="passkey__error">{{ errorMessage }}</p>
168
134
  </v-row>
169
- </v-toolbar>
135
+ </div>
136
+
137
+ <div class="passkey__footer">
138
+ <AppButton variant="ghost" @click="emit('close')">Cancel</AppButton>
139
+ <AppButton :disabled="!canSubmit" @click="handleSubmit">
140
+ {{ isEditMode ? "Save" : "Assign" }}
141
+ </AppButton>
142
+ </div>
170
143
  </v-card>
171
144
  </template>
172
145
 
@@ -448,4 +421,53 @@ async function handleSubmit() {
448
421
  }
449
422
  </script>
450
423
 
451
- <style scoped></style>
424
+ <style scoped>
425
+ .passkey__head {
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: space-between;
429
+ padding-right: 12px;
430
+ }
431
+
432
+ .passkey__body {
433
+ padding: 4px 20px 0;
434
+ }
435
+
436
+ .passkey__name,
437
+ .passkey__current {
438
+ font-size: var(--fs-cell);
439
+ color: var(--text2);
440
+ }
441
+
442
+ .passkey__error {
443
+ width: 100%;
444
+ margin: 0;
445
+ text-align: center;
446
+ font-size: var(--fs-cell);
447
+ color: var(--err);
448
+ }
449
+
450
+ /* The chip's own remove control - `currentColor`, so it stays on whatever
451
+ tone the chip carries rather than picking a second colour. */
452
+ .passkey__remove {
453
+ display: inline-flex;
454
+ margin-left: 2px;
455
+ padding: 0;
456
+ border: none;
457
+ background: none;
458
+ color: inherit;
459
+ cursor: pointer;
460
+ opacity: 0.75;
461
+ }
462
+
463
+ .passkey__remove:hover {
464
+ opacity: 1;
465
+ }
466
+
467
+ .passkey__footer {
468
+ display: flex;
469
+ justify-content: flex-end;
470
+ gap: 8px;
471
+ padding: 16px 20px 20px;
472
+ }
473
+ </style>
@@ -0,0 +1,62 @@
1
+ <!--
2
+ THE BUTTON.
3
+
4
+ A `<button>`, not a restyled `v-btn`. Vuetify's button carries an overlay
5
+ layer, a ripple, a min-width, uppercase letter-spacing and its own height
6
+ scale, all at a specificity a class has to out-shout one declaration at a
7
+ time - which is why the product's primary buttons still read as Vuetify grey
8
+ under a correct token set.
9
+
10
+ Variants, straight from the handoff:
11
+ primary 40px pill-radius accent fill, 13.5px/800, white label
12
+ ghost 40px, 1px border, card fill, 13.5px/700 in `--text2` - the SAME
13
+ box as primary; the handoff differs only in fill, border and weight
14
+ icon 32px transparent square, 8px radius
15
+ row 30px bordered square - the `more_vert` at the end of a table row
16
+
17
+ Two more the handoff does NOT draw, added as NEW design on the owner's
18
+ ruling of 2026-08-14 - see `assets/css/primitives.css` for why check-out is
19
+ tonal and not solid red:
20
+ ok 40px solid `--ok` fill, white label - CHECK IN
21
+ err 40px tonal `--err` fill, `--err` label, `--err` edge - CHECK OUT
22
+ -->
23
+ <template>
24
+ <component
25
+ :is="to ? resolveLink : 'button'"
26
+ :to="to || undefined"
27
+ :type="to ? undefined : type"
28
+ :disabled="disabled || undefined"
29
+ class="app-btn"
30
+ :class="variant === 'primary' ? '' : `app-btn--${variant}`"
31
+ >
32
+ <v-icon v-if="icon" :icon="icon" :size="iconSize" />
33
+ <slot />
34
+ </component>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { resolveComponent } from "vue";
39
+
40
+ const props = defineProps({
41
+ variant: {
42
+ type: String as PropType<"primary" | "ghost" | "icon" | "row" | "ok" | "err">,
43
+ default: "primary",
44
+ },
45
+ icon: { type: String, default: "" },
46
+ type: { type: String, default: "button" },
47
+ disabled: { type: Boolean, default: false },
48
+ /** Given, the button routes - same `to` a `v-btn` would have taken. */
49
+ to: { type: [String, Object] as PropType<any>, default: undefined },
50
+ });
51
+
52
+ const resolveLink = resolveComponent("NuxtLink");
53
+
54
+ const iconSize = computed(() => {
55
+ if (props.variant === "primary") return 18;
56
+ if (props.variant === "row") return 17;
57
+ /* The check-in tick and the check-out cross are drawn at 16px - a glyph, not
58
+ a second focal point beside a two-word label. */
59
+ if (props.variant === "ok" || props.variant === "err") return 16;
60
+ return 18;
61
+ });
62
+ </script>
@@ -0,0 +1,20 @@
1
+ <!--
2
+ THE CARD.
3
+
4
+ 14px radius, a 1px `--border` rule and the design's one-pixel shadow, on
5
+ `--card`. A plain element rather than a `v-card`, because `v-card` brings its
6
+ own elevation, radius and surface colour at a specificity that a class has to
7
+ fight - which is why the radius kept coming back as 4px in Phase 3.
8
+ -->
9
+ <template>
10
+ <div class="app-card" :class="{ 'app-card--flush': flush }">
11
+ <slot />
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ defineProps({
17
+ /** Let content (a menu, a dropdown) escape the card's rounded corners. */
18
+ flush: { type: Boolean, default: false },
19
+ });
20
+ </script>
@@ -0,0 +1,49 @@
1
+ <!--
2
+ THE DATE FIELD.
3
+
4
+ The same 40px box with a calendar glyph, as the handoff draws the two date
5
+ ends of a filter row. `<input type="date">` - the browser's own picker, which
6
+ is the one a phone user already knows and the one that is localised, keyboard
7
+ accessible and free.
8
+ -->
9
+ <template>
10
+ <label v-bind="rootAttrs" class="app-field" :class="{ 'app-field--compact': compact }">
11
+ <v-icon class="app-field__icon" icon="mdi-calendar-month-outline" size="17" />
12
+ <span v-if="label" class="app-field__label">{{ label }}</span>
13
+ <input
14
+ class="app-field__input"
15
+ v-bind="inputAttrs"
16
+ type="date"
17
+ :value="model"
18
+ :min="min || undefined"
19
+ :max="max || undefined"
20
+ :disabled="disabled"
21
+ @input="model = ($event.target as HTMLInputElement).value"
22
+ />
23
+ </label>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { computed, useAttrs } from "vue";
28
+
29
+ /** Same split as `AppField`, and for the same reason: the root is a `<label>`,
30
+ so an `aria-label` left to fall through would never reach the input. */
31
+ defineOptions({ inheritAttrs: false });
32
+
33
+ const attrs = useAttrs();
34
+ const rootAttrs = computed(() => ({ class: attrs.class, style: attrs.style }));
35
+ const inputAttrs = computed(() => {
36
+ const { class: _c, style: _s, ...rest } = attrs;
37
+ return rest;
38
+ });
39
+
40
+ const model = defineModel({ type: String, default: "" });
41
+
42
+ defineProps({
43
+ label: { type: String, default: "" },
44
+ min: { type: String, default: "" },
45
+ max: { type: String, default: "" },
46
+ compact: { type: Boolean, default: false },
47
+ disabled: { type: Boolean, default: false },
48
+ });
49
+ </script>