@7365admin1/layer-common 1.10.0 → 1.10.2

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 (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCard/AvailableStats.vue +55 -0
  4. package/components/AccessCardAddForm.vue +284 -19
  5. package/components/AccessCardAssignToUnitForm.vue +440 -0
  6. package/components/AccessManagement.vue +218 -85
  7. package/components/AddSupplyForm.vue +165 -0
  8. package/components/AreaChecklistHistoryLogs.vue +235 -0
  9. package/components/AreaChecklistHistoryMain.vue +176 -0
  10. package/components/AreaFormDialog.vue +266 -0
  11. package/components/AreaMain.vue +863 -0
  12. package/components/AttendanceCheckInOutDialog.vue +416 -0
  13. package/components/AttendanceDetailsDialog.vue +184 -0
  14. package/components/AttendanceMain.vue +155 -0
  15. package/components/AttendanceMapSearchDialog.vue +393 -0
  16. package/components/AttendanceSettingsDialog.vue +398 -0
  17. package/components/BuildingManagement/buildings.vue +5 -5
  18. package/components/BuildingManagement/units.vue +5 -5
  19. package/components/BulletinBoardManagement.vue +322 -0
  20. package/components/ChecklistItemRow.vue +54 -0
  21. package/components/CheckoutItemMain.vue +705 -0
  22. package/components/CleaningScheduleMain.vue +271 -0
  23. package/components/DocumentManagement.vue +4 -0
  24. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  25. package/components/EntryPassMain.vue +252 -200
  26. package/components/HygieneUpdateMoreAction.vue +238 -0
  27. package/components/ManageChecklistMain.vue +384 -0
  28. package/components/MemberMain.vue +48 -20
  29. package/components/MyAttendanceMain.vue +224 -0
  30. package/components/OnlineFormsConfiguration.vue +9 -2
  31. package/components/PhotoUpload.vue +410 -0
  32. package/components/ScheduleAreaMain.vue +313 -0
  33. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  34. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  35. package/components/ScheduleTaskForm.vue +471 -0
  36. package/components/ScheduleTaskMain.vue +345 -0
  37. package/components/ScheduleTastTicketMain.vue +182 -0
  38. package/components/SignaturePad.vue +17 -5
  39. package/components/StockCard.vue +191 -0
  40. package/components/SupplyManagementMain.vue +557 -0
  41. package/components/TableHygiene.vue +617 -0
  42. package/components/UnitMain.vue +451 -0
  43. package/components/VisitorManagement.vue +28 -15
  44. package/composables/useAccessManagement.ts +163 -0
  45. package/composables/useAreaPermission.ts +51 -0
  46. package/composables/useAreas.ts +99 -0
  47. package/composables/useAttendance.ts +89 -0
  48. package/composables/useAttendancePermission.ts +68 -0
  49. package/composables/useBuilding.ts +2 -2
  50. package/composables/useBuildingUnit.ts +2 -2
  51. package/composables/useBulletin.ts +82 -0
  52. package/composables/useCard.ts +2 -0
  53. package/composables/useCheckout.ts +61 -0
  54. package/composables/useCheckoutPermission.ts +80 -0
  55. package/composables/useCleaningPermission.ts +229 -0
  56. package/composables/useCleaningSchedulePermission.ts +58 -0
  57. package/composables/useCleaningSchedules.ts +233 -0
  58. package/composables/useCountry.ts +8 -0
  59. package/composables/useDashboardData.ts +2 -2
  60. package/composables/useFeedback.ts +1 -1
  61. package/composables/useLocation.ts +78 -0
  62. package/composables/useOnlineForm.ts +16 -9
  63. package/composables/usePeople.ts +87 -72
  64. package/composables/useQR.ts +29 -0
  65. package/composables/useScheduleTask.ts +89 -0
  66. package/composables/useScheduleTaskArea.ts +85 -0
  67. package/composables/useScheduleTaskPermission.ts +68 -0
  68. package/composables/useSiteEntryPassSettings.ts +4 -15
  69. package/composables/useStock.ts +45 -0
  70. package/composables/useSupply.ts +63 -0
  71. package/composables/useSupplyPermission.ts +92 -0
  72. package/composables/useUnitPermission.ts +51 -0
  73. package/composables/useUnits.ts +82 -0
  74. package/composables/useWebUsb.ts +389 -0
  75. package/composables/useWorkOrder.ts +1 -1
  76. package/nuxt.config.ts +3 -0
  77. package/package.json +4 -1
  78. package/types/area.d.ts +22 -0
  79. package/types/attendance.d.ts +38 -0
  80. package/types/checkout-item.d.ts +27 -0
  81. package/types/cleaner-schedule.d.ts +54 -0
  82. package/types/location.d.ts +42 -0
  83. package/types/schedule-task.d.ts +18 -0
  84. package/types/stock.d.ts +16 -0
  85. package/types/supply.d.ts +11 -0
  86. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,63 @@
1
+ export default function useSupply() {
2
+ function getSupplies({ page = 1, search = "", limit = 10, site = "" } = {}) {
3
+ return useNuxtApp().$api<Record<string, any>>(
4
+ `/api/hygiene-supplies/site/${site}`,
5
+ {
6
+ method: "GET",
7
+ query: { page, search, limit, site },
8
+ }
9
+ );
10
+ }
11
+
12
+ function getSupplyById(id: string) {
13
+ return useNuxtApp().$api<Record<string, any>>(
14
+ `/api/hygiene-supplies/id/${id}`,
15
+ {
16
+ method: "GET",
17
+ }
18
+ );
19
+ }
20
+
21
+ function createSupply(payload: TSupplyCreate, site: string) {
22
+ return useNuxtApp().$api<Record<string, any>>(
23
+ `/api/hygiene-supplies/site/${site}`,
24
+ {
25
+ method: "POST",
26
+ body: {
27
+ name: payload.name,
28
+ unitOfMeasurement: payload.unitOfMeasurement,
29
+ },
30
+ }
31
+ );
32
+ }
33
+
34
+ function updateSupply(id: string, payload: TSupplyCreate) {
35
+ return useNuxtApp().$api<Record<string, any>>(
36
+ `/api/hygiene-supplies/id/${id}`,
37
+ {
38
+ method: "PATCH",
39
+ body: {
40
+ name: payload.name,
41
+ unitOfMeasurement: payload.unitOfMeasurement,
42
+ },
43
+ }
44
+ );
45
+ }
46
+
47
+ function deleteSupply(id: string) {
48
+ return useNuxtApp().$api<Record<string, any>>(
49
+ `/api/hygiene-supplies/id/${id}`,
50
+ {
51
+ method: "DELETE",
52
+ }
53
+ );
54
+ }
55
+
56
+ return {
57
+ getSupplies,
58
+ getSupplyById,
59
+ createSupply,
60
+ updateSupply,
61
+ deleteSupply,
62
+ };
63
+ }
@@ -0,0 +1,92 @@
1
+ export function useSupplyPermission() {
2
+ const { hasPermission } = usePermission();
3
+ const { permissions } = useCleaningPermission();
4
+ const { userAppRole } = useLocalSetup();
5
+
6
+ const canViewSupplies = computed(() => {
7
+ if (!userAppRole.value) return true;
8
+ if (userAppRole.value.permissions.includes("*")) return true;
9
+ return hasPermission(
10
+ userAppRole.value,
11
+ permissions,
12
+ "supply-mgmt",
13
+ "see-all-supplies"
14
+ );
15
+ });
16
+
17
+ const canAddSupply = computed(() => {
18
+ if (!userAppRole.value) return true;
19
+ if (userAppRole.value.permissions.includes("*")) return true;
20
+ return hasPermission(
21
+ userAppRole.value,
22
+ permissions,
23
+ "supply-mgmt",
24
+ "add-supply"
25
+ );
26
+ });
27
+
28
+ const canUpdateSupply = computed(() => {
29
+ if (!userAppRole.value) return true;
30
+ if (userAppRole.value.permissions.includes("*")) return true;
31
+ return hasPermission(
32
+ userAppRole.value,
33
+ permissions,
34
+ "supply-mgmt",
35
+ "update-supply"
36
+ );
37
+ });
38
+
39
+ const canDeleteSupply = computed(() => {
40
+ if (!userAppRole.value) return true;
41
+ if (userAppRole.value.permissions.includes("*")) return true;
42
+ return hasPermission(
43
+ userAppRole.value,
44
+ permissions,
45
+ "supply-mgmt",
46
+ "delete-supply"
47
+ );
48
+ });
49
+
50
+ const canAddStock = computed(() => {
51
+ if (!userAppRole.value) return true;
52
+ if (userAppRole.value.permissions.includes("*")) return true;
53
+ return hasPermission(
54
+ userAppRole.value,
55
+ permissions,
56
+ "supply-mgmt",
57
+ "add-stock"
58
+ );
59
+ });
60
+
61
+ const canViewStock = computed(() => {
62
+ if (!userAppRole.value) return true;
63
+ if (userAppRole.value.permissions.includes("*")) return true;
64
+ return hasPermission(
65
+ userAppRole.value,
66
+ permissions,
67
+ "supply-mgmt",
68
+ "view-stock"
69
+ );
70
+ });
71
+
72
+ const canRequestItem = computed(() => {
73
+ if (!userAppRole.value) return true;
74
+ if (userAppRole.value.permissions.includes("*")) return true;
75
+ return hasPermission(
76
+ userAppRole.value,
77
+ permissions,
78
+ "supply-mgmt",
79
+ "request-item"
80
+ );
81
+ });
82
+
83
+ return {
84
+ canViewSupplies,
85
+ canAddSupply,
86
+ canUpdateSupply,
87
+ canDeleteSupply,
88
+ canAddStock,
89
+ canViewStock,
90
+ canRequestItem,
91
+ };
92
+ }
@@ -0,0 +1,51 @@
1
+ export function useUnitPermission() {
2
+ const { hasPermission } = usePermission();
3
+ const { permissions } = useCleaningPermission();
4
+
5
+ const { userAppRole } = useLocalSetup();
6
+
7
+ const canViewUnits = computed(() => {
8
+ if (!userAppRole.value) return true;
9
+ if (userAppRole.value.permissions.includes("*")) return true;
10
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "see-all-units");
11
+ });
12
+
13
+ const canCreateUnit = computed(() => {
14
+ if (!userAppRole.value) return true;
15
+ if (userAppRole.value.permissions.includes("*")) return true;
16
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "add-unit");
17
+ });
18
+
19
+ const canUpdateUnit = computed(() => {
20
+ if (!userAppRole.value) return true;
21
+ if (userAppRole.value.permissions.includes("*")) return true;
22
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "update-unit");
23
+ });
24
+
25
+ const canViewUnitDetails = computed(() => {
26
+ if (!userAppRole.value) return true;
27
+ if (userAppRole.value.permissions.includes("*")) return true;
28
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "see-unit-details");
29
+ });
30
+
31
+ const canDeleteUnit = computed(() => {
32
+ if (!userAppRole.value) return true;
33
+ if (userAppRole.value.permissions.includes("*")) return true;
34
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "delete-unit");
35
+ });
36
+
37
+ const canImportUnit = computed(() => {
38
+ if (!userAppRole.value) return true;
39
+ if (userAppRole.value.permissions.includes("*")) return true;
40
+ return hasPermission(userAppRole.value, permissions, "unit-mgmt", "import-units");
41
+ });
42
+
43
+ return {
44
+ canViewUnits,
45
+ canCreateUnit,
46
+ canUpdateUnit,
47
+ canViewUnitDetails,
48
+ canDeleteUnit,
49
+ canImportUnit,
50
+ };
51
+ }
@@ -0,0 +1,82 @@
1
+ export default function useUnits() {
2
+ async function getUnits({
3
+ site = "",
4
+ search = "",
5
+ page = 1,
6
+ limit = 10,
7
+ } = {}) {
8
+ return useNuxtApp().$api<Record<string, any>>(
9
+ `/api/hygiene-units/site/${site}`,
10
+ {
11
+ method: "GET",
12
+ query: {
13
+ search,
14
+ page,
15
+ limit,
16
+ },
17
+ },
18
+ );
19
+ }
20
+
21
+ function createUnit(name: string, site: string) {
22
+ return useNuxtApp().$api<Record<string, any>>(
23
+ `/api/hygiene-units/site/${site}`,
24
+ {
25
+ method: "POST",
26
+ body: {
27
+ name,
28
+ },
29
+ },
30
+ );
31
+ }
32
+
33
+ function updateUnit(id: string, name: string) {
34
+ return useNuxtApp().$api<Record<string, any>>(
35
+ `/api/hygiene-units/id/${id}`,
36
+ {
37
+ method: "PATCH",
38
+ body: {
39
+ name,
40
+ },
41
+ },
42
+ );
43
+ }
44
+
45
+ function deleteUnit(id: string) {
46
+ return useNuxtApp().$api<Record<string, any>>(
47
+ `/api/hygiene-units/id/${id}`,
48
+ {
49
+ method: "DELETE",
50
+ },
51
+ );
52
+ }
53
+
54
+ function uploadUnits(file: File, site: string) {
55
+ const formData = new FormData();
56
+ formData.append("file", file);
57
+
58
+ return useNuxtApp().$api<Record<string, any>>(
59
+ `/api/hygiene-units/site/${site}/upload`,
60
+ {
61
+ method: "POST",
62
+ body: formData,
63
+ },
64
+ );
65
+ }
66
+
67
+ function downloadUnits(site: string) {
68
+ return useNuxtApp().$api<Blob>(`/api/hygiene-units/site/${site}/download`, {
69
+ method: "GET",
70
+ responseType: "blob",
71
+ });
72
+ }
73
+
74
+ return {
75
+ getUnits,
76
+ createUnit,
77
+ updateUnit,
78
+ deleteUnit,
79
+ uploadUnits,
80
+ downloadUnits,
81
+ };
82
+ }
@@ -0,0 +1,389 @@
1
+ import QRCode from "qrcode";
2
+
3
+ // Web USB type definitions
4
+ interface USBDevice {
5
+ vendorId: number;
6
+ productId: number;
7
+ manufacturerName?: string;
8
+ productName?: string;
9
+ serialNumber?: string;
10
+ open(): Promise<void>;
11
+ close(): Promise<void>;
12
+ selectConfiguration(configurationValue: number): Promise<void>;
13
+ claimInterface(interfaceNumber: number): Promise<void>;
14
+ releaseInterface(interfaceNumber: number): Promise<void>;
15
+ transferOut(
16
+ endpointNumber: number,
17
+ data: BufferSource,
18
+ ): Promise<USBOutTransferResult>;
19
+ configuration: {
20
+ interfaces: Array<{
21
+ interfaceNumber: number;
22
+ alternates: Array<{
23
+ endpoints: Array<{
24
+ endpointNumber: number;
25
+ direction: "in" | "out";
26
+ }>;
27
+ }>;
28
+ }>;
29
+ };
30
+ }
31
+
32
+ interface USBOutTransferResult {
33
+ status: "ok" | "stall" | "babble";
34
+ bytesWritten: number;
35
+ }
36
+
37
+ declare global {
38
+ interface Navigator {
39
+ usb: {
40
+ getDevices(): Promise<USBDevice[]>;
41
+ requestDevice(options: {
42
+ filters: Array<{ vendorId?: number; productId?: number }>;
43
+ }): Promise<USBDevice>;
44
+ };
45
+ }
46
+ }
47
+
48
+ export default function useWebUsb() {
49
+ const isWebUsbSupported = computed(() => {
50
+ return typeof navigator !== "undefined" && "usb" in navigator;
51
+ });
52
+
53
+ const getAvailableDevices = async () => {
54
+ if (!isWebUsbSupported.value) {
55
+ throw new Error("Web USB is not supported in this browser");
56
+ }
57
+ try {
58
+ const devices = await navigator.usb.getDevices();
59
+ return devices.map((device: USBDevice) => ({
60
+ vendorId: device.vendorId,
61
+ productId: device.productId,
62
+ manufacturerName: device.manufacturerName || "Unknown",
63
+ productName: device.productName || "Unknown",
64
+ serialNumber: device.serialNumber || "Unknown",
65
+ deviceId: `${device.vendorId}:${device.productId}`,
66
+ }));
67
+ } catch (error) {
68
+ console.error("Error getting USB devices:", error);
69
+ throw new Error("Failed to get USB devices");
70
+ }
71
+ };
72
+
73
+ const requestDevice = async () => {
74
+ if (!isWebUsbSupported.value) {
75
+ throw new Error("Web USB is not supported in this browser");
76
+ }
77
+ try {
78
+ const device = await navigator.usb.requestDevice({ filters: [] });
79
+ return {
80
+ vendorId: device.vendorId,
81
+ productId: device.productId,
82
+ manufacturerName: device.manufacturerName || "Unknown",
83
+ productName: device.productName || "Unknown",
84
+ serialNumber: device.serialNumber || "Unknown",
85
+ deviceId: `${device.vendorId}:${device.productId}`,
86
+ };
87
+ } catch (error: any) {
88
+ if (error.name === "NotFoundError") {
89
+ throw new Error("No device selected");
90
+ }
91
+ console.error("Error requesting USB device:", error);
92
+ throw new Error("Failed to access USB device");
93
+ }
94
+ };
95
+
96
+ const connectToDevice = async (vendorId: number, productId: number) => {
97
+ if (!isWebUsbSupported.value) {
98
+ throw new Error("Web USB is not supported in this browser");
99
+ }
100
+ try {
101
+ let devices = await navigator.usb.getDevices();
102
+ let device = devices.find(
103
+ (d: USBDevice) => d.vendorId === vendorId && d.productId === productId,
104
+ );
105
+ if (!device) {
106
+ device = await navigator.usb.requestDevice({
107
+ filters: [{ vendorId, productId }],
108
+ });
109
+ }
110
+ await device.open();
111
+ await device.selectConfiguration(1);
112
+ await device.claimInterface(0);
113
+ return device;
114
+ } catch (error) {
115
+ console.error("Error connecting to USB device:", error);
116
+ throw new Error("Failed to connect to USB device");
117
+ }
118
+ };
119
+
120
+ const testConnection = async (
121
+ vendorId: number,
122
+ productId: number,
123
+ forQr?: boolean,
124
+ urlImage?: string,
125
+ doorLevel?: number | string | null,
126
+ liftLevel?: number | string | null,
127
+ companyName?: string,
128
+ address?: string,
129
+ qrHeader?: string,
130
+ qrSubText?: string,
131
+ ) => {
132
+ if (!isWebUsbSupported.value) {
133
+ throw new Error("Web USB is not supported in this browser");
134
+ }
135
+
136
+ let device: USBDevice | null = null;
137
+ try {
138
+ device = await connectToDevice(vendorId, productId);
139
+
140
+ const deviceInfo = {
141
+ vendorId: device.vendorId,
142
+ productId: device.productId,
143
+ manufacturerName: device.manufacturerName || "Unknown",
144
+ productName: device.productName || "Unknown",
145
+ serialNumber: device.serialNumber || "Unknown",
146
+ };
147
+
148
+ let printTestResult;
149
+ if (forQr && urlImage) {
150
+ printTestResult = await printQrCode(
151
+ device,
152
+ urlImage,
153
+ doorLevel ?? null,
154
+ liftLevel ?? null,
155
+ companyName,
156
+ address,
157
+ qrHeader,
158
+ qrSubText,
159
+ );
160
+ } else {
161
+ printTestResult = await testPrint(device);
162
+ }
163
+
164
+ return {
165
+ success: true,
166
+ deviceInfo,
167
+ printTest: printTestResult,
168
+ message: printTestResult.success
169
+ ? "Connection and print test successful"
170
+ : "Connection successful but print test failed",
171
+ };
172
+ } catch (error: any) {
173
+ return {
174
+ success: false,
175
+ error: error.message,
176
+ message: "Connection failed",
177
+ };
178
+ } finally {
179
+ if (device) {
180
+ try {
181
+ await device.close();
182
+ } catch (closeError) {
183
+ console.error("Error closing device:", closeError);
184
+ }
185
+ }
186
+ }
187
+ };
188
+
189
+ const testPrint = async (device: USBDevice) => {
190
+ try {
191
+ const testCommands = [
192
+ 0x1b, 0x40,
193
+ 0x1b, 0x61, 0x01,
194
+ 0x1b, 0x21, 0x30,
195
+ ...new TextEncoder().encode("TEST PRINT\n"),
196
+ 0x1b, 0x21, 0x00,
197
+ ...new TextEncoder().encode("Printer connection test successful!\n"),
198
+ ...new TextEncoder().encode("Date: " + new Date().toLocaleString() + "\n"),
199
+ 0x1d, 0x56, 0x00,
200
+ 0x0a, 0x0a, 0x0a, 0x0a,
201
+ ];
202
+
203
+ const testData = new Uint8Array(testCommands);
204
+ const usbInterface = device.configuration.interfaces[0];
205
+ const alternate = usbInterface.alternates[0];
206
+ const outputEndpoint = alternate.endpoints.find(
207
+ (ep: any) => ep.direction === "out",
208
+ );
209
+
210
+ if (!outputEndpoint) {
211
+ throw new Error("No output endpoint found");
212
+ }
213
+
214
+ await device.transferOut(outputEndpoint.endpointNumber, testData);
215
+ return { success: true, message: "Test print sent successfully" };
216
+ } catch (error: any) {
217
+ console.error("Test print error:", error);
218
+ return { success: false, error: error.message, message: "Test print failed" };
219
+ }
220
+ };
221
+
222
+ async function createReceiptLayout(
223
+ qrUrlImage: string,
224
+ doorLevel: number | string | null,
225
+ liftLevel: number | string | null,
226
+ companyName?: string,
227
+ address?: string,
228
+ qrHeader?: string,
229
+ qrSubText?: string,
230
+ ): Promise<HTMLCanvasElement> {
231
+ const canvas = document.createElement("canvas");
232
+ canvas.width = 576;
233
+ canvas.height = 620;
234
+ const ctx = canvas.getContext("2d")!;
235
+ ctx.fillStyle = "#fff";
236
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
237
+ ctx.fillStyle = "#000";
238
+ ctx.textAlign = "center";
239
+
240
+ ctx.font = "bold 30px Arial";
241
+ ctx.fillText(qrHeader || "Welcome", canvas.width / 2, 40);
242
+
243
+ ctx.font = "20px Arial";
244
+ ctx.fillText(
245
+ qrSubText || "Proceed to Security Desk to check-out after visit",
246
+ canvas.width / 2,
247
+ 70,
248
+ );
249
+
250
+ const qrSmall = 140;
251
+ const qrCenter = 250;
252
+ const margin = 20;
253
+ const qrCanvas = await QRCode.toCanvas(qrUrlImage, { width: qrCenter });
254
+
255
+ ctx.drawImage(qrCanvas, margin, 90, qrSmall, qrSmall);
256
+ ctx.drawImage(qrCanvas, canvas.width - qrSmall - margin, 90, qrSmall, qrSmall);
257
+ ctx.drawImage(qrCanvas, (canvas.width - qrCenter) / 2, 110, qrCenter, qrCenter);
258
+ ctx.drawImage(qrCanvas, margin, 300, qrSmall, qrSmall);
259
+ ctx.drawImage(qrCanvas, canvas.width - qrSmall - margin, 300, qrSmall, qrSmall);
260
+
261
+ ctx.font = "25px Arial";
262
+ ctx.fillText(companyName || "", canvas.width / 2, 470);
263
+
264
+ ctx.font = "25px Arial";
265
+ ctx.fillText(address || "", canvas.width / 2, 505);
266
+
267
+ const now = new Date();
268
+ const pad = (n: number) => n.toString().padStart(2, "0");
269
+ const dateStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
270
+ const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
271
+
272
+ ctx.font = "20px Arial";
273
+ ctx.fillText(`Date: ${dateStr} ${timeStr}`, canvas.width / 2, 540);
274
+
275
+ let accessText = "Access: ";
276
+ if (doorLevel && liftLevel) {
277
+ accessText += "Door / Elevator";
278
+ } else if (doorLevel) {
279
+ accessText += "Door";
280
+ } else if (liftLevel) {
281
+ accessText += "Elevator";
282
+ } else {
283
+ accessText += "N/A";
284
+ }
285
+ ctx.fillText(accessText, canvas.width / 2, 570);
286
+
287
+ ctx.font = "italic 20px Arial";
288
+ let instructionText = "";
289
+ if (doorLevel && liftLevel) {
290
+ instructionText = `Scan ${doorLevel}. Scan and press Level ${liftLevel}. One time use.`;
291
+ } else if (doorLevel) {
292
+ instructionText = `Scan ${doorLevel}. One time use.`;
293
+ } else if (liftLevel) {
294
+ instructionText = `Scan and press Level ${liftLevel}. One time use.`;
295
+ } else {
296
+ instructionText = "One time use.";
297
+ }
298
+ ctx.fillText(instructionText, canvas.width / 2, 600);
299
+
300
+ return canvas;
301
+ }
302
+
303
+ function canvasToRaster(canvas: HTMLCanvasElement): Uint8Array {
304
+ const ctx = canvas.getContext("2d");
305
+ if (!ctx) throw new Error("Canvas 2D context not available");
306
+
307
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
308
+ const pixels = imageData.data;
309
+ const width = canvas.width;
310
+ const height = canvas.height;
311
+ const rowBytes = Math.ceil(width / 8);
312
+ const bytes: number[] = [];
313
+
314
+ bytes.push(0x1d, 0x76, 0x30, 0x00);
315
+ bytes.push(rowBytes & 0xff, (rowBytes >> 8) & 0xff);
316
+ bytes.push(height & 0xff, (height >> 8) & 0xff);
317
+
318
+ for (let y = 0; y < height; y++) {
319
+ for (let xByte = 0; xByte < rowBytes; xByte++) {
320
+ let byte = 0;
321
+ for (let bit = 0; bit < 8; bit++) {
322
+ const x = xByte * 8 + bit;
323
+ const idx = (y * width + x) * 4;
324
+ if (x < width) {
325
+ const r = pixels[idx];
326
+ const g = pixels[idx + 1];
327
+ const b = pixels[idx + 2];
328
+ const avg = (r + g + b) / 3;
329
+ if (avg < 128) {
330
+ byte |= 0x80 >> bit;
331
+ }
332
+ }
333
+ }
334
+ bytes.push(byte);
335
+ }
336
+ }
337
+
338
+ return new Uint8Array(bytes);
339
+ }
340
+
341
+ const printQrCode = async (
342
+ device: USBDevice,
343
+ urlImage: string,
344
+ doorLevel: number | string | null,
345
+ liftLevel: number | string | null,
346
+ companyName?: string,
347
+ address?: string,
348
+ qrHeader?: string,
349
+ qrSubText?: string,
350
+ ) => {
351
+ try {
352
+ const usbInterface = device.configuration.interfaces[0];
353
+ const alternate = usbInterface.alternates[0];
354
+ const outputEndpoint = alternate.endpoints.find(
355
+ (ep: any) => ep.direction === "out",
356
+ );
357
+ if (!outputEndpoint) throw new Error("No output endpoint found");
358
+
359
+ const canvas = await createReceiptLayout(
360
+ urlImage,
361
+ doorLevel,
362
+ liftLevel,
363
+ companyName,
364
+ address,
365
+ qrHeader,
366
+ qrSubText,
367
+ );
368
+ const rasterData = canvasToRaster(canvas);
369
+
370
+ await device.transferOut(outputEndpoint.endpointNumber, rasterData.buffer);
371
+
372
+ const CUT = new Uint8Array([0x1d, 0x56, 0x41, 0x10]);
373
+ await device.transferOut(outputEndpoint.endpointNumber, CUT.buffer);
374
+
375
+ return { success: true, message: "QR print sent successfully" };
376
+ } catch (error: any) {
377
+ console.error("Print QR code error:", error);
378
+ return { success: false, error: error.message };
379
+ }
380
+ };
381
+
382
+ return {
383
+ isWebUsbSupported,
384
+ getAvailableDevices,
385
+ requestDevice,
386
+ connectToDevice,
387
+ testConnection,
388
+ };
389
+ }
@@ -73,7 +73,7 @@ export default function useWorkOrder() {
73
73
 
74
74
  function deleteWorkOrder(id: string) {
75
75
  return useNuxtApp().$api<Record<string, any>>(`/api/work-orders/deleted/work-order`, {
76
- method: "DELETE",
76
+ method: "PUT",
77
77
  query: { id },
78
78
  });
79
79
  }
package/nuxt.config.ts CHANGED
@@ -32,6 +32,9 @@ export default defineNuxtConfig({
32
32
  APP_PEST_CONTROL: (process.env.APP_PEST_CONTROL as string) ?? "",
33
33
  APP_LANDSCAPING: (process.env.APP_LANDSCAPING as string) ?? "",
34
34
  APP_POOL_MAINTENANCE: (process.env.APP_POOL_MAINTENANCE as string) ?? "",
35
+ ENTRYPASS_ACM_URL:
36
+ (process.env.ENTRYPASS_ACM_URL as string) ??
37
+ "https://entrypass-xsocket.iservice365.app/",
35
38
  },
36
39
  },
37
40