@7365admin1/layer-common 4.149.2 → 4.151.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.
- package/CHANGELOG.md +16 -4
- package/components/EntryPassInformation.vue +13 -0
- package/components/HidAccessPermissions.vue +202 -40
- package/components/HidEnabledGate.vue +29 -10
- package/components/HidIntercomManagement.vue +26 -13
- package/components/HidProfileQrCode.vue +6 -2
- package/components/HidQrCodeConfiguration.vue +2 -2
- package/components/HidReaderForm.vue +239 -1
- package/components/HidReaderManagement.vue +26 -8
- package/components/HidReaderUserRoster.vue +3 -1
- package/components/HidServiceSettingsPanel.vue +2 -2
- package/components/HidUserEnrollment.vue +1662 -162
- package/components/OvernightParkingAvailability.vue +123 -11
- package/components/Robot/Detail.vue +23 -4
- package/components/SiteSettings.vue +5 -2
- package/components/VisitorForm.vue +39 -11
- package/composables/useCleaningPermission.ts +11 -0
- package/composables/useHidAccessPermission.ts +176 -81
- package/composables/useHidAmico.ts +37 -0
- package/composables/useHidNavigation.ts +44 -23
- package/composables/useLandscapingPermission.ts +11 -0
- package/composables/useMechanicalElectricalPermission.ts +5 -0
- package/composables/usePestControlPermission.ts +11 -0
- package/composables/usePoolMaintenancePermission.ts +11 -0
- package/composables/usePropertyPermission.ts +31 -7
- package/composables/useSecurityPermission.ts +33 -13
- package/composables/useServiceProvider.ts +9 -4
- package/constants/permissions.ts +73 -21
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +3 -0
- package/types/overnight-parking.d.ts +14 -0
- package/types/site.d.ts +7 -1
- package/utils/hid-enrolment-subject.ts +226 -4
- package/utils/hid-permission-assignments.ts +75 -6
- package/utils/hid-qr-validity.ts +25 -0
- package/utils/hid-reader-audiences.ts +557 -0
- package/utils/hid-registration.ts +24 -0
- package/utils/module-routes.ts +15 -2
- package/utils/nav-sections.ts +1 -1
- package/utils/robot-credential-form.ts +4 -2
- package/utils/robot-display.ts +28 -6
- package/utils/robot-setup.ts +1 -0
|
@@ -6,17 +6,67 @@
|
|
|
6
6
|
>
|
|
7
7
|
<v-row no-gutters>
|
|
8
8
|
<v-col cols="12">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<!-- Which QR code types a resident's visitor invitation can carry. -->
|
|
10
|
+
<template v-for="option in visitorQrCodes" :key="option.type">
|
|
11
|
+
<div class="d-flex align-center justify-space-between py-3">
|
|
12
|
+
<div>
|
|
13
|
+
<div class="text-body-2 font-weight-medium">{{ visitorQrLabels[option.type].title }}</div>
|
|
14
|
+
<p class="text-caption text-medium-emphasis mt-1 mb-0">
|
|
15
|
+
{{ visitorQrLabels[option.type].description }}
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
12
18
|
<v-switch
|
|
13
|
-
v-model="
|
|
14
|
-
hide-details
|
|
19
|
+
v-model="option.isEnabled"
|
|
15
20
|
color="success"
|
|
16
|
-
|
|
21
|
+
inset
|
|
22
|
+
density="compact"
|
|
23
|
+
hide-details
|
|
24
|
+
class="flex-grow-0 ml-4"
|
|
17
25
|
/>
|
|
18
|
-
</
|
|
19
|
-
|
|
26
|
+
</div>
|
|
27
|
+
<!-- How long an HID visitor pass lasts. Only asked once HID is on. -->
|
|
28
|
+
<v-radio-group
|
|
29
|
+
v-if="option.type === 'hid' && option.isEnabled"
|
|
30
|
+
v-model="option.usage"
|
|
31
|
+
inline
|
|
32
|
+
hide-details
|
|
33
|
+
density="compact"
|
|
34
|
+
color="success"
|
|
35
|
+
class="pb-3"
|
|
36
|
+
>
|
|
37
|
+
<v-radio
|
|
38
|
+
v-for="usage in hidQrUsageOptions"
|
|
39
|
+
:key="usage.value"
|
|
40
|
+
:value="usage.value"
|
|
41
|
+
class="mr-6"
|
|
42
|
+
>
|
|
43
|
+
<template #label>
|
|
44
|
+
<div>
|
|
45
|
+
<div class="text-body-2">{{ usage.title }}</div>
|
|
46
|
+
<div class="text-caption text-medium-emphasis">{{ usage.description }}</div>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
</v-radio>
|
|
50
|
+
</v-radio-group>
|
|
51
|
+
<v-divider />
|
|
52
|
+
</template>
|
|
53
|
+
<div class="d-flex align-center justify-space-between py-3">
|
|
54
|
+
<div>
|
|
55
|
+
<div class="text-body-2 font-weight-medium">Allow Opening Barrier via Plate Number?</div>
|
|
56
|
+
<p class="text-caption text-medium-emphasis mt-1 mb-0">
|
|
57
|
+
When enabled, the barrier can be opened for visitors by matching their vehicle plate number.
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<v-switch
|
|
61
|
+
v-model="isAllowPlateNumberOpenBarrier"
|
|
62
|
+
color="success"
|
|
63
|
+
inset
|
|
64
|
+
density="compact"
|
|
65
|
+
hide-details
|
|
66
|
+
class="flex-grow-0 ml-4"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
<v-divider class="mb-4" />
|
|
20
70
|
<v-row
|
|
21
71
|
no-gutters
|
|
22
72
|
class="d-flex align-center ga-2 justify-space-between"
|
|
@@ -220,6 +270,41 @@ const isAllowPlateNumberOpenBarrier = ref(false);
|
|
|
220
270
|
const autoApproveOvernightParking = ref(false);
|
|
221
271
|
const isHydrating = ref(true);
|
|
222
272
|
|
|
273
|
+
const visitorQrLabels: Record<TVisitorQrCodeType, { title: string; description: string }> = {
|
|
274
|
+
hid: {
|
|
275
|
+
title: "HID QR Code Visitor Invitation",
|
|
276
|
+
description: "When enabled, HID QR codes will be generated for visitor invitations instead of standard entry pass QR codes.",
|
|
277
|
+
},
|
|
278
|
+
entryPass: {
|
|
279
|
+
title: "Entry Pass QR Code Visitor Invitation",
|
|
280
|
+
description: "When enabled, entry pass QR codes will be generated for visitor invitations.",
|
|
281
|
+
},
|
|
282
|
+
security: {
|
|
283
|
+
title: "Security QR Code Visitor Invitation",
|
|
284
|
+
description: "When enabled, security QR codes will be generated for visitor invitations.",
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
// Whole day until the end of the visit day, or one entry and then it expires.
|
|
288
|
+
// Sites that saved before this existed have none, and read as whole day.
|
|
289
|
+
const DEFAULT_HID_QR_USAGE: TVisitorQrUsage = "wholeDay";
|
|
290
|
+
const hidQrUsageOptions: { value: TVisitorQrUsage; title: string; description: string }[] = [
|
|
291
|
+
{
|
|
292
|
+
value: "wholeDay",
|
|
293
|
+
title: "Whole day usage",
|
|
294
|
+
description: "The visitor can use the QR code as many times as needed until the end of the visit day.",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
value: "singleUse",
|
|
298
|
+
title: "Single usage",
|
|
299
|
+
description: "The QR code expires after the visitor's first entry.",
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
const visitorQrCodes = ref<TVisitorQrCode[]>([
|
|
303
|
+
{ type: "hid", isEnabled: false, usage: DEFAULT_HID_QR_USAGE },
|
|
304
|
+
{ type: "security", isEnabled: false },
|
|
305
|
+
{ type: "entryPass", isEnabled: false },
|
|
306
|
+
]);
|
|
307
|
+
|
|
223
308
|
const { requiredRule } = useUtils();
|
|
224
309
|
const { generateTimeSlots, generateTimeSlotsFromStart } = useFacilityUtils();
|
|
225
310
|
const { currentUser } = useLocalAuth();
|
|
@@ -290,6 +375,16 @@ watch(
|
|
|
290
375
|
autoApproveOvernightParking.value =
|
|
291
376
|
data?.autoApproveOvernightParking === true ||
|
|
292
377
|
data?.isAutoApproved === true;
|
|
378
|
+
// Set in place so the toggle watcher only fires on a real change.
|
|
379
|
+
visitorQrCodes.value.forEach((option) => {
|
|
380
|
+
const saved = (data?.visitorQrCodes as TVisitorQrCode[] | undefined)?.find(
|
|
381
|
+
(q) => q.type === option.type
|
|
382
|
+
);
|
|
383
|
+
option.isEnabled = saved?.isEnabled === true;
|
|
384
|
+
if (option.type === "hid") {
|
|
385
|
+
option.usage = saved?.usage ?? DEFAULT_HID_QR_USAGE;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
293
388
|
setTimeout(() => {
|
|
294
389
|
isHydrating.value = false;
|
|
295
390
|
}, 500);
|
|
@@ -374,7 +469,7 @@ watch(
|
|
|
374
469
|
isAllowPlateNumberOpenBarrier,
|
|
375
470
|
(newValue, oldValue) => {
|
|
376
471
|
if (oldValue === newValue || isHydrating.value) return;
|
|
377
|
-
handleSave("toggle");
|
|
472
|
+
handleSave("toggle", "Plate number barrier setting updated successfully.");
|
|
378
473
|
},
|
|
379
474
|
{ immediate: false }
|
|
380
475
|
);
|
|
@@ -388,7 +483,19 @@ watch(
|
|
|
388
483
|
{ immediate: false }
|
|
389
484
|
);
|
|
390
485
|
|
|
391
|
-
|
|
486
|
+
watch(
|
|
487
|
+
visitorQrCodes,
|
|
488
|
+
() => {
|
|
489
|
+
if (isHydrating.value) return;
|
|
490
|
+
handleSave("toggle", "Visitor QR settings updated successfully.");
|
|
491
|
+
},
|
|
492
|
+
{ deep: true }
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
async function handleSave(
|
|
496
|
+
action: "toggle" | "save",
|
|
497
|
+
successMessage = "Overnight parking settings updated successfully."
|
|
498
|
+
) {
|
|
392
499
|
if (!isValid.value && action === "save") {
|
|
393
500
|
message.value = "Please fill in all required fields.";
|
|
394
501
|
messageColor.value = "error";
|
|
@@ -403,6 +510,11 @@ async function handleSave(action: "toggle" | "save") {
|
|
|
403
510
|
isAllowPlateNumberOpenBarrier: isAllowPlateNumberOpenBarrier.value,
|
|
404
511
|
autoApproveOvernightParking: autoApproveOvernightParking.value,
|
|
405
512
|
isAutoApproved: autoApproveOvernightParking.value,
|
|
513
|
+
visitorQrCodes: visitorQrCodes.value.map(({ type, isEnabled, usage }) => ({
|
|
514
|
+
type,
|
|
515
|
+
isEnabled,
|
|
516
|
+
...(type === "hid" ? { usage: usage ?? DEFAULT_HID_QR_USAGE } : {}),
|
|
517
|
+
})),
|
|
406
518
|
updatedBy: updatedByValue.value,
|
|
407
519
|
};
|
|
408
520
|
|
|
@@ -414,7 +526,7 @@ async function handleSave(action: "toggle" | "save") {
|
|
|
414
526
|
userType.value
|
|
415
527
|
);
|
|
416
528
|
refreshAvailability();
|
|
417
|
-
message.value =
|
|
529
|
+
message.value = successMessage;
|
|
418
530
|
messageColor.value = "success";
|
|
419
531
|
messageSnackbar.value = true;
|
|
420
532
|
} catch (error) {
|
|
@@ -255,6 +255,11 @@
|
|
|
255
255
|
<v-select v-model="jobOpts.levelFlag" :items="CLEAN_POWER_CHOICES" label="Power" density="compact" hide-details class="mb-2" />
|
|
256
256
|
<v-select v-model="jobOpts.cleanTimes" :items="CLEAN_TIMES_CHOICES" label="Times to clean" density="compact" hide-details class="mb-2" />
|
|
257
257
|
</template>
|
|
258
|
+
<!-- A brand with its own named modes (VIGGO): the robot's list + times. -->
|
|
259
|
+
<template v-if="offered.modes">
|
|
260
|
+
<v-select v-model="jobOpts.cleanModeId" :items="robotModeChoices(robotModes)" label="Cleaning mode" density="compact" hide-details class="mb-2" />
|
|
261
|
+
<v-select v-model="jobOpts.cleanTimes" :items="CLEAN_TIMES_CHOICES" label="Times to clean" density="compact" hide-details class="mb-2" />
|
|
262
|
+
</template>
|
|
258
263
|
<v-select v-if="offered.rounds" v-model="jobOpts.rounds" :items="PATROL_ROUND_CHOICES" label="Rounds" density="compact" hide-details class="mb-2" />
|
|
259
264
|
<div class="hero-dialog__body">{{ confirming.text.body }}</div>
|
|
260
265
|
<div v-if="optionsLine" class="hero-dialog__body">{{ optionsLine }}</div>
|
|
@@ -390,8 +395,10 @@ import {
|
|
|
390
395
|
PATROL_ROUND_CHOICES,
|
|
391
396
|
jobOptionsLine,
|
|
392
397
|
jobOptionsOffered,
|
|
398
|
+
robotModeChoices,
|
|
393
399
|
withJobOptions,
|
|
394
400
|
type TJobOptions,
|
|
401
|
+
type TRobotCleanMode,
|
|
395
402
|
} from "../../utils/robot-display";
|
|
396
403
|
|
|
397
404
|
const props = withDefaults(
|
|
@@ -431,6 +438,7 @@ const {
|
|
|
431
438
|
resolveRobotAlerts,
|
|
432
439
|
getPatrolHistory,
|
|
433
440
|
getSiteFaults,
|
|
441
|
+
getRobotRecords,
|
|
434
442
|
} = useRobots();
|
|
435
443
|
|
|
436
444
|
const record = ref<Record<string, any> | null>(null);
|
|
@@ -824,10 +832,21 @@ function pickRoute(id: unknown) {
|
|
|
824
832
|
}
|
|
825
833
|
/* Job options on a start: reset each time the confirm opens. */
|
|
826
834
|
const jobOpts = ref<TJobOptions>({ ...JOB_OPTION_DEFAULTS });
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
835
|
+
/** The robot's own cleaning modes (VIGGO reports them in its records), read when a start is confirmed. */
|
|
836
|
+
const robotModes = ref<TRobotCleanMode[]>([]);
|
|
837
|
+
const offered = computed(() => jobOptionsOffered(confirming.value?.kind, robotType.value, caps.value, robotModes.value));
|
|
838
|
+
const optionsLine = computed(() => jobOptionsLine(offered.value, jobOpts.value, robotModes.value));
|
|
839
|
+
watch(confirmOpen, async (open) => {
|
|
840
|
+
if (!open) return;
|
|
841
|
+
jobOpts.value = { ...JOB_OPTION_DEFAULTS };
|
|
842
|
+
const kind = confirming.value?.kind;
|
|
843
|
+
if (record.value?.brand !== "viggo" || (kind !== "full" && kind !== "area")) return;
|
|
844
|
+
try {
|
|
845
|
+
const rec = await getRobotRecords(props.robotId);
|
|
846
|
+
robotModes.value = Array.isArray(rec?.cleanModes) ? rec.cleanModes : [];
|
|
847
|
+
} catch {
|
|
848
|
+
robotModes.value = []; // the job still goes, in the robot's default mode
|
|
849
|
+
}
|
|
831
850
|
});
|
|
832
851
|
function confirmSend(stayHere = false) {
|
|
833
852
|
const c = confirming.value;
|
|
@@ -186,10 +186,12 @@
|
|
|
186
186
|
</v-expansion-panel-text>
|
|
187
187
|
</v-expansion-panel>
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
<!-- HID on or off for the site and the HID QR configuration:
|
|
190
|
+
`hid-mgmt:manage-hid-settings` (`useHidAccessPermission`). -->
|
|
191
|
+
<v-expansion-panel v-if="canManageHidSettings">
|
|
190
192
|
<v-expansion-panel-title>
|
|
191
193
|
<v-icon class="mr-2">mdi-face-recognition</v-icon>
|
|
192
|
-
HID
|
|
194
|
+
HID Reader
|
|
193
195
|
</v-expansion-panel-title>
|
|
194
196
|
<v-expansion-panel-text>
|
|
195
197
|
<HidServiceSettingsPanel
|
|
@@ -312,6 +314,7 @@ const { canManageSiteInformation,
|
|
|
312
314
|
canManageVehicleQr,
|
|
313
315
|
canManageSiteSettings: canManageAnySiteSetting,
|
|
314
316
|
} = useSettingsPermission();
|
|
317
|
+
const { canManageHidSettings } = useHidAccessPermission();
|
|
315
318
|
|
|
316
319
|
const { getSiteById, updateSitebyId, updateSiteInformation } =
|
|
317
320
|
useSiteSettings();
|
|
@@ -350,6 +350,7 @@
|
|
|
350
350
|
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
351
351
|
:hid-reader-name="hidQrReader?.name || ''"
|
|
352
352
|
:hid-portal-name="hidQrReader?.portalName || ''"
|
|
353
|
+
:hid-qr-usage="hidQrUsage"
|
|
353
354
|
@update:available-qr-count="(val) => availableQrCount = val" />
|
|
354
355
|
</v-col>
|
|
355
356
|
|
|
@@ -838,6 +839,7 @@ import useLocalAuth from "../composables/useLocalAuth";
|
|
|
838
839
|
import useBuilding from "../composables/useBuilding";
|
|
839
840
|
import useVisitor, { getVisitorFieldSwitches, NRIC_LAST4_HINT, VEHICLE_REQUIRED_MESSAGE } from "../composables/useVisitor";
|
|
840
841
|
import useSiteEntryPassSettings from "../composables/useSiteEntryPassSettings";
|
|
842
|
+
import { hidQrUsageFromSettings, minutesUntilEndOfDay } from "../utils/hid-qr-validity";
|
|
841
843
|
import useAccessManagement from "../composables/useAccessManagement";
|
|
842
844
|
import usePeople from "../composables/usePeople";
|
|
843
845
|
import useHidAmico from "../composables/useHidAmico";
|
|
@@ -912,7 +914,7 @@ const currentAutofillSource = ref<AutofillSource>(null);
|
|
|
912
914
|
|
|
913
915
|
const { requiredRule, debounce, UTCToLocalTIme, maskNRIC, getImage } = useUtils();
|
|
914
916
|
const { addFile } = useFile();
|
|
915
|
-
const { getSiteById, updateSitebyId } = useSiteSettings();
|
|
917
|
+
const { getSiteById, getOvernightParkingAvailabilityV2, updateSitebyId } = useSiteSettings();
|
|
916
918
|
const { currentUser } = useLocalAuth();
|
|
917
919
|
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
918
920
|
useBuilding();
|
|
@@ -927,7 +929,7 @@ const {
|
|
|
927
929
|
const { getBySiteId: getEntryPassSettingsBySiteId } =
|
|
928
930
|
useSiteEntryPassSettings();
|
|
929
931
|
const { createVisitorPass, signQr, visitorCheckout } = useAccessManagement();
|
|
930
|
-
const {
|
|
932
|
+
const { getEnrollmentReaders, issueVisitorQr } = useHidAmico();
|
|
931
933
|
const { testConnection } = useWebUsb();
|
|
932
934
|
const { findVisitorPeopleAnyType, deletePersonRecords } = usePeople();
|
|
933
935
|
const { getPassKeysByPageSearch } = usePassKey();
|
|
@@ -1230,11 +1232,6 @@ const hidQrTemplate = computed(() => {
|
|
|
1230
1232
|
return hidQrCodePassConfig.value?.template || {};
|
|
1231
1233
|
});
|
|
1232
1234
|
|
|
1233
|
-
const hidQrValidityMinutes = computed(() => {
|
|
1234
|
-
const value = Number(hidQrCodePassConfig.value?.validityMinutes);
|
|
1235
|
-
return Number.isFinite(value) && value > 0 ? value : 60;
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
1235
|
const hidQrConfiguredReaderId = computed(() => String(hidQrCodePassConfig.value?.readerId || ""));
|
|
1239
1236
|
type HidQrReaderSummary = {
|
|
1240
1237
|
_id: string;
|
|
@@ -1256,7 +1253,7 @@ const hidQrReaderId = computed(() => (
|
|
|
1256
1253
|
* `hidQrCodePassAllowed`, which is why the switch belongs here and not in each
|
|
1257
1254
|
* of them.
|
|
1258
1255
|
*/
|
|
1259
|
-
const HID_VISITOR_QR_ENABLED =
|
|
1256
|
+
const HID_VISITOR_QR_ENABLED = true;
|
|
1260
1257
|
|
|
1261
1258
|
const hidQrCodePassAllowed = computed(() => {
|
|
1262
1259
|
return (
|
|
@@ -1266,6 +1263,12 @@ const hidQrCodePassAllowed = computed(() => {
|
|
|
1266
1263
|
);
|
|
1267
1264
|
});
|
|
1268
1265
|
|
|
1266
|
+
// The site's Whole day / Single usage choice for HID visitor passes, shown
|
|
1267
|
+
// under the HID QR Code card as the resident app's invite form shows it.
|
|
1268
|
+
// Null until read, and when it cannot be read: then no hint, not a guess.
|
|
1269
|
+
// Loaded by the watcher below `siteData`.
|
|
1270
|
+
const hidQrUsage = ref<"wholeDay" | "singleUse" | null>(null);
|
|
1271
|
+
|
|
1269
1272
|
// The entry pass is a contractor's, with the site's NFC pass switch on, as
|
|
1270
1273
|
// the security mobile app (the web-only HID QR pass, switched off above, keeps
|
|
1271
1274
|
// its own types).
|
|
@@ -2298,8 +2301,14 @@ watch(
|
|
|
2298
2301
|
hidQrReader.value = null;
|
|
2299
2302
|
if (!site) return;
|
|
2300
2303
|
try {
|
|
2301
|
-
|
|
2302
|
-
|
|
2304
|
+
/* The member's list, not the admin one. `POST /readers/:id/visitor-qr`
|
|
2305
|
+
asks for membership plus `hid-mgmt:use-hid-access` (API-core #1772),
|
|
2306
|
+
while `GET /readers` asks for the HID view right, so a member who may
|
|
2307
|
+
issue a visitor pass but holds no admin right got the pass route and
|
|
2308
|
+
not the reader it needs. `enrollment-readers` is the site's enabled
|
|
2309
|
+
readers with a portal, `{ data: { items } }`, for any member. */
|
|
2310
|
+
const response = await getEnrollmentReaders(site);
|
|
2311
|
+
const readers: unknown = response?.data?.items ?? [];
|
|
2303
2312
|
const availableReaders = Array.isArray(readers)
|
|
2304
2313
|
? readers.filter((reader: unknown): reader is HidQrReaderSummary => (
|
|
2305
2314
|
typeof reader === "object"
|
|
@@ -2326,6 +2335,23 @@ watch(
|
|
|
2326
2335
|
{ immediate: true },
|
|
2327
2336
|
);
|
|
2328
2337
|
|
|
2338
|
+
// Below `siteData` for the same reason as the reader watcher above:
|
|
2339
|
+
// `hidQrCodePassAllowed` reads it. Only fetched where HID QR is offered.
|
|
2340
|
+
watch(
|
|
2341
|
+
[hidQrCodePassAllowed, () => prop.site],
|
|
2342
|
+
async ([allowed, site]) => {
|
|
2343
|
+
hidQrUsage.value = null;
|
|
2344
|
+
if (!allowed || !site) return;
|
|
2345
|
+
try {
|
|
2346
|
+
const settings = await getOvernightParkingAvailabilityV2(String(site), "site");
|
|
2347
|
+
hidQrUsage.value = hidQrUsageFromSettings(settings);
|
|
2348
|
+
} catch {
|
|
2349
|
+
hidQrUsage.value = null;
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
{ immediate: true },
|
|
2353
|
+
);
|
|
2354
|
+
|
|
2329
2355
|
const {
|
|
2330
2356
|
data: blocksListData,
|
|
2331
2357
|
refresh: refreshBlockListData,
|
|
@@ -3388,7 +3414,9 @@ async function prepareHidQrCodePreview(visitorId: string) {
|
|
|
3388
3414
|
hidQrVisitorId.value = visitorId;
|
|
3389
3415
|
const response = await issueVisitorQr(hidQrReaderId.value, {
|
|
3390
3416
|
visitorId,
|
|
3391
|
-
|
|
3417
|
+
// The whole visit day, as the resident app issues it. Worked out per
|
|
3418
|
+
// call so a regenerate later in the day still ends at midnight.
|
|
3419
|
+
validityMinutes: minutesUntilEndOfDay(),
|
|
3392
3420
|
});
|
|
3393
3421
|
const credential = "data" in response ? response.data : response;
|
|
3394
3422
|
hidQrPrintPayload.value = credential.qrValue;
|
|
@@ -84,6 +84,17 @@ export default function useCleaningPermission() {
|
|
|
84
84
|
description: "Allows the user to manage and update site settings.",
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
|
+
// HID access, user side only (decision of 2026-10-16). The service apps
|
|
88
|
+
// have no HID admin screens yet, so a role here can be prepared with the
|
|
89
|
+
// one string that lets a member use their own QR code, face, PIN, card and
|
|
90
|
+
// intercom. Mirrored from core `src/utils/hid-access.util.ts`.
|
|
91
|
+
"hid-mgmt": {
|
|
92
|
+
"use-hid-access": {
|
|
93
|
+
check: true,
|
|
94
|
+
description:
|
|
95
|
+
"Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
87
98
|
toilet_locations: {
|
|
88
99
|
"create-toilet-location": {
|
|
89
100
|
check: true,
|