@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
@@ -13,26 +13,35 @@
13
13
  :prepend-icon="prop.icon"
14
14
  append-icon="mdi-chevron-down"
15
15
  :to="props.route"
16
- class="text-subtitle-2 font-weight-bold"
16
+ class="switch-context__activator"
17
17
  >
18
- <v-list-item-title class="text-truncate">
18
+ <!-- The design prints the organisation and site names in full, but
19
+ mid-word line breaks ("Manageme-nt", "Condominiu-m") are not
20
+ acceptable. Wrap at word boundaries onto up to two lines, then
21
+ ellipsis; the title attribute keeps the full name on hover. -->
22
+ <v-list-item-title
23
+ class="switch-context__name"
24
+ :title="name || `Select ${prop.title}`"
25
+ >
19
26
  {{ name || `Select ${prop.title}` }}
20
27
  </v-list-item-title>
21
28
  </v-list-item>
22
29
  </v-list>
23
30
  </template>
24
31
 
25
- <v-card width="300px" rounded="lg" class="pa-4">
26
- <span class="text-subtitle-2 font-weight-bold">
32
+ <v-card width="300px" elevation="0" class="switch-context__panel">
33
+ <span class="switch-context__heading">
27
34
  Switch {{ prop.title }} context
28
35
  </span>
29
36
  <v-text-field
30
37
  v-model="search"
31
- @keydown.enter="emit('search')"
38
+ @keydown.enter="triggerSearch"
39
+ clearable
40
+ @click:clear="clearSearch"
32
41
  density="compact"
33
42
  width="100%"
34
43
  hide-details
35
- class="mb-2"
44
+ class="filter-field mb-2"
36
45
  >
37
46
  <template #prepend-inner>
38
47
  <v-icon>mdi-magnify</v-icon>
@@ -42,7 +51,7 @@
42
51
  <v-list class="pa-0 my-2" density="compact" max-height="200px">
43
52
  <v-list-item
44
53
  density="compact"
45
- class="text-caption font-weight-bold"
54
+ class="switch-context__row"
46
55
  v-for="item in prop.items"
47
56
  :key="item.value"
48
57
  @click="selectItem(item.value)"
@@ -55,7 +64,41 @@
55
64
  </v-list-item>
56
65
  </v-list>
57
66
 
58
- <slot name="footer"></slot>
67
+ <!-- The two actions under the list. Seven applications hand-wrote this
68
+ same block, and all seven wrote `color="primary" size="large"` on a
69
+ filled button - `primary` is the design's FOREGROUND token, so the
70
+ fill was off-palette and 44px against the design's 40px. Stating it
71
+ once here means an application passes two labels instead of thirty
72
+ lines. Anything passed to #footer still overrides it wholesale. -->
73
+ <slot name="footer">
74
+ <v-row v-if="prop.manageLabel || prop.addLabel" no-gutters>
75
+ <v-col
76
+ v-if="prop.manageLabel"
77
+ cols="12"
78
+ :class="{ 'mb-2': prop.addLabel }"
79
+ >
80
+ <v-btn
81
+ block
82
+ variant="flat"
83
+ class="screen-btn-primary"
84
+ @click="emit('manage')"
85
+ >
86
+ {{ prop.manageLabel }}
87
+ </v-btn>
88
+ </v-col>
89
+
90
+ <v-col v-if="prop.addLabel" cols="12">
91
+ <v-btn
92
+ block
93
+ variant="flat"
94
+ class="screen-btn-primary"
95
+ @click="emit('add')"
96
+ >
97
+ {{ prop.addLabel }}
98
+ </v-btn>
99
+ </v-col>
100
+ </v-row>
101
+ </slot>
59
102
  </v-card>
60
103
  </v-menu>
61
104
  </template>
@@ -82,6 +125,25 @@ const prop = defineProps({
82
125
  required: false,
83
126
  default: "",
84
127
  },
128
+ /**
129
+ * The default #footer's two buttons. Each draws only when its label is a
130
+ * non-empty string, so an application that states neither gets the empty
131
+ * footer it has today, and one that states only `addLabel` gets one button.
132
+ * Deliberately opt-in by label rather than by a boolean: the wording differs
133
+ * per context ("Manage organization" / "Manage sites") and is not derivable
134
+ * from `title`, which is already "Organizations" in one place and "site" in
135
+ * the other.
136
+ */
137
+ manageLabel: {
138
+ type: String,
139
+ required: false,
140
+ default: "",
141
+ },
142
+ addLabel: {
143
+ type: String,
144
+ required: false,
145
+ default: "",
146
+ },
85
147
  });
86
148
 
87
149
  const menu = defineModel<boolean>("menu", { required: false, default: false });
@@ -94,15 +156,145 @@ const search = defineModel<string>("search", {
94
156
  default: "",
95
157
  });
96
158
 
97
- const emit = defineEmits(["search", "select"]);
159
+ const emit = defineEmits(["search", "select", "manage", "add"]);
98
160
 
99
161
  const name = computed(() => {
100
162
  return prop.items.find((item) => item.value === selected.value)?.text ?? "";
101
163
  });
102
164
 
165
+ /**
166
+ * THE BREADCRUMB'S SITE HALF.
167
+ *
168
+ * The top bar draws `Site name / Page`, but the site's NAME is only ever
169
+ * resolved here - each application loads its own site list and hands it to
170
+ * this selector, so the bar had nothing to print and showed the page alone.
171
+ * The label already on screen is mirrored into shared state for the bar to
172
+ * read. It is a label: no request is made, and what the selector selects is
173
+ * untouched.
174
+ */
175
+ const shellSiteName = useState<string>("shell-context-site-name", () => "");
176
+
177
+ watchEffect(() => {
178
+ if (prop.title?.toLowerCase() === "site" && name.value) {
179
+ shellSiteName.value = name.value;
180
+ }
181
+ });
182
+
183
+ let searchDebounce: ReturnType<typeof setTimeout> | null = null;
184
+ let skipNextWatch = false;
185
+
186
+ const triggerSearch = () => {
187
+ if (searchDebounce) clearTimeout(searchDebounce);
188
+ emit("search");
189
+ };
190
+
191
+ const clearSearch = () => {
192
+ skipNextWatch = true;
193
+ search.value = "";
194
+ triggerSearch();
195
+ };
196
+
197
+ watch(search, () => {
198
+ if (skipNextWatch) {
199
+ skipNextWatch = false;
200
+ return;
201
+ }
202
+ if (searchDebounce) clearTimeout(searchDebounce);
203
+ searchDebounce = setTimeout(triggerSearch, 1000);
204
+ });
205
+
206
+ onBeforeUnmount(() => {
207
+ if (searchDebounce) clearTimeout(searchDebounce);
208
+ });
209
+
103
210
  const selectItem = (value: string) => {
104
211
  selected.value = value;
105
212
  menu.value = false;
106
213
  emit("select", value);
107
214
  };
108
215
  </script>
216
+
217
+ <style scoped>
218
+ /**
219
+ * The rail's context selector and the panel it opens.
220
+ *
221
+ * The panel was a factory `v-card` with `pa-4` and Vuetify's `text-subtitle-2`
222
+ * / `text-caption` scale, so it was the one menu in the shell that did NOT
223
+ * match the profile menu the top bar already draws. It takes the SAME surface
224
+ * as `Layout/Header`'s `.app-profile-menu` - 14px radius, the design's menu
225
+ * shadow, a 1px border - rather than a third treatment invented here.
226
+ *
227
+ * The typography utilities also sat on elements that CONTAIN icons
228
+ * (`prepend-icon`, the chevron, the tick), which is the exact shape the central
229
+ * icon-font guard exists to catch. Tokens carry no such risk.
230
+ */
231
+ .switch-context__activator {
232
+ font-size: var(--fs-nav);
233
+ font-weight: 700;
234
+ color: var(--text);
235
+ }
236
+
237
+ /* The panel is a `v-card`, and Vuetify puts it inside `.v-overlay__content`.
238
+ `screens.css` gives EVERY card in an overlay the modal shape - 16px and the
239
+ modal shadow - with `!important`, because dialogs open from sixty components
240
+ and the shape had to be stated once. A menu is an overlay too, so the panel
241
+ was silently taking the modal shape: measured 16px and
242
+ `rgba(10, 12, 16, 0.3) 0 24px 64px`, not the 14px `--r-menu` and the lighter
243
+ `--shadow-menu` it asks for.
244
+ `!important` alone does NOT win it back: `.switch-context__panel[data-v-...]`
245
+ and `.v-overlay__content > .v-card` are both two classes, the scope attribute
246
+ counts, and a tie goes to whichever sheet loaded last - which is screens.css.
247
+ Measured still 16px with `!important` on the plain class. So the selector
248
+ carries the overlay class too, to out-specify rather than tie. */
249
+ .v-overlay__content > .switch-context__panel {
250
+ border-radius: var(--r-menu) !important;
251
+ box-shadow: var(--shadow-menu) !important;
252
+ border: 1px solid var(--border);
253
+ background: var(--card);
254
+ padding: 14px;
255
+ }
256
+
257
+ .switch-context__heading {
258
+ display: block;
259
+ font-size: var(--fs-btn);
260
+ font-weight: 800;
261
+ line-height: 1.3;
262
+ color: var(--text);
263
+ margin-bottom: 10px;
264
+ }
265
+
266
+ /* The profile menu's row, to the pixel - same padding, same radius, same
267
+ weight - because it is the same control one panel over.
268
+ `!important` ONLY on the radius, and it is not optional: a `v-list` with no
269
+ `rounded` prop stamps Vuetify's own `rounded-0` utility onto every
270
+ `v-list-item` inside it, and that utility IS `!important`. So this rule was
271
+ losing silently - measured `0px`, not the 9px it asks for - and the hover
272
+ painted a square block inside a 14px-rounded panel. The header's rows escape
273
+ it only because they are plain `<button>`s, which is why the two menus did
274
+ not match despite the same value. */
275
+ .switch-context__row {
276
+ border-radius: var(--r-inner-sm) !important;
277
+ font-size: var(--fs-btn);
278
+ font-weight: var(--fw-btn);
279
+ color: var(--text2);
280
+ }
281
+
282
+ .switch-context__row:hover {
283
+ background: var(--hover);
284
+ }
285
+
286
+ /* Wrap the full organisation/site name at word boundaries onto up to two
287
+ lines, then ellipsis - never break mid-word. Full name is on the title
288
+ attribute for hover. */
289
+ .switch-context__name {
290
+ display: -webkit-box;
291
+ line-clamp: 2;
292
+ -webkit-line-clamp: 2;
293
+ -webkit-box-orient: vertical;
294
+ overflow: hidden;
295
+ white-space: normal;
296
+ overflow-wrap: normal;
297
+ word-break: normal;
298
+ line-height: 1.25;
299
+ }
300
+ </style>
@@ -5,10 +5,8 @@
5
5
  <slot name="actions">
6
6
  <v-btn
7
7
  v-if="canCreate"
8
- class="text-none"
9
- rounded="pill"
10
- variant="tonal"
11
- size="large"
8
+ class="text-none screen-btn-primary"
9
+ variant="flat"
12
10
  @click="emits('create')"
13
11
  >
14
12
  {{ createLabel }}
@@ -18,16 +16,11 @@
18
16
  </v-col>
19
17
 
20
18
  <v-col cols="12">
21
- <v-card
22
- width="100%"
23
- variant="outlined"
24
- border="thin"
25
- rounded="lg"
26
- :loading="loading"
27
- >
19
+ <v-card width="100%" variant="flat" class="table-card" :loading="loading">
28
20
  <v-toolbar
29
21
  density="compact"
30
- color="grey-lighten-4"
22
+ color="transparent"
23
+ class="table-card__toolbar"
31
24
  :extension-height="extensionHeight"
32
25
  >
33
26
  <template #prepend>
@@ -46,7 +39,7 @@
46
39
 
47
40
  <template #append>
48
41
  <v-row no-gutters justify="end" align="center">
49
- <span class="mr-2 text-caption text-fontgray">
42
+ <span class="mr-2 table-card__range">
50
43
  {{ pageRange }}
51
44
  </span>
52
45
  <local-pagination
@@ -68,25 +61,23 @@
68
61
  :max-height="`calc(100vh - (${offset}px))`"
69
62
  class="overflow-y-auto"
70
63
  >
71
- <v-row
72
- v-if="groupedItems.length === 0"
73
- no-gutters
74
- justify="center"
75
- class="py-10"
76
- >
77
- <v-col
78
- cols="auto"
79
- class="text-center text-medium-emphasis text-body-2"
80
- >
81
- {{ noDataText }}
82
- </v-col>
83
- </v-row>
64
+ <div v-if="groupedItems.length === 0" class="table-card__empty">
65
+ <!--
66
+ The empty block was hard-coded, so a caller had no way to say
67
+ "this is not empty, the request failed" - every 500 rendered as
68
+ the neutral "nothing here". The default is unchanged.
69
+ -->
70
+ <slot name="no-data">
71
+ <v-icon icon="mdi-magnify-close" size="32" />
72
+ <span>{{ noDataText }}</span>
73
+ </slot>
74
+ </div>
84
75
 
85
76
  <template
86
77
  v-for="(group, groupIndex) in groupedItems"
87
78
  :key="`group-${groupIndex}`"
88
79
  >
89
- <v-sheet color="grey-lighten-4" border="b t">
80
+ <v-sheet color="transparent" class="table-card__group" border="b t">
90
81
  <v-row no-gutters align="center" class="px-4 py-2">
91
82
  <v-col cols="auto" class="d-flex align-center ga-2">
92
83
  <span
@@ -94,39 +85,37 @@
94
85
  >
95
86
  Set {{ group.set }}
96
87
  </span>
97
- <v-chip
88
+ <StatusChip
98
89
  v-if="group.isScheduleTask"
99
- size="x-small"
100
- color="info"
101
- variant="tonal"
102
- prepend-icon="mdi-calendar-clock"
90
+ tone="info"
91
+ :dot="false"
103
92
  class="text-none"
104
93
  >
94
+ <v-icon size="12">mdi-calendar-clock</v-icon>
105
95
  Schedule Task
106
- </v-chip>
96
+ </StatusChip>
107
97
  <slot name="group-header-chips" :group="group">
108
- <v-chip
98
+ <StatusChip
109
99
  v-if="group.completedByName && isGroupComplete(group)"
110
- size="x-small"
111
- color="success"
112
- variant="tonal"
113
- prepend-icon="mdi-check-circle-outline"
100
+ status="Completed"
101
+ :dot="false"
114
102
  class="text-none"
115
103
  >
104
+ <v-icon size="12">mdi-check-circle-outline</v-icon>
116
105
  Completed · {{ group.completedByName }}
117
- </v-chip>
118
- <v-chip
106
+ </StatusChip>
107
+ <StatusChip
119
108
  v-else-if="
120
109
  group.completedByName && isGroupInProgress(group)
121
110
  "
122
- size="x-small"
123
- color="warning"
124
- variant="tonal"
125
- prepend-icon="mdi-progress-clock"
111
+ status="Ongoing"
112
+ tone="warn"
113
+ :dot="false"
126
114
  class="text-none"
127
115
  >
116
+ <v-icon size="12">mdi-progress-clock</v-icon>
128
117
  Ongoing · {{ group.completedByName }}
129
- </v-chip>
118
+ </StatusChip>
130
119
  </slot>
131
120
  </v-col>
132
121
  <v-spacer />
@@ -139,7 +128,8 @@
139
128
  <v-sheet
140
129
  v-for="item in group.items"
141
130
  :key="item[itemValue]"
142
- color="white"
131
+ color="transparent"
132
+ class="table-card__row"
143
133
  border="b"
144
134
  >
145
135
  <v-row no-gutters align="center" class="px-4 py-2">
@@ -1,12 +1,10 @@
1
1
  <template>
2
- <v-card
3
- width="100%"
4
- variant="outlined"
5
- border="thin"
6
- rounded="lg"
7
- :loading="loading"
8
- >
9
- <v-toolbar density="compact" color="grey-lighten-4">
2
+ <v-card width="100%" elevation="0" class="table-card" :loading="loading">
3
+ <v-toolbar
4
+ density="compact"
5
+ class="table-card__toolbar px-3"
6
+ color="transparent"
7
+ >
10
8
  <template #prepend>
11
9
  <v-checkbox v-model="selectAll" hide-details class="mx-1"></v-checkbox>
12
10
 
@@ -17,7 +15,7 @@
17
15
 
18
16
  <template #append>
19
17
  <v-row no-gutters justify="end" align="center">
20
- <span class="mr-2 text-caption text-fontgray">
18
+ <span class="table-card__range mr-2">
21
19
  {{ props.pageRange }}
22
20
  </span>
23
21
  <div class="arrow-navigation">
@@ -41,7 +39,9 @@
41
39
  </template>
42
40
 
43
41
  <template v-if="$slots.extension" v-slot:extension>
44
- <slot name="extension"></slot>
42
+ <div class="table-card__extension">
43
+ <slot name="extension"></slot>
44
+ </div>
45
45
  </template>
46
46
  </v-toolbar>
47
47
  <div :style="{ overflowY: 'auto', height }">
@@ -1,55 +1,46 @@
1
- <template>
2
- <v-card width="100%" variant="outlined" border="thin" rounded="lg">
3
- <v-toolbar density="compact" class="px-3 border-b-sm" color="transparent">
4
- <template #prepend>
5
- <slot name="title">Title</slot>
6
- </template>
1
+ <!--
2
+ THE SECOND LIST CARD - the one `ListView` and `WorkOrder/ListView` draw.
7
3
 
8
- <template #append>
9
- <v-row no-gutters justify="end" align="center">
10
- <slot name="action-button"></slot>
11
- </v-row>
12
- </template>
4
+ Was a `v-card` with TWO `v-toolbar`s: the caller's title + action button on
5
+ the top one, and the page range + arrows on a second one under the table. The
6
+ design has neither. The page name and the screen's primary action belong on
7
+ ONE title row ABOVE the card (`PageHeader`), and the range and the pager
8
+ belong on the card's own single top row (`CardToolbar`) - which is exactly
9
+ the shape `TableMain` was converted to.
13
10
 
14
- <template v-if="$slots.extension" v-slot:extension>
15
- <slot name="extension"></slot>
16
- </template>
17
- </v-toolbar>
18
-
19
- <v-data-table
20
- v-model="selected"
21
- :headers="props.headers"
22
- :items="props.items"
23
- item-value="_id"
24
- items-per-page="20"
25
- fixed-header
26
- hide-default-footer
27
- selec
28
- :loading="props.loading"
29
- @click:row="handleRowClick"
30
- :style="`max-height:${props.height}`"
31
- :class="{ 'clickable-rows': props.clickableRows }"
32
- >
33
- <template v-if="$slots.item" v-slot:item>
34
- <slot name="item" :items="props.items"></slot>
11
+ VISUAL ONLY. Same headers, same items, same slots, same row click, same
12
+ `page` model, same `update:pagination` payloads, same increment/decrement
13
+ guards. Nothing is fetched, filtered, sorted or emitted differently; the two
14
+ toolbars became one row and moved, and that is the whole change.
15
+ -->
16
+ <template>
17
+ <div class="list-card">
18
+ <!--
19
+ The fold: the caller's `title` slot used to be the `v-toolbar`'s prepend,
20
+ i.e. a heading INSIDE the card, and its action button sat on the same
21
+ bar. Both are the design's page-title row, so they move up to it. Only
22
+ drawn when the caller actually supplies a title - a caller that passes
23
+ none used to get the literal fallback word "Title" on the bar, which is
24
+ not a heading anybody asked for.
25
+ -->
26
+ <PageHeader v-if="$slots.title">
27
+ <template #title>
28
+ <slot name="title" />
35
29
  </template>
36
30
 
37
- <template v-slot:item="{ item }">
38
- <tr @click="handleRowClick(item)">
39
- <td v-for="col in props.headers" :key="col.value">
40
- <slot :name="col.value" :item="item">
41
- {{ item[col.value] }}
42
- </slot>
43
- </td>
44
- </tr>
31
+ <template v-if="$slots['action-button']" #actions>
32
+ <slot name="action-button"></slot>
45
33
  </template>
46
- </v-data-table>
47
- <v-toolbar density="compact" class="px-3 border-t-sm" color="transparent">
48
- <template #append>
49
- <v-row no-gutters justify="end" align="center">
50
- <span class="mr-2 text-caption text-fontgray">
51
- {{ props.pageRange }}
52
- </span>
34
+ </PageHeader>
35
+
36
+ <AppCard class="table-card">
37
+ <!--
38
+ ONE toolbar. The range keeps the exact string the caller computed and
39
+ the arrows keep their own disabled rules and emits; they have simply
40
+ stopped costing a second bar under the table.
41
+ -->
42
+ <CardToolbar :count="props.pageRange">
43
+ <template #right>
53
44
  <div class="arrow-navigation">
54
45
  <v-btn
55
46
  icon="mdi-chevron-left"
@@ -66,14 +57,58 @@
66
57
  @click="increment"
67
58
  />
68
59
  </div>
69
- </v-row>
70
- </template>
60
+ </template>
61
+ </CardToolbar>
71
62
 
72
- <template v-if="$slots.extension" v-slot:extension>
63
+ <!--
64
+ `extension` used to be rendered by BOTH toolbars, i.e. the same content
65
+ twice, above and below the table. It is one row now, under the toolbar,
66
+ where `TableMain` puts a filter row.
67
+ -->
68
+ <div v-if="$slots.extension" class="table-card__extension">
73
69
  <slot name="extension"></slot>
74
- </template>
75
- </v-toolbar>
76
- </v-card>
70
+ </div>
71
+
72
+ <v-data-table
73
+ v-model="selected"
74
+ :headers="props.headers"
75
+ :items="props.items"
76
+ item-value="_id"
77
+ items-per-page="20"
78
+ fixed-header
79
+ hide-default-footer
80
+ selec
81
+ :loading="props.loading"
82
+ @click:row="handleRowClick"
83
+ :style="`max-height:${props.height}`"
84
+ :class="{ 'clickable-rows': props.clickableRows }"
85
+ >
86
+ <template v-if="$slots.item" v-slot:item>
87
+ <slot name="item" :items="props.items"></slot>
88
+ </template>
89
+
90
+ <template v-slot:item="{ item }">
91
+ <tr @click="handleRowClick(item)">
92
+ <td v-for="col in props.headers" :key="col.value">
93
+ <slot :name="col.value" :item="item">
94
+ {{ item[col.value] }}
95
+ </slot>
96
+ </td>
97
+ </tr>
98
+ </template>
99
+
100
+ <!--
101
+ Forwarded so a caller can say "this is not empty, the request
102
+ failed". Without it every 500 rendered as the table's neutral
103
+ "No data available". Vuetify's own default is unchanged when the
104
+ caller passes nothing.
105
+ -->
106
+ <template v-if="$slots['no-data']" #no-data>
107
+ <slot name="no-data" />
108
+ </template>
109
+ </v-data-table>
110
+ </AppCard>
111
+ </div>
77
112
  </template>
78
113
 
79
114
  <script setup lang="ts">
@@ -159,6 +194,22 @@ watch(selectAll, (curr) => {
159
194
  }
160
195
 
161
196
  .clickable-rows tbody tr:hover {
162
- background-color: #f5f5f5;
197
+ background-color: var(--hover);
198
+ }
199
+
200
+ /*
201
+ Every caller wraps its title in `<span class="text-h6 font-weight-regular">`,
202
+ because the old toolbar drew no type of its own. Vuetify's typography classes
203
+ are `!important`, so that span would keep 20px/400 inside the design's
204
+ 22px/800 heading and the fold would look like it had not happened. The
205
+ heading owns the type now; the caller's span is left to carry whatever else
206
+ it says. Confined to this card's own page head, so no other `PageHeader` is
207
+ touched - and no app repo has to be edited for the title to land.
208
+ */
209
+ .list-card :deep(.app-page-head__title) * {
210
+ font-size: inherit !important;
211
+ font-weight: inherit !important;
212
+ line-height: inherit !important;
213
+ letter-spacing: inherit !important;
163
214
  }
164
215
  </style>