@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
@@ -28,45 +28,90 @@
28
28
  primary-tinted fill (see `NavigationItem.vue`); 0.12 left the fill at
29
29
  1.15:1, 0.18 is 1.44:1 light, 1.33:1 dark.
30
30
  -->
31
+ <!--
32
+ RESPONSIVE: THE RAIL BECOMES AN OVERLAY BELOW 1024px.
33
+
34
+ `permanent` was hardcoded, which is why the prototype's own behaviour - and
35
+ ours - was a 252px rail still sitting there on a 360px phone with the
36
+ content squeezed into what was left. Dropping `permanent` and naming the
37
+ breakpoint hands the whole behaviour to Vuetify: at >= 1024 the drawer is
38
+ part of the layout exactly as it is today, and below it the same drawer
39
+ becomes temporary, floats OVER the content with a scrim, and closes on the
40
+ scrim, on Escape and on a navigation.
41
+
42
+ 1024 rather than Vuetify's default 1280: at 1024 the rail still leaves a
43
+ 772px content column, which is a usable table. Below that it does not.
44
+
45
+ Nothing about the menu changes - same items, same order, same permissions,
46
+ same routes. Only where the panel is drawn.
47
+ -->
48
+ <!--
49
+ PHASE 2 - THE GROUPED SIDEBAR.
50
+
51
+ The rail is 252px as drawn, the brand row is the design's 34px tile plus
52
+ the product name, and the menu is printed in labelled sections.
53
+
54
+ The MENU ITSELF IS UNTOUCHED. Each application still builds its own array,
55
+ gated item by item on its own permissions, and it is rendered in exactly
56
+ that order; `utils/nav-sections.ts` only decides which label to print above
57
+ which item, and an item whose title it does not know keeps its place under
58
+ the heading it follows. Nothing here can add, hide, move or rename a menu
59
+ entry, and no route, permission or click handler is involved.
60
+ -->
31
61
  <v-navigation-drawer
32
62
  v-model="drawer"
33
- permanent
63
+ :mobile-breakpoint="1024"
64
+ :width="252"
34
65
  class="bg-brand-surface brand-drawer"
35
66
  >
36
- <v-list>
37
- <v-list-item>
38
- <v-list-item-title class="text-h6">
39
- {{ props.title || APP_NAME }}
40
- </v-list-item-title>
41
- </v-list-item>
67
+ <div class="brand-drawer__brand">
68
+ <div class="brand-drawer__logo">
69
+ <v-icon :icon="brandIcon" size="19" />
70
+ </div>
42
71
 
43
- <slot name="action" />
72
+ <div class="brand-drawer__names">
73
+ <div class="brand-drawer__app">{{ props.title || APP_NAME }}</div>
74
+ <div class="brand-drawer__suite">iService365</div>
75
+ </div>
76
+ </div>
77
+
78
+ <v-list>
79
+ <!--
80
+ The site selector, in the design's bordered card. The slot's CONTENTS
81
+ are whatever the application puts there, still inside the same
82
+ `v-list` it has always been inside - this only draws the box around it.
83
+ -->
84
+ <div v-if="$slots.action" class="brand-drawer__context">
85
+ <slot name="action" />
86
+ </div>
44
87
 
45
88
  <!-- NAVIGATION ITEMS -->
46
- <v-tooltip
47
- v-for="(item, index) in navigationItems"
89
+ <template
90
+ v-for="({ item, section }, index) in sectionedItems"
48
91
  :key="`${item.title}-${index}`"
49
- location="right"
50
- :disabled="!item.disabled"
51
92
  >
52
- <template #activator="{ props: tooltipProps }">
53
- <div v-bind="tooltipProps">
54
- <NavigationItem
55
- :title="item.title"
56
- :icon="item.icon"
57
- :route="item.route"
58
- :children="item.children"
59
- :link="item.link"
60
- :class="{
61
- 'opacity-50 pointer-events-none': item.disabled,
62
- }"
63
- @click="() => handleClick(item)"
64
- />
65
- </div>
66
- </template>
67
-
68
- <span>Please complete onboarding first</span>
69
- </v-tooltip>
93
+ <div v-if="section" class="brand-drawer__section">{{ section }}</div>
94
+
95
+ <v-tooltip location="right" :disabled="!item.disabled">
96
+ <template #activator="{ props: tooltipProps }">
97
+ <div v-bind="tooltipProps">
98
+ <NavigationItem
99
+ :title="item.title"
100
+ :icon="item.icon"
101
+ :route="item.route"
102
+ :children="item.children"
103
+ :link="item.link"
104
+ :class="{
105
+ 'opacity-50 pointer-events-none': item.disabled,
106
+ }"
107
+ @click="() => handleClick(item)"
108
+ />
109
+ </div>
110
+ </template>
111
+
112
+ <span>Please complete onboarding first</span>
113
+ </v-tooltip>
114
+ </template>
70
115
 
71
116
  <!-- SERVICES SLOT -->
72
117
  <slot name="services" />
@@ -75,6 +120,10 @@
75
120
  </template>
76
121
 
77
122
  <script setup lang="ts">
123
+ import { useDisplay } from "vuetify";
124
+
125
+ import { withSections } from "../../utils/nav-sections";
126
+
78
127
  const props = defineProps({
79
128
  navigationItems: { type: Array, required: true },
80
129
  title: { type: String, default: "" },
@@ -85,6 +134,32 @@ const emit = defineEmits(["navigate"]);
85
134
  const { drawer } = useLocal();
86
135
  const { APP_NAME } = useRuntimeConfig().public;
87
136
  const route = useRoute();
137
+
138
+ /**
139
+ * `drawer` is shared application state and it defaults to OPEN, which is right
140
+ * for a rail and wrong for an overlay - a phone would load with the menu
141
+ * covering the page behind a scrim. So the default follows the viewport, and a
142
+ * user who resizes past the breakpoint gets the state that suits where they
143
+ * landed.
144
+ *
145
+ * Only the DEFAULT is managed here. Once the hamburger has been used, the
146
+ * user's choice stands until the layout itself changes underneath them.
147
+ */
148
+ const { mobile } = useDisplay({ mobileBreakpoint: 1024 });
149
+
150
+ watch(mobile, (isOverlay) => (drawer.value = !isOverlay), { immediate: true });
151
+
152
+ /**
153
+ * An overlay drawer that survives a navigation leaves the scrim over the page
154
+ * the user just asked for. A rail must NOT close - that would be a step added
155
+ * to every desktop navigation.
156
+ */
157
+ watch(
158
+ () => route.fullPath,
159
+ () => {
160
+ if (mobile.value) drawer.value = false;
161
+ }
162
+ );
88
163
  const orgId = computed(() => String(route.params.org ?? ""));
89
164
  const siteId = computed(() => String(route.params.site ?? ""));
90
165
  const { useHidFaceReaderMenu } = useHidNavigation();
@@ -118,6 +193,39 @@ const navigationItems = computed(() => {
118
193
  return items;
119
194
  });
120
195
 
196
+ const sectionedItems = computed(() => withSections(navigationItems.value));
197
+
198
+ /**
199
+ * The design's brand tile carries the product's own mark. One icon for eleven
200
+ * applications would be one application's icon on ten of them, so the tile
201
+ * follows `APP_NAME` - the same value the row already prints - and falls back
202
+ * to the suite's shield for anything not listed.
203
+ */
204
+ const BRAND_ICONS: Record<string, string> = {
205
+ security: "mdi-shield-check",
206
+ "property-management": "mdi-office-building",
207
+ hygiene: "mdi-broom",
208
+ landscape: "mdi-flower",
209
+ "pest-control": "mdi-bug",
210
+ "pool-mgmt": "mdi-pool",
211
+ "mechanical-electrical": "mdi-flash",
212
+ admin: "mdi-shield-crown",
213
+ org: "mdi-domain",
214
+ account: "mdi-account-circle",
215
+ main: "mdi-view-grid",
216
+ };
217
+
218
+ const brandIcon = computed(() => {
219
+ // `APP_NAME` is an environment value and is spelled for people ("Property
220
+ // Management"), so it is normalised rather than matched literally.
221
+ const key = String(APP_NAME ?? "")
222
+ .toLowerCase()
223
+ .trim()
224
+ .replace(/[\s_]+/g, "-");
225
+
226
+ return BRAND_ICONS[key] ?? "mdi-shield-check";
227
+ });
228
+
121
229
  const handleClick = (item: any) => {
122
230
  if (item.disabled) return;
123
231
  emit("navigate", item);
@@ -130,5 +238,167 @@ const handleClick = (item: any) => {
130
238
  --v-border-opacity: 0.45;
131
239
  --v-hover-opacity: 0.08;
132
240
  --v-activated-opacity: 0.18;
241
+
242
+ /**
243
+ * Phase 1 loaded Manrope and named it `--font-sans` but deliberately set it
244
+ * on nothing: switching eleven applications to a new typeface before a
245
+ * single surface had been rebuilt would have left the product looking
246
+ * half-migrated. The shell IS the surface Phase 2 rebuilds, so it is the
247
+ * first thing to wear it.
248
+ */
249
+ font-family: var(--font-sans);
250
+ }
251
+
252
+ /* ---------------------------------------------------------------- */
253
+ /* The brand row: 34px accent tile + product name + suite name. */
254
+ /* ---------------------------------------------------------------- */
255
+
256
+ .brand-drawer__brand {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 10px;
260
+ padding: 14px 16px 10px;
261
+ }
262
+
263
+ .brand-drawer__logo {
264
+ flex: 0 0 34px;
265
+ width: 34px;
266
+ height: 34px;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ border-radius: var(--r-inner);
271
+ /* A white glyph, so the fill has to be the one accent that carries white:
272
+ `--accent` is 3.23:1 under it, `--accent-strong` is 4.57:1. */
273
+ background: var(--accent-strong);
274
+ color: var(--on-accent-strong);
275
+ }
276
+
277
+ .brand-drawer__names {
278
+ min-width: 0;
279
+ }
280
+
281
+ .brand-drawer__app {
282
+ font-size: var(--fs-card-title);
283
+ font-weight: var(--fw-card-title);
284
+ line-height: 1.2;
285
+ color: var(--text);
286
+ /* The design prints the application's name in full ("Property Management").
287
+ Truncating it to "Property Managem…" in a 252px rail loses the word that
288
+ tells the user which product they are in, so it wraps instead. */
289
+ overflow-wrap: anywhere;
290
+ text-transform: capitalize;
291
+ }
292
+
293
+ .brand-drawer__suite {
294
+ font-size: 11.5px;
295
+ font-weight: 600;
296
+ line-height: 1.3;
297
+ color: var(--muted);
298
+ }
299
+
300
+ /**
301
+ * The organisation / site selector, drawn as the design's bordered card.
302
+ *
303
+ * Two applications have no selector to draw: `web-app-admin` passes no slot at
304
+ * all (handled by the `v-if`), and `web-app-org`'s super-admin layout passes an
305
+ * EMPTY one - `<template #action> </template>` - which the `v-if` cannot see.
306
+ * An empty bordered box on that rail would be a box around nothing, so a card
307
+ * with no element inside it is not drawn.
308
+ */
309
+ .brand-drawer__context:not(:has(*)) {
310
+ display: none;
311
+ }
312
+
313
+ .brand-drawer__context {
314
+ margin: 2px 4px 4px;
315
+ border: 1px solid var(--border);
316
+ border-radius: var(--r-inner-lg);
317
+ background: var(--card);
318
+ overflow: hidden;
319
+ }
320
+
321
+ /* ---------------------------------------------------------------- */
322
+ /* Section labels and the menu items under them. */
323
+ /* ---------------------------------------------------------------- */
324
+
325
+ .brand-drawer__section {
326
+ padding: 14px 12px 6px;
327
+ font-size: var(--fs-section-label);
328
+ font-weight: var(--fw-section-label);
329
+ letter-spacing: var(--ls-section-label);
330
+ line-height: 1.2;
331
+ text-transform: uppercase;
332
+ color: var(--muted);
333
+ }
334
+
335
+ /* The first label sits directly under the site selector, so it does not need
336
+ the separating space the ones between two menu blocks do. */
337
+ .brand-drawer__section:first-child {
338
+ padding-top: 4px;
339
+ }
340
+
341
+ /**
342
+ * `:deep` because the items are `NavigationItem`s - a child component - and
343
+ * the design's item shape (10px radius, 8px 12px, 13px label) belongs to the
344
+ * rail rather than to that component, which is also rendered elsewhere.
345
+ *
346
+ * ONLY shape and type. The active state's colour still comes from
347
+ * `color="primary"` on the item itself, which is `--accent-text`.
348
+ */
349
+ .brand-drawer :deep(.v-list) {
350
+ padding: 0 8px 12px;
351
+ }
352
+
353
+ /**
354
+ * The two `!important`s are not carelessness: Vuetify puts `rounded-0` and
355
+ * `text-subtitle-2` ON the item, and both of those are utility classes that
356
+ * are themselves `!important`. Nothing weaker can reach the design's 10px
357
+ * radius or its 13px label. Both are confined to `.brand-drawer`.
358
+ */
359
+ .brand-drawer :deep(.v-list-item) {
360
+ border-radius: var(--r-inner) !important;
361
+ min-height: 38px;
362
+ padding-top: 8px;
363
+ padding-bottom: 8px;
364
+ padding-inline: 12px;
365
+ font-size: var(--fs-nav) !important;
366
+ }
367
+
368
+ .brand-drawer :deep(.v-list-item__prepend > .v-icon) {
369
+ font-size: 19px;
370
+ opacity: 1;
371
+ }
372
+
373
+ /* The icon-to-label gap USED to be stated here and it never applied: Vuetify's
374
+ own rule is `.v-list-item__prepend > .v-icon ~ .v-list-item__spacer`, three
375
+ classes, and `.brand-drawer :deep(.v-list-item__spacer)` is also three once
376
+ the scope attribute is counted - a tie, decided by load order, and Vuetify
377
+ loaded last. Measured, the gap was still Vuetify's 32px. The rule now lives
378
+ in `NavigationItem.vue` in Vuetify's own selector shape, so it wins on
379
+ specificity; every item in this list is a `NavigationItem`. */
380
+
381
+ .brand-drawer :deep(.v-list-item--active) {
382
+ font-weight: var(--fw-section-label);
383
+ }
384
+
385
+ /**
386
+ * The design's active item is a 12% accent wash, which is exactly what
387
+ * `--accent-soft` is. Vuetify paints the same idea with an overlay whose
388
+ * opacity is theme-wide, so the fill is stated here instead of leaving the
389
+ * rail's active row to a number tuned for tables and cards.
390
+ */
391
+ .brand-drawer :deep(.v-list-item--active > .v-list-item__overlay) {
392
+ opacity: 0;
393
+ }
394
+
395
+ .brand-drawer :deep(.v-list-item--active) {
396
+ background: var(--accent-soft);
397
+ }
398
+
399
+ /* A child row indents by the icon slot it does not have, so the labels of a
400
+ group line up with the labels of its siblings rather than with their icons. */
401
+ .brand-drawer :deep(.v-list-group__items .v-list-item) {
402
+ padding-inline-start: 41px !important;
133
403
  }
134
404
  </style>
@@ -1,18 +1,30 @@
1
+ <!--
2
+ A label/value row. Both columns were `text-subtitle-2` - Vuetify's 14px/500
3
+ Roboto - so the label that names a value and the value itself were drawn
4
+ identically, and the trailing chevron was 30px, larger than any icon the
5
+ design draws anywhere.
6
+
7
+ DESIGN GAP, STATED: the handoff has no list row of this shape. The values
8
+ below are the design's own, taken from the nearest things it does draw: the
9
+ table cell type (`--fs-cell` / `--fw-cell`) for both columns, `--muted` for
10
+ the label exactly as the handoff labels a value at line 659, and 18px for the
11
+ trailing icon, the size its own row icons use (lines 87 and 90).
12
+ -->
1
13
  <template>
2
14
  <v-list-item :class="props.divider ? border : defaultBorder">
3
15
  <v-row>
4
- <v-col cols="4" class="text-subtitle-2">
16
+ <v-col cols="4" class="app-list-row__label">
5
17
  <slot name="title"> List Title </slot>
6
18
  </v-col>
7
19
 
8
- <v-col cols="8" class="text-subtitle-2">
20
+ <v-col cols="8" class="app-list-row__value">
9
21
  <slot name="value"> value </slot>
10
22
  </v-col>
11
23
  </v-row>
12
24
 
13
25
  <template #append>
14
26
  <slot name="append">
15
- <v-icon size="30">{{ props.icon }}</v-icon>
27
+ <v-icon size="18">{{ props.icon }}</v-icon>
16
28
  </slot>
17
29
  </template>
18
30
  </v-list-item>
@@ -33,3 +45,21 @@ const props = defineProps({
33
45
  const defaultBorder = "pa-0 pl-8 pr-6 py-4";
34
46
  const border = "border-b-sm" + " " + defaultBorder;
35
47
  </script>
48
+
49
+ <style scoped>
50
+ .app-list-row__label,
51
+ .app-list-row__value {
52
+ font-family: var(--font-sans);
53
+ font-size: var(--fs-cell);
54
+ font-weight: var(--fw-cell);
55
+ letter-spacing: 0;
56
+ }
57
+
58
+ .app-list-row__label {
59
+ color: var(--muted);
60
+ }
61
+
62
+ .app-list-row__value {
63
+ color: var(--text);
64
+ }
65
+ </style>
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="arrow-navigation">
3
- <v-btn icon="mdi-chevron-left" variant="text" density="comfortable" :disabled="page <= 1" @click="decrement" />
3
+ <v-btn class="pager-arrow" icon="mdi-chevron-left" variant="text" density="comfortable" :disabled="page <= 1" @click="decrement" />
4
4
  <v-btn
5
- icon="mdi-chevron-right" variant="text" density="comfortable" :disabled="page >= props.length"
5
+ class="pager-arrow" icon="mdi-chevron-right" variant="text" density="comfortable" :disabled="page >= props.length"
6
6
  @click="increment" />
7
7
  </div>
8
8
  </template>
@@ -28,4 +28,28 @@ const props = defineProps({
28
28
  default: 0
29
29
  }
30
30
  });
31
- </script>
31
+ </script>
32
+
33
+ <style scoped>
34
+ /*
35
+ * The pager sits in EVERY converted table's toolbar (TableMain's `right`
36
+ * slot), so it was the last piece of factory Vuetify on an otherwise
37
+ * on-design card: Vuetify's own `on-surface` ink and its comfortable-density
38
+ * box. The design draws a toolbar arrow borderless - so this is ink, size and
39
+ * hover only, no border and no fill invented.
40
+ */
41
+ .arrow-navigation :deep(.pager-arrow) {
42
+ width: var(--icon-btn);
43
+ height: var(--icon-btn);
44
+ border-radius: var(--r-inner);
45
+ color: var(--text2);
46
+ }
47
+
48
+ .arrow-navigation :deep(.pager-arrow:hover) {
49
+ background: var(--hover);
50
+ }
51
+
52
+ .arrow-navigation :deep(.pager-arrow.v-btn--disabled) {
53
+ color: var(--muted);
54
+ }
55
+ </style>