@7365admin1/layer-common 4.97.1-staging.570 → 4.97.1-staging.572

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.
@@ -206,6 +206,13 @@
206
206
  </div>
207
207
  </div>
208
208
  </v-card>
209
+ <!-- How long the pass lasts, from the site's setting; the same words
210
+ as the resident app's invite form. -->
211
+ <div v-if="hidQrUsage" class="text-caption text-primary font-weight-medium mt-2">
212
+ {{ hidQrUsage === "singleUse"
213
+ ? "HID: Single use, expires after the first entry"
214
+ : "HID: Valid all day, until 11:59 pm" }}
215
+ </div>
209
216
  <v-alert
210
217
  v-if="!hidPortalName"
211
218
  type="warning"
@@ -341,6 +348,12 @@ const props = defineProps({
341
348
  type: String,
342
349
  default: "",
343
350
  },
351
+ // The site's HID pass usage, for the hint under the HID QR Code card. Null
352
+ // shows no hint (not loaded, or a caller that does not pass it).
353
+ hidQrUsage: {
354
+ type: String as PropType<"wholeDay" | "singleUse" | null>,
355
+ default: null,
356
+ },
344
357
  excludedCardIds: {
345
358
  type: Array as PropType<string[]>,
346
359
  default: () => [],
@@ -254,6 +254,7 @@
254
254
  :loading="entryPassSettingsPending || siteDataPending" :site-id="prop.site"
255
255
  :unit-id="visitor.unit || null" :visitor-type="prop.type" :hid-qr-code-enabled="hidQrCodePassAllowed"
256
256
  :hid-qr-printer-configured="hasHidQrPrinterConfig"
257
+ :hid-qr-usage="hidQrUsage"
257
258
  :hid-reader-name="hidQrReader?.name || ''"
258
259
  :hid-portal-name="hidQrReader?.portalName || ''"
259
260
  @update:available-qr-count="(val) => availableQrCount = val" />
@@ -622,7 +623,7 @@ import useSiteEntryPassSettings from "../composables/useSiteEntryPassSettings";
622
623
  import useAccessManagement from "../composables/useAccessManagement";
623
624
  import usePeople from "../composables/usePeople";
624
625
  import useHidAmico from "../composables/useHidAmico";
625
- import { minutesUntilEndOfDay } from "../utils/hid-qr-validity";
626
+ import { hidQrUsageFromSettings, minutesUntilEndOfDay } from "../utils/hid-qr-validity";
626
627
 
627
628
  const prop = defineProps({
628
629
  type: {
@@ -659,7 +660,7 @@ type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
659
660
  const currentAutofillSource = ref<AutofillSource>(null);
660
661
 
661
662
  const { requiredRule, debounce, UTCToLocalTIme, maskNRIC } = useUtils();
662
- const { getSiteById } = useSiteSettings();
663
+ const { getSiteById, getOvernightParkingAvailabilityV2 } = useSiteSettings();
663
664
  const { currentUser } = useLocalAuth();
664
665
  const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
665
666
  useBuilding();
@@ -979,6 +980,12 @@ const hidQrCodePassAllowed = computed(() => {
979
980
  );
980
981
  });
981
982
 
983
+ // The site's Whole day / Single usage choice for HID visitor passes, shown
984
+ // under the HID QR Code card as the resident app's invite form shows it.
985
+ // Null until read, and when it cannot be read: then no hint, not a guess.
986
+ // Loaded by the watcher below `siteData`.
987
+ const hidQrUsage = ref<"wholeDay" | "singleUse" | null>(null);
988
+
982
989
  const showEntryPassInformation = computed(() => {
983
990
  return Boolean(
984
991
  entryPassSettings.value?.data?.settings?.nfcPass ||
@@ -1591,6 +1598,23 @@ watch(
1591
1598
  { immediate: true },
1592
1599
  );
1593
1600
 
1601
+ // Below `siteData` for the same reason as the reader watcher above:
1602
+ // `hidQrCodePassAllowed` reads it. Only fetched where HID QR is offered.
1603
+ watch(
1604
+ [hidQrCodePassAllowed, () => prop.site],
1605
+ async ([allowed, site]) => {
1606
+ hidQrUsage.value = null;
1607
+ if (!allowed || !site) return;
1608
+ try {
1609
+ const settings = await getOvernightParkingAvailabilityV2(String(site), "site");
1610
+ hidQrUsage.value = hidQrUsageFromSettings(settings);
1611
+ } catch {
1612
+ hidQrUsage.value = null;
1613
+ }
1614
+ },
1615
+ { immediate: true },
1616
+ );
1617
+
1594
1618
  const {
1595
1619
  data: blocksListData,
1596
1620
  refresh: refreshBlockListData,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.97.1-staging.570",
5
+ "version": "4.97.1-staging.572",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
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.",
@@ -4,6 +4,20 @@
4
4
  * (`minutesUntilEndOfDay` in its VisitorForm.tsx). Never less than 1: the API
5
5
  * refuses a zero validity.
6
6
  */
7
+ /**
8
+ * How long the site's HID visitor pass lasts: the `usage` on the HID entry of
9
+ * `visitorQrCodes` (Site Settings > Resident Visitors). Anything but
10
+ * "singleUse" is the whole visit day - the same reading as core's
11
+ * `getHidVisitorQrUsage` and the resident app's invite form.
12
+ */
13
+ export function hidQrUsageFromSettings(settings: unknown): "wholeDay" | "singleUse" {
14
+ const codes = (settings as { visitorQrCodes?: unknown } | null)?.visitorQrCodes;
15
+ const hid = Array.isArray(codes)
16
+ ? codes.find((code) => (code as { type?: unknown } | null)?.type === "hid")
17
+ : undefined;
18
+ return (hid as { usage?: unknown } | undefined)?.usage === "singleUse" ? "singleUse" : "wholeDay";
19
+ }
20
+
7
21
  export function minutesUntilEndOfDay(now: Date = new Date()) {
8
22
  const midnight = new Date(now);
9
23
  midnight.setHours(24, 0, 0, 0);
@@ -88,6 +88,7 @@ export function offlineReason(robot: Record<string, any>, state: Record<string,
88
88
  const why = state.reason ? ` (${state.reason})` : "";
89
89
  if (robot?.brand === "ounitech") return `${name}: the Ounitech cloud did not answer${why}. Ask Ounitech to link this robot's serial to our account.`;
90
90
  if (robot?.brand === "padbot") return `${name}: the PadBot cloud did not answer${why}. Check the robot's key and token.`;
91
+ if (robot?.brand === "oneway") return `${name}: OneWay's RMS did not answer${why}. Check the robot's API key and server address, and ask OneWay to put this robot under our company key.`;
91
92
  return `${name}: its cloud did not answer${why}.`;
92
93
  }
93
94
  return `${name}: its cloud says it is offline - switched off, charging dock unplugged or out of Wi-Fi.`;
@@ -129,7 +130,7 @@ export function robotSetupSteps(input: TSetupInput): TSetupStep[] {
129
130
  }
130
131
 
131
132
  // 2. Robots added.
132
- const prereq = "Before adding: Ounitech must link each robot's serial to our Seven365 account; for PadBot, save the robot's points (charger, stops) in the PadBot app first.";
133
+ const prereq = "Before adding: Ounitech must link each robot's serial to our Seven365 account; for PadBot, save the robot's points (charger, stops) in the PadBot app first; for OneWay, get our company API key from OneWay and have the robot's routes and charging point set in OneWay's RMS.";
133
134
  if (!robots) {
134
135
  steps.push({ key: "robots", title: "Robots added", status: "check", reason: "Could not read this site's robots.", button: { label: "Open fleet", target: "fleet" } });
135
136
  } else {