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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/assets/css/primitives.css +1113 -0
  2. package/assets/css/screens.css +1097 -0
  3. package/assets/css/shell.css +258 -0
  4. package/assets/css/tokens.css +236 -0
  5. package/components/AcceptDialog.vue +17 -18
  6. package/components/AccessCard/AvailableStats.vue +24 -16
  7. package/components/AccessCardAddForm.vue +42 -42
  8. package/components/AccessCardAssignToUnitForm.vue +13 -21
  9. package/components/AccessCardDeleteDialog.vue +41 -39
  10. package/components/AccessCardDetailsDialog.vue +125 -79
  11. package/components/AccessCardPreviewDialog.vue +58 -84
  12. package/components/AccessCardQrTagging.vue +60 -63
  13. package/components/AccessCardReplaceForm.vue +27 -23
  14. package/components/AccessManagement.vue +86 -85
  15. package/components/AddEqupmentForm.vue +18 -22
  16. package/components/AddPassKeyToVisitor.vue +95 -73
  17. package/components/AppButton.vue +62 -0
  18. package/components/AppCard.vue +20 -0
  19. package/components/AppDateField.vue +49 -0
  20. package/components/AppField.vue +76 -0
  21. package/components/AppKpiTile.vue +65 -0
  22. package/components/AppSegmented.vue +38 -0
  23. package/components/AppSelect.vue +247 -0
  24. package/components/AreaChecklistHistoryLogs.vue +51 -44
  25. package/components/AreaChecklistHistoryMain.vue +14 -7
  26. package/components/AreaFormDialog.vue +71 -59
  27. package/components/AreaMain.vue +73 -107
  28. package/components/AttachmentsViewer.vue +84 -22
  29. package/components/AttendanceCheckInOutDialog.vue +85 -88
  30. package/components/AttendanceDetailsDialog.vue +57 -63
  31. package/components/AttendanceMain.vue +4 -3
  32. package/components/AttendanceMapSearchDialog.vue +42 -44
  33. package/components/AttendanceSettingsDialog.vue +79 -63
  34. package/components/Avatar/Main.vue +54 -2
  35. package/components/BillingMain.vue +26 -30
  36. package/components/BtnUploadFile.vue +53 -11
  37. package/components/BuildingForm.vue +34 -27
  38. package/components/BuildingManagement/buildings.vue +39 -39
  39. package/components/BuildingManagement/units.vue +26 -42
  40. package/components/BuildingUnitFormAdd.vue +20 -23
  41. package/components/BuildingUnitFormEdit.vue +8 -4
  42. package/components/BulletinBoardForm.vue +19 -24
  43. package/components/BulletinBoardManagement.vue +49 -57
  44. package/components/BulletinBoardView.vue +9 -6
  45. package/components/BulletinExpirationChip.vue +13 -4
  46. package/components/Button/Close.vue +2 -2
  47. package/components/CameraForm.vue +21 -22
  48. package/components/CameraMain.vue +50 -49
  49. package/components/Card/DeleteConfirmation.vue +24 -22
  50. package/components/Card/MemberInfoSummary.vue +28 -3
  51. package/components/Card/Toggle.vue +15 -3
  52. package/components/CardToolbar.vue +37 -0
  53. package/components/Carousel.vue +56 -36
  54. package/components/Chat/Bubbles.vue +43 -14
  55. package/components/Chat/Information.vue +49 -32
  56. package/components/Chat/ListCard.vue +38 -9
  57. package/components/Chat/Message.vue +41 -15
  58. package/components/Chat/Navigation.vue +27 -15
  59. package/components/Chat/SkeletonLoader.vue +9 -2
  60. package/components/ChecklistItemRow.vue +25 -1
  61. package/components/CleaningScheduleMain.vue +44 -49
  62. package/components/ClientDetailForm.vue +81 -56
  63. package/components/ClientMain.vue +92 -82
  64. package/components/ConfirmDialog.vue +14 -8
  65. package/components/Container/Standard.vue +9 -7
  66. package/components/CreateSubsciptionPlan.vue +148 -89
  67. package/components/DashboardEmptyState.vue +48 -3
  68. package/components/DashboardMain.vue +924 -676
  69. package/components/DashboardPanel.vue +84 -21
  70. package/components/DeliveryCompany.vue +34 -20
  71. package/components/Dialog/DeleteConfirmation.vue +20 -21
  72. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  73. package/components/Dialog/ReusablePrompt.vue +30 -29
  74. package/components/Dialog/UpdateMoreAction.vue +62 -59
  75. package/components/DocumentForm.vue +14 -21
  76. package/components/DocumentManagement.vue +162 -108
  77. package/components/DocumentViewer.vue +29 -12
  78. package/components/DrawImage.vue +29 -21
  79. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  80. package/components/EntryPassInformation.vue +40 -10
  81. package/components/EntryPassMain.vue +49 -27
  82. package/components/EquipmentItemMain.vue +84 -97
  83. package/components/EquipmentManagementMain.vue +81 -103
  84. package/components/Facility/BookingSetup.vue +43 -52
  85. package/components/Feedback/Form.vue +26 -12
  86. package/components/FeedbackMain.vue +35 -49
  87. package/components/FileInput.vue +75 -58
  88. package/components/FileInputWithList.vue +59 -12
  89. package/components/FormDialog.vue +60 -21
  90. package/components/HidAccessLogDashboard.vue +512 -490
  91. package/components/HidCameraCaptureDialog.vue +216 -0
  92. package/components/HidEnabledGate.vue +67 -9
  93. package/components/HidIntercomManagement.vue +1322 -653
  94. package/components/HidQrCodeConfiguration.vue +1030 -258
  95. package/components/HidReaderForm.vue +142 -59
  96. package/components/HidReaderManagement.vue +211 -323
  97. package/components/HidServiceSettingsPanel.vue +44 -13
  98. package/components/HidUserEnrollment.vue +1185 -412
  99. package/components/HygieneUpdateMoreAction.vue +44 -44
  100. package/components/IncidentReport/Authorities.vue +30 -18
  101. package/components/IncidentReport/IncidentInformation.vue +10 -10
  102. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  103. package/components/IncidentReport/affectedEntities.vue +41 -20
  104. package/components/Input/Date.vue +3 -0
  105. package/components/Input/DatePicker.vue +1 -2
  106. package/components/Input/DateTimePicker.vue +1 -1
  107. package/components/Input/File.vue +55 -10
  108. package/components/Input/FileV2.vue +39 -6
  109. package/components/Input/InputPhoneNumberV2.vue +2 -1
  110. package/components/Input/ListGroupSelection.vue +56 -8
  111. package/components/Input/NRICNumber.vue +1 -0
  112. package/components/Input/NewDate.vue +1 -0
  113. package/components/Input/Number.vue +1 -0
  114. package/components/Input/Password.vue +1 -0
  115. package/components/Input/PhoneNumber.vue +21 -1
  116. package/components/Input/VehicleNumber.vue +1 -0
  117. package/components/InputLabel.vue +2 -2
  118. package/components/InvitationClientForm.vue +21 -19
  119. package/components/InvitationForm.vue +15 -21
  120. package/components/InvitationMain.vue +46 -61
  121. package/components/Layout/Header.vue +263 -36
  122. package/components/Layout/NavigationDrawer.vue +300 -30
  123. package/components/ListItem.vue +33 -3
  124. package/components/LocalPagination.vue +27 -3
  125. package/components/ManageChecklistMain.vue +136 -86
  126. package/components/MemberInformation.vue +32 -16
  127. package/components/MemberMain.vue +113 -93
  128. package/components/MyAttendanceMain.vue +3 -5
  129. package/components/NavigationItem.vue +83 -3
  130. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  131. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  132. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  133. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  134. package/components/Nfc/NFCTagForm.vue +22 -8
  135. package/components/Nfc/NFCTagMain.vue +41 -43
  136. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  137. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  139. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  140. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  141. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  142. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  143. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  144. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  145. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  146. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  147. package/components/NotificationSettings.vue +373 -0
  148. package/components/NumberSettingField.vue +9 -2
  149. package/components/OnlineFormConfigurationForm.vue +725 -237
  150. package/components/OnlineFormFill.vue +213 -106
  151. package/components/OnlineFormRenderPreview.vue +25 -30
  152. package/components/OnlineFormsConfiguration.vue +113 -109
  153. package/components/OrgViewDialog.vue +34 -16
  154. package/components/OvernightParkingAvailability.vue +49 -26
  155. package/components/OvernightParkingManagement.vue +78 -18
  156. package/components/PageHeader.vue +43 -0
  157. package/components/PassInformation.vue +109 -53
  158. package/components/PasswordConfirmation.vue +44 -26
  159. package/components/PeopleForm.vue +41 -20
  160. package/components/PhotoUpload.vue +110 -48
  161. package/components/PlaceholderComponent.vue +35 -19
  162. package/components/PlanActionsMenu.vue +12 -2
  163. package/components/PlanStatusBadge.vue +7 -8
  164. package/components/PlateNumberDisplay.vue +9 -6
  165. package/components/QrTemplate/PrintDialog.vue +66 -43
  166. package/components/RolePermissionFormCreate.vue +104 -79
  167. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  168. package/components/RolePermissionMain.vue +183 -81
  169. package/components/ScanVisitorQRCode.vue +99 -35
  170. package/components/ScheduleAreaMain.vue +34 -59
  171. package/components/ScheduleTaskForm.vue +61 -42
  172. package/components/ScheduleTaskMain.vue +56 -69
  173. package/components/SearchVehicleNumberUser.vue +39 -37
  174. package/components/ServiceProviderFormCreate.vue +29 -20
  175. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  176. package/components/ServiceProviderMain.vue +601 -432
  177. package/components/Signature.vue +40 -34
  178. package/components/SignaturePad.vue +5 -29
  179. package/components/SiteSettings.vue +41 -11
  180. package/components/SiteSettingsWorkOrder.vue +61 -30
  181. package/components/SlideCardGroup.vue +35 -11
  182. package/components/Snackbar.vue +36 -3
  183. package/components/SpecificAttr.vue +18 -8
  184. package/components/StatusChip.vue +156 -0
  185. package/components/StockCard.vue +17 -12
  186. package/components/SubscriptionPlanTable.vue +207 -140
  187. package/components/SwitchContext.vue +201 -9
  188. package/components/TableHygiene.vue +36 -46
  189. package/components/TableList.vue +10 -10
  190. package/components/TableListSecondary.vue +105 -54
  191. package/components/TableMain.vue +251 -78
  192. package/components/TableV3.vue +61 -70
  193. package/components/TiptapEditor.vue +33 -40
  194. package/components/TiptapEditorQr.vue +5 -2
  195. package/components/Tooltip/Info.vue +69 -24
  196. package/components/UnitMain.vue +40 -60
  197. package/components/UnitPersonCard.vue +42 -13
  198. package/components/VehicleAddSelection.vue +48 -32
  199. package/components/VehicleForm.vue +112 -129
  200. package/components/VehicleManagement.vue +26 -12
  201. package/components/VehicleUpdateMoreAction.vue +59 -46
  202. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  203. package/components/VisitorForm.vue +774 -210
  204. package/components/VisitorFormSelection.vue +40 -37
  205. package/components/VisitorManagement.vue +239 -82
  206. package/components/VisitorPassKeyQRScanner.vue +65 -18
  207. package/components/VisitorSearchResultSummary.vue +60 -16
  208. package/components/VisitorSocketPopUp.vue +22 -14
  209. package/components/VisitorsReportPreview.vue +44 -29
  210. package/components/WorkOrder/Create.vue +26 -17
  211. package/components/WorkOrder/ListView.vue +38 -7
  212. package/components/WorkOrder/Main.vue +62 -59
  213. package/composables/useFeedback.ts +3 -3
  214. package/composables/useHidAmico.ts +269 -0
  215. package/composables/useHidNavigation.ts +2 -9
  216. package/composables/useMember.ts +3 -3
  217. package/composables/useNFCPatrolDailyReport.ts +13 -0
  218. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  219. package/composables/useNFCPatrolReport.ts +13 -0
  220. package/composables/useNotificationPreference.ts +87 -0
  221. package/composables/useOnlineFormPages.ts +2 -0
  222. package/composables/useRole.ts +22 -0
  223. package/composables/useServiceProvider.ts +3 -3
  224. package/composables/useServiceProviderInvitation.ts +145 -0
  225. package/composables/useSipWebPhone.ts +336 -0
  226. package/composables/useSubscriptionPlan.ts +62 -0
  227. package/composables/useThemePreference.ts +63 -0
  228. package/composables/useVisitor.ts +6 -1
  229. package/composables/useWebUsb.ts +127 -37
  230. package/composables/useWorkOrder.ts +17 -3
  231. package/error.vue +30 -6
  232. package/nuxt.config.ts +36 -0
  233. package/package.json +25 -4
  234. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  235. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  237. package/pages/notification-settings.vue +19 -0
  238. package/pages/payment-method-linked.vue +34 -10
  239. package/pages/require-customer.vue +48 -10
  240. package/pages/require-organization-membership.vue +32 -8
  241. package/pages/unauthorized.vue +29 -4
  242. package/plugins/secure-member.client.ts +21 -56
  243. package/plugins/vuetify.ts +69 -2
  244. package/types/nfc-patrol-report.ts +3 -2
  245. package/types/site.d.ts +71 -0
  246. package/types/subscription.ts +18 -11
  247. package/utils/breadcrumb.test.ts +108 -0
  248. package/utils/breadcrumb.ts +82 -0
  249. package/utils/dashboard.test.ts +118 -0
  250. package/utils/dashboard.ts +53 -0
  251. package/utils/data.test.ts +95 -0
  252. package/utils/data.ts +40 -2
  253. package/utils/hid-discover.test.ts +102 -0
  254. package/utils/list-page.test.ts +60 -0
  255. package/utils/list-page.ts +38 -0
  256. package/utils/materialSymbols.ts +363 -0
  257. package/utils/nav-sections.test.ts +190 -0
  258. package/utils/nav-sections.ts +151 -0
  259. package/utils/nfc-patrol-report.test.ts +130 -0
  260. package/utils/nfc-patrol-report.ts +101 -0
  261. package/utils/role.test.ts +69 -0
  262. package/utils/role.ts +10 -0
  263. package/utils/route-name.test.ts +85 -0
  264. package/utils/route-name.ts +24 -0
  265. package/utils/status.test.ts +110 -0
  266. package/utils/status.ts +135 -0
  267. package/utils/table-card.test.ts +84 -0
  268. package/utils/theme-aa-ledger.ts +148 -0
  269. package/utils/theme.test.ts +485 -7
  270. package/utils/theme.ts +295 -110
  271. package/.changeset/README.md +0 -8
  272. package/.changeset/config.json +0 -11
  273. package/.editorconfig +0 -12
  274. package/.github/workflows/main.yml +0 -17
  275. package/.github/workflows/publish-staging.yml +0 -47
  276. package/.github/workflows/publish.yml +0 -39
  277. package/PUBLISHING.md +0 -269
  278. package/components/HidIdentityMapping.vue +0 -975
  279. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  280. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  281. package/components/ScheduleTastTicketMain.vue +0 -182
  282. package/components/TableWithButton.vue +0 -94
  283. package/test/visitor-socket.test.mjs +0 -36
@@ -1,46 +1,40 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn class="text-none" rounded="pill" variant="tonal" @click="setBuilding()" size="large"
6
- v-if="canCreate && canCreateBuilding">
7
- Add Block
8
- </v-btn>
9
- </v-row>
10
- </v-col>
3
+ <!-- Title row; nav calls this screen "Blocks". The Add pill moves onto it. -->
11
4
  <v-col cols="12">
12
- <v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
13
- <v-toolbar density="compact" color="grey-lighten-4">
14
- <template #prepend>
15
- <v-btn fab icon density="comfortable" @click="getBuildings()">
16
- <v-icon>mdi-refresh</v-icon>
17
- </v-btn>
18
- <v-text-field v-model="searchInput" autocomplete="off" placeholder="Search" density="compact" hide-details
19
- dense clearable class="mx-2" style="width: 200px;" />
20
- </template>
21
-
22
- <template #append>
23
- <v-row no-gutters justify="end" align="center">
24
- <span class="mr-2 text-caption text-fontgray">
25
- {{ pageRange }}
26
- </span>
27
- <local-pagination v-model="page" :length="pages" @update:value="getBuildings()" />
28
- </v-row>
29
- </template>
30
- </v-toolbar>
31
-
32
- <v-data-table :headers="headers" :items="items" item-value="_id" items-per-page="20" fixed-header
33
- hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
34
- <template #item.createdAt="{ value }">
35
- {{ new Date(value).toLocaleDateString() }}
36
- </template>
37
- <template #item.levels="{ item }">
38
- {{ item.levels.length }}
39
- </template>
40
- </v-data-table>
41
- </v-card>
5
+ <PageHeader title="Blocks">
6
+ <template #actions>
7
+ <AppButton v-if="canCreate && canCreateBuilding" icon="mdi-plus" @click="setBuilding()">
8
+ Add Block
9
+ </AppButton>
10
+ </template>
11
+ </PageHeader>
42
12
  </v-col>
43
13
 
14
+ <!--
15
+ Was `v-card` + a `grey-lighten-4` `v-toolbar` + a bare `v-data-table`.
16
+ `TableMain` draws the same card, the same refresh/search/page-range/pager
17
+ row and the same table - with the design's uppercase header row.
18
+ -->
19
+ <TableMain :headers="headers" :items="items" :loading="loading" :page="page" :pages="pages"
20
+ :pageRange="pageRange" show-header @refresh="getBuildings()"
21
+ @update:page="page = $event; getBuildings()" @row-click="(data: any) => tableRowClickHandler(null, data)">
22
+ <template #prepend-additional>
23
+ <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
24
+ </template>
25
+ <template #item.createdAt="{ value }">
26
+ {{ new Date(value).toLocaleDateString() }}
27
+ </template>
28
+ <!--
29
+ `item.levels.length` threw during render for any block whose record has
30
+ no `levels` array, and a TypeError in a row template tears the whole
31
+ table down - one incomplete block blanked every block.
32
+ -->
33
+ <template #item.levels="{ item }">
34
+ {{ levelCount(item) }}
35
+ </template>
36
+ </TableMain>
37
+
44
38
  <!-- Create Dialog -->
45
39
  <v-dialog v-model="createDialog" width="450" persistent>
46
40
  <BuildingForm :site="site" mode="add" :blocks="items" @cancel="createDialog = false" @success="successCreate()"
@@ -69,7 +63,9 @@
69
63
 
70
64
  <v-col cols="12">
71
65
  <strong>Levels:</strong>
72
- {{ selectedBuilding?.levels.length ?? "N/A" }}
66
+ <!-- The optional chain stopped one property early: `?.levels.length`
67
+ still reads `.length` of undefined when the block has no levels. -->
68
+ {{ selectedBuilding ? levelCount(selectedBuilding) : "N/A" }}
73
69
  </v-col>
74
70
  <v-col cols="12">
75
71
  <strong>Files:</strong>
@@ -162,6 +158,10 @@
162
158
  </template>
163
159
 
164
160
  <script setup lang="ts">
161
+ // Relative import, never Nuxt auto-import: this layer's `utils/` are auto-imported
162
+ // into the consuming apps but not reliably into the layer's own components.
163
+ import { levelCount } from "../../utils/data";
164
+
165
165
  definePageMeta({
166
166
  middleware: ["01-auth", "02-org"],
167
167
  memberOnly: true,
@@ -1,50 +1,34 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn class="text-none" rounded="pill" variant="tonal" @click="setBuilding()" size="large"
6
- v-if="canCreate && canCreateUnit">
7
- Add Unit
8
- </v-btn>
9
- <v-btn class="text-none ml-2" rounded="pill" variant="tonal" @click="openBulkUploadFilePicker()" size="large"
10
- :loading="bulkUploadLoading" v-if="canCreate && canCreateUnit">
11
- <v-icon start>mdi-upload</v-icon>
12
- Bulk Upload
13
- </v-btn>
14
- <input ref="bulkUploadFileInput" type="file" accept=".xlsx,.xls,.csv" class="d-none"
15
- @change="handleBulkUploadFileChange" />
16
- </v-row>
17
- </v-col>
3
+ <!-- Title row; nav calls this screen "Units". Both pills move onto it. -->
18
4
  <v-col cols="12">
19
- <v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
20
- <v-toolbar density="compact" color="grey-lighten-4">
21
- <template #prepend>
22
- <v-btn fab icon density="comfortable" @click="getBuildingUnit()">
23
- <v-icon>mdi-refresh</v-icon>
24
- </v-btn>
25
- <v-text-field v-model="searchInput" placeholder="Search" density="compact" hide-details dense clearable
26
- class="mx-2" style="width: 200px;" />
27
- </template>
28
-
29
- <template #append>
30
- <v-row no-gutters justify="end" align="center">
31
- <span class="mr-2 text-caption text-fontgray">
32
- {{ pageRange }}
33
- </span>
34
- <local-pagination v-model="page" :length="pages" @update:value="getBuildingUnit()" />
35
- </v-row>
36
- </template>
37
- </v-toolbar>
38
-
39
- <v-data-table :headers="props.headers" :items="items" item-value="_id" items-per-page="20" fixed-header
40
- hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
41
- <template #item.block="{ value, item }">
42
- {{ value ? `Block ${value}` : "" }}
43
- </template>
44
- </v-data-table>
45
- </v-card>
5
+ <PageHeader title="Units">
6
+ <template #actions>
7
+ <AppButton v-if="canCreate && canCreateUnit" variant="ghost" icon="mdi-upload"
8
+ :disabled="bulkUploadLoading" @click="openBulkUploadFilePicker()">
9
+ Bulk Upload
10
+ </AppButton>
11
+ <AppButton v-if="canCreate && canCreateUnit" icon="mdi-plus" @click="setBuilding()">
12
+ Add Unit
13
+ </AppButton>
14
+ </template>
15
+ </PageHeader>
16
+ <input ref="bulkUploadFileInput" type="file" accept=".xlsx,.xls,.csv" class="d-none"
17
+ @change="handleBulkUploadFileChange" />
46
18
  </v-col>
47
19
 
20
+ <!-- Was `v-card` + a `grey-lighten-4` `v-toolbar` + a bare `v-data-table`. -->
21
+ <TableMain :headers="props.headers" :items="items" :loading="loading" :page="page" :pages="pages"
22
+ :pageRange="pageRange" show-header @refresh="getBuildingUnit()"
23
+ @update:page="page = $event; getBuildingUnit()" @row-click="(data: any) => tableRowClickHandler(null, data)">
24
+ <template #prepend-additional>
25
+ <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
26
+ </template>
27
+ <template #item.block="{ value, item }">
28
+ {{ value ? `Block ${value}` : "" }}
29
+ </template>
30
+ </TableMain>
31
+
48
32
  <!-- Create Dialog -->
49
33
  <v-dialog v-model="createDialog" :width="smAndUp ? '50svw' : '100%'" persistent>
50
34
  <BuildingUnitFormAdd :site="site" @cancel="createDialog = false" @success="successCreate()"
@@ -1,13 +1,10 @@
1
1
  <template>
2
- <v-card width="100%">
3
- <v-toolbar>
4
- <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-h5"> Add Unit </span>
6
- </v-row>
7
- </v-toolbar>
8
- <v-card-text style="max-height: 100vh; overflow-y: auto">
2
+ <v-card width="100%" class="screen-modal">
3
+ <v-card-title>Add Unit</v-card-title>
4
+
5
+ <v-card-text class="screen-modal__body pa-0">
9
6
  <v-form v-model="validForm" :disabled="disable">
10
- <v-row no-gutters>
7
+ <v-row no-gutters class="px-6 pt-4">
11
8
  <!-- <v-col cols="12" class="mt-2">
12
9
  <v-row no-gutters>
13
10
  <InputLabel class="text-capitalize" title="Name" required />
@@ -25,11 +22,11 @@
25
22
  <v-row>
26
23
  <v-col cols="12" md="6" class="mt-2">
27
24
  <v-row no-gutters>
28
- <InputLabel class="text-capitalize" title="Block" required />
25
+ <InputLabel class="text-capitalize font-weight-bold" title="Block" required />
29
26
  <v-col cols="12">
30
27
  <v-autocomplete v-model="buildingUnit.building" :items="buildings" item-title="name"
31
- item-value="_id" v-model:search="searchBuilding" :hide-no-data="false" density="comfortable"
32
- :rules="[requiredRule]" variant="outlined">
28
+ item-value="_id" v-model:search="searchBuilding" :hide-no-data="false" class="filter-field"
29
+ density="comfortable" :rules="[requiredRule]" variant="outlined">
33
30
  <template v-slot:item="{ props: itemProps, item }">
34
31
  <v-list-item v-bind="itemProps" :title="item?.raw?.name">
35
32
 
@@ -45,20 +42,20 @@
45
42
 
46
43
  <v-col cols="12" md="6" class="mt-2">
47
44
  <v-row no-gutters>
48
- <InputLabel class="text-capitalize" title="Level" required />
45
+ <InputLabel class="text-capitalize font-weight-bold" title="Level" required />
49
46
  <v-col cols="12">
50
47
  <v-select v-model="buildingUnit.level" :items="buildingLevels" item-title="name" item-value="_id"
51
- density="comfortable" :rules="[requiredRule]"></v-select>
48
+ class="filter-field" density="comfortable" :rules="[requiredRule]"></v-select>
52
49
  </v-col>
53
50
  </v-row>
54
51
  </v-col>
55
52
 
56
53
  <v-col cols="12" md="6" class="mt-2">
57
54
  <v-row no-gutters>
58
- <InputLabel class="text-capitalize" title="No. of Units to be added" required />
55
+ <InputLabel class="text-capitalize font-weight-bold" title="No. of Units to be added" required />
59
56
  <v-col cols="12">
60
- <v-text-field v-model.number="buildingUnitQty" density="comfortable" :rules="[requiredRule]"
61
- type="number" @update:model-value="setBuildingLevels()"></v-text-field>
57
+ <v-text-field v-model.number="buildingUnitQty" class="filter-field" density="comfortable"
58
+ :rules="[requiredRule]" type="number" @update:model-value="setBuildingLevels()"></v-text-field>
62
59
  </v-col>
63
60
  </v-row>
64
61
  </v-col>
@@ -176,7 +173,7 @@
176
173
  <v-row no-gutters>
177
174
  <!-- <InputLabel class="text-capitalize font-weight-bold" :title="`#${unitLabelIndex + 1}`" /> -->
178
175
  <v-col cols="12">
179
- <v-text-field v-model.trim="unitLabels[unitLabelIndex]"
176
+ <v-text-field v-model.trim="unitLabels[unitLabelIndex]" class="filter-field"
180
177
  density="comfortable"></v-text-field>
181
178
  </v-col>
182
179
  </v-row>
@@ -225,22 +222,22 @@
225
222
  </v-form>
226
223
  </v-card-text>
227
224
 
228
- <v-toolbar density="compact">
225
+ <v-card-actions class="screen-modal__footer">
229
226
  <v-row no-gutters>
230
- <v-col cols="6">
231
- <v-btn tile block variant="text" class="text-none" size="48" @click="cancel" :disabled="disable">
227
+ <v-col cols="6" class="pr-2">
228
+ <v-btn block variant="outlined" class="text-none screen-btn-ghost" @click="cancel" :disabled="disable">
232
229
  Cancel
233
230
  </v-btn>
234
231
  </v-col>
235
232
 
236
- <v-col cols="6">
237
- <v-btn tile block variant="flat" color="black" class="text-none" size="48" :disabled="!validForm || disable"
233
+ <v-col cols="6" class="pl-2">
234
+ <v-btn block variant="flat" class="text-none screen-btn-primary" :disabled="!validForm || disable"
238
235
  @click="submit" :loading="disable">
239
236
  Submit
240
237
  </v-btn>
241
238
  </v-col>
242
239
  </v-row>
243
- </v-toolbar>
240
+ </v-card-actions>
244
241
 
245
242
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
246
243
  </v-card>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <v-card width="100%" :loading="getUnitPeoplePending || loading.ownerProcessing" :disabled="loading.ownerProcessing">
3
- <v-toolbar>
3
+ <v-toolbar class="screen-dialog-bar">
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
5
  <span class="font-weight-bold text-h5"> Unit Info ( {{ buildingUnit.name }} ) </span>
6
6
  </v-row>
7
7
  </v-toolbar>
8
- <v-toolbar density="compact">
8
+ <v-toolbar class="screen-dialog-bar" density="compact">
9
9
  <v-tabs v-model="tab" class="mt-0" active-class="bg-success">
10
10
  <v-tab value="general">General</v-tab>
11
11
  <v-tab value="residents">Residents/Tenants</v-tab>
@@ -253,7 +253,7 @@
253
253
  </v-row>
254
254
  </v-col>
255
255
 
256
- <v-toolbar class="pa-0" density="compact">
256
+ <v-toolbar class="pa-0 screen-dialog-bar" density="compact">
257
257
  <v-row no-gutters>
258
258
  <v-col :cols="canUpdateUnit || canDeleteUnit ? 6 : 12" class="pa-0">
259
259
  <v-btn block variant="text" class="text-none" size="large" @click="emit('cancel')" height="48">
@@ -263,8 +263,12 @@
263
263
 
264
264
  <v-col v-if="canUpdateUnit || canDeleteUnit" cols="6" class="pa-0">
265
265
  <v-menu>
266
+ <!-- Was `color="black"`: a fixed slab that stayed black on the dark
267
+ card. `primary-button` is the theme's FILL token, and its pair
268
+ `on-primary-button` supplies the white label - unlike
269
+ `primary`, which is a FOREGROUND here. -->
266
270
  <template #activator="{ props }">
267
- <v-btn block variant="flat" color="black" class="text-none" height="48" v-bind="props" tile
271
+ <v-btn block variant="flat" color="primary-button" class="text-none" height="48" v-bind="props" tile
268
272
  :disabled="!canUpdateUnit && !canDeleteUnit">
269
273
  More actions
270
274
  </v-btn>
@@ -1,18 +1,14 @@
1
1
  <template>
2
2
  <v-card
3
+ class="screen-modal"
3
4
  width="100%"
4
5
  :loading="loading.processing"
5
6
  :disabled="loading.processing || getAnnouncementByIdPending"
6
7
  :key="`form-key-${formKey}`"
7
8
  >
8
- <v-toolbar>
9
- <v-row no-gutters class="fill-height px-6" align="center">
10
- <span class="font-weight-bold text-h5 text-capitalize">
11
- {{ prop.mode }} Announcement
12
- </span>
13
- </v-row>
14
- </v-toolbar>
15
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-6">
9
+ <v-card-title class="text-capitalize">{{ prop.mode }} Announcement</v-card-title>
10
+
11
+ <v-card-text class="screen-modal__body pa-6">
16
12
  <v-form
17
13
  ref="formRef"
18
14
  v-model="validForm"
@@ -41,7 +37,7 @@
41
37
  <template v-slot:prepend>
42
38
  <v-checkbox-btn
43
39
  :color="
44
- selectedSomeRecipients ? 'indigo-darken-4' : undefined
40
+ selectedSomeRecipients ? 'primary-button' : undefined
45
41
  "
46
42
  :indeterminate="
47
43
  selectedSomeRecipients && !selectedAllRecipients
@@ -61,7 +57,7 @@
61
57
  density="compact"
62
58
  color="transparent"
63
59
  flat
64
- class="ml-3 text-black"
60
+ class="ml-3"
65
61
  @mousedown.stop
66
62
  @click.stop="handleRemoveRecipient(item)"
67
63
  />
@@ -112,10 +108,14 @@
112
108
  >Attachments</v-col
113
109
  >
114
110
  <v-col cols="12">
111
+ <!-- Was `color="primary-button"` on an OUTLINED button, i.e. a
112
+ FILL token used as ink - the exact case `utils/theme.ts`
113
+ warns about. Measured 3.64:1 on the dark card, under AA. It
114
+ is a secondary action, so it takes the design's ghost
115
+ button, whose `--text2` ink is readable in both themes. -->
115
116
  <v-btn
116
117
  text="Upload"
117
- class="text-capitalize"
118
- color="primary-button"
118
+ class="text-capitalize screen-btn-ghost"
119
119
  variant="outlined"
120
120
  min-width="150"
121
121
  @click="handleUpload"
@@ -161,34 +161,29 @@
161
161
  {{ errorMessage }}
162
162
  </p>
163
163
  </v-row>
164
- <v-toolbar density="compact">
164
+ <v-card-actions class="screen-modal__footer">
165
165
  <v-row no-gutters>
166
- <v-col cols="6">
166
+ <v-col cols="6" class="pr-2">
167
167
  <v-btn
168
- tile
169
168
  block
170
- variant="text"
171
- class="text-none"
172
- size="48"
169
+ variant="outlined"
170
+ class="text-none screen-btn-ghost"
173
171
  @click="close"
174
172
  text="Close"
175
173
  />
176
174
  </v-col>
177
- <v-col cols="6">
175
+ <v-col cols="6" class="pl-2">
178
176
  <v-btn
179
- tile
180
177
  block
181
178
  variant="flat"
182
- color="primary-button"
183
- class="text-none"
184
- size="48"
179
+ class="text-none screen-btn-primary"
185
180
  :text="prop.mode === 'add' ? 'Add Bulletin' : 'Save'"
186
181
  :disabled="loading.processing || !validForm"
187
182
  @click="onSubmit"
188
183
  />
189
184
  </v-col>
190
185
  </v-row>
191
- </v-toolbar>
186
+ </v-card-actions>
192
187
  </v-card>
193
188
  </template>
194
189
 
@@ -1,17 +1,39 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <TableMain :headers="headers" :items="paginatePlaceholderItem" v-model:search="searchInput"
3
+ <!-- Title left, primary action right - the design's opening row. -->
4
+ <v-col cols="12">
5
+ <PageHeader title="Bulletin Board">
6
+ <template #actions>
7
+ <AppButton v-if="canCreateBulletinBoard" icon="mdi-plus" @click="handleCreateEvent">
8
+ Add Announcement
9
+ </AppButton>
10
+ </template>
11
+ </PageHeader>
12
+ </v-col>
13
+
14
+ <TableMain :headers="headers" :items="items" v-model:search="searchInput"
4
15
  :loading="getAnnouncementPending" :page="page" :pages="pages" :pageRange="pageRange"
5
- @refresh="getAnnouncementsRefresh" :show-header="APP_CONSTANTS.RESIDENT === props.recipients" @update:page="handleUpdatePage" @row-click="handleRowClick"
6
- @create="handleCreateEvent" :can-create="canCreateBulletinBoard" create-label="Add Announcement">
7
- <template #extension>
8
- <v-row no-gutters class="w-100 d-flex flex-column">
9
- <v-tabs v-model="status" color="primary" :height="40" @update:model-value="toRoute" class="w-100">
10
- <v-tab v-for="tab in tabOptions" :value="tab.status" :key="tab.status" class="text-capitalize">
11
- {{ tab.name }}
12
- </v-tab>
13
- </v-tabs>
14
- </v-row>
16
+ @refresh="getAnnouncementsRefresh" :show-header="APP_CONSTANTS.RESIDENT === props.recipients" @update:page="handleUpdatePage" @row-click="handleRowClick">
17
+ <!--
18
+ The tabs move ONTO the toolbar row (the design draws them there)
19
+ instead of a stacked strip above the table. Same options, same
20
+ order, same `toRoute` - which already sets the status itself.
21
+ -->
22
+ <template #tabs>
23
+ <button v-for="tab in tabOptions" :key="tab.status" type="button" role="tab" class="app-tab"
24
+ :class="{ 'app-tab--active': status === tab.status }" :aria-selected="status === tab.status"
25
+ @click="status = tab.status as any; toRoute(tab.status)">
26
+ {{ tab.name }}
27
+ </button>
28
+ </template>
29
+
30
+ <!--
31
+ A failed request is not an empty bulletin board. Without this the
32
+ table drew its neutral "No data available" for a 500, which reads
33
+ as "there are no announcements" - a statement the server never made.
34
+ -->
35
+ <template v-if="getAnnouncementError" #no-data>
36
+ <DashboardEmptyState error />
15
37
  </template>
16
38
 
17
39
  <template v-slot:item.createdAt="{ item }">
@@ -62,6 +84,10 @@
62
84
  <script setup lang="ts">
63
85
  import useBulletin from '../composables/useBulletin';
64
86
  import { APP_CONSTANTS } from '../constants/app';
87
+ // Imported by relative path, not left to Nuxt's auto-import: this layer's
88
+ // components always import their utils explicitly, and a missing import here
89
+ // is a ReferenceError that blanks the screen in every consuming app.
90
+ import { readListPage } from '../utils/list-page';
65
91
 
66
92
  definePageMeta({
67
93
  memberOnly: true,
@@ -122,6 +148,13 @@ const headers = computed(() => {
122
148
  return arr;
123
149
 
124
150
  });
151
+ // THE SERVER PAGES THIS LIST. `getAll` is called with `page`, so `items` is
152
+ // already one page of rows and `pages` / `pageRange` are the server's own
153
+ // answer for the whole list. A computed used to re-derive both from
154
+ // `items.value.length` - i.e. from the page it had just been handed - so 47
155
+ // announcements read "1-10 of 10", the pager was told there was 1 page, and
156
+ // page 2 was unreachable. Nothing here recomputes them any more; the watcher
157
+ // below is the only writer.
125
158
  const items = ref<TAnnouncement[]>([]);
126
159
  const page = ref(1);
127
160
  const pages = ref(0);
@@ -150,28 +183,11 @@ const activeAnnouncementObj = computed(() => {
150
183
  return items.value.find(x => x._id === selectedAnnouncementId.value)
151
184
  })
152
185
 
153
- const paginatePlaceholderItem = computed(() => {
154
- const pageSize = 10
155
- const total = items.value.length
156
- const currentPage = page.value
157
-
158
- const start = (currentPage - 1) * pageSize
159
- const end = start + pageSize
160
-
161
- const from = total === 0 ? 0 : start + 1
162
- const to = Math.min(end, total)
163
- pageRange.value = `${from}-${to} of ${total}`
164
-
165
- pages.value = Math.ceil(total / pageSize)
166
-
167
- return items.value.slice(start, end)
168
- })
169
-
170
-
171
186
  const {
172
187
  data: getAnnouncementReq,
173
188
  refresh: getAnnouncementsRefresh,
174
189
  pending: getAnnouncementPending,
190
+ error: getAnnouncementError,
175
191
  } = await useLazyAsyncData(
176
192
  `get-all-announcements-${page.value}`,
177
193
  () => getAll({ page: page.value, site: siteId, status: status.value, recipients: props.recipients ?? "" }),
@@ -181,37 +197,13 @@ const {
181
197
  );
182
198
 
183
199
  watch(getAnnouncementReq, (newVal) => {
184
- items.value = newVal?.items || [];
185
- pages.value = newVal?.pages || 0;
186
- pageRange.value = newVal?.pageRange || "-- - -- of --";
200
+ const listPage = readListPage<TAnnouncement>(newVal);
201
+ items.value = listPage.items;
202
+ pages.value = listPage.pages;
203
+ pageRange.value = listPage.pageRange;
187
204
  });
188
205
 
189
206
 
190
- function eventStatusFormat(status: any) {
191
- switch (status) {
192
- case 'In Progress':
193
- return {
194
- color: 'orange',
195
- text: 'In Progress'
196
- };
197
- case 'Active':
198
- return {
199
- color: 'green',
200
- text: status
201
- }
202
- case 'Scheduled':
203
- return {
204
- color: 'blue',
205
- text: status
206
- }
207
- default:
208
- return {
209
- color: 'grey',
210
- text: status
211
- }
212
- }
213
- }
214
-
215
207
  const tabOptions = [
216
208
  { name: "Active", status: "active" },
217
209
  { name: "Upcoming", status: "upcoming" },
@@ -1,14 +1,17 @@
1
1
  <template>
2
- <v-card width="100%" :loading="processing">
2
+ <v-card width="100%" class="screen-modal" :loading="processing">
3
3
  <v-toolbar>
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-subtitle-1 text-capitalize">
5
+ <span class="screen-card-title text-capitalize">
6
6
  Preview Announcement
7
7
  </span>
8
8
  </v-row>
9
9
  </v-toolbar>
10
10
 
11
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0 my-3">
11
+ <v-card-text
12
+ style="max-height: 100vh; overflow-y: auto"
13
+ class="screen-modal__body pb-0 my-3"
14
+ >
12
15
  <v-row no-gutters>
13
16
  <template v-for="(label, key) in formattedFields" :key="key">
14
17
  <v-col cols="12" class="mb-5">
@@ -39,7 +42,7 @@
39
42
  <v-col class="font-weight-bold">Attachments:</v-col>
40
43
  <v-col cols="9" class="px-10">
41
44
  <template v-if="videosArray.length > 0">
42
- <div class="text-caption text-medium-emphasis mb-1">
45
+ <div class="text-caption screen-hint mb-1">
43
46
  Videos ({{ videosArray.length }})
44
47
  </div>
45
48
  <v-list density="compact" class="pa-0 rounded-lg border mb-3">
@@ -71,7 +74,7 @@
71
74
 
72
75
  <template v-if="photosArray.length > 0">
73
76
  <div
74
- class="text-caption text-medium-emphasis mb-1"
77
+ class="text-caption screen-hint mb-1"
75
78
  :class="{ 'mt-3': videosArray.length > 0 }"
76
79
  >
77
80
  Photos ({{ photosArray.length }})
@@ -110,7 +113,7 @@
110
113
 
111
114
  <template v-if="filesArray.length > 0">
112
115
  <div
113
- class="text-caption text-medium-emphasis mb-1"
116
+ class="text-caption screen-hint mb-1"
114
117
  :class="{
115
118
  'mt-3': videosArray.length > 0 || photosArray.length > 0,
116
119
  }"
@@ -1,8 +1,17 @@
1
1
  <template>
2
- <v-chip
3
- size="small"
4
- :color="value ? 'green' : 'red'"
5
- :text="value ? 'Yes' : 'No'"
2
+ <!--
3
+ The design's status pill rather than a raw v-chip. Vuetify's `green` and
4
+ `red` are fixed hues that are in no token file: the red one printed
5
+ `rgb(244,67,54)` and measured 3.4:1 against the surface behind it, below AA
6
+ on the word people actually read, and the green one measured 2.78:1.
7
+ `approved` and `rejected` are the shared tone words, so this chip now takes
8
+ `--ok` / `--err` and their paired `--ok-bg` / `--err-bg` - the handoff's own
9
+ values - and carries its own background instead of borrowing whatever it
10
+ happens to sit on.
11
+ -->
12
+ <StatusChip
13
+ :status="value ? 'approved' : 'rejected'"
14
+ :label="value ? 'Yes' : 'No'"
6
15
  />
7
16
  </template>
8
17
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <v-btn v-if="!prop.iconOnly" text="Close" class="bg-grey-lighten-2 text-capitalize" :height="prop.height" @click="emit('click')" />
3
- <v-btn v-else icon="mdi-close" size="small" class="bg-grey-lighten-2 text-capitalize" :height="prop.height" @click="emit('click')" />
2
+ <v-btn v-if="!prop.iconOnly" text="Close" variant="flat" class="screen-btn-ghost" :height="prop.height" @click="emit('click')" />
3
+ <v-btn v-else icon="mdi-close" variant="flat" size="small" class="screen-modal__close" :height="prop.height" @click="emit('click')" />
4
4
  </template>
5
5
 
6
6
  <script setup lang="ts">