@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
@@ -0,0 +1,363 @@
1
+ /**
2
+ * mdi -> Material Symbols Outlined.
3
+ *
4
+ * The design handoff draws every icon with Google's variable
5
+ * `Material Symbols Outlined` font (`opsz,wght,FILL,GRAD@20..48,300..700,0..1,0`),
6
+ * using the icon's name as the element's text and letting the font's ligature
7
+ * table turn it into a glyph. The product ships Material Design Icons (`mdi`)
8
+ * instead - a different, heavier family - so every screen mixes two icon
9
+ * languages.
10
+ *
11
+ * Rather than rewrite ~680 call sites across this package (and again in eleven
12
+ * applications), the Vuetify icon set in `plugins/vuetify.ts` translates on the
13
+ * way out: a component still asks for `mdi-close`, this table answers `close`,
14
+ * and the span renders the Material Symbols glyph. Vuetify's own internal
15
+ * aliases (`$dropdown`, `$checkboxOn`, `$next`, ...) resolve to `mdi-*` strings
16
+ * too, so they are listed here as well and convert with everything else.
17
+ *
18
+ * Anything NOT in this table falls back to the previous Iconify rendering, so
19
+ * an unmapped name draws its old icon rather than a blank box.
20
+ *
21
+ * Every value below was checked against Google's published codepoint list for
22
+ * the variable font, so no entry is a name that does not exist.
23
+ *
24
+ * Where mdi draws something Material Symbols has no equivalent for, the entry
25
+ * is marked NEAREST with what was lost. Those are deliberate, not oversights.
26
+ */
27
+ export const MDI_TO_SYMBOL: Record<string, string> = {
28
+ // --- navigation & chevrons ---
29
+ "mdi-chevron-left": "chevron_left",
30
+ "mdi-chevron-right": "chevron_right",
31
+ "mdi-chevron-up": "expand_less",
32
+ "mdi-chevron-down": "expand_more",
33
+ "mdi-chevron-down-box-outline": "expand_more", // NEAREST: no boxed chevron
34
+ "mdi-menu": "menu",
35
+ "mdi-menu-down": "arrow_drop_down",
36
+ "mdi-menu-right": "arrow_right",
37
+ "mdi-unfold-more-horizontal": "unfold_more",
38
+ "mdi-arrow-left": "arrow_back",
39
+ "mdi-arrow-right": "arrow_forward",
40
+ "mdi-arrow-up": "arrow_upward",
41
+ "mdi-arrow-down": "arrow_downward",
42
+ "mdi-arrow-u-left-top": "undo",
43
+ "mdi-page-first": "first_page",
44
+ "mdi-page-last": "last_page",
45
+ "mdi-open-in-new": "open_in_new",
46
+ "mdi-swap-horizontal": "swap_horiz",
47
+ "mdi-drag": "drag_indicator",
48
+ "mdi-dots-vertical": "more_vert",
49
+ "mdi-dots-horizontal": "more_horiz",
50
+
51
+ // --- actions ---
52
+ "mdi-close": "close",
53
+ "mdi-close-circle": "cancel",
54
+ "mdi-close-circle-outline": "cancel",
55
+ "mdi-check": "check",
56
+ "mdi-check-circle": "check_circle",
57
+ "mdi-check-circle-outline": "check_circle",
58
+ "mdi-plus": "add",
59
+ "mdi-minus": "remove",
60
+ "mdi-minus-box": "indeterminate_check_box",
61
+ "mdi-refresh": "refresh",
62
+ "mdi-cached": "cached",
63
+ "mdi-restore": "restore",
64
+ "mdi-magnify": "search",
65
+ "mdi-magnify-close": "search_off",
66
+ "mdi-pencil": "edit",
67
+ "mdi-pencil-outline": "edit",
68
+ "mdi-square-edit-outline": "edit_square",
69
+ "mdi-delete": "delete",
70
+ "mdi-delete-outline": "delete",
71
+ "mdi-trash-outline": "delete",
72
+ "mdi-trash-can-outline": "delete",
73
+ "mdi-content-save": "save",
74
+ "mdi-content-copy": "content_copy",
75
+ "mdi-download": "download",
76
+ "mdi-download-outline": "download",
77
+ "mdi-upload": "upload",
78
+ "mdi-tray-arrow-up": "file_upload",
79
+ "mdi-export": "output",
80
+ "mdi-printer": "print",
81
+ "mdi-printer-alert": "print_error",
82
+ "mdi-printer-check": "print", // NEAREST: no printer-with-tick
83
+ "mdi-draw": "draw",
84
+ "mdi-tune-variant": "tune",
85
+ "mdi-cog": "settings",
86
+ "mdi-cog-outline": "settings",
87
+ "mdi-backspace": "backspace",
88
+ "mdi-eyedropper": "colorize",
89
+ "mdi-palette": "palette",
90
+ "mdi-sort-alphabetical-ascending": "sort_by_alpha",
91
+
92
+ // --- status & feedback ---
93
+ "mdi-alert": "warning",
94
+ "mdi-alert-outline": "warning",
95
+ "mdi-alert-circle": "error",
96
+ "mdi-alert-circle-outline": "error",
97
+ "mdi-information": "info",
98
+ "mdi-information-outline": "info",
99
+ "mdi-information-symbol": "info",
100
+ "mdi-help-circle": "help",
101
+ "mdi-loading": "progress_activity",
102
+ "mdi-progress-clock": "pending", // NEAREST: no clock-with-progress-arc
103
+ "mdi-history": "history",
104
+ "mdi-trending-up": "trending_up",
105
+ "mdi-flag": "flag",
106
+ "mdi-flash": "bolt",
107
+ "mdi-bug": "bug_report",
108
+ "mdi-bug-outline": "bug_report",
109
+ "mdi-radar": "radar",
110
+ "mdi-emoticon-happy-outline": "sentiment_satisfied",
111
+ "mdi-star": "star",
112
+ "mdi-star-outline": "star",
113
+ "mdi-star-half-full": "star_half",
114
+ "mdi-circle": "circle",
115
+ "mdi-circle-outline": "radio_button_unchecked",
116
+
117
+ // --- form controls (Vuetify's internal aliases live here) ---
118
+ "mdi-checkbox-marked": "check_box",
119
+ "mdi-checkbox-blank-outline": "check_box_outline_blank",
120
+ "mdi-checkbox-multiple-outline": "checklist", // NEAREST: no stacked checkboxes
121
+ "mdi-radiobox-marked": "radio_button_checked",
122
+ "mdi-radiobox-blank": "radio_button_unchecked",
123
+ "mdi-form-textbox": "text_fields",
124
+ "mdi-text": "subject",
125
+ "mdi-format-list-bulleted": "format_list_bulleted",
126
+ "mdi-format-list-numbered": "format_list_numbered",
127
+ "mdi-format-header-1": "format_h1",
128
+ "mdi-keyboard-return": "keyboard_return",
129
+ "mdi-keyboard-space": "space_bar",
130
+ "mdi-apple-keyboard-command": "keyboard_command_key",
131
+ "mdi-apple-keyboard-control": "keyboard_control_key",
132
+ "mdi-apple-keyboard-option": "keyboard_option_key",
133
+ "mdi-apple-keyboard-shift": "shift",
134
+
135
+ // --- time & calendar ---
136
+ "mdi-calendar": "calendar_month",
137
+ "mdi-calendar-month-outline": "calendar_month",
138
+ "mdi-calendar-range": "date_range",
139
+ "mdi-calendar-check": "event_available",
140
+ "mdi-calendar-remove": "event_busy",
141
+ "mdi-calendar-clock": "calendar_clock",
142
+ "mdi-calendar-start": "today", // NEAREST: no start/end calendar pair
143
+ "mdi-calendar-end": "event", // NEAREST: no start/end calendar pair
144
+ "mdi-clock-outline": "schedule",
145
+ "mdi-clock-check": "update",
146
+ "mdi-clock-check-outline": "update",
147
+ "mdi-clock-time-four-outline": "schedule", // NEAREST: no per-hour clock faces
148
+ "mdi-clock-time-eight-outline": "schedule", // NEAREST: no per-hour clock faces
149
+ "mdi-timer-outline": "timer",
150
+
151
+ // --- files & documents ---
152
+ "mdi-file": "draft",
153
+ "mdi-file-outline": "draft",
154
+ "mdi-file-document-outline": "description",
155
+ "mdi-file-document-remove-outline": "scan_delete",
156
+ "mdi-file-question-outline": "unknown_document",
157
+ "mdi-file-pdf": "picture_as_pdf",
158
+ "mdi-file-pdf-box": "picture_as_pdf",
159
+ "mdi-file-pdf-outline": "picture_as_pdf",
160
+ "mdi-file-word-box": "description", // NEAREST: no per-format document icons
161
+ "mdi-file-excel": "table_chart", // NEAREST: no per-format document icons
162
+ "mdi-file-excel-box": "table_chart", // NEAREST: no per-format document icons
163
+ "mdi-file-powerpoint-box": "slideshow", // NEAREST: no per-format document icons
164
+ "mdi-file-csv": "csv",
165
+ "mdi-file-delimited-outline": "csv",
166
+ "mdi-folder": "folder",
167
+ "mdi-folder-plus-outline": "create_new_folder",
168
+ "mdi-folder-zip-outline": "folder_zip",
169
+ "mdi-zip-box-outline": "folder_zip",
170
+ "mdi-paperclip": "attach_file",
171
+ "mdi-clipboard-list": "assignment",
172
+ "mdi-clipboard-text-outline": "assignment",
173
+ "mdi-clipboard-check-outline": "assignment_turned_in",
174
+ "mdi-cloud-upload": "cloud_upload",
175
+ "mdi-cloud-upload-outline": "cloud_upload",
176
+ "mdi-database-sync-outline": "sync", // NEAREST: no database-with-sync
177
+ "mdi-database-search-outline": "manage_search", // NEAREST: no database-with-search
178
+
179
+ // --- media & camera ---
180
+ "mdi-camera": "photo_camera",
181
+ "mdi-camera-outline": "photo_camera",
182
+ "mdi-camera-off": "no_photography",
183
+ "mdi-camera-off-outline": "no_photography",
184
+ "mdi-camera-plus-outline": "add_a_photo",
185
+ "mdi-camera-retake": "flip_camera_android", // NEAREST: retake reads as flip
186
+ "mdi-camera-switch": "cameraswitch",
187
+ "mdi-cctv": "videocam", // NEAREST: no dome/CCTV camera
188
+ "mdi-video": "videocam",
189
+ "mdi-image": "image",
190
+ "mdi-image-broken": "broken_image",
191
+ "mdi-play": "play_arrow",
192
+ "mdi-play-circle-outline": "play_circle",
193
+ "mdi-pause": "pause",
194
+ "mdi-fullscreen": "fullscreen",
195
+ "mdi-fullscreen-exit": "fullscreen_exit",
196
+ "mdi-microphone": "mic",
197
+ "mdi-microphone-off": "mic_off",
198
+ "mdi-volume-high": "volume_up",
199
+ "mdi-volume-medium": "volume_down",
200
+ "mdi-volume-low": "volume_mute",
201
+ "mdi-volume-variant-off": "volume_off",
202
+ "mdi-eye": "visibility",
203
+ "mdi-eye-outline": "visibility",
204
+ "mdi-eye-off": "visibility_off",
205
+ "mdi-eye-off-outline": "visibility_off",
206
+ "mdi-view-grid": "grid_view",
207
+
208
+ // --- people & accounts ---
209
+ "mdi-account": "person",
210
+ "mdi-user": "person",
211
+ "mdi-account-circle": "account_circle",
212
+ "mdi-account-plus": "person_add",
213
+ "mdi-account-off": "person_off",
214
+ "mdi-account-search-outline": "person_search",
215
+ "mdi-account-cog-outline": "manage_accounts",
216
+ "mdi-account-arrow-right": "how_to_reg", // NEAREST: person-with-arrow reads as check-in
217
+ "mdi-account-multiple": "group",
218
+ "mdi-account-group": "groups",
219
+ "mdi-account-badge": "badge",
220
+ "mdi-card-account-details": "badge",
221
+ "mdi-card-account-details-outline": "badge",
222
+ "mdi-card-account-details-star": "badge", // NEAREST: no starred ID card
223
+ "mdi-card-bulleted-outline": "badge", // NEAREST: access card reads as badge
224
+ "mdi-credit-card-outline": "credit_card",
225
+ "mdi-face-recognition": "face", // NEAREST: no face-recognition frame
226
+ "mdi-briefcase": "work",
227
+ "mdi-login": "login",
228
+ "mdi-logout": "logout",
229
+
230
+ // --- communication ---
231
+ "mdi-phone": "call",
232
+ "mdi-phone-outline": "call",
233
+ "mdi-phone-hangup": "call_end",
234
+ "mdi-phone-incoming": "call_received",
235
+ "mdi-phone-outgoing": "call_made",
236
+ "mdi-phone-in-talk": "phone_in_talk",
237
+ "mdi-phone-in-talk-outline": "phone_in_talk",
238
+ "mdi-email": "mail",
239
+ "mdi-message": "chat_bubble",
240
+ "mdi-message-outline": "chat_bubble",
241
+ "mdi-comment-multiple": "forum",
242
+ "mdi-link-variant": "link",
243
+ "mdi-lan-connect": "lan",
244
+ "mdi-lan-disconnect": "link_off", // NEAREST: no disconnected-LAN glyph
245
+ "mdi-bluetooth": "bluetooth",
246
+ "mdi-nfc": "nfc",
247
+
248
+ // --- places, security & operations ---
249
+ "mdi-office-building": "apartment",
250
+ "mdi-office-building-marker-outline": "location_city",
251
+ "mdi-building": "apartment",
252
+ "mdi-domain": "domain",
253
+ "mdi-home-city-outline": "home_work",
254
+ "mdi-warehouse": "warehouse",
255
+ "mdi-storefront-outline": "storefront",
256
+ "mdi-map-marker": "location_on",
257
+ "mdi-map-search": "travel_explore", // NEAREST: no map-with-magnifier
258
+ "mdi-door-open": "door_open",
259
+ "mdi-key": "key",
260
+ "mdi-lock": "lock",
261
+ "mdi-lock-outline": "lock",
262
+ "mdi-shield-outline": "shield",
263
+ "mdi-shield-check": "verified_user",
264
+ "mdi-shield-crown": "admin_panel_settings", // NEAREST: no crowned shield
265
+ "mdi-qrcode": "qr_code",
266
+ "mdi-qrcode-scan": "qr_code_scanner",
267
+ "mdi-barcode-scan": "barcode_scanner",
268
+ "mdi-car": "directions_car",
269
+ "mdi-car-back": "directions_car", // NEAREST: no rear-view car
270
+ "mdi-truck": "local_shipping",
271
+ "mdi-cart-plus": "add_shopping_cart",
272
+ "mdi-pool": "pool",
273
+ "mdi-broom": "cleaning_services",
274
+ "mdi-spray-bottle": "sanitizer",
275
+ "mdi-tools": "handyman",
276
+ "mdi-tree-outline": "park",
277
+ "mdi-flower": "local_florist",
278
+ "mdi-weather-sunny": "light_mode",
279
+ "mdi-weather-night": "dark_mode",
280
+
281
+ // mdi's deliberate spacer. No glyph, but `.v-icon` still holds the 1em box.
282
+ "mdi-blank": "",
283
+
284
+ // ------------------------------------------------------------------
285
+ // Names this package never uses, but the eleven applications do.
286
+ // ------------------------------------------------------------------
287
+ // The applications draw their icons through this same set, so a name
288
+ // missing here is an mdi glyph sitting next to a Material Symbols one on
289
+ // the same screen - the exact split the sweep exists to remove. Adding
290
+ // them here means no application repository needs a change of its own.
291
+ "mdi-view-dashboard": "dashboard",
292
+ "mdi-chart-bar": "bar_chart",
293
+ "mdi-update": "update",
294
+ "mdi-calendar-clock-outline": "calendar_clock",
295
+ "mdi-calendar-check-outline": "event_available",
296
+ "mdi-timer-check-outline": "timer", // NEAREST: no timer-with-tick
297
+ "mdi-timer-remove-outline": "timer_off",
298
+ "mdi-progress-check": "task_alt", // NEAREST: no progress-arc family
299
+ "mdi-progress-alert": "error", // NEAREST: no progress-arc family
300
+ "mdi-progress-question": "help", // NEAREST: no progress-arc family
301
+ "mdi-comment-outline": "chat_bubble",
302
+ "mdi-message-text": "chat",
303
+ "mdi-email-outline": "mail",
304
+ "mdi-email-fast-outline": "forward_to_inbox",
305
+ "mdi-bullhorn-outline": "campaign",
306
+ "mdi-web": "language",
307
+ "mdi-cellphone": "smartphone",
308
+ "mdi-cellphone-cog": "phonelink_setup",
309
+ "mdi-nfc-variant": "nfc",
310
+ "mdi-nfc-tap": "contactless",
311
+ "mdi-handshake": "handshake",
312
+ "mdi-hand-coin-outline": "payments", // NEAREST: no hand-holding-coin
313
+ "mdi-wallet-outline": "account_balance_wallet",
314
+ "mdi-credit-card": "credit_card",
315
+ "mdi-account-credit-card-outline": "credit_card", // NEAREST: no person-with-card
316
+ "mdi-sale-outline": "sell",
317
+ "mdi-rocket-launch": "rocket_launch",
318
+ "mdi-rocket-launch-outline": "rocket_launch",
319
+ "mdi-robot-happy-outline": "smart_toy",
320
+ "mdi-account-hard-hat": "engineering",
321
+ "mdi-account-wrench": "engineering", // NEAREST: no person-with-spanner
322
+ "mdi-wrench-clock": "build", // NEAREST: no spanner-with-clock
323
+ "mdi-account-clock": "pending_actions", // NEAREST: no person-with-clock
324
+ "mdi-account-clock-outline": "pending_actions", // NEAREST: no person-with-clock
325
+ "mdi-account-remove": "person_remove",
326
+ "mdi-account-cog": "manage_accounts",
327
+ "mdi-account-multiple-outline": "group",
328
+ "mdi-account-group-outline": "groups",
329
+ "mdi-account-multiple-check-outline": "how_to_reg", // NEAREST: no group-with-tick
330
+ "mdi-badge-account-outline": "badge",
331
+ "mdi-shield": "shield",
332
+ "mdi-shield-account": "admin_panel_settings",
333
+ "mdi-shield-account-outline": "admin_panel_settings",
334
+ "mdi-shield-search": "policy",
335
+ "mdi-office-building-outline": "apartment",
336
+ "mdi-office-building-marker": "location_city",
337
+ "mdi-office-building-cog-outline": "apartment", // NEAREST: no building-with-cog
338
+ "mdi-home-modern": "house",
339
+ "mdi-sign-text": "signpost",
340
+ "mdi-car-side": "directions_car", // NEAREST: one car glyph for all views
341
+ "mdi-car-clock": "directions_car", // NEAREST: no car-with-clock
342
+ "mdi-car-emergency": "emergency",
343
+ "mdi-key-variant": "key",
344
+ "mdi-pencil-box": "edit_square",
345
+ "mdi-trash": "delete",
346
+ "mdi-delete-forever-outline": "delete_forever",
347
+ "mdi-close-box": "disabled_by_default",
348
+ "mdi-circle-slice-8": "circle",
349
+ "mdi-help-circle-outline": "help",
350
+ "mdi-folder-alert": "folder", // NEAREST: no folder-with-alert
351
+ "mdi-note-text-outline": "description",
352
+ "mdi-card-text-outline": "article",
353
+ "mdi-list-box-outline": "list_alt",
354
+ "mdi-form-select": "dynamic_form", // NEAREST: no form-with-select
355
+ "mdi-microsoft-excel": "table_chart", // NEAREST: no per-format document icons
356
+ "mdi-tray-arrow-down": "file_download",
357
+ "mdi-image-off-outline": "hide_image",
358
+ "mdi-eye-arrow-right-outline": "visibility", // NEAREST: no eye-with-arrow
359
+
360
+ // Landed on staging while this branch was open (the HID screens).
361
+ "mdi-account-off-outline": "person_off",
362
+ "mdi-deskphone": "deskphone",
363
+ };
@@ -0,0 +1,190 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { NAV_SECTIONS, withSections } from "./nav-sections.ts";
5
+
6
+ /** The Security menu, item for item and in `web-app-security`'s own order. */
7
+ const SECURITY = [
8
+ "Dashboard",
9
+ "Feedbacks",
10
+ "Work Orders",
11
+ "Vehicle Mgmt",
12
+ "Building Mgmt",
13
+ "Visitor Mgmt",
14
+ "Pass & Key Mgmt",
15
+ "HID Face Reader",
16
+ "Daily Occurrence Books",
17
+ "Manpower",
18
+ "Virtual Patrol",
19
+ "Robot Mgmt",
20
+ "Incident Reports",
21
+ "Bulletin Board",
22
+ "Attendance",
23
+ "My Attendance",
24
+ "Invitations",
25
+ "Members",
26
+ "Roles & Permissions",
27
+ "Settings",
28
+ ].map((title) => ({ title }));
29
+
30
+ /**
31
+ * THE RULE THAT MATTERS. Grouping may reorder the rail; it may never change
32
+ * the menu. Same objects, same count - nothing added, hidden or duplicated.
33
+ */
34
+ test("every menu item comes back exactly once, and nothing else does", () => {
35
+ const out = withSections(SECURITY);
36
+
37
+ assert.equal(out.length, SECURITY.length);
38
+ assert.deepEqual(
39
+ new Set(out.map((e) => e.item)),
40
+ new Set(SECURITY)
41
+ );
42
+ });
43
+
44
+ test("Security is labelled the way the design draws it", () => {
45
+ const labelled = withSections(SECURITY)
46
+ .filter((e) => e.section)
47
+ .map((e) => `${e.section}: ${e.item.title}`);
48
+
49
+ assert.deepEqual(labelled, [
50
+ "OVERVIEW: Dashboard",
51
+ "SERVICE DESK: Feedbacks",
52
+ "PROPERTY: Vehicle Mgmt",
53
+ "SECURITY OPERATIONS: HID Face Reader",
54
+ "COMMUNITY: Bulletin Board",
55
+ "WORKFORCE: Attendance",
56
+ "ADMINISTRATION: Invitations",
57
+ ]);
58
+ });
59
+
60
+ /**
61
+ * A permission-gated menu is a SUBSET, and every application's is different.
62
+ * Dropping the items a user cannot see must not leave a heading behind or move
63
+ * one onto the wrong item.
64
+ */
65
+ test("a user who can only see three modules gets three correct headings", () => {
66
+ const out = withSections(
67
+ [{ title: "Dashboard" }, { title: "Work Orders" }, { title: "Settings" }]
68
+ );
69
+
70
+ assert.deepEqual(
71
+ out.map((e) => e.section),
72
+ ["OVERVIEW", "SERVICE DESK", "ADMINISTRATION"]
73
+ );
74
+ });
75
+
76
+ /**
77
+ * The eleven applications add menu items without touching this layer. An
78
+ * unknown title must appear where its author put it, under the heading it
79
+ * follows - never suppressed, and never given a heading of its own.
80
+ */
81
+ test("an unmapped module inherits the section above it", () => {
82
+ const out = withSections([
83
+ { title: "Dashboard" },
84
+ { title: "Something New" },
85
+ { title: "Members" },
86
+ ]);
87
+
88
+ assert.deepEqual(
89
+ out.map((e) => e.section),
90
+ ["OVERVIEW", "", "ADMINISTRATION"]
91
+ );
92
+ assert.equal(out[1].item.title, "Something New");
93
+ });
94
+
95
+ test("an unmapped FIRST item prints no heading rather than a blank one", () => {
96
+ const out = withSections([{ title: "Something New" }, { title: "Members" }]);
97
+
98
+ assert.deepEqual(
99
+ out.map((e) => e.section),
100
+ ["", "ADMINISTRATION"]
101
+ );
102
+ });
103
+
104
+ /**
105
+ * The applications do not agree on order: Landscape lists Feedbacks after its
106
+ * equipment modules. Those two Feedbacks/Work Orders items are collected under
107
+ * ONE SERVICE DESK heading instead of printing the heading twice.
108
+ */
109
+ test("a section that comes back later is grouped, not labelled twice", () => {
110
+ const out = withSections([
111
+ { title: "Feedbacks" },
112
+ { title: "Equipment Items" },
113
+ { title: "Work Orders" },
114
+ ]);
115
+
116
+ assert.deepEqual(
117
+ out.map((e) => `${e.section}|${e.item.title}`),
118
+ [
119
+ "SERVICE DESK|Feedbacks",
120
+ "|Work Orders",
121
+ "EQUIPMENT|Equipment Items",
122
+ ]
123
+ );
124
+ });
125
+
126
+ /**
127
+ * Property Management is the screen that showed the defect: its own menu order
128
+ * printed SERVICE DESK, PROPERTY and SECURITY OPERATIONS twice each.
129
+ */
130
+ test("Property Management prints each heading exactly once", () => {
131
+ const PM = [
132
+ "Dashboard",
133
+ "Service Providers",
134
+ "Feedbacks",
135
+ "Work Orders",
136
+ "Facility Mgmt",
137
+ "Pass & Key Mgmt",
138
+ "HID Face Reader",
139
+ "Online Forms",
140
+ "NFC Patrol",
141
+ "Bulletin Board",
142
+ "Bulletin Videos",
143
+ "Visitor Mgmt",
144
+ "Document Mgmt",
145
+ "Access Mgmt",
146
+ ].map((title) => ({ title }));
147
+
148
+ const out = withSections(PM);
149
+ const headings = out.map((e) => e.section).filter(Boolean);
150
+
151
+ assert.deepEqual(headings, [
152
+ "OVERVIEW",
153
+ "SERVICE DESK",
154
+ "PROPERTY",
155
+ "SECURITY OPERATIONS",
156
+ "COMMUNITY",
157
+ ]);
158
+ assert.equal(new Set(headings).size, headings.length);
159
+ assert.deepEqual(
160
+ out.map((e) => e.item.title),
161
+ [
162
+ "Dashboard",
163
+ "Service Providers",
164
+ "Feedbacks",
165
+ "Work Orders",
166
+ "Online Forms",
167
+ "Facility Mgmt",
168
+ "Pass & Key Mgmt",
169
+ "Visitor Mgmt",
170
+ "Document Mgmt",
171
+ "Access Mgmt",
172
+ "HID Face Reader",
173
+ "NFC Patrol",
174
+ "Bulletin Board",
175
+ "Bulletin Videos",
176
+ ]
177
+ );
178
+ });
179
+
180
+ test("a missing or empty title does not throw", () => {
181
+ const out = withSections([{}, { title: "" }] as Array<{ title?: string }>);
182
+ assert.equal(out.length, 2);
183
+ });
184
+
185
+ /** Every section name is the uppercase form the type scale is drawn for. */
186
+ test("section names are uppercase", () => {
187
+ for (const section of Object.values(NAV_SECTIONS)) {
188
+ assert.equal(section, section.toUpperCase(), section);
189
+ }
190
+ });