@7365admin1/layer-common 4.1.2 → 4.1.3-staging.249
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/components/HidQrCodeConfiguration.vue +86 -564
- package/components/HidReaderManagement.vue +14 -2
- package/components/Input/DatePicker.vue +13 -5
- package/components/Input/DateTimePicker.vue +13 -5
- package/components/VisitorManagement.vue +10 -3
- package/package.json +1 -1
- package/types/site.d.ts +4 -0
|
@@ -21,30 +21,6 @@
|
|
|
21
21
|
<div class="config-body">
|
|
22
22
|
<div class="section-label config-body__title">HID Configuration</div>
|
|
23
23
|
|
|
24
|
-
<div class="form-section">
|
|
25
|
-
<div class="section-label">Visitor QR Reader</div>
|
|
26
|
-
<p class="section-description">
|
|
27
|
-
Select the HID reader and portal used when creating a Visitor QR pass.
|
|
28
|
-
</p>
|
|
29
|
-
<div class="reader-grid">
|
|
30
|
-
<AppSelect
|
|
31
|
-
v-model="form.readerId"
|
|
32
|
-
:items="visitorQrReaderOptions"
|
|
33
|
-
placeholder="Select a HID reader"
|
|
34
|
-
:disabled="Boolean(savingSection) || visitorQrReaderOptions.length === 0"
|
|
35
|
-
/>
|
|
36
|
-
<AppButton
|
|
37
|
-
icon="mdi-content-save-outline"
|
|
38
|
-
:disabled="Boolean(savingSection) || !form.readerId"
|
|
39
|
-
@click="saveSection('reader')"
|
|
40
|
-
>
|
|
41
|
-
Save Reader
|
|
42
|
-
</AppButton>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<v-divider />
|
|
47
|
-
|
|
48
24
|
<div class="form-section identification-section">
|
|
49
25
|
<div class="section-label">Enable/disable reader identification method</div>
|
|
50
26
|
<p class="section-description">
|
|
@@ -137,79 +113,67 @@
|
|
|
137
113
|
<v-divider />
|
|
138
114
|
|
|
139
115
|
<div v-if="isPermissionManager" class="permission-section">
|
|
140
|
-
<div class="permission-
|
|
141
|
-
<div class="
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
</p>
|
|
146
|
-
</div>
|
|
147
|
-
<AppSelect
|
|
148
|
-
v-model="permissionReaderId"
|
|
149
|
-
class="permission-location__select"
|
|
150
|
-
:items="permissionLocations"
|
|
151
|
-
item-title="label"
|
|
152
|
-
item-value="readerId"
|
|
153
|
-
placeholder="Select a Face Reader location"
|
|
154
|
-
:disabled="permissionLocations.length === 0"
|
|
155
|
-
@update:model-value="loadPermissions"
|
|
156
|
-
/>
|
|
116
|
+
<div class="permission-copy">
|
|
117
|
+
<div class="section-label">Visitor QR Access Permission</div>
|
|
118
|
+
<p class="section-description">
|
|
119
|
+
Select the connected HID Reader and Portal gates where Visitor QR codes may be scanned.
|
|
120
|
+
</p>
|
|
157
121
|
</div>
|
|
158
122
|
|
|
159
123
|
<v-alert
|
|
160
|
-
v-if="
|
|
124
|
+
v-if="visitorQrGateOptions.length === 0"
|
|
161
125
|
class="mb-4"
|
|
162
126
|
type="warning"
|
|
163
127
|
variant="tonal"
|
|
164
128
|
density="compact"
|
|
165
129
|
>
|
|
166
|
-
|
|
130
|
+
Connect a HID Reader and select its portal before configuring Visitor QR gates.
|
|
167
131
|
</v-alert>
|
|
168
132
|
|
|
169
|
-
<v-divider />
|
|
170
|
-
|
|
171
133
|
<div class="permission-row">
|
|
172
134
|
<div class="permission-copy">
|
|
173
|
-
<div class="section-label">
|
|
135
|
+
<div class="section-label">Resident QR Gates</div>
|
|
174
136
|
<p class="section-description">
|
|
175
|
-
|
|
137
|
+
QR codes created by a Resident app can be scanned only at these gates.
|
|
176
138
|
</p>
|
|
177
139
|
</div>
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
>
|
|
187
|
-
<span class="permission-count">{{ permissionCount(option.category) }}</span>
|
|
188
|
-
<span>{{ option.label }}</span>
|
|
189
|
-
</AppButton>
|
|
190
|
-
</div>
|
|
140
|
+
<AppSelect
|
|
141
|
+
v-model="form.qrGatePermissions.resident"
|
|
142
|
+
class="permission-location__select"
|
|
143
|
+
:items="visitorQrGateOptions"
|
|
144
|
+
multiple
|
|
145
|
+
placeholder="Select Resident QR gates"
|
|
146
|
+
:disabled="Boolean(savingSection) || visitorQrGateOptions.length === 0"
|
|
147
|
+
/>
|
|
191
148
|
</div>
|
|
192
149
|
|
|
193
150
|
<v-divider />
|
|
194
151
|
|
|
195
|
-
<div class="permission-row
|
|
152
|
+
<div class="permission-row">
|
|
196
153
|
<div class="permission-copy">
|
|
197
|
-
<div class="section-label">
|
|
154
|
+
<div class="section-label">Property Management QR Gates</div>
|
|
198
155
|
<p class="section-description">
|
|
199
|
-
|
|
156
|
+
QR codes created by the Property Management app can be scanned only at these gates.
|
|
200
157
|
</p>
|
|
201
158
|
</div>
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
159
|
+
<AppSelect
|
|
160
|
+
v-model="form.qrGatePermissions.propertyManagement"
|
|
161
|
+
class="permission-location__select"
|
|
162
|
+
:items="visitorQrGateOptions"
|
|
163
|
+
multiple
|
|
164
|
+
placeholder="Select Property Management QR gates"
|
|
165
|
+
:disabled="Boolean(savingSection) || visitorQrGateOptions.length === 0"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div class="permission-actions permission-actions--single mt-4">
|
|
170
|
+
<AppButton
|
|
171
|
+
icon="mdi-content-save-outline"
|
|
172
|
+
:disabled="Boolean(savingSection)"
|
|
173
|
+
@click="saveSection('qr-gates')"
|
|
174
|
+
>
|
|
175
|
+
Save QR Gate Permissions
|
|
176
|
+
</AppButton>
|
|
213
177
|
</div>
|
|
214
178
|
</div>
|
|
215
179
|
</div>
|
|
@@ -254,81 +218,6 @@
|
|
|
254
218
|
</v-card>
|
|
255
219
|
</v-dialog>
|
|
256
220
|
|
|
257
|
-
<v-dialog v-model="permissionDialog" max-width="680" persistent>
|
|
258
|
-
<v-card class="permission-dialog">
|
|
259
|
-
<v-card-title class="permission-dialog__header">
|
|
260
|
-
<div>
|
|
261
|
-
<div class="permission-dialog__title">{{ permissionDialogTitle }}</div>
|
|
262
|
-
<div class="permission-dialog__subtitle">{{ permissionDialogDescription }}</div>
|
|
263
|
-
</div>
|
|
264
|
-
<AppButton
|
|
265
|
-
variant="icon"
|
|
266
|
-
icon="mdi-close"
|
|
267
|
-
aria-label="Close permission dialog"
|
|
268
|
-
@click="closePermissionDialog"
|
|
269
|
-
/>
|
|
270
|
-
</v-card-title>
|
|
271
|
-
|
|
272
|
-
<v-card-text class="permission-dialog__body">
|
|
273
|
-
<AppField
|
|
274
|
-
v-model="permissionSearch"
|
|
275
|
-
search
|
|
276
|
-
placeholder="Search by name or details"
|
|
277
|
-
/>
|
|
278
|
-
|
|
279
|
-
<div class="permission-list-summary" aria-live="polite">
|
|
280
|
-
<span>{{ visiblePermissionCandidates.length }} entries</span>
|
|
281
|
-
<span class="permission-list-summary__selected">
|
|
282
|
-
{{ selectedPermissionIds.size }} selected
|
|
283
|
-
</span>
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
<div class="permission-list" :class="{ 'permission-list--loading': loadingPermissionCandidates }">
|
|
287
|
-
<div v-if="loadingPermissionCandidates" class="permission-empty">
|
|
288
|
-
<v-progress-circular indeterminate class="permission-spinner" size="28" />
|
|
289
|
-
<span>Loading permissions...</span>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
<template v-else-if="visiblePermissionCandidates.length">
|
|
293
|
-
<label
|
|
294
|
-
v-for="candidate in visiblePermissionCandidates"
|
|
295
|
-
:key="`${candidate.category}:${candidate.subjectId}`"
|
|
296
|
-
class="permission-list-item"
|
|
297
|
-
:class="{ 'permission-list-item--selected': selectedPermissionIds.has(candidate.subjectId) }"
|
|
298
|
-
>
|
|
299
|
-
<span class="permission-list-item__content">
|
|
300
|
-
<strong>{{ candidate.name }}</strong>
|
|
301
|
-
<small>{{ candidate.subtitle || permissionCategoryLabel(candidate.category) }}</small>
|
|
302
|
-
</span>
|
|
303
|
-
<span class="permission-list-item__group">{{ permissionCategoryLabel(candidate.category) }}</span>
|
|
304
|
-
<v-checkbox-btn
|
|
305
|
-
class="permission-list-item__checkbox"
|
|
306
|
-
:model-value="selectedPermissionIds.has(candidate.subjectId)"
|
|
307
|
-
:aria-label="`Select ${candidate.name}`"
|
|
308
|
-
@update:model-value="togglePermissionCandidate(candidate.subjectId, Boolean($event))"
|
|
309
|
-
/>
|
|
310
|
-
</label>
|
|
311
|
-
</template>
|
|
312
|
-
|
|
313
|
-
<div v-else class="permission-empty">
|
|
314
|
-
<v-icon icon="mdi-account-search-outline" size="30" />
|
|
315
|
-
<span>{{ permissionEmptyMessage }}</span>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
</v-card-text>
|
|
319
|
-
|
|
320
|
-
<v-card-actions class="permission-dialog__actions">
|
|
321
|
-
<AppButton variant="ghost" @click="closePermissionDialog">Cancel</AppButton>
|
|
322
|
-
<AppButton
|
|
323
|
-
:disabled="loadingPermissionCandidates || savingPermissions"
|
|
324
|
-
@click="savePermissions"
|
|
325
|
-
>
|
|
326
|
-
Save Permissions
|
|
327
|
-
</AppButton>
|
|
328
|
-
</v-card-actions>
|
|
329
|
-
</v-card>
|
|
330
|
-
</v-dialog>
|
|
331
|
-
|
|
332
221
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
333
222
|
</section>
|
|
334
223
|
</template>
|
|
@@ -339,10 +228,14 @@ type HidIdentificationMethod = keyof NonNullable<THidQrCodePassConfig["identific
|
|
|
339
228
|
|
|
340
229
|
type HidQrConfig = Omit<
|
|
341
230
|
THidQrCodePassConfig,
|
|
342
|
-
"identificationMethods" | "onlineMode" | "updatedAt"
|
|
231
|
+
"identificationMethods" | "onlineMode" | "updatedAt" | "qrGatePermissions"
|
|
343
232
|
> & {
|
|
344
233
|
onlineMode: boolean;
|
|
345
234
|
identificationMethods: Record<HidIdentificationMethod, boolean>;
|
|
235
|
+
qrGatePermissions: {
|
|
236
|
+
resident: string[];
|
|
237
|
+
propertyManagement: string[];
|
|
238
|
+
};
|
|
346
239
|
};
|
|
347
240
|
|
|
348
241
|
type HidReaderOption = {
|
|
@@ -350,6 +243,8 @@ type HidReaderOption = {
|
|
|
350
243
|
name?: string;
|
|
351
244
|
deviceId?: string;
|
|
352
245
|
location?: string;
|
|
246
|
+
portalId?: number;
|
|
247
|
+
portalName?: string;
|
|
353
248
|
};
|
|
354
249
|
|
|
355
250
|
type HidReaderListResponse = {
|
|
@@ -374,9 +269,6 @@ const {
|
|
|
374
269
|
getReaders,
|
|
375
270
|
getReaderConfiguration,
|
|
376
271
|
setReaderConfiguration,
|
|
377
|
-
getSitePermissions,
|
|
378
|
-
getPermissionCandidates,
|
|
379
|
-
updateSitePermissions,
|
|
380
272
|
} = useHidAmico();
|
|
381
273
|
const {
|
|
382
274
|
requestDevice: requestPrinterDevice,
|
|
@@ -387,6 +279,10 @@ const defaultConfig: HidQrConfig = {
|
|
|
387
279
|
enabled: false,
|
|
388
280
|
onlineMode: false,
|
|
389
281
|
readerId: "",
|
|
282
|
+
qrGatePermissions: {
|
|
283
|
+
resident: [],
|
|
284
|
+
propertyManagement: [],
|
|
285
|
+
},
|
|
390
286
|
qrFormat: "0",
|
|
391
287
|
identificationMethods: {
|
|
392
288
|
facial: true,
|
|
@@ -416,35 +312,15 @@ const testingPrinter = ref(false);
|
|
|
416
312
|
const message = ref("");
|
|
417
313
|
const messageColor = ref("success");
|
|
418
314
|
const messageSnackbar = ref(false);
|
|
419
|
-
const permissionState = reactive<THidSitePermissions>({
|
|
420
|
-
orgId: props.org,
|
|
421
|
-
site: props.site,
|
|
422
|
-
readerId: "",
|
|
423
|
-
location: "",
|
|
424
|
-
assignments: [],
|
|
425
|
-
counts: {
|
|
426
|
-
resident: 0,
|
|
427
|
-
propertyManagement: 0,
|
|
428
|
-
serviceProvider: 0,
|
|
429
|
-
intercom: 0,
|
|
430
|
-
},
|
|
431
|
-
});
|
|
432
|
-
const permissionDialog = ref(false);
|
|
433
|
-
const permissionReaderId = ref("");
|
|
434
|
-
const permissionDialogCategory = ref<THidPermissionCategory | "intercom">("resident");
|
|
435
|
-
const permissionCandidates = ref<THidPermissionCandidate[]>([]);
|
|
436
|
-
const selectedPermissionIds = ref(new Set<string>());
|
|
437
|
-
const permissionSearch = ref("");
|
|
438
|
-
const loadingPermissionCandidates = ref(false);
|
|
439
|
-
const savingPermissions = ref(false);
|
|
440
|
-
|
|
441
315
|
const hasPrinterConfig = computed(() =>
|
|
442
316
|
Boolean(form.printer.vendorId && form.printer.productId),
|
|
443
317
|
);
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
318
|
+
const visitorQrGateOptions = computed(() => readers.value
|
|
319
|
+
.filter((reader) => Boolean(reader.portalId))
|
|
320
|
+
.map((reader) => ({
|
|
321
|
+
title: String(reader.name || reader.deviceId || reader._id) + " - " + String(reader.portalName || "Portal"),
|
|
322
|
+
value: reader._id,
|
|
323
|
+
})));
|
|
448
324
|
const identificationMethodOptions: Array<{
|
|
449
325
|
key: HidIdentificationMethod;
|
|
450
326
|
label: string;
|
|
@@ -455,37 +331,13 @@ const identificationMethodOptions: Array<{
|
|
|
455
331
|
{ key: "idPassword", label: "ID Password" },
|
|
456
332
|
{ key: "pin", label: "PIN" },
|
|
457
333
|
];
|
|
458
|
-
const accessPermissionOptions: Array<{
|
|
459
|
-
category: THidPermissionCategory;
|
|
460
|
-
label: string;
|
|
461
|
-
}> = [
|
|
462
|
-
{ category: "resident", label: "Resident" },
|
|
463
|
-
{ category: "property_management", label: "Property Management" },
|
|
464
|
-
{ category: "service_provider", label: "Service Provider" },
|
|
465
|
-
];
|
|
466
|
-
|
|
467
|
-
const permissionLocations = computed(() => {
|
|
468
|
-
const seen = new Set<string>();
|
|
469
|
-
return readers.value.reduce<Array<{ readerId: string; location: string; label: string }>>((items, reader) => {
|
|
470
|
-
const location = reader.location?.trim();
|
|
471
|
-
const key = location?.toLocaleLowerCase();
|
|
472
|
-
if (!location || !key || seen.has(key)) return items;
|
|
473
|
-
seen.add(key);
|
|
474
|
-
items.push({
|
|
475
|
-
readerId: reader._id,
|
|
476
|
-
location,
|
|
477
|
-
label: location,
|
|
478
|
-
});
|
|
479
|
-
return items;
|
|
480
|
-
}, []);
|
|
481
|
-
});
|
|
482
|
-
const selectedPermissionLocation = computed(() => (
|
|
483
|
-
permissionLocations.value.find((item) => item.readerId === permissionReaderId.value)?.location
|
|
484
|
-
|| "the selected location"
|
|
485
|
-
));
|
|
486
|
-
|
|
487
334
|
const siteName = computed(() => siteData.value?.name || "Site");
|
|
488
335
|
const siteLabel = computed(() => siteData.value?.code || siteData.value?.block || props.site);
|
|
336
|
+
const primaryQrGateReaderId = computed(() => (
|
|
337
|
+
form.qrGatePermissions.resident[0]
|
|
338
|
+
|| form.qrGatePermissions.propertyManagement[0]
|
|
339
|
+
|| ""
|
|
340
|
+
));
|
|
489
341
|
const validityText = computed(() =>
|
|
490
342
|
form.validityMinutes ? `${form.validityMinutes} minutes` : "Not set",
|
|
491
343
|
);
|
|
@@ -502,30 +354,6 @@ const validityInput = computed({
|
|
|
502
354
|
form.validityMinutes = value === "" ? null : Number(value);
|
|
503
355
|
},
|
|
504
356
|
});
|
|
505
|
-
const visiblePermissionCandidates = computed(() => {
|
|
506
|
-
const search = permissionSearch.value.trim().toLowerCase();
|
|
507
|
-
if (!search) return permissionCandidates.value;
|
|
508
|
-
return permissionCandidates.value.filter((candidate) => (
|
|
509
|
-
candidate.name.toLowerCase().includes(search)
|
|
510
|
-
|| candidate.subtitle?.toLowerCase().includes(search)
|
|
511
|
-
));
|
|
512
|
-
});
|
|
513
|
-
const permissionDialogTitle = computed(() => (
|
|
514
|
-
permissionDialogCategory.value === "intercom"
|
|
515
|
-
? "Intercom Permissions"
|
|
516
|
-
: `${permissionCategoryLabel(permissionDialogCategory.value)} Access Permission`
|
|
517
|
-
));
|
|
518
|
-
const permissionDialogDescription = computed(() => (
|
|
519
|
-
permissionDialogCategory.value === "intercom"
|
|
520
|
-
? `Select from people and providers who already have HID access permission at ${selectedPermissionLocation.value}.`
|
|
521
|
-
: `Select the ${permissionCategoryLabel(permissionDialogCategory.value).toLowerCase()} entries allowed to use HID access at ${selectedPermissionLocation.value}.`
|
|
522
|
-
));
|
|
523
|
-
const permissionEmptyMessage = computed(() => (
|
|
524
|
-
permissionDialogCategory.value === "intercom" && permissionState.assignments.length === 0
|
|
525
|
-
? "Add an access permission before assigning intercom access."
|
|
526
|
-
: "No matching entries found."
|
|
527
|
-
));
|
|
528
|
-
|
|
529
357
|
watch(
|
|
530
358
|
() => [props.org, props.site],
|
|
531
359
|
() => loadConfig(),
|
|
@@ -549,7 +377,20 @@ async function loadConfig() {
|
|
|
549
377
|
// Visitor QR is reader-local. Do not restore a legacy Online Mode value
|
|
550
378
|
// from the site record and accidentally make a scan depend on Monitor.
|
|
551
379
|
onlineMode: false,
|
|
552
|
-
|
|
380
|
+
qrGatePermissions: {
|
|
381
|
+
resident: Array.isArray(config.qrGatePermissions?.resident)
|
|
382
|
+
? config.qrGatePermissions.resident.map(String)
|
|
383
|
+
: [],
|
|
384
|
+
propertyManagement: Array.isArray(config.qrGatePermissions?.propertyManagement)
|
|
385
|
+
? config.qrGatePermissions.propertyManagement.map(String)
|
|
386
|
+
: [],
|
|
387
|
+
},
|
|
388
|
+
readerId: String(
|
|
389
|
+
config.qrGatePermissions?.resident?.[0]
|
|
390
|
+
|| config.qrGatePermissions?.propertyManagement?.[0]
|
|
391
|
+
|| config.readerId
|
|
392
|
+
|| "",
|
|
393
|
+
),
|
|
553
394
|
qrFormat: "0",
|
|
554
395
|
identificationMethods: {
|
|
555
396
|
facial: configuredMethods?.facial ?? true,
|
|
@@ -570,132 +411,7 @@ async function loadConfig() {
|
|
|
570
411
|
validityMinutes: config.validityMinutes ? Number(config.validityMinutes) : null,
|
|
571
412
|
});
|
|
572
413
|
|
|
573
|
-
|
|
574
|
-
|| permissionLocations.value[0]?.readerId
|
|
575
|
-
|| "";
|
|
576
|
-
|
|
577
|
-
await Promise.all([
|
|
578
|
-
loadReaderConfiguration(),
|
|
579
|
-
isPermissionManager.value ? loadPermissions() : Promise.resolve(),
|
|
580
|
-
]);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function loadPermissions() {
|
|
584
|
-
if (!props.org || !props.site || !permissionReaderId.value) {
|
|
585
|
-
Object.assign(permissionState, {
|
|
586
|
-
orgId: props.org,
|
|
587
|
-
site: props.site,
|
|
588
|
-
readerId: "",
|
|
589
|
-
location: "",
|
|
590
|
-
assignments: [],
|
|
591
|
-
counts: { resident: 0, propertyManagement: 0, serviceProvider: 0, intercom: 0 },
|
|
592
|
-
});
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
try {
|
|
596
|
-
const response = await getSitePermissions(props.site, props.org, permissionReaderId.value);
|
|
597
|
-
Object.assign(permissionState, response.data);
|
|
598
|
-
} catch (error: unknown) {
|
|
599
|
-
console.error("Unable to load HID permissions:", errorConverter(error));
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function permissionCategoryLabel(category: THidPermissionCategory) {
|
|
604
|
-
return accessPermissionOptions.find((option) => option.category === category)?.label || "Permission";
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function permissionCount(category: THidPermissionCategory) {
|
|
608
|
-
if (category === "property_management") return permissionState.counts.propertyManagement;
|
|
609
|
-
if (category === "service_provider") return permissionState.counts.serviceProvider;
|
|
610
|
-
return permissionState.counts.resident;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
async function openPermissionDialog(category: THidPermissionCategory | "intercom") {
|
|
614
|
-
if (!props.org || !permissionReaderId.value) return;
|
|
615
|
-
permissionDialogCategory.value = category;
|
|
616
|
-
permissionSearch.value = "";
|
|
617
|
-
permissionCandidates.value = [];
|
|
618
|
-
selectedPermissionIds.value = new Set<string>();
|
|
619
|
-
permissionDialog.value = true;
|
|
620
|
-
loadingPermissionCandidates.value = true;
|
|
621
|
-
|
|
622
|
-
try {
|
|
623
|
-
const response = await getPermissionCandidates(props.site, {
|
|
624
|
-
orgId: props.org,
|
|
625
|
-
readerId: permissionReaderId.value,
|
|
626
|
-
category,
|
|
627
|
-
page: 1,
|
|
628
|
-
limit: 500,
|
|
629
|
-
});
|
|
630
|
-
permissionCandidates.value = response.items ?? response.data?.items ?? [];
|
|
631
|
-
selectedPermissionIds.value = new Set(
|
|
632
|
-
permissionCandidates.value
|
|
633
|
-
.filter((candidate) => category === "intercom" ? candidate.intercom : candidate.selected)
|
|
634
|
-
.map((candidate) => candidate.subjectId),
|
|
635
|
-
);
|
|
636
|
-
} catch (error: unknown) {
|
|
637
|
-
showMessage(errorConverter(error), "error");
|
|
638
|
-
} finally {
|
|
639
|
-
loadingPermissionCandidates.value = false;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function closePermissionDialog() {
|
|
644
|
-
if (savingPermissions.value) return;
|
|
645
|
-
permissionDialog.value = false;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function togglePermissionCandidate(subjectId: string, selected: boolean) {
|
|
649
|
-
const next = new Set(selectedPermissionIds.value);
|
|
650
|
-
if (selected) next.add(subjectId);
|
|
651
|
-
else next.delete(subjectId);
|
|
652
|
-
selectedPermissionIds.value = next;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
async function savePermissions() {
|
|
656
|
-
const category = permissionDialogCategory.value;
|
|
657
|
-
let assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>;
|
|
658
|
-
|
|
659
|
-
if (category === "intercom") {
|
|
660
|
-
assignments = permissionState.assignments.map((assignment) => ({
|
|
661
|
-
subjectId: assignment.subjectId,
|
|
662
|
-
category: assignment.category,
|
|
663
|
-
intercom: selectedPermissionIds.value.has(assignment.subjectId),
|
|
664
|
-
}));
|
|
665
|
-
} else {
|
|
666
|
-
const retained = permissionState.assignments
|
|
667
|
-
.filter((assignment) => assignment.category !== category)
|
|
668
|
-
.map((assignment) => ({
|
|
669
|
-
subjectId: assignment.subjectId,
|
|
670
|
-
category: assignment.category,
|
|
671
|
-
intercom: assignment.intercom,
|
|
672
|
-
}));
|
|
673
|
-
const selected = permissionCandidates.value
|
|
674
|
-
.filter((candidate) => selectedPermissionIds.value.has(candidate.subjectId))
|
|
675
|
-
.map((candidate) => ({
|
|
676
|
-
subjectId: candidate.subjectId,
|
|
677
|
-
category: candidate.category,
|
|
678
|
-
intercom: candidate.intercom,
|
|
679
|
-
}));
|
|
680
|
-
assignments = [...retained, ...selected];
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
savingPermissions.value = true;
|
|
684
|
-
try {
|
|
685
|
-
const response = await updateSitePermissions(
|
|
686
|
-
props.site,
|
|
687
|
-
props.org,
|
|
688
|
-
permissionReaderId.value,
|
|
689
|
-
assignments,
|
|
690
|
-
);
|
|
691
|
-
Object.assign(permissionState, response.data);
|
|
692
|
-
permissionDialog.value = false;
|
|
693
|
-
showMessage("HID permissions updated.", "success");
|
|
694
|
-
} catch (error: unknown) {
|
|
695
|
-
showMessage(errorConverter(error), "error");
|
|
696
|
-
} finally {
|
|
697
|
-
savingPermissions.value = false;
|
|
698
|
-
}
|
|
414
|
+
await loadReaderConfiguration();
|
|
699
415
|
}
|
|
700
416
|
|
|
701
417
|
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -746,7 +462,13 @@ function buildPayload() {
|
|
|
746
462
|
hidQrCodePass: {
|
|
747
463
|
enabled: form.identificationMethods.qrCode,
|
|
748
464
|
onlineMode: false,
|
|
749
|
-
|
|
465
|
+
// Kept only for old background/mobile flows. Gate permissions, not this
|
|
466
|
+
// compatibility value, decide where a new Visitor QR may be scanned.
|
|
467
|
+
readerId: primaryQrGateReaderId.value,
|
|
468
|
+
qrGatePermissions: {
|
|
469
|
+
resident: [...new Set(form.qrGatePermissions.resident)],
|
|
470
|
+
propertyManagement: [...new Set(form.qrGatePermissions.propertyManagement)],
|
|
471
|
+
},
|
|
750
472
|
// Visitor QR passes always use Amico alphanumeric qrcodes.
|
|
751
473
|
qrFormat: "0",
|
|
752
474
|
identificationMethods: { ...form.identificationMethods },
|
|
@@ -768,6 +490,10 @@ function buildPayload() {
|
|
|
768
490
|
|
|
769
491
|
async function saveSection(section: string) {
|
|
770
492
|
if (!props.site) return;
|
|
493
|
+
if (section === "qr-gates" && !primaryQrGateReaderId.value) {
|
|
494
|
+
showMessage("Select at least one Resident or Property Management QR gate.", "error");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
771
497
|
|
|
772
498
|
savingSection.value = section;
|
|
773
499
|
try {
|
|
@@ -1025,10 +751,6 @@ function showMessage(text: string, color: string) {
|
|
|
1025
751
|
padding: 18px 0;
|
|
1026
752
|
}
|
|
1027
753
|
|
|
1028
|
-
.permission-row--intercom {
|
|
1029
|
-
padding-bottom: 4px;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
754
|
.permission-copy {
|
|
1033
755
|
min-width: 0;
|
|
1034
756
|
}
|
|
@@ -1044,190 +766,6 @@ function showMessage(text: string, color: string) {
|
|
|
1044
766
|
justify-content: end;
|
|
1045
767
|
}
|
|
1046
768
|
|
|
1047
|
-
/* A ghost button carrying a count badge. `AppButton --ghost` supplies the
|
|
1048
|
-
height, radius, border and type; only the alignment is local. */
|
|
1049
|
-
.permission-button {
|
|
1050
|
-
justify-content: flex-start;
|
|
1051
|
-
min-height: 42px;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
.permission-count {
|
|
1055
|
-
display: inline-grid;
|
|
1056
|
-
place-items: center;
|
|
1057
|
-
width: 20px;
|
|
1058
|
-
height: 20px;
|
|
1059
|
-
margin-right: 8px;
|
|
1060
|
-
border-radius: 50%;
|
|
1061
|
-
background: var(--accent-soft);
|
|
1062
|
-
color: var(--accent-text);
|
|
1063
|
-
font-size: 11px;
|
|
1064
|
-
font-weight: 700;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
.permission-button:not(:disabled):hover {
|
|
1068
|
-
border-color: var(--accent);
|
|
1069
|
-
color: var(--accent-text);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
.permission-dialog {
|
|
1073
|
-
overflow: hidden;
|
|
1074
|
-
border-radius: var(--r-modal);
|
|
1075
|
-
background: var(--card);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
.permission-dialog__header {
|
|
1079
|
-
display: flex;
|
|
1080
|
-
align-items: flex-start;
|
|
1081
|
-
justify-content: space-between;
|
|
1082
|
-
gap: 16px;
|
|
1083
|
-
padding: 16px 18px 14px;
|
|
1084
|
-
border-bottom: 1px solid var(--border);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
.permission-dialog__title {
|
|
1088
|
-
color: var(--text);
|
|
1089
|
-
font-family: var(--font-sans);
|
|
1090
|
-
font-size: 18px;
|
|
1091
|
-
font-weight: 800;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
.permission-dialog__subtitle {
|
|
1095
|
-
margin-top: 4px;
|
|
1096
|
-
color: var(--muted);
|
|
1097
|
-
font-size: 12px;
|
|
1098
|
-
font-weight: 600;
|
|
1099
|
-
white-space: normal;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
.permission-dialog__body {
|
|
1103
|
-
display: grid;
|
|
1104
|
-
gap: 10px;
|
|
1105
|
-
padding: 14px 18px 16px;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
.permission-list-summary {
|
|
1109
|
-
display: flex;
|
|
1110
|
-
align-items: center;
|
|
1111
|
-
justify-content: space-between;
|
|
1112
|
-
min-height: 22px;
|
|
1113
|
-
color: var(--muted);
|
|
1114
|
-
font-size: 11px;
|
|
1115
|
-
font-weight: 600;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
.permission-list-summary__selected {
|
|
1119
|
-
color: var(--ok);
|
|
1120
|
-
font-weight: 700;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
.permission-list {
|
|
1124
|
-
min-height: 180px;
|
|
1125
|
-
max-height: min(360px, 48vh);
|
|
1126
|
-
overflow-y: auto;
|
|
1127
|
-
border: 1px solid var(--border);
|
|
1128
|
-
border-radius: var(--r-inner);
|
|
1129
|
-
background: var(--card);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
.permission-list--loading {
|
|
1133
|
-
display: grid;
|
|
1134
|
-
place-items: center;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
.permission-list-item {
|
|
1138
|
-
display: grid;
|
|
1139
|
-
grid-template-columns: minmax(0, 1fr) max-content 32px;
|
|
1140
|
-
gap: 12px;
|
|
1141
|
-
align-items: center;
|
|
1142
|
-
min-height: 56px;
|
|
1143
|
-
padding: 7px 12px;
|
|
1144
|
-
border-bottom: 1px solid var(--border);
|
|
1145
|
-
cursor: pointer;
|
|
1146
|
-
transition: background-color var(--motion) ease;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
.permission-list-item:last-child {
|
|
1150
|
-
border-bottom: 0;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
.permission-list-item:hover {
|
|
1154
|
-
background: var(--hover);
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
.permission-list-item--selected,
|
|
1158
|
-
.permission-list-item--selected:hover {
|
|
1159
|
-
background: var(--accent-soft);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
.permission-list-item__content {
|
|
1163
|
-
display: grid;
|
|
1164
|
-
gap: 2px;
|
|
1165
|
-
min-width: 0;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
.permission-list-item__content strong,
|
|
1169
|
-
.permission-list-item__content small {
|
|
1170
|
-
overflow: hidden;
|
|
1171
|
-
text-overflow: ellipsis;
|
|
1172
|
-
white-space: nowrap;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
.permission-list-item__content strong {
|
|
1176
|
-
color: var(--text);
|
|
1177
|
-
font-size: 13px;
|
|
1178
|
-
font-weight: 700;
|
|
1179
|
-
line-height: 1.35;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
.permission-list-item__content small,
|
|
1183
|
-
.permission-list-item__group {
|
|
1184
|
-
color: var(--muted);
|
|
1185
|
-
font-size: 11px;
|
|
1186
|
-
font-weight: 600;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
.permission-list-item__group {
|
|
1190
|
-
max-width: 170px;
|
|
1191
|
-
overflow: hidden;
|
|
1192
|
-
padding: 4px 9px;
|
|
1193
|
-
border-radius: var(--r-tag);
|
|
1194
|
-
background: var(--hover);
|
|
1195
|
-
text-overflow: ellipsis;
|
|
1196
|
-
white-space: nowrap;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
.permission-list-item__checkbox {
|
|
1200
|
-
justify-self: end;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
.permission-list-item__checkbox :deep(.v-selection-control) {
|
|
1204
|
-
min-height: 32px;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
.permission-empty {
|
|
1208
|
-
display: flex;
|
|
1209
|
-
flex-direction: column;
|
|
1210
|
-
align-items: center;
|
|
1211
|
-
justify-content: center;
|
|
1212
|
-
gap: 10px;
|
|
1213
|
-
min-height: 180px;
|
|
1214
|
-
padding: 24px;
|
|
1215
|
-
color: var(--muted);
|
|
1216
|
-
font-size: 13px;
|
|
1217
|
-
text-align: center;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
.permission-spinner {
|
|
1221
|
-
color: var(--accent);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
.permission-dialog__actions {
|
|
1225
|
-
justify-content: flex-end;
|
|
1226
|
-
gap: 8px;
|
|
1227
|
-
padding: 12px 18px;
|
|
1228
|
-
border-top: 1px solid var(--border);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
769
|
/*
|
|
1232
770
|
THE PRINTED PASS - the one thing here that is NOT themed.
|
|
1233
771
|
|
|
@@ -1368,21 +906,5 @@ function showMessage(text: string, color: string) {
|
|
|
1368
906
|
justify-content: stretch;
|
|
1369
907
|
}
|
|
1370
908
|
|
|
1371
|
-
.permission-list-item {
|
|
1372
|
-
grid-template-columns: minmax(0, 1fr) 32px;
|
|
1373
|
-
gap: 8px;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
.permission-list-item__group {
|
|
1377
|
-
grid-column: 1;
|
|
1378
|
-
grid-row: 2;
|
|
1379
|
-
justify-self: start;
|
|
1380
|
-
max-width: 100%;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
.permission-list-item__checkbox {
|
|
1384
|
-
grid-column: 2;
|
|
1385
|
-
grid-row: 1 / span 2;
|
|
1386
|
-
}
|
|
1387
909
|
}
|
|
1388
910
|
</style>
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
<div class="detail-grid">
|
|
123
123
|
<span>HID reader name</span><strong>{{ selectedReader?.name || "N/A" }}</strong>
|
|
124
124
|
<span>Device ID</span><strong>{{ selectedReader?.deviceId || "N/A" }}</strong>
|
|
125
|
+
<span>Firmware</span><strong>{{ selectedReader?.firmwareVersion || "Not reported" }}</strong>
|
|
125
126
|
<span>Location</span><strong>{{ selectedReader?.location || "N/A" }}</strong>
|
|
126
127
|
<span>HID Portal</span><strong>{{ selectedReader?.portalName || selectedReader?.portalId || "N/A" }}</strong>
|
|
127
128
|
<span>Monitor Path</span><strong>{{ selectedReader?.monitorPath || "N/A" }}</strong>
|
|
@@ -573,7 +574,12 @@ async function runConfirmedAction() {
|
|
|
573
574
|
if (pendingAction.value === "test") {
|
|
574
575
|
const response = await testReader(selectedReader.value._id);
|
|
575
576
|
const message = response?.data?.message || response?.message || "Device connection successful.";
|
|
576
|
-
|
|
577
|
+
const firmwareVersion = String(
|
|
578
|
+
response?.data?.firmwareVersion ?? response?.firmwareVersion ?? "",
|
|
579
|
+
).trim();
|
|
580
|
+
await applyReaderSyncState(selectedReader.value._id, "completed", message, {
|
|
581
|
+
...(firmwareVersion ? { firmwareVersion } : {}),
|
|
582
|
+
});
|
|
577
583
|
showResult(true, "Test Device Connection", message);
|
|
578
584
|
}
|
|
579
585
|
if (pendingAction.value === "sync") {
|
|
@@ -622,12 +628,18 @@ async function runConfirmedAction() {
|
|
|
622
628
|
}
|
|
623
629
|
}
|
|
624
630
|
|
|
625
|
-
async function applyReaderSyncState(
|
|
631
|
+
async function applyReaderSyncState(
|
|
632
|
+
readerId: string,
|
|
633
|
+
status: "completed" | "failed",
|
|
634
|
+
message: string,
|
|
635
|
+
extra: Pick<HidRecord, "firmwareVersion"> = {},
|
|
636
|
+
) {
|
|
626
637
|
const lastSyncAt = new Date().toISOString();
|
|
627
638
|
const patch: HidRecord = {
|
|
628
639
|
lastSyncAt,
|
|
629
640
|
lastSyncStatus: status,
|
|
630
641
|
lastSyncMessage: message,
|
|
642
|
+
...extra,
|
|
631
643
|
};
|
|
632
644
|
if (status === "failed") {
|
|
633
645
|
patch.status = "offline";
|
|
@@ -35,9 +35,15 @@ const dateFormattedReadOnly = ref<string | null>(null)
|
|
|
35
35
|
|
|
36
36
|
const dateInput = ref<HTMLInputElement | null>(null)
|
|
37
37
|
const datePickerRef = ref<HTMLInputElement | null>(null)
|
|
38
|
+
let nativeInput: HTMLInputElement | null = null
|
|
38
39
|
|
|
39
40
|
const isInitialLoad = ref(true)
|
|
40
41
|
|
|
42
|
+
function handleNativeInputClick(e: MouseEvent) {
|
|
43
|
+
e.stopPropagation()
|
|
44
|
+
openDatePicker()
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
function clearDate() {
|
|
42
48
|
date.value = null
|
|
43
49
|
dateFormattedReadOnly.value = null
|
|
@@ -88,15 +94,17 @@ watch(date, () => {
|
|
|
88
94
|
onMounted(async () => {
|
|
89
95
|
await nextTick()
|
|
90
96
|
isInitialLoad.value = false
|
|
91
|
-
|
|
97
|
+
nativeInput = (datePickerRef.value as any)?.$el?.querySelector('input')
|
|
92
98
|
if (nativeInput) {
|
|
93
|
-
nativeInput.addEventListener('click',
|
|
94
|
-
e.stopPropagation()
|
|
95
|
-
openDatePicker()
|
|
96
|
-
})
|
|
99
|
+
nativeInput.addEventListener('click', handleNativeInputClick)
|
|
97
100
|
}
|
|
98
101
|
})
|
|
99
102
|
|
|
103
|
+
onBeforeUnmount(() => {
|
|
104
|
+
nativeInput?.removeEventListener('click', handleNativeInputClick)
|
|
105
|
+
nativeInput = null
|
|
106
|
+
})
|
|
107
|
+
|
|
100
108
|
defineExpose({
|
|
101
109
|
validate
|
|
102
110
|
})
|
|
@@ -70,9 +70,15 @@ const inputPlaceholder = computed(
|
|
|
70
70
|
|
|
71
71
|
const dateInput = ref<HTMLInputElement | null>(null);
|
|
72
72
|
const dateTimePickerRef = ref<HTMLInputElement | null>(null);
|
|
73
|
+
let nativeInput: HTMLInputElement | null = null;
|
|
73
74
|
|
|
74
75
|
const isInitialLoad = ref(true);
|
|
75
76
|
|
|
77
|
+
function handleNativeInputClick(e: MouseEvent) {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
openDatePicker();
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
function openDatePicker() {
|
|
77
83
|
setTimeout(() => {
|
|
78
84
|
dateInput.value?.showPicker?.();
|
|
@@ -177,17 +183,19 @@ onMounted(async () => {
|
|
|
177
183
|
await nextTick();
|
|
178
184
|
isInitialLoad.value = false;
|
|
179
185
|
// Wait until Vuetify renders its internal input
|
|
180
|
-
|
|
186
|
+
nativeInput = (dateTimePickerRef.value as any)?.$el?.querySelector(
|
|
181
187
|
"input"
|
|
182
188
|
);
|
|
183
189
|
if (nativeInput) {
|
|
184
|
-
nativeInput.addEventListener("click",
|
|
185
|
-
e.stopPropagation();
|
|
186
|
-
openDatePicker();
|
|
187
|
-
});
|
|
190
|
+
nativeInput.addEventListener("click", handleNativeInputClick);
|
|
188
191
|
}
|
|
189
192
|
});
|
|
190
193
|
|
|
194
|
+
onBeforeUnmount(() => {
|
|
195
|
+
nativeInput?.removeEventListener("click", handleNativeInputClick);
|
|
196
|
+
nativeInput = null;
|
|
197
|
+
});
|
|
198
|
+
|
|
191
199
|
defineExpose({
|
|
192
200
|
validate,
|
|
193
201
|
});
|
|
@@ -1758,9 +1758,14 @@ function closeApproveRejectVisitorDialog() {
|
|
|
1758
1758
|
async function updateVisitorApprovalStatus() {
|
|
1759
1759
|
isApprovingRejectingVisitor.value = true;
|
|
1760
1760
|
try {
|
|
1761
|
-
|
|
1761
|
+
const approval = {
|
|
1762
|
+
approvedBy: currentUser.value?._id,
|
|
1762
1763
|
status: approveRejectVisitorDialog.value.status,
|
|
1763
1764
|
remarks: visitorApproveRejectRemarks.value,
|
|
1765
|
+
approvedAt: new Date(),
|
|
1766
|
+
};
|
|
1767
|
+
await updateVisitor(selectedVisitorForApproval.value._id, {
|
|
1768
|
+
approval,
|
|
1764
1769
|
updatedBy: currentUser.value?._id,
|
|
1765
1770
|
} as any);
|
|
1766
1771
|
showMessage(
|
|
@@ -1891,7 +1896,9 @@ const {
|
|
|
1891
1896
|
// A self-service submission awaiting Approve/Reject (Self-Service
|
|
1892
1897
|
// Auto-Approvals off) lands here as "pending" alongside already
|
|
1893
1898
|
// "approved" guests, rather than being hidden until someone acts on it.
|
|
1894
|
-
|
|
1899
|
+
// "rejected" is included too, so a rejected submission stays visible
|
|
1900
|
+
// on this tab instead of disappearing once acted on.
|
|
1901
|
+
params.status = "approved,pending,rejected";
|
|
1895
1902
|
} else if (activeTab.value === "resident-transactions") {
|
|
1896
1903
|
params.status = "registered";
|
|
1897
1904
|
params.type = "resident,tenant";
|
|
@@ -2429,7 +2436,7 @@ function buildReportParams(): any {
|
|
|
2429
2436
|
// report generated from this tab would silently omit contractors and
|
|
2430
2437
|
// pending rows the screen is actually showing.
|
|
2431
2438
|
params.type = "guest,contractor";
|
|
2432
|
-
params.status = "approved,pending";
|
|
2439
|
+
params.status = "approved,pending,rejected";
|
|
2433
2440
|
} else if (activeTab.value === "resident-transactions") {
|
|
2434
2441
|
params.status = "registered";
|
|
2435
2442
|
params.type = "resident,tenant";
|
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.1.
|
|
5
|
+
"version": "4.1.3-staging.249",
|
|
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.",
|
package/types/site.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ declare type THidQrCodePassConfig = {
|
|
|
40
40
|
enabled: boolean;
|
|
41
41
|
onlineMode?: boolean;
|
|
42
42
|
readerId: string;
|
|
43
|
+
qrGatePermissions?: {
|
|
44
|
+
resident: string[];
|
|
45
|
+
propertyManagement: string[];
|
|
46
|
+
};
|
|
43
47
|
qrFormat: THidQrCodeFormat;
|
|
44
48
|
identificationMethods?: {
|
|
45
49
|
facial: boolean;
|