@7365admin1/layer-common 3.1.4-staging.53 → 3.2.0

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.
@@ -496,79 +496,10 @@
496
496
  </v-card-actions>
497
497
  </v-card>
498
498
  </v-dialog>
499
-
500
- <v-dialog v-model="hidQrPreviewDialog" max-width="440" persistent>
501
- <v-card class="hid-qr-preview-card">
502
- <v-toolbar color="transparent" density="comfortable" class="px-2">
503
- <v-toolbar-title class="text-subtitle-1 font-weight-bold">
504
- Visitor QR Code
505
- </v-toolbar-title>
506
- <v-btn icon="mdi-close" variant="text" title="Close" @click="closeHidQrPreview" />
507
- </v-toolbar>
508
-
509
- <v-card-text class="px-8 pb-6 text-center">
510
- <div class="text-h6 font-weight-bold">
511
- {{ hidQrTemplate.header || "Visitor Access Pass" }}
512
- </div>
513
- <div class="text-body-2 text-medium-emphasis mt-1">
514
- {{ hidQrTemplate.subtext || "Scan at the HID reader for entry" }}
515
- </div>
516
-
517
- <div class="hid-qr-image-shell my-5">
518
- <v-img :src="hidQrPreview.imageUrl" width="280" height="280" alt="Visitor access QR code" />
519
- </div>
520
-
521
- <div class="text-h6 font-weight-bold">{{ visitor.name }}</div>
522
- <div v-if="hidQrPreview.expiresAt" class="text-caption text-medium-emphasis mt-1">
523
- Valid until {{ formatHidQrExpiry(hidQrPreview.expiresAt) }}
524
- </div>
525
-
526
- <v-alert v-if="hidQrPreviewError" type="error" variant="tonal" density="compact" class="mt-4 text-left">
527
- {{ hidQrPreviewError }}
528
- </v-alert>
529
- </v-card-text>
530
-
531
- <v-divider />
532
- <v-card-actions class="pa-0">
533
- <v-btn
534
- variant="text"
535
- height="52"
536
- class="flex-grow-1 text-none"
537
- prepend-icon="mdi-download"
538
- @click="downloadHidQrCode"
539
- >
540
- Download
541
- </v-btn>
542
- <v-btn
543
- v-if="hasHidQrPrinterConfig"
544
- variant="flat"
545
- color="primary-button"
546
- height="52"
547
- class="flex-grow-1 text-none"
548
- prepend-icon="mdi-printer"
549
- :loading="hidQrPrinting"
550
- @click="printHidQrCodePass"
551
- >
552
- Print
553
- </v-btn>
554
- <v-btn
555
- v-else
556
- variant="flat"
557
- color="primary-button"
558
- height="52"
559
- class="flex-grow-1 text-none"
560
- @click="closeHidQrPreview"
561
- >
562
- Close
563
- </v-btn>
564
- </v-card-actions>
565
- </v-card>
566
- </v-dialog>
567
499
  </v-card>
568
500
  </template>
569
501
 
570
502
  <script setup lang="ts">
571
- import QRCode from "qrcode";
572
503
  import { propsFactory } from "vuetify/lib/util/propsFactory.mjs";
573
504
  import useBuildingUnit from "../composables/useBuildingUnit";
574
505
  import useWebUsb from "../composables/useWebUsb";
@@ -580,7 +511,6 @@ import useVisitor from "../composables/useVisitor";
580
511
  import useSiteEntryPassSettings from "../composables/useSiteEntryPassSettings";
581
512
  import useAccessManagement from "../composables/useAccessManagement";
582
513
  import usePeople from "../composables/usePeople";
583
- import useHidAmico from "../composables/useHidAmico";
584
514
 
585
515
  const prop = defineProps({
586
516
  type: {
@@ -632,7 +562,6 @@ const {
632
562
  const { getBySiteId: getEntryPassSettingsBySiteId } =
633
563
  useSiteEntryPassSettings();
634
564
  const { createVisitorPass, signQr } = useAccessManagement();
635
- const { issueVisitorQr } = useHidAmico();
636
565
  const { testConnection } = useWebUsb();
637
566
  const {
638
567
  findPersonByNRIC,
@@ -681,15 +610,6 @@ const hidQrPrinted = ref(false);
681
610
  const isPhoneFieldFocused = ref(false);
682
611
  const isPlateNumberFieldFocused = ref(false);
683
612
  const isNricFieldFocused = ref(false);
684
- const hidQrPrinting = ref(false);
685
- const hidQrPreviewDialog = ref(false);
686
- const hidQrPreviewError = ref("");
687
- const hidQrPendingCompletion = ref<"done" | "done:more" | null>(null);
688
- const hidQrPreview = reactive({
689
- imageUrl: "",
690
- issuedAt: "",
691
- expiresAt: "",
692
- });
693
613
 
694
614
  const registeredUnitCompanyName = ref("N/A");
695
615
 
@@ -790,8 +710,7 @@ const requireNRIC = computed(() => {
790
710
  });
791
711
 
792
712
  const hidQrCodePassConfig = computed(() => {
793
- const site = siteData.value as TSite | null;
794
- return site?.metadata?.hidQrCodePass;
713
+ return (siteData.value as any)?.metadata?.hidQrCodePass || {};
795
714
  });
796
715
 
797
716
  const hidQrCodePassEnabled = computed(() => {
@@ -817,12 +736,6 @@ const hidQrValidityMinutes = computed(() => {
817
736
  return Number.isFinite(value) && value > 0 ? value : 60;
818
737
  });
819
738
 
820
- const hidQrReaderId = computed(() => String(hidQrCodePassConfig.value?.readerId || ""));
821
- const hidQrFormat = computed<"0" | "1" | "2">(() => {
822
- const value = String(hidQrCodePassConfig.value?.qrFormat || "0");
823
- return value === "1" || value === "2" ? value : "0";
824
- });
825
-
826
739
  const hidQrCodePassAllowed = computed(() => {
827
740
  return (
828
741
  hidQrCodePassEnabled.value &&
@@ -1797,7 +1710,7 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1797
1710
  return;
1798
1711
  }
1799
1712
 
1800
- if (passType.value === "HID_QR" && !validateHidQrReaderConfig()) return;
1713
+ if (passType.value === "HID_QR" && !validateHidQrPrinterConfig()) return;
1801
1714
  if (
1802
1715
  prop.mode === "add" &&
1803
1716
  !options.skipNonCheckedOutCheck &&
@@ -1887,10 +1800,11 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1887
1800
 
1888
1801
  if (res) {
1889
1802
  if (passType.value === "HID_QR") {
1890
- const previewReady = await prepareHidQrCodePreview(getVisitorIdFromResponse(res));
1891
- if (!previewReady) return;
1892
- hidQrPendingCompletion.value = createMore.value ? "done:more" : "done";
1893
- return;
1803
+ const printed = await printHidQrCodePass(
1804
+ getVisitorIdFromResponse(res),
1805
+ false
1806
+ );
1807
+ if (!printed) return;
1894
1808
  }
1895
1809
 
1896
1810
  if (
@@ -1923,9 +1837,12 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1923
1837
  });
1924
1838
 
1925
1839
  if (passType.value === "QR") {
1840
+ console.log("[QR Print] passRes:", passRes);
1926
1841
  const accessCards: any[] =
1927
1842
  (passRes as any)?.data?.accessCards ?? [];
1843
+ console.log("[QR Print] accessCards:", accessCards);
1928
1844
  const printer = entryPassSettings.value?.data?.settings?.printer;
1845
+ console.log("[QR Print] printer config:", printer);
1929
1846
  if (!printer?.vendorId || !printer?.productId) {
1930
1847
  console.warn("[QR Print] Aborted: no printer configured.");
1931
1848
  } else if (!accessCards.length) {
@@ -1946,14 +1863,31 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1946
1863
  const address = [blockLabel, levelLabel, unitLabel]
1947
1864
  .filter(Boolean)
1948
1865
  .join("/");
1866
+ console.log(
1867
+ "[QR Print] Printing",
1868
+ accessCards.length,
1869
+ "card(s). vendorId:",
1870
+ vendorId,
1871
+ "productId:",
1872
+ productId,
1873
+ "address:",
1874
+ address
1875
+ );
1949
1876
 
1950
1877
  await nextTick();
1951
1878
  for (const qrCode of accessCards) {
1879
+ console.log("[QR Print] Signing QR for card:", qrCode._id);
1952
1880
  const signed = await signQr({
1953
1881
  cardId: qrCode._id,
1954
1882
  purpose: "vms",
1955
1883
  });
1956
1884
  const qrData = signed?.data;
1885
+ console.log(
1886
+ "[QR Print] signQr result:",
1887
+ signed,
1888
+ "| qrData:",
1889
+ qrData
1890
+ );
1957
1891
  if (!qrData) {
1958
1892
  console.warn(
1959
1893
  "[QR Print] Skipping card",
@@ -2013,23 +1947,17 @@ function parseUsbId(value: unknown) {
2013
1947
  : parseInt(text, 10);
2014
1948
  }
2015
1949
 
2016
- function getVisitorIdFromResponse(response: unknown) {
1950
+ function getVisitorIdFromResponse(response: any) {
2017
1951
  if (typeof response === "string") return response;
2018
- if (!response || typeof response !== "object") return "";
2019
-
2020
- const result = response as Record<string, unknown>;
2021
- const nested = result.data && typeof result.data === "object"
2022
- ? result.data as Record<string, unknown>
2023
- : {};
2024
1952
  return (
2025
- result._id ||
2026
- result.id ||
2027
- nested._id ||
2028
- nested.id ||
2029
- result.insertedId ||
2030
- nested.insertedId ||
1953
+ response?._id ||
1954
+ response?.id ||
1955
+ response?.data?._id ||
1956
+ response?.data?.id ||
1957
+ response?.insertedId ||
1958
+ response?.data?.insertedId ||
2031
1959
  ""
2032
- ).toString();
1960
+ );
2033
1961
  }
2034
1962
 
2035
1963
  function getHidQrPrinterUsbIds() {
@@ -2038,19 +1966,7 @@ function getHidQrPrinterUsbIds() {
2038
1966
  return { vendorId, productId };
2039
1967
  }
2040
1968
 
2041
- function validateHidQrReaderConfig() {
2042
- if (!hidQrReaderId.value) {
2043
- errorMessage.value =
2044
- "No HID reader is configured for QR Code Pass. Please select a reader in Settings.";
2045
- return false;
2046
- }
2047
-
2048
- return true;
2049
- }
2050
-
2051
1969
  function validateHidQrPrinterConfig() {
2052
- if (!validateHidQrReaderConfig()) return false;
2053
-
2054
1970
  if (!hasHidQrPrinterConfig.value) {
2055
1971
  errorMessage.value =
2056
1972
  "No printer is configured on HID settings. Please configure the HID QR Code printer before continuing.";
@@ -2068,6 +1984,42 @@ function validateHidQrPrinterConfig() {
2068
1984
  return true;
2069
1985
  }
2070
1986
 
1987
+ function buildHidQrCodePayload(visitorId = "") {
1988
+ if (hidQrPrintPayload.value) return hidQrPrintPayload.value;
1989
+
1990
+ const issuedAt = new Date();
1991
+ const expiresAt = new Date(
1992
+ issuedAt.getTime() + hidQrValidityMinutes.value * 60 * 1000
1993
+ );
1994
+ const nonce =
1995
+ typeof crypto !== "undefined" && "randomUUID" in crypto
1996
+ ? crypto.randomUUID()
1997
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1998
+
1999
+ hidQrPrintPayload.value = JSON.stringify({
2000
+ purpose: "hid-amico-visitor-access",
2001
+ visitorId,
2002
+ site: prop.site,
2003
+ org: prop.org,
2004
+ visitorType: prop.type,
2005
+ name: visitor.name || "",
2006
+ company: visitor.company || "",
2007
+ contact: visitor.contact || "",
2008
+ nric: visitor.nric || "",
2009
+ plateNumber: visitor.plateNumber || "",
2010
+ block: visitor.block || "",
2011
+ level: visitor.level || "",
2012
+ unit: visitor.unit || "",
2013
+ unitName: visitor.unitName || "",
2014
+ address: buildVisitorAddress(),
2015
+ issuedAt: issuedAt.toISOString(),
2016
+ expiresAt: expiresAt.toISOString(),
2017
+ nonce,
2018
+ });
2019
+
2020
+ return hidQrPrintPayload.value;
2021
+ }
2022
+
2071
2023
  function buildVisitorAddress() {
2072
2024
  const blockLabel =
2073
2025
  blocksArray.value.find((b: any) => b.value === visitor.block)?.title || "";
@@ -2080,58 +2032,20 @@ function buildVisitorAddress() {
2080
2032
  return [blockLabel, levelLabel, unitLabel].filter(Boolean).join("/");
2081
2033
  }
2082
2034
 
2083
- async function prepareHidQrCodePreview(visitorId: string) {
2084
- hidQrPreviewError.value = "";
2085
- if (!visitorId) {
2086
- errorMessage.value = "Unable to identify the visitor for HID QR Code enrollment.";
2087
- return false;
2088
- }
2089
-
2090
- try {
2091
- const response = await issueVisitorQr(hidQrReaderId.value, {
2092
- visitorId,
2093
- validityMinutes: hidQrValidityMinutes.value,
2094
- qrFormat: hidQrFormat.value,
2095
- });
2096
- const credential = "data" in response ? response.data : response;
2097
- hidQrPrintPayload.value = credential.qrValue;
2098
- if (!hidQrPrintPayload.value) {
2099
- errorMessage.value = "HID QR Code enrollment did not return a QR value.";
2100
- return false;
2101
- }
2102
-
2103
- hidQrPreview.imageUrl = await QRCode.toDataURL(hidQrPrintPayload.value, {
2104
- width: 360,
2105
- margin: 2,
2106
- errorCorrectionLevel: "M",
2107
- });
2108
- hidQrPreview.issuedAt = credential.issuedAt || "";
2109
- hidQrPreview.expiresAt = credential.expiresAt || "";
2110
- hidQrPreviewDialog.value = true;
2111
- return true;
2112
- } catch (error: unknown) {
2113
- errorMessage.value = errorConverter(error) || "Unable to generate HID QR Code.";
2114
- return false;
2115
- }
2116
- }
2117
-
2118
- async function printHidQrCodePass() {
2035
+ async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
2119
2036
  if (hidQrPrinted.value) return true;
2120
2037
 
2121
2038
  if (!validateHidQrPrinterConfig()) return false;
2122
2039
 
2123
2040
  const { vendorId, productId } = getHidQrPrinterUsbIds();
2124
2041
 
2125
- hidQrPrinting.value = true;
2126
- hidQrPreviewError.value = "";
2042
+ if (manageProcessing) processing.value = true;
2127
2043
  try {
2128
- if (!hidQrPrintPayload.value) return false;
2129
-
2130
2044
  const printResult = await testConnection(
2131
2045
  vendorId,
2132
2046
  productId,
2133
2047
  true,
2134
- hidQrPrintPayload.value,
2048
+ buildHidQrCodePayload(visitorId),
2135
2049
  null,
2136
2050
  null,
2137
2051
  visitor.company || visitor.name || "",
@@ -2141,7 +2055,7 @@ async function printHidQrCodePass() {
2141
2055
  );
2142
2056
 
2143
2057
  if (!printResult?.success || printResult?.printTest?.success === false) {
2144
- hidQrPreviewError.value =
2058
+ errorMessage.value =
2145
2059
  printResult?.printTest?.error ||
2146
2060
  printResult?.error ||
2147
2061
  "Unable to print HID QR Code. Please check the printer connection.";
@@ -2150,40 +2064,14 @@ async function printHidQrCodePass() {
2150
2064
 
2151
2065
  hidQrPrinted.value = true;
2152
2066
  return true;
2153
- } catch (error: unknown) {
2154
- hidQrPreviewError.value = errorConverter(error) || "Unable to print HID QR Code.";
2067
+ } catch (error: any) {
2068
+ errorMessage.value = error?.message || "Unable to print HID QR Code.";
2155
2069
  return false;
2156
2070
  } finally {
2157
- hidQrPrinting.value = false;
2071
+ if (manageProcessing) processing.value = false;
2158
2072
  }
2159
2073
  }
2160
2074
 
2161
- function formatHidQrExpiry(value: string) {
2162
- const date = new Date(value);
2163
- return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
2164
- }
2165
-
2166
- function downloadHidQrCode() {
2167
- if (!hidQrPreview.imageUrl) return;
2168
- const safeName = String(visitor.name || "visitor")
2169
- .trim()
2170
- .replace(/[^a-z0-9]+/gi, "-")
2171
- .replace(/^-|-$/g, "")
2172
- .toLowerCase();
2173
- const link = document.createElement("a");
2174
- link.href = hidQrPreview.imageUrl;
2175
- link.download = `hid-visitor-qr-${safeName || "visitor"}.png`;
2176
- link.click();
2177
- }
2178
-
2179
- function closeHidQrPreview() {
2180
- hidQrPreviewDialog.value = false;
2181
- const completion = hidQrPendingCompletion.value;
2182
- hidQrPendingCompletion.value = null;
2183
- if (completion === "done:more") emit("done:more");
2184
- else if (completion === "done") emit("done");
2185
- }
2186
-
2187
2075
  watch(
2188
2076
  () => visitor.plateNumber,
2189
2077
  (newVal) => {
@@ -2324,15 +2212,4 @@ onMounted(() => {
2324
2212
  font-size: 0.95rem;
2325
2213
  opacity: 1;
2326
2214
  }
2327
-
2328
- .hid-qr-preview-card {
2329
- border-radius: 6px;
2330
- }
2331
-
2332
- .hid-qr-image-shell {
2333
- display: inline-flex;
2334
- padding: 12px;
2335
- border: 1px solid #dce3e8;
2336
- background: #ffffff;
2337
- }
2338
2215
  </style>
@@ -1601,6 +1601,7 @@ const {
1601
1601
  watch(getVisitorReq, (newData: any) => {
1602
1602
  if (newData) {
1603
1603
  items.value = newData.items ?? [];
1604
+ // items.value = [{ _id: "testid", name: "John Doe", type: "Contractor", company: "ABC Corp", location: "Block A, Level 1, Unit 101", contact: "12345678", plateNumber: "SGX1234A", checkIn: new Date().toISOString(), checkOut: null, status: "registered", visitorPass: [{ _id: "1", keyId: "pass1", prefixAndName: "VIP Pass" }, { _id: "2", keyId: "pass2", prefixAndName: "VIP Pass 2" }], passKeys: [{ _id: "1", keyId: "key2", prefixAndName: "Master Key" }] }];
1604
1605
  pages.value = newData.pages ?? 0;
1605
1606
  pageRange.value = newData?.pageRange ?? "-- - -- of --";
1606
1607
  }
@@ -80,7 +80,7 @@ export function useEquipmentManagementPermission() {
80
80
  userAppRole.value,
81
81
  permissions,
82
82
  "supply-mgmt",
83
- "request-item"
83
+ "checkout-item"
84
84
  );
85
85
  });
86
86
 
@@ -76,7 +76,7 @@ export function useEquipmentPermission() {
76
76
  userAppRole.value,
77
77
  permissions,
78
78
  "supply-mgmt",
79
- "request-item"
79
+ "checkout-item"
80
80
  );
81
81
  });
82
82
 
@@ -28,26 +28,6 @@ type HidIdentityPayload = {
28
28
  metadata?: Record<string, any>;
29
29
  };
30
30
 
31
- type HidVisitorQrData = {
32
- visitorId: string;
33
- readerId: string;
34
- hidUserId: number;
35
- qrValue: string;
36
- qrFormat: "0" | "1" | "2";
37
- issuedAt: string;
38
- expiresAt: string;
39
- };
40
-
41
- type HidVisitorQrResponse = HidVisitorQrData | { data: HidVisitorQrData };
42
-
43
- type HidPermissionListResponse = {
44
- items: THidPermissionCandidate[];
45
- page: number;
46
- pages: number;
47
- pageRange: string;
48
- limit: number;
49
- };
50
-
51
31
  export default function useHidAmico() {
52
32
  const basePath = "/api/access-management/hid";
53
33
 
@@ -98,16 +78,6 @@ export default function useHidAmico() {
98
78
  });
99
79
  }
100
80
 
101
- function issueVisitorQr(
102
- readerId: string,
103
- payload: { visitorId: string; validityMinutes: number; qrFormat: "0" | "1" | "2" },
104
- ) {
105
- return useNuxtApp().$api<HidVisitorQrResponse>(`${basePath}/readers/${readerId}/visitor-qr`, {
106
- method: "POST",
107
- body: payload,
108
- });
109
- }
110
-
111
81
  function getReaderConfiguration(readerId: string, payload: Record<string, string[]> = {}) {
112
82
  return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/configuration`, {
113
83
  method: "POST",
@@ -181,38 +151,6 @@ export default function useHidAmico() {
181
151
  });
182
152
  }
183
153
 
184
- function getSitePermissions(siteId: string) {
185
- return useNuxtApp().$api<{ data: THidSitePermissions }>(
186
- `${basePath}/sites/${siteId}/permissions`,
187
- { method: "GET" },
188
- );
189
- }
190
-
191
- function getPermissionCandidates(
192
- siteId: string,
193
- query: {
194
- category: THidPermissionCategory | "intercom";
195
- search?: string;
196
- page?: number;
197
- limit?: number;
198
- },
199
- ) {
200
- return useNuxtApp().$api<HidPermissionListResponse>(
201
- `${basePath}/sites/${siteId}/permission-candidates`,
202
- { method: "GET", query },
203
- );
204
- }
205
-
206
- function updateSitePermissions(
207
- siteId: string,
208
- assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>,
209
- ) {
210
- return useNuxtApp().$api<{ data: THidSitePermissions; message: string }>(
211
- `${basePath}/sites/${siteId}/permissions`,
212
- { method: "PUT", body: { assignments } },
213
- );
214
- }
215
-
216
154
  return {
217
155
  getReaders,
218
156
  createReader,
@@ -221,7 +159,6 @@ export default function useHidAmico() {
221
159
  testReader,
222
160
  syncReader,
223
161
  runObjectOperation,
224
- issueVisitorQr,
225
162
  getReaderConfiguration,
226
163
  setReaderConfiguration,
227
164
  getLogs,
@@ -233,8 +170,5 @@ export default function useHidAmico() {
233
170
  getIntercomStatus,
234
171
  makeIntercomCall,
235
172
  finalizeIntercomCall,
236
- getSitePermissions,
237
- getPermissionCandidates,
238
- updateSitePermissions,
239
173
  };
240
174
  }
@@ -41,6 +41,13 @@ export default function useHidNavigation() {
41
41
  params: { org, site },
42
42
  },
43
43
  },
44
+ {
45
+ title: "Identity Mapping",
46
+ route: {
47
+ name: "org-site-access-mgmt-identity-mapping",
48
+ params: { org, site },
49
+ },
50
+ },
44
51
  {
45
52
  title: "Access Logs",
46
53
  route: {