@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
@@ -11,6 +11,10 @@ interface USBDevice {
11
11
  close(): Promise<void>;
12
12
  selectConfiguration(configurationValue: number): Promise<void>;
13
13
  claimInterface(interfaceNumber: number): Promise<void>;
14
+ selectAlternateInterface(
15
+ interfaceNumber: number,
16
+ alternateSetting: number,
17
+ ): Promise<void>;
14
18
  releaseInterface(interfaceNumber: number): Promise<void>;
15
19
  transferOut(
16
20
  endpointNumber: number,
@@ -19,14 +23,27 @@ interface USBDevice {
19
23
  configuration: {
20
24
  interfaces: Array<{
21
25
  interfaceNumber: number;
26
+ alternate: USBAlternateInterface;
22
27
  alternates: Array<{
28
+ alternateSetting: number;
29
+ interfaceClass: number;
23
30
  endpoints: Array<{
24
31
  endpointNumber: number;
25
32
  direction: "in" | "out";
26
33
  }>;
27
34
  }>;
28
35
  }>;
29
- };
36
+ } | null;
37
+ configurations: Array<{ configurationValue: number }>;
38
+ }
39
+
40
+ interface USBAlternateInterface {
41
+ alternateSetting: number;
42
+ interfaceClass: number;
43
+ endpoints: Array<{
44
+ endpointNumber: number;
45
+ direction: "in" | "out";
46
+ }>;
30
47
  }
31
48
 
32
49
  interface USBOutTransferResult {
@@ -39,13 +56,86 @@ declare global {
39
56
  usb: {
40
57
  getDevices(): Promise<USBDevice[]>;
41
58
  requestDevice(options: {
42
- filters: Array<{ vendorId?: number; productId?: number }>;
59
+ filters: Array<{
60
+ vendorId?: number;
61
+ productId?: number;
62
+ classCode?: number;
63
+ }>;
43
64
  }): Promise<USBDevice>;
44
65
  };
45
66
  }
46
67
  }
47
68
 
48
69
  export default function useWebUsb() {
70
+ const printerConnections = new WeakMap<
71
+ USBDevice,
72
+ { interfaceNumber: number; endpointNumber: number }
73
+ >();
74
+
75
+ const protectedInterfaceClasses = new Set([0x01, 0x03, 0x08, 0x0b, 0x0e, 0x10, 0xe0]);
76
+
77
+ function getErrorMessage(error: unknown, fallback: string) {
78
+ return error instanceof Error && error.message ? error.message : fallback;
79
+ }
80
+
81
+ function findPrinterInterface(device: USBDevice) {
82
+ const candidates = (device.configuration?.interfaces || []).flatMap((usbInterface) =>
83
+ usbInterface.alternates.flatMap((alternate) => {
84
+ const outputEndpoint = alternate.endpoints.find((endpoint) => endpoint.direction === "out");
85
+ if (!outputEndpoint || protectedInterfaceClasses.has(alternate.interfaceClass)) return [];
86
+
87
+ return [{
88
+ interfaceNumber: usbInterface.interfaceNumber,
89
+ alternateSetting: alternate.alternateSetting,
90
+ activeAlternateSetting: usbInterface.alternate.alternateSetting,
91
+ interfaceClass: alternate.interfaceClass,
92
+ endpointNumber: outputEndpoint.endpointNumber,
93
+ }];
94
+ }),
95
+ );
96
+
97
+ candidates.sort((left, right) => {
98
+ const priority = (interfaceClass: number) => interfaceClass === 0x07 ? 0 : interfaceClass === 0xff ? 1 : 2;
99
+ return priority(left.interfaceClass) - priority(right.interfaceClass);
100
+ });
101
+
102
+ return candidates[0] || null;
103
+ }
104
+
105
+ async function preparePrinterDevice(device: USBDevice) {
106
+ await device.open();
107
+ if (!device.configuration) {
108
+ await device.selectConfiguration(device.configurations[0]?.configurationValue || 1);
109
+ }
110
+
111
+ const printerInterface = findPrinterInterface(device);
112
+ if (!printerInterface) {
113
+ throw new Error(
114
+ "The selected USB device is not a compatible printer. Select the receipt printer instead of a Bluetooth, camera, keyboard, or other system device.",
115
+ );
116
+ }
117
+
118
+ await device.claimInterface(printerInterface.interfaceNumber);
119
+ if (printerInterface.alternateSetting !== printerInterface.activeAlternateSetting) {
120
+ await device.selectAlternateInterface(
121
+ printerInterface.interfaceNumber,
122
+ printerInterface.alternateSetting,
123
+ );
124
+ }
125
+
126
+ printerConnections.set(device, {
127
+ interfaceNumber: printerInterface.interfaceNumber,
128
+ endpointNumber: printerInterface.endpointNumber,
129
+ });
130
+ return device;
131
+ }
132
+
133
+ function getPrinterConnection(device: USBDevice) {
134
+ const connection = printerConnections.get(device);
135
+ if (!connection) throw new Error("Printer interface is not connected");
136
+ return connection;
137
+ }
138
+
49
139
  const isWebUsbSupported = computed(() => {
50
140
  return typeof navigator !== "undefined" && "usb" in navigator;
51
141
  });
@@ -75,7 +165,9 @@ export default function useWebUsb() {
75
165
  throw new Error("Web USB is not supported in this browser");
76
166
  }
77
167
  try {
78
- const device = await navigator.usb.requestDevice({ filters: [] });
168
+ const device = await navigator.usb.requestDevice({
169
+ filters: [{ classCode: 0x07 }, { classCode: 0xff }],
170
+ });
79
171
  return {
80
172
  vendorId: device.vendorId,
81
173
  productId: device.productId,
@@ -84,12 +176,12 @@ export default function useWebUsb() {
84
176
  serialNumber: device.serialNumber || "Unknown",
85
177
  deviceId: `${device.vendorId}:${device.productId}`,
86
178
  };
87
- } catch (error: any) {
88
- if (error.name === "NotFoundError") {
179
+ } catch (error: unknown) {
180
+ if (error instanceof DOMException && error.name === "NotFoundError") {
89
181
  throw new Error("No device selected");
90
182
  }
91
183
  console.error("Error requesting USB device:", error);
92
- throw new Error("Failed to access USB device");
184
+ throw new Error(getErrorMessage(error, "Failed to access USB printer"));
93
185
  }
94
186
  };
95
187
 
@@ -110,14 +202,17 @@ export default function useWebUsb() {
110
202
  });
111
203
  }
112
204
  console.log("[WebUSB] Opening device:", device.vendorId, device.productId);
113
- await device.open();
114
- await device.selectConfiguration(1);
115
- await device.claimInterface(0);
205
+ await preparePrinterDevice(device);
116
206
  console.log("[WebUSB] Device opened and interface claimed.");
117
207
  return device;
118
- } catch (error) {
208
+ } catch (error: unknown) {
119
209
  console.error("Error connecting to USB device:", error);
120
- throw new Error("Failed to connect to USB device");
210
+ try {
211
+ await device?.close();
212
+ } catch (closeError) {
213
+ console.error("Error closing rejected USB device:", closeError);
214
+ }
215
+ throw new Error(getErrorMessage(error, "Failed to connect to USB printer"));
121
216
  }
122
217
  };
123
218
 
@@ -173,15 +268,20 @@ export default function useWebUsb() {
173
268
  ? "Connection and print test successful"
174
269
  : "Connection successful but print test failed",
175
270
  };
176
- } catch (error: any) {
271
+ } catch (error: unknown) {
177
272
  return {
178
273
  success: false,
179
- error: error.message,
274
+ error: getErrorMessage(error, "Failed to connect to USB printer"),
180
275
  message: "Connection failed",
181
276
  };
182
277
  } finally {
183
278
  if (device) {
184
279
  try {
280
+ const connection = printerConnections.get(device);
281
+ if (connection) {
282
+ await device.releaseInterface(connection.interfaceNumber);
283
+ printerConnections.delete(device);
284
+ }
185
285
  await device.close();
186
286
  } catch (closeError) {
187
287
  console.error("Error closing device:", closeError);
@@ -205,21 +305,16 @@ export default function useWebUsb() {
205
305
  ];
206
306
 
207
307
  const testData = new Uint8Array(testCommands);
208
- const usbInterface = device.configuration.interfaces[0];
209
- const alternate = usbInterface.alternates[0];
210
- const outputEndpoint = alternate.endpoints.find(
211
- (ep: any) => ep.direction === "out",
212
- );
213
-
214
- if (!outputEndpoint) {
215
- throw new Error("No output endpoint found");
216
- }
217
-
218
- await device.transferOut(outputEndpoint.endpointNumber, testData);
308
+ const { endpointNumber } = getPrinterConnection(device);
309
+ await device.transferOut(endpointNumber, testData);
219
310
  return { success: true, message: "Test print sent successfully" };
220
- } catch (error: any) {
311
+ } catch (error: unknown) {
221
312
  console.error("Test print error:", error);
222
- return { success: false, error: error.message, message: "Test print failed" };
313
+ return {
314
+ success: false,
315
+ error: getErrorMessage(error, "Test print failed"),
316
+ message: "Test print failed",
317
+ };
223
318
  }
224
319
  };
225
320
 
@@ -354,13 +449,8 @@ export default function useWebUsb() {
354
449
  ) => {
355
450
  try {
356
451
  console.log("[WebUSB] printQrCode start — doorLevel:", doorLevel, "liftLevel:", liftLevel, "company:", companyName, "address:", address);
357
- const usbInterface = device.configuration.interfaces[0];
358
- const alternate = usbInterface.alternates[0];
359
- const outputEndpoint = alternate.endpoints.find(
360
- (ep: any) => ep.direction === "out",
361
- );
362
- if (!outputEndpoint) throw new Error("No output endpoint found");
363
- console.log("[WebUSB] Using endpoint:", outputEndpoint.endpointNumber);
452
+ const { endpointNumber } = getPrinterConnection(device);
453
+ console.log("[WebUSB] Using endpoint:", endpointNumber);
364
454
 
365
455
  const canvas = await createReceiptLayout(
366
456
  urlImage,
@@ -375,17 +465,17 @@ export default function useWebUsb() {
375
465
  const rasterData = canvasToRaster(canvas);
376
466
  console.log("[WebUSB] Raster data size:", rasterData.byteLength, "bytes");
377
467
 
378
- await device.transferOut(outputEndpoint.endpointNumber, rasterData.buffer);
468
+ await device.transferOut(endpointNumber, rasterData.buffer);
379
469
  console.log("[WebUSB] Raster data sent.");
380
470
 
381
471
  const CUT = new Uint8Array([0x1d, 0x56, 0x41, 0x10]);
382
- await device.transferOut(outputEndpoint.endpointNumber, CUT.buffer);
472
+ await device.transferOut(endpointNumber, CUT.buffer);
383
473
  console.log("[WebUSB] Cut command sent.");
384
474
 
385
475
  return { success: true, message: "QR print sent successfully" };
386
- } catch (error: any) {
476
+ } catch (error: unknown) {
387
477
  console.error("[WebUSB] printQrCode error:", error);
388
- return { success: false, error: error.message };
478
+ return { success: false, error: getErrorMessage(error, "Unable to print QR code") };
389
479
  }
390
480
  };
391
481
 
@@ -57,9 +57,23 @@ export default function useWorkOrder() {
57
57
 
58
58
  const workOrder = useState("workOrder", () => new MWorkOrder());
59
59
  const workOrders = useState<Array<TWorkOrder>>("workOrders", () => []);
60
- const page = useState("page", () => 1);
61
- const pages = useState("pages", () => 0);
62
- const pageRange = useState("pageRange", () => "-- - -- of --");
60
+ /*
61
+ * Namespaced, like every other composable in this folder (`facilityPage`,
62
+ * `commentPage`, `unit-billPage`). These three were registered on the bare
63
+ * keys "page" / "pages" / "pageRange", which `useState` treats as ONE
64
+ * app-global cell per key - so `useFeedback`, `useMember` and
65
+ * `useServiceProvider`, which register the same bare keys, were handed the
66
+ * very same refs. Two of those screens open at once and they share one page
67
+ * counter.
68
+ *
69
+ * Nothing depended on the collision: these three are never assigned anywhere
70
+ * in this composable, and `WorkOrder/ListView.vue` is the only file in the
71
+ * layer or in any of the 11 web apps that destructures them - every real
72
+ * work-order page keeps its own local `ref(1)`.
73
+ */
74
+ const page = useState("workOrderPage", () => 1);
75
+ const pages = useState("workOrderPages", () => 0);
76
+ const pageRange = useState("workOrderPageRange", () => "-- - -- of --");
63
77
 
64
78
  function setWorkOrder(data?: TWorkOrder) {
65
79
  workOrder.value = data || new MWorkOrder();
package/error.vue CHANGED
@@ -1,3 +1,16 @@
1
+ <!--
2
+ THE ERROR PAGE - what a user sees at their worst moment.
3
+
4
+ It was Vuetify's own chrome end to end: `text-h2` (a 60px Roboto display
5
+ size the design's scale does not contain), and a `variant="tonal"
6
+ rounded="xl" size="large"` button - a 24px-cornered 44px pill in the theme's
7
+ tinted wash, where every other action in the product is a 40px, 10px-cornered
8
+ `.screen-btn-primary`.
9
+
10
+ Nothing about the behaviour changes: same `v-app`/`v-main` wrapper (this page
11
+ renders OUTSIDE the app layout, so it has to bring its own), same message,
12
+ same route home.
13
+ -->
1
14
  <script setup lang="ts">
2
15
  import type { NuxtError } from "#app";
3
16
 
@@ -17,16 +30,14 @@ const props = defineProps({
17
30
  >
18
31
  <v-col cols="9">
19
32
  <v-row>
20
- <v-col cols="12" class="text-center text-h2">
21
- <span class="font-weight-bold">{{ props.error?.message }}</span>
33
+ <v-col cols="12" class="text-center">
34
+ <p class="error-page__message">{{ props.error?.message }}</p>
22
35
  </v-col>
23
36
  <v-col cols="12">
24
37
  <v-row no-gutters justify="center">
25
38
  <v-btn
26
- rounded="xl"
27
- size="large"
28
- class="text-none font-weight-bold"
29
- variant="tonal"
39
+ class="screen-btn-primary"
40
+ variant="flat"
30
41
  :to="{ name: 'index' }"
31
42
  >
32
43
  Go back
@@ -39,3 +50,16 @@ const props = defineProps({
39
50
  </v-main>
40
51
  </v-app>
41
52
  </template>
53
+
54
+ <style scoped>
55
+ /* The design's page title, one step up: an error message is the only thing on
56
+ the screen, so it keeps display weight without borrowing Roboto's 60px. */
57
+ .error-page__message {
58
+ font-family: var(--font-sans);
59
+ font-size: calc(var(--fs-h1) * 1.6);
60
+ font-weight: var(--fw-h1);
61
+ letter-spacing: var(--ls-h1);
62
+ color: var(--text);
63
+ margin-bottom: 24px;
64
+ }
65
+ </style>
package/nuxt.config.ts CHANGED
@@ -7,6 +7,42 @@ export default defineNuxtConfig({
7
7
  transpile: ["vuetify"],
8
8
  },
9
9
 
10
+ /**
11
+ * THE REDESIGN'S TYPEFACE - LOADED, DELIBERATELY NOT APPLIED YET.
12
+ *
13
+ * `--font-sans` in `assets/css/tokens.css` names Manrope, and a token that
14
+ * names a font nobody loaded is a trap: the next phase writes
15
+ * `font-family: var(--font-sans)`, silently falls back to Arial, and looks
16
+ * fine to whoever wrote it. So the stylesheet is requested here.
17
+ *
18
+ * Nothing sets `font-family` on the page in Phase 1. Switching eleven
19
+ * applications to a new typeface before one screen has been rebuilt would
20
+ * leave the whole product looking half-migrated; each phase adopts the token
21
+ * as it rebuilds its own surfaces. Browsers do not download a webfont no
22
+ * element uses, so until then this costs one small CSS request.
23
+ *
24
+ * The second family is the redesign's icon set. Its axis ranges are the
25
+ * handoff's, character for character (`opsz,wght,FILL,GRAD@20..48,300..700,
26
+ * 0..1,0`) - do not widen or narrow them without a design change to point
27
+ * at. `plugins/vuetify.ts` and `assets/css/primitives.css` do the applying.
28
+ */
29
+ app: {
30
+ head: {
31
+ link: [
32
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
33
+ {
34
+ rel: "preconnect",
35
+ href: "https://fonts.gstatic.com",
36
+ crossorigin: "",
37
+ },
38
+ {
39
+ rel: "stylesheet",
40
+ href: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,300..700,0..1,0&display=swap",
41
+ },
42
+ ],
43
+ },
44
+ },
45
+
10
46
  runtimeConfig: {
11
47
  public: {
12
48
  cookieConfig: {
package/package.json CHANGED
@@ -2,9 +2,30 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.7",
5
+ "version": "3.2.8-staging.201",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
+ "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
9
+ "files": [
10
+ "CHANGELOG.md",
11
+ "app.vue",
12
+ "error.vue",
13
+ "nuxt.config.ts",
14
+ "tsconfig.json",
15
+ ".nuxtrc",
16
+ "assets",
17
+ "components",
18
+ "composables",
19
+ "constants",
20
+ "layouts",
21
+ "middleware",
22
+ "pages",
23
+ "plugins",
24
+ "public",
25
+ "services",
26
+ "types",
27
+ "utils"
28
+ ],
8
29
  "publishConfig": {
9
30
  "access": "public"
10
31
  },
@@ -14,9 +35,8 @@
14
35
  "build": "nuxt build .playground",
15
36
  "generate": "nuxt generate .playground",
16
37
  "preview": "nuxt preview .playground",
17
- "test": "esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && node --test \"test/*.test.mjs\"",
18
- "test:theme": "node --test \"utils/theme.test.ts\"",
19
- "test:camera-wall": "node --test \"utils/camera-wall.test.ts\"",
38
+ "test": "esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && node --test \"test/*.test.mjs\" && yarn test:units",
39
+ "test:units": "node --experimental-strip-types --test \"utils/*.test.ts\"",
20
40
  "release": "yarn run build && changeset publish"
21
41
  },
22
42
  "devDependencies": {
@@ -43,6 +63,7 @@
43
63
  "qrcode": "^1.5.4",
44
64
  "qrcode.vue": "^3.4.1",
45
65
  "sass": "^1.80.6",
66
+ "sip.js": "0.21.2",
46
67
  "socket.io-client": "^4.8.3",
47
68
  "vue-draggable-next": "^2.3.0",
48
69
  "vue3-signature": "^0.2.4",
@@ -3,9 +3,9 @@
3
3
  <HidEnabledGate
4
4
  :site="siteId"
5
5
  :org="orgId"
6
- message="Enable HID as a service for this site before managing HID administrators."
6
+ message="Enable HID as a service for this site before managing HID users."
7
7
  >
8
- <HidUserEnrollment :site="siteId" identity-type="admin" />
8
+ <HidUserEnrollment :site="siteId" />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
@@ -3,9 +3,9 @@
3
3
  <HidEnabledGate
4
4
  :site="siteId"
5
5
  :org="orgId"
6
- message="Enable HID as a service for this site before mapping identities."
6
+ message="Enable HID as a service for this site before managing physical HID cards."
7
7
  >
8
- <HidIdentityMapping :site="siteId" :org="orgId" />
8
+ <HidUserEnrollment :site="siteId" card-management />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
@@ -5,7 +5,7 @@
5
5
  :org="orgId"
6
6
  message="Enable HID as a service for this site before configuring HID intercom."
7
7
  >
8
- <HidIntercomManagement :site="siteId" />
8
+ <HidIntercomManagement :site="siteId" :org="orgId" />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <NotificationSettings />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ /**
7
+ * Notification settings, reachable from Profile.
8
+ *
9
+ * The page lives in the shared layer so every web app has the route and there
10
+ * is only one screen to keep correct; the link to it sits in Profile, in the
11
+ * account app, which is where "Manage Account" already sends everybody.
12
+ */
13
+ definePageMeta({
14
+ middleware: ["01-auth"],
15
+ });
16
+
17
+ const { authenticate } = useLocalAuth();
18
+ authenticate();
19
+ </script>
@@ -1,20 +1,44 @@
1
+ <!--
2
+ The "payment method linked" confirmation the payment window returns to.
3
+
4
+ THE DEFECT IS THE CLOSE BUTTON, NOT THE CARD. Measured before assuming:
5
+ `color="success lighten-1"` is Vuetify **2** syntax, but Vuetify 3 takes the
6
+ first token and applies it, so the card IS green - `rgb(15,138,98)` light,
7
+ `rgb(65,199,149)` dark. Only `lighten-1` is dead, and it changes nothing.
8
+ The green card stays: a success page is green on purpose, and swapping it for
9
+ a neutral surface would be a design decision, not a conversion.
10
+
11
+ What was wrong is `color="white" variant="tonal"` on Close. `tonal` paints
12
+ its tint on an OVERLAY, so the button's own background measured
13
+ `rgba(0,0,0,0)` and all that reached the glass was white text - on the green
14
+ card. Light: white on `rgb(15,138,98)` = 4.35:1. **Dark: white on
15
+ `rgb(65,199,149)` = 2.12:1**, which is the worst reading on this page and the
16
+ reason it needed touching at all. `.screen-btn-ghost` is the design's
17
+ secondary button - the card surface with `--text2` ink and a `--border` - so
18
+ it reads on green in both themes, and it is the 40px/10px button the rest of
19
+ the product uses rather than a 44px 24px-cornered pill.
20
+
21
+ The title had the same defect in the other direction: a literal `text-white`
22
+ on a fill that changes with the theme. See the comment on it below.
23
+
24
+ Behaviour is unchanged: same `window.close()`.
25
+ -->
1
26
  <template>
2
27
  <v-row no-gutters class="fill-height" justify="center" align-content="center">
3
- <v-card class="pa-4" elevation="4" color="success lighten-1">
4
- <v-card-title class="text-white d-flex align-center">
28
+ <v-card class="pa-4" elevation="4" color="success">
29
+ <!-- `text-white` was a literal on a THEMED fill, and the fill changes with
30
+ the theme: white on dark's `rgb(65,199,149)` measures **2.14:1**.
31
+ Dropping it lets the card use its own paired ink - Vuetify sets
32
+ `on-success` from `color="success"` - which follows the fill. -->
33
+ <v-card-title class="d-flex align-center">
5
34
  <v-icon class="mr-2">mdi-check-circle</v-icon>
6
35
  Successfully Linked Payment Method
7
36
  </v-card-title>
8
37
 
9
38
  <v-card-actions class="d-flex justify-center">
10
- <v-btn
11
- color="white"
12
- variant="tonal"
13
- rounded="xl"
14
- size="large"
15
- @click="closeWindow"
16
- >Close</v-btn
17
- >
39
+ <v-btn class="screen-btn-ghost" variant="flat" @click="closeWindow">
40
+ Close
41
+ </v-btn>
18
42
  </v-card-actions>
19
43
  </v-card>
20
44
  </v-row>
@@ -1,23 +1,32 @@
1
+ <!--
2
+ The "you must have a customer" gate.
3
+
4
+ Was Vuetify's chrome: `text-subtitle-1` for the sentence, `text-subtitle-2`
5
+ on the buttons, and `variant="tonal" rounded="xl"` - a 24px-cornered pill in
6
+ the theme's tinted wash, where the product's action is a 40px, 10px-cornered
7
+ button. The sentence is now `--text2` (9.62:1 on a card) at the design's
8
+ reading size, and the actions are the design's primary and ghost buttons.
9
+
10
+ No behaviour change: same handlers, same destinations, same `plain` layout.
11
+ -->
1
12
  <template>
2
13
  <v-row no-gutters class="fill-height" align="center" justify="center">
3
- <v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
14
+ <v-col cols="12" lg="6" md="6" sm="6" class="text-center gate-page__message">
4
15
  You must have a customer to view this page. Please join or contact your
5
16
  administrator for access.
6
17
  <v-row no-gutters justify="center" align="center" class="mt-2">
7
18
  <v-btn
19
+ class="screen-btn-ghost"
20
+ variant="flat"
8
21
  @click="goToAccount"
9
- rounded="xl"
10
- variant="tonal"
11
- class="text-none text-subtitle-2"
12
22
  >
13
23
  Go to Organization Page
14
24
  </v-btn>
15
- <span class="mx-2">or</span>
25
+ <span class="mx-2 gate-page__or">or</span>
16
26
  <v-btn
27
+ class="screen-btn-primary"
28
+ variant="flat"
17
29
  @click="createCustomer"
18
- rounded="xl"
19
- variant="tonal"
20
- class="text-none text-subtitle-2"
21
30
  >
22
31
  Create an Customer
23
32
  </v-btn>
@@ -27,6 +36,10 @@
27
36
  </template>
28
37
 
29
38
  <script setup lang="ts">
39
+ // Relative import, never Nuxt auto-import: this layer's `utils/` are auto-imported
40
+ // into the consuming apps but not reliably into the layer's own components.
41
+ import { pickRouteName } from "../utils/route-name";
42
+
30
43
  definePageMeta({
31
44
  layout: "plain",
32
45
  });
@@ -44,9 +57,19 @@ authenticate();
44
57
  const { currentUser } = useLocalAuth();
45
58
 
46
59
  function createCustomer() {
47
- if (APP_NAME.toLowerCase() === "org") {
60
+ // The org app's page is `pages/org/[organization]/customers/add.vue`, whose
61
+ // route name is `org-organization-customers-add` - singular. The plural spelt
62
+ // here matched nothing, so the only way off this dead-end page threw. The
63
+ // `hasRoute` check also keeps a mis-set APP_NAME from taking this branch in an
64
+ // app that has no such page; that app falls through to the cross-app URL.
65
+ const customerAdd = pickRouteName(
66
+ useRouter(),
67
+ "org-organization-customers-add",
68
+ "organizations-customers-add"
69
+ );
70
+ if (APP_NAME.toLowerCase() === "org" && customerAdd) {
48
71
  navigateTo({
49
- name: "org-organizations-customers-add",
72
+ name: customerAdd,
50
73
  params: { organization: currentUser.value?.defaultOrg },
51
74
  });
52
75
  } else {
@@ -54,3 +77,18 @@ function createCustomer() {
54
77
  }
55
78
  }
56
79
  </script>
80
+
81
+ <style scoped>
82
+ .gate-page__message {
83
+ font-family: var(--font-sans);
84
+ font-size: var(--fs-cell);
85
+ color: var(--text2);
86
+ line-height: 1.6;
87
+ }
88
+
89
+ .gate-page__or {
90
+ font-family: var(--font-sans);
91
+ font-size: var(--fs-cell);
92
+ color: var(--muted);
93
+ }
94
+ </style>