@7365admin1/layer-common 3.2.1-staging.72 → 3.2.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.
- package/CHANGELOG.md +39 -0
- package/components/EntryPassInformation.vue +4 -4
- package/components/FeedbackMain.vue +7 -7
- package/components/HidAccessLogDashboard.vue +44 -119
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidIntercomManagement.vue +201 -808
- package/components/HidQrCodeConfiguration.vue +95 -907
- package/components/HidServiceSettingsPanel.vue +1 -20
- package/components/HidUserEnrollment.vue +81 -392
- package/components/Layout/NavigationDrawer.vue +2 -43
- package/components/NavigationItem.vue +0 -9
- package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
- package/components/OnlineFormConfigurationForm.vue +224 -620
- package/components/OnlineFormFill.vue +8 -36
- package/components/OnlineFormsConfiguration.vue +2 -6
- package/components/QrTemplate/CreditCardLandscape.vue +12 -19
- package/components/QrTemplate/PrintDialog.vue +196 -209
- package/components/SiteSettings.vue +2 -4
- package/components/VisitorForm.vue +78 -201
- package/components/VisitorManagement.vue +2 -1
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +0 -127
- package/composables/useHidNavigation.ts +7 -0
- package/composables/useOnlineFormPages.ts +0 -2
- package/composables/useSiteSettings.ts +0 -50
- package/composables/useWebUsb.ts +37 -127
- package/middleware/member.ts +4 -0
- package/package.json +1 -2
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/plugins/secure-member.client.ts +56 -21
- package/plugins/vuetify.ts +9 -37
- package/types/site.d.ts +0 -71
- package/.changeset/camera-wall-gated-endpoint.md +0 -47
- package/.changeset/camera-wall-plain-english.md +0 -60
- package/.changeset/camera-wall-selection-and-guidance.md +0 -65
- package/.changeset/dark-primary-contrast.md +0 -39
- package/components/CameraWall.vue +0 -1034
- package/components/CameraWallTile.vue +0 -818
- package/components/HidCameraCaptureDialog.vue +0 -199
- package/composables/useSipWebPhone.ts +0 -311
- package/utils/camera-wall.test.ts +0 -571
- package/utils/camera-wall.ts +0 -926
- package/utils/theme.test.ts +0 -201
- package/utils/theme.ts +0 -136
|
@@ -9,55 +9,65 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<div class="config-body">
|
|
12
|
-
<div class="
|
|
12
|
+
<div class="section-head">
|
|
13
13
|
<div>
|
|
14
|
-
<h3>
|
|
15
|
-
<p>
|
|
14
|
+
<h3>HID QR Code Pass</h3>
|
|
15
|
+
<p>Allow users to generate and scan digital QR codes at compatible HID readers for touchless access control.</p>
|
|
16
16
|
</div>
|
|
17
17
|
<v-switch
|
|
18
|
-
v-model="form.
|
|
19
|
-
class="hid-config-switch"
|
|
18
|
+
v-model="form.enabled"
|
|
20
19
|
color="success"
|
|
21
20
|
hide-details
|
|
22
21
|
inset
|
|
23
|
-
:loading="savingSection === '
|
|
24
|
-
:
|
|
25
|
-
|
|
22
|
+
:loading="savingSection === 'enabled'"
|
|
23
|
+
@update:model-value="saveSection('enabled')"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="form-section">
|
|
28
|
+
<div class="section-label">HID Reader</div>
|
|
29
|
+
<v-select
|
|
30
|
+
v-model="form.readerId"
|
|
31
|
+
:items="readerOptions"
|
|
32
|
+
item-title="title"
|
|
33
|
+
item-value="value"
|
|
34
|
+
placeholder="Select HID reader"
|
|
35
|
+
variant="outlined"
|
|
36
|
+
density="compact"
|
|
37
|
+
hide-details
|
|
38
|
+
class="reader-select"
|
|
26
39
|
/>
|
|
27
40
|
</div>
|
|
28
41
|
|
|
29
42
|
<v-divider />
|
|
30
43
|
|
|
31
|
-
<div class="form-section
|
|
32
|
-
<div class="section-label">
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
<div class="form-section">
|
|
45
|
+
<div class="section-label">QR Code Format</div>
|
|
46
|
+
<div class="validity-grid">
|
|
47
|
+
<v-select
|
|
48
|
+
v-model="form.qrFormat"
|
|
49
|
+
:items="qrFormatOptions"
|
|
50
|
+
item-title="title"
|
|
51
|
+
item-value="value"
|
|
52
|
+
placeholder="Select format"
|
|
53
|
+
variant="outlined"
|
|
54
|
+
density="compact"
|
|
55
|
+
hide-details
|
|
56
|
+
/>
|
|
57
|
+
<v-btn
|
|
58
|
+
class="text-none save-button"
|
|
59
|
+
variant="flat"
|
|
60
|
+
prepend-icon="mdi-qrcode-edit"
|
|
61
|
+
height="40"
|
|
62
|
+
:loading="savingSection === 'qr-format'"
|
|
63
|
+
:disabled="!form.readerId"
|
|
64
|
+
@click="saveSection('qr-format')"
|
|
42
65
|
>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
v-model="form.identificationMethods[method.key]"
|
|
46
|
-
class="hid-config-switch"
|
|
47
|
-
color="success"
|
|
48
|
-
hide-details
|
|
49
|
-
inset
|
|
50
|
-
density="compact"
|
|
51
|
-
:loading="savingSection === `method-${method.key}`"
|
|
52
|
-
:disabled="Boolean(savingSection) || !form.onlineMode"
|
|
53
|
-
@update:model-value="saveIdentificationMethod(method.key)"
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
66
|
+
Save Format
|
|
67
|
+
</v-btn>
|
|
56
68
|
</div>
|
|
57
69
|
</div>
|
|
58
70
|
|
|
59
|
-
<v-divider />
|
|
60
|
-
|
|
61
71
|
<div class="form-section">
|
|
62
72
|
<div class="section-label">Printer Setup</div>
|
|
63
73
|
<div class="printer-grid">
|
|
@@ -89,7 +99,6 @@
|
|
|
89
99
|
variant="outlined"
|
|
90
100
|
prepend-icon="mdi-printer-check"
|
|
91
101
|
height="40"
|
|
92
|
-
:loading="testingPrinter"
|
|
93
102
|
:disabled="!hasPrinterConfig"
|
|
94
103
|
@click="testPrint"
|
|
95
104
|
>
|
|
@@ -175,88 +184,6 @@
|
|
|
175
184
|
</v-btn>
|
|
176
185
|
</div>
|
|
177
186
|
</div>
|
|
178
|
-
|
|
179
|
-
<v-divider />
|
|
180
|
-
|
|
181
|
-
<div v-if="isPermissionManager" class="permission-section">
|
|
182
|
-
<div class="permission-location">
|
|
183
|
-
<div class="permission-copy">
|
|
184
|
-
<div class="section-label">Permission Location</div>
|
|
185
|
-
<p class="section-description">
|
|
186
|
-
Permissions are scoped to the location configured on the HID Face Reader.
|
|
187
|
-
</p>
|
|
188
|
-
</div>
|
|
189
|
-
<v-select
|
|
190
|
-
v-model="permissionReaderId"
|
|
191
|
-
class="permission-location__select"
|
|
192
|
-
:items="permissionLocations"
|
|
193
|
-
item-title="label"
|
|
194
|
-
item-value="readerId"
|
|
195
|
-
placeholder="Select a Face Reader location"
|
|
196
|
-
variant="outlined"
|
|
197
|
-
density="compact"
|
|
198
|
-
hide-details
|
|
199
|
-
:disabled="permissionLocations.length === 0"
|
|
200
|
-
@update:model-value="loadPermissions"
|
|
201
|
-
/>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
<v-alert
|
|
205
|
-
v-if="permissionLocations.length === 0"
|
|
206
|
-
class="mb-4"
|
|
207
|
-
type="warning"
|
|
208
|
-
variant="tonal"
|
|
209
|
-
density="compact"
|
|
210
|
-
>
|
|
211
|
-
Add a Location to a Face Reader before assigning HID permissions.
|
|
212
|
-
</v-alert>
|
|
213
|
-
|
|
214
|
-
<v-divider />
|
|
215
|
-
|
|
216
|
-
<div class="permission-row">
|
|
217
|
-
<div class="permission-copy">
|
|
218
|
-
<div class="section-label">Access Permission</div>
|
|
219
|
-
<p class="section-description">
|
|
220
|
-
Select who can use the enabled HID identification methods at this site.
|
|
221
|
-
</p>
|
|
222
|
-
</div>
|
|
223
|
-
<div class="permission-actions">
|
|
224
|
-
<v-btn
|
|
225
|
-
v-for="option in accessPermissionOptions"
|
|
226
|
-
:key="option.category"
|
|
227
|
-
class="permission-button text-none"
|
|
228
|
-
variant="outlined"
|
|
229
|
-
:disabled="!form.onlineMode || !permissionReaderId"
|
|
230
|
-
@click="openPermissionDialog(option.category)"
|
|
231
|
-
>
|
|
232
|
-
<span class="permission-count">{{ permissionCount(option.category) }}</span>
|
|
233
|
-
<span>{{ option.label }}</span>
|
|
234
|
-
</v-btn>
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
<v-divider />
|
|
239
|
-
|
|
240
|
-
<div class="permission-row permission-row--intercom">
|
|
241
|
-
<div class="permission-copy">
|
|
242
|
-
<div class="section-label">Access Intercom</div>
|
|
243
|
-
<p class="section-description">
|
|
244
|
-
Choose which authorized people and providers may use HID intercom access.
|
|
245
|
-
</p>
|
|
246
|
-
</div>
|
|
247
|
-
<div class="permission-actions permission-actions--single">
|
|
248
|
-
<v-btn
|
|
249
|
-
class="permission-button text-none"
|
|
250
|
-
variant="outlined"
|
|
251
|
-
:disabled="!form.onlineMode || !permissionReaderId || permissionState.assignments.length === 0"
|
|
252
|
-
@click="openPermissionDialog('intercom')"
|
|
253
|
-
>
|
|
254
|
-
<span class="permission-count">{{ permissionState.counts.intercom }}</span>
|
|
255
|
-
<span>Intercom Permissions</span>
|
|
256
|
-
</v-btn>
|
|
257
|
-
</div>
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
187
|
</div>
|
|
261
188
|
</v-card>
|
|
262
189
|
|
|
@@ -295,158 +222,37 @@
|
|
|
295
222
|
</v-card>
|
|
296
223
|
</v-dialog>
|
|
297
224
|
|
|
298
|
-
<v-dialog v-model="permissionDialog" max-width="680" persistent>
|
|
299
|
-
<v-card class="permission-dialog">
|
|
300
|
-
<v-card-title class="permission-dialog__header">
|
|
301
|
-
<div>
|
|
302
|
-
<div class="permission-dialog__title">{{ permissionDialogTitle }}</div>
|
|
303
|
-
<div class="permission-dialog__subtitle">{{ permissionDialogDescription }}</div>
|
|
304
|
-
</div>
|
|
305
|
-
<v-btn
|
|
306
|
-
icon="mdi-close"
|
|
307
|
-
variant="text"
|
|
308
|
-
density="comfortable"
|
|
309
|
-
aria-label="Close permission dialog"
|
|
310
|
-
@click="closePermissionDialog"
|
|
311
|
-
/>
|
|
312
|
-
</v-card-title>
|
|
313
|
-
|
|
314
|
-
<v-card-text class="permission-dialog__body">
|
|
315
|
-
<v-text-field
|
|
316
|
-
v-model="permissionSearch"
|
|
317
|
-
class="permission-search"
|
|
318
|
-
prepend-inner-icon="mdi-magnify"
|
|
319
|
-
placeholder="Search by name or details"
|
|
320
|
-
variant="outlined"
|
|
321
|
-
density="compact"
|
|
322
|
-
hide-details
|
|
323
|
-
clearable
|
|
324
|
-
/>
|
|
325
|
-
|
|
326
|
-
<div class="permission-list-summary" aria-live="polite">
|
|
327
|
-
<span>{{ visiblePermissionCandidates.length }} entries</span>
|
|
328
|
-
<span class="permission-list-summary__selected">
|
|
329
|
-
{{ selectedPermissionIds.size }} selected
|
|
330
|
-
</span>
|
|
331
|
-
</div>
|
|
332
|
-
|
|
333
|
-
<div class="permission-list" :class="{ 'permission-list--loading': loadingPermissionCandidates }">
|
|
334
|
-
<div v-if="loadingPermissionCandidates" class="permission-empty">
|
|
335
|
-
<v-progress-circular indeterminate color="primary" size="28" />
|
|
336
|
-
<span>Loading permissions...</span>
|
|
337
|
-
</div>
|
|
338
|
-
|
|
339
|
-
<template v-else-if="visiblePermissionCandidates.length">
|
|
340
|
-
<label
|
|
341
|
-
v-for="candidate in visiblePermissionCandidates"
|
|
342
|
-
:key="`${candidate.category}:${candidate.subjectId}`"
|
|
343
|
-
class="permission-list-item"
|
|
344
|
-
:class="{ 'permission-list-item--selected': selectedPermissionIds.has(candidate.subjectId) }"
|
|
345
|
-
>
|
|
346
|
-
<span class="permission-list-item__content">
|
|
347
|
-
<strong>{{ candidate.name }}</strong>
|
|
348
|
-
<small>{{ candidate.subtitle || permissionCategoryLabel(candidate.category) }}</small>
|
|
349
|
-
</span>
|
|
350
|
-
<span class="permission-list-item__group">{{ permissionCategoryLabel(candidate.category) }}</span>
|
|
351
|
-
<v-checkbox-btn
|
|
352
|
-
class="permission-list-item__checkbox"
|
|
353
|
-
:model-value="selectedPermissionIds.has(candidate.subjectId)"
|
|
354
|
-
:aria-label="`Select ${candidate.name}`"
|
|
355
|
-
color="success"
|
|
356
|
-
@update:model-value="togglePermissionCandidate(candidate.subjectId, Boolean($event))"
|
|
357
|
-
/>
|
|
358
|
-
</label>
|
|
359
|
-
</template>
|
|
360
|
-
|
|
361
|
-
<div v-else class="permission-empty">
|
|
362
|
-
<v-icon icon="mdi-account-search-outline" size="30" />
|
|
363
|
-
<span>{{ permissionEmptyMessage }}</span>
|
|
364
|
-
</div>
|
|
365
|
-
</div>
|
|
366
|
-
</v-card-text>
|
|
367
|
-
|
|
368
|
-
<v-card-actions class="permission-dialog__actions">
|
|
369
|
-
<v-btn class="text-none" variant="text" @click="closePermissionDialog">Cancel</v-btn>
|
|
370
|
-
<v-btn
|
|
371
|
-
class="text-none save-button"
|
|
372
|
-
variant="flat"
|
|
373
|
-
min-width="140"
|
|
374
|
-
:loading="savingPermissions"
|
|
375
|
-
:disabled="loadingPermissionCandidates"
|
|
376
|
-
@click="savePermissions"
|
|
377
|
-
>
|
|
378
|
-
Save Permissions
|
|
379
|
-
</v-btn>
|
|
380
|
-
</v-card-actions>
|
|
381
|
-
</v-card>
|
|
382
|
-
</v-dialog>
|
|
383
|
-
|
|
384
225
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
385
226
|
</section>
|
|
386
227
|
</template>
|
|
387
228
|
|
|
388
229
|
<script setup lang="ts">
|
|
389
|
-
type
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
identificationMethods: Record<HidIdentificationMethod, boolean>;
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
type HidReaderOption = {
|
|
400
|
-
_id: string;
|
|
401
|
-
name?: string;
|
|
402
|
-
deviceId?: string;
|
|
403
|
-
location?: string;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
type HidReaderListResponse = {
|
|
407
|
-
items?: HidReaderOption[];
|
|
408
|
-
readers?: HidReaderOption[];
|
|
409
|
-
data?: {
|
|
410
|
-
items?: HidReaderOption[];
|
|
411
|
-
readers?: HidReaderOption[];
|
|
230
|
+
type HidQrConfig = {
|
|
231
|
+
enabled: boolean;
|
|
232
|
+
readerId: string;
|
|
233
|
+
qrFormat: "0" | "1" | "2";
|
|
234
|
+
printer: {
|
|
235
|
+
vendorId: string;
|
|
236
|
+
productId: string;
|
|
412
237
|
};
|
|
238
|
+
template: {
|
|
239
|
+
header: string;
|
|
240
|
+
subtext: string;
|
|
241
|
+
};
|
|
242
|
+
validityMinutes: number | null;
|
|
413
243
|
};
|
|
414
244
|
|
|
415
245
|
const props = defineProps<{
|
|
416
246
|
site: string;
|
|
417
|
-
org: string;
|
|
418
247
|
}>();
|
|
419
248
|
|
|
420
|
-
const runtimeConfig = useRuntimeConfig();
|
|
421
|
-
const isPermissionManager = computed(() => runtimeConfig.public.APP === "security_agency");
|
|
422
|
-
|
|
423
249
|
const { getSiteById, updateSitebyId } = useSiteSettings();
|
|
424
|
-
const {
|
|
425
|
-
getReaders,
|
|
426
|
-
getReaderConfiguration,
|
|
427
|
-
setReaderConfiguration,
|
|
428
|
-
getSitePermissions,
|
|
429
|
-
getPermissionCandidates,
|
|
430
|
-
updateSitePermissions,
|
|
431
|
-
} = useHidAmico();
|
|
432
|
-
const {
|
|
433
|
-
requestDevice: requestPrinterDevice,
|
|
434
|
-
testConnection: testPrinterConnection,
|
|
435
|
-
} = useWebUsb();
|
|
250
|
+
const { getReaders, setReaderConfiguration } = useHidAmico();
|
|
436
251
|
|
|
437
252
|
const defaultConfig: HidQrConfig = {
|
|
438
253
|
enabled: false,
|
|
439
|
-
onlineMode: false,
|
|
440
254
|
readerId: "",
|
|
441
255
|
qrFormat: "0",
|
|
442
|
-
identificationMethods: {
|
|
443
|
-
facial: true,
|
|
444
|
-
card: true,
|
|
445
|
-
qrCode: false,
|
|
446
|
-
idPassword: true,
|
|
447
|
-
pin: false,
|
|
448
|
-
bluetooth: false,
|
|
449
|
-
},
|
|
450
256
|
printer: {
|
|
451
257
|
vendorId: "",
|
|
452
258
|
productId: "",
|
|
@@ -458,111 +264,36 @@ const defaultConfig: HidQrConfig = {
|
|
|
458
264
|
validityMinutes: null,
|
|
459
265
|
};
|
|
460
266
|
|
|
461
|
-
const siteData = ref<
|
|
462
|
-
const readers = ref<
|
|
267
|
+
const siteData = ref<Record<string, any> | null>(null);
|
|
268
|
+
const readers = ref<Record<string, any>[]>([]);
|
|
463
269
|
const form = reactive<HidQrConfig>(structuredClone(defaultConfig));
|
|
464
270
|
const previewDialog = ref(false);
|
|
465
271
|
const savingSection = ref("");
|
|
466
|
-
const testingPrinter = ref(false);
|
|
467
272
|
const message = ref("");
|
|
468
273
|
const messageColor = ref("success");
|
|
469
274
|
const messageSnackbar = ref(false);
|
|
470
|
-
const permissionState = reactive<THidSitePermissions>({
|
|
471
|
-
orgId: props.org,
|
|
472
|
-
site: props.site,
|
|
473
|
-
readerId: "",
|
|
474
|
-
location: "",
|
|
475
|
-
assignments: [],
|
|
476
|
-
counts: {
|
|
477
|
-
resident: 0,
|
|
478
|
-
propertyManagement: 0,
|
|
479
|
-
serviceProvider: 0,
|
|
480
|
-
intercom: 0,
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
const permissionDialog = ref(false);
|
|
484
|
-
const permissionReaderId = ref("");
|
|
485
|
-
const permissionDialogCategory = ref<THidPermissionCategory | "intercom">("resident");
|
|
486
|
-
const permissionCandidates = ref<THidPermissionCandidate[]>([]);
|
|
487
|
-
const selectedPermissionIds = ref(new Set<string>());
|
|
488
|
-
const permissionSearch = ref("");
|
|
489
|
-
const loadingPermissionCandidates = ref(false);
|
|
490
|
-
const savingPermissions = ref(false);
|
|
491
275
|
|
|
492
276
|
const hasPrinterConfig = computed(() =>
|
|
493
277
|
Boolean(form.printer.vendorId && form.printer.productId),
|
|
494
278
|
);
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
{
|
|
501
|
-
{
|
|
502
|
-
{
|
|
503
|
-
{ key: "pin", label: "PIN" },
|
|
504
|
-
{ key: "bluetooth", label: "Bluetooth" },
|
|
505
|
-
];
|
|
506
|
-
const accessPermissionOptions: Array<{
|
|
507
|
-
category: THidPermissionCategory;
|
|
508
|
-
label: string;
|
|
509
|
-
}> = [
|
|
510
|
-
{ category: "resident", label: "Resident" },
|
|
511
|
-
{ category: "property_management", label: "Property Management" },
|
|
512
|
-
{ category: "service_provider", label: "Service Provider" },
|
|
279
|
+
const readerOptions = computed(() => readers.value.map((reader) => ({
|
|
280
|
+
title: reader.name || reader.deviceId || reader._id,
|
|
281
|
+
value: reader._id,
|
|
282
|
+
})));
|
|
283
|
+
const qrFormatOptions = [
|
|
284
|
+
{ title: "Alphanumeric", value: "0" },
|
|
285
|
+
{ title: "Numeric only", value: "1" },
|
|
286
|
+
{ title: "Hexadecimal numeric", value: "2" },
|
|
513
287
|
];
|
|
514
288
|
|
|
515
|
-
const permissionLocations = computed(() => {
|
|
516
|
-
const seen = new Set<string>();
|
|
517
|
-
return readers.value.reduce<Array<{ readerId: string; location: string; label: string }>>((items, reader) => {
|
|
518
|
-
const location = reader.location?.trim();
|
|
519
|
-
const key = location?.toLocaleLowerCase();
|
|
520
|
-
if (!location || !key || seen.has(key)) return items;
|
|
521
|
-
seen.add(key);
|
|
522
|
-
items.push({
|
|
523
|
-
readerId: reader._id,
|
|
524
|
-
location,
|
|
525
|
-
label: location,
|
|
526
|
-
});
|
|
527
|
-
return items;
|
|
528
|
-
}, []);
|
|
529
|
-
});
|
|
530
|
-
const selectedPermissionLocation = computed(() => (
|
|
531
|
-
permissionLocations.value.find((item) => item.readerId === permissionReaderId.value)?.location
|
|
532
|
-
|| "the selected location"
|
|
533
|
-
));
|
|
534
|
-
|
|
535
289
|
const siteName = computed(() => siteData.value?.name || "Site");
|
|
536
290
|
const siteLabel = computed(() => siteData.value?.code || siteData.value?.block || props.site);
|
|
537
291
|
const validityText = computed(() =>
|
|
538
292
|
form.validityMinutes ? `${form.validityMinutes} minutes` : "Not set",
|
|
539
293
|
);
|
|
540
|
-
const visiblePermissionCandidates = computed(() => {
|
|
541
|
-
const search = permissionSearch.value.trim().toLowerCase();
|
|
542
|
-
if (!search) return permissionCandidates.value;
|
|
543
|
-
return permissionCandidates.value.filter((candidate) => (
|
|
544
|
-
candidate.name.toLowerCase().includes(search)
|
|
545
|
-
|| candidate.subtitle?.toLowerCase().includes(search)
|
|
546
|
-
));
|
|
547
|
-
});
|
|
548
|
-
const permissionDialogTitle = computed(() => (
|
|
549
|
-
permissionDialogCategory.value === "intercom"
|
|
550
|
-
? "Intercom Permissions"
|
|
551
|
-
: `${permissionCategoryLabel(permissionDialogCategory.value)} Access Permission`
|
|
552
|
-
));
|
|
553
|
-
const permissionDialogDescription = computed(() => (
|
|
554
|
-
permissionDialogCategory.value === "intercom"
|
|
555
|
-
? `Select from people and providers who already have HID access permission at ${selectedPermissionLocation.value}.`
|
|
556
|
-
: `Select the ${permissionCategoryLabel(permissionDialogCategory.value).toLowerCase()} entries allowed to use HID access at ${selectedPermissionLocation.value}.`
|
|
557
|
-
));
|
|
558
|
-
const permissionEmptyMessage = computed(() => (
|
|
559
|
-
permissionDialogCategory.value === "intercom" && permissionState.assignments.length === 0
|
|
560
|
-
? "Add an access permission before assigning intercom access."
|
|
561
|
-
: "No matching entries found."
|
|
562
|
-
));
|
|
563
294
|
|
|
564
295
|
watch(
|
|
565
|
-
() =>
|
|
296
|
+
() => props.site,
|
|
566
297
|
() => loadConfig(),
|
|
567
298
|
{ immediate: true },
|
|
568
299
|
);
|
|
@@ -574,24 +305,13 @@ async function loadConfig() {
|
|
|
574
305
|
getSiteById(props.site),
|
|
575
306
|
getReaders({ site: props.site, page: 1, limit: 100 }),
|
|
576
307
|
]);
|
|
577
|
-
siteData.value = site
|
|
578
|
-
|
|
579
|
-
readers.value = readerList.items ?? readerList.data?.items ?? readerList.data?.readers ?? [];
|
|
308
|
+
siteData.value = site;
|
|
309
|
+
readers.value = readerResponse?.items ?? readerResponse?.data?.items ?? readerResponse?.data?.readers ?? [];
|
|
580
310
|
const config = siteData.value?.metadata?.hidQrCodePass || {};
|
|
581
|
-
const configuredMethods = config.identificationMethods;
|
|
582
311
|
Object.assign(form, {
|
|
583
312
|
enabled: Boolean(config.enabled),
|
|
584
|
-
onlineMode: Boolean(config.onlineMode),
|
|
585
313
|
readerId: String(config.readerId || readers.value[0]?._id || ""),
|
|
586
314
|
qrFormat: ["0", "1", "2"].includes(String(config.qrFormat)) ? String(config.qrFormat) as "0" | "1" | "2" : "0",
|
|
587
|
-
identificationMethods: {
|
|
588
|
-
facial: configuredMethods?.facial ?? true,
|
|
589
|
-
card: configuredMethods?.card ?? true,
|
|
590
|
-
qrCode: configuredMethods?.qrCode ?? Boolean(config.enabled),
|
|
591
|
-
idPassword: configuredMethods?.idPassword ?? true,
|
|
592
|
-
pin: configuredMethods?.pin ?? false,
|
|
593
|
-
bluetooth: configuredMethods?.bluetooth ?? false,
|
|
594
|
-
},
|
|
595
315
|
printer: {
|
|
596
316
|
vendorId: String(config.printer?.vendorId || ""),
|
|
597
317
|
productId: String(config.printer?.productId || ""),
|
|
@@ -602,175 +322,15 @@ async function loadConfig() {
|
|
|
602
322
|
},
|
|
603
323
|
validityMinutes: config.validityMinutes ? Number(config.validityMinutes) : null,
|
|
604
324
|
});
|
|
605
|
-
|
|
606
|
-
permissionReaderId.value = permissionLocations.value.find((item) => item.readerId === form.readerId)?.readerId
|
|
607
|
-
|| permissionLocations.value[0]?.readerId
|
|
608
|
-
|| "";
|
|
609
|
-
|
|
610
|
-
await Promise.all([
|
|
611
|
-
loadReaderConfiguration(),
|
|
612
|
-
isPermissionManager.value ? loadPermissions() : Promise.resolve(),
|
|
613
|
-
]);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async function loadPermissions() {
|
|
617
|
-
if (!props.org || !props.site || !permissionReaderId.value) {
|
|
618
|
-
Object.assign(permissionState, {
|
|
619
|
-
orgId: props.org,
|
|
620
|
-
site: props.site,
|
|
621
|
-
readerId: "",
|
|
622
|
-
location: "",
|
|
623
|
-
assignments: [],
|
|
624
|
-
counts: { resident: 0, propertyManagement: 0, serviceProvider: 0, intercom: 0 },
|
|
625
|
-
});
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
try {
|
|
629
|
-
const response = await getSitePermissions(props.site, props.org, permissionReaderId.value);
|
|
630
|
-
Object.assign(permissionState, response.data);
|
|
631
|
-
} catch (error: unknown) {
|
|
632
|
-
console.error("Unable to load HID permissions:", errorConverter(error));
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function permissionCategoryLabel(category: THidPermissionCategory) {
|
|
637
|
-
return accessPermissionOptions.find((option) => option.category === category)?.label || "Permission";
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function permissionCount(category: THidPermissionCategory) {
|
|
641
|
-
if (category === "property_management") return permissionState.counts.propertyManagement;
|
|
642
|
-
if (category === "service_provider") return permissionState.counts.serviceProvider;
|
|
643
|
-
return permissionState.counts.resident;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async function openPermissionDialog(category: THidPermissionCategory | "intercom") {
|
|
647
|
-
if (!props.org || !permissionReaderId.value) return;
|
|
648
|
-
permissionDialogCategory.value = category;
|
|
649
|
-
permissionSearch.value = "";
|
|
650
|
-
permissionCandidates.value = [];
|
|
651
|
-
selectedPermissionIds.value = new Set<string>();
|
|
652
|
-
permissionDialog.value = true;
|
|
653
|
-
loadingPermissionCandidates.value = true;
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
const response = await getPermissionCandidates(props.site, {
|
|
657
|
-
orgId: props.org,
|
|
658
|
-
readerId: permissionReaderId.value,
|
|
659
|
-
category,
|
|
660
|
-
page: 1,
|
|
661
|
-
limit: 500,
|
|
662
|
-
});
|
|
663
|
-
permissionCandidates.value = response.items || [];
|
|
664
|
-
selectedPermissionIds.value = new Set(
|
|
665
|
-
permissionCandidates.value
|
|
666
|
-
.filter((candidate) => category === "intercom" ? candidate.intercom : candidate.selected)
|
|
667
|
-
.map((candidate) => candidate.subjectId),
|
|
668
|
-
);
|
|
669
|
-
} catch (error: unknown) {
|
|
670
|
-
showMessage(errorConverter(error), "error");
|
|
671
|
-
} finally {
|
|
672
|
-
loadingPermissionCandidates.value = false;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function closePermissionDialog() {
|
|
677
|
-
if (savingPermissions.value) return;
|
|
678
|
-
permissionDialog.value = false;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function togglePermissionCandidate(subjectId: string, selected: boolean) {
|
|
682
|
-
const next = new Set(selectedPermissionIds.value);
|
|
683
|
-
if (selected) next.add(subjectId);
|
|
684
|
-
else next.delete(subjectId);
|
|
685
|
-
selectedPermissionIds.value = next;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
async function savePermissions() {
|
|
689
|
-
const category = permissionDialogCategory.value;
|
|
690
|
-
let assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>;
|
|
691
|
-
|
|
692
|
-
if (category === "intercom") {
|
|
693
|
-
assignments = permissionState.assignments.map((assignment) => ({
|
|
694
|
-
subjectId: assignment.subjectId,
|
|
695
|
-
category: assignment.category,
|
|
696
|
-
intercom: selectedPermissionIds.value.has(assignment.subjectId),
|
|
697
|
-
}));
|
|
698
|
-
} else {
|
|
699
|
-
const retained = permissionState.assignments
|
|
700
|
-
.filter((assignment) => assignment.category !== category)
|
|
701
|
-
.map((assignment) => ({
|
|
702
|
-
subjectId: assignment.subjectId,
|
|
703
|
-
category: assignment.category,
|
|
704
|
-
intercom: assignment.intercom,
|
|
705
|
-
}));
|
|
706
|
-
const selected = permissionCandidates.value
|
|
707
|
-
.filter((candidate) => selectedPermissionIds.value.has(candidate.subjectId))
|
|
708
|
-
.map((candidate) => ({
|
|
709
|
-
subjectId: candidate.subjectId,
|
|
710
|
-
category: candidate.category,
|
|
711
|
-
intercom: candidate.intercom,
|
|
712
|
-
}));
|
|
713
|
-
assignments = [...retained, ...selected];
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
savingPermissions.value = true;
|
|
717
|
-
try {
|
|
718
|
-
const response = await updateSitePermissions(
|
|
719
|
-
props.site,
|
|
720
|
-
props.org,
|
|
721
|
-
permissionReaderId.value,
|
|
722
|
-
assignments,
|
|
723
|
-
);
|
|
724
|
-
Object.assign(permissionState, response.data);
|
|
725
|
-
permissionDialog.value = false;
|
|
726
|
-
showMessage("HID permissions updated.", "success");
|
|
727
|
-
} catch (error: unknown) {
|
|
728
|
-
showMessage(errorConverter(error), "error");
|
|
729
|
-
} finally {
|
|
730
|
-
savingPermissions.value = false;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
735
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function getConfigurationData(response: unknown) {
|
|
739
|
-
if (!isUnknownRecord(response)) return {};
|
|
740
|
-
return isUnknownRecord(response.data) ? response.data : response;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function isEnabledConfiguration(value: unknown) {
|
|
744
|
-
return value === true || value === 1 || value === "1";
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
async function loadReaderConfiguration() {
|
|
748
|
-
if (!form.readerId) return;
|
|
749
|
-
|
|
750
|
-
try {
|
|
751
|
-
const response = await getReaderConfiguration(form.readerId, {
|
|
752
|
-
general: ["online"],
|
|
753
|
-
});
|
|
754
|
-
const configuration = getConfigurationData(response);
|
|
755
|
-
const general = isUnknownRecord(configuration.general) ? configuration.general : {};
|
|
756
|
-
|
|
757
|
-
if (general.online !== undefined) {
|
|
758
|
-
form.onlineMode = isEnabledConfiguration(general.online);
|
|
759
|
-
}
|
|
760
|
-
} catch (error: unknown) {
|
|
761
|
-
console.warn("Unable to load HID reader configuration:", errorConverter(error));
|
|
762
|
-
}
|
|
763
325
|
}
|
|
764
326
|
|
|
765
327
|
function buildPayload() {
|
|
766
328
|
const metadata = {
|
|
767
329
|
...(siteData.value?.metadata || {}),
|
|
768
330
|
hidQrCodePass: {
|
|
769
|
-
enabled: form.
|
|
770
|
-
onlineMode: form.onlineMode,
|
|
331
|
+
enabled: form.enabled,
|
|
771
332
|
readerId: form.readerId,
|
|
772
333
|
qrFormat: form.qrFormat,
|
|
773
|
-
identificationMethods: { ...form.identificationMethods },
|
|
774
334
|
printer: {
|
|
775
335
|
vendorId: form.printer.vendorId.trim(),
|
|
776
336
|
productId: form.printer.productId.trim(),
|
|
@@ -792,45 +352,17 @@ async function saveSection(section: string) {
|
|
|
792
352
|
|
|
793
353
|
savingSection.value = section;
|
|
794
354
|
try {
|
|
355
|
+
if (section === "qr-format") {
|
|
356
|
+
await setReaderConfiguration(form.readerId, {
|
|
357
|
+
face_id: {
|
|
358
|
+
qrcode_legacy_mode_enabled: form.qrFormat,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
795
362
|
await updateSitebyId(props.site, buildPayload());
|
|
796
363
|
await loadConfig();
|
|
797
|
-
showMessage("HID
|
|
798
|
-
} catch (error:
|
|
799
|
-
showMessage(errorConverter(error), "error");
|
|
800
|
-
} finally {
|
|
801
|
-
savingSection.value = "";
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
async function saveOnlineMode() {
|
|
806
|
-
if (!props.site || !form.readerId) return;
|
|
807
|
-
|
|
808
|
-
savingSection.value = "online-mode";
|
|
809
|
-
try {
|
|
810
|
-
await setReaderConfiguration(form.readerId, {
|
|
811
|
-
general: { online: form.onlineMode ? "1" : "0" },
|
|
812
|
-
});
|
|
813
|
-
await updateSitebyId(props.site, buildPayload());
|
|
814
|
-
showMessage("Online mode updated.", "success");
|
|
815
|
-
} catch (error: unknown) {
|
|
816
|
-
await loadConfig();
|
|
817
|
-
showMessage(errorConverter(error), "error");
|
|
818
|
-
} finally {
|
|
819
|
-
savingSection.value = "";
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
async function saveIdentificationMethod(method: HidIdentificationMethod) {
|
|
824
|
-
if (!props.site) return;
|
|
825
|
-
|
|
826
|
-
form.enabled = form.identificationMethods.qrCode;
|
|
827
|
-
|
|
828
|
-
savingSection.value = `method-${method}`;
|
|
829
|
-
try {
|
|
830
|
-
await updateSitebyId(props.site, buildPayload());
|
|
831
|
-
showMessage(`${identificationMethodOptions.find((item) => item.key === method)?.label || "Identification method"} updated.`, "success");
|
|
832
|
-
} catch (error: unknown) {
|
|
833
|
-
await loadConfig();
|
|
364
|
+
showMessage("HID QR code settings saved.", "success");
|
|
365
|
+
} catch (error: any) {
|
|
834
366
|
showMessage(errorConverter(error), "error");
|
|
835
367
|
} finally {
|
|
836
368
|
savingSection.value = "";
|
|
@@ -844,36 +376,19 @@ async function scanPrinter() {
|
|
|
844
376
|
}
|
|
845
377
|
|
|
846
378
|
try {
|
|
847
|
-
const device = await
|
|
379
|
+
const device = await (navigator as any).usb.requestDevice({ filters: [] });
|
|
848
380
|
form.printer.vendorId = `0x${Number(device.vendorId).toString(16).padStart(4, "0")}`;
|
|
849
381
|
form.printer.productId = `0x${Number(device.productId).toString(16).padStart(4, "0")}`;
|
|
850
382
|
showMessage("Printer selected. Save settings to keep it.", "success");
|
|
851
|
-
} catch (error:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
showMessage(message || "Unable to scan printer.", "error");
|
|
383
|
+
} catch (error: any) {
|
|
384
|
+
if (error?.name !== "NotFoundError") {
|
|
385
|
+
showMessage(error?.message || "Unable to scan printer.", "error");
|
|
855
386
|
}
|
|
856
387
|
}
|
|
857
388
|
}
|
|
858
389
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const productId = Number(form.printer.productId);
|
|
862
|
-
if (!vendorId || !productId) {
|
|
863
|
-
showMessage("Please scan a valid USB printer first.", "error");
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
testingPrinter.value = true;
|
|
868
|
-
try {
|
|
869
|
-
const result = await testPrinterConnection(vendorId, productId);
|
|
870
|
-
showMessage(
|
|
871
|
-
result.success ? result.message : result.error || result.message,
|
|
872
|
-
result.success ? "success" : "error",
|
|
873
|
-
);
|
|
874
|
-
} finally {
|
|
875
|
-
testingPrinter.value = false;
|
|
876
|
-
}
|
|
390
|
+
function testPrint() {
|
|
391
|
+
showMessage("Printer settings are ready. Connect print command when HID QR payload generation is available.", "success");
|
|
877
392
|
}
|
|
878
393
|
|
|
879
394
|
function showMessage(text: string, color: string) {
|
|
@@ -917,15 +432,15 @@ function showMessage(text: string, color: string) {
|
|
|
917
432
|
padding: 30px 38px;
|
|
918
433
|
}
|
|
919
434
|
|
|
920
|
-
.
|
|
435
|
+
.section-head {
|
|
921
436
|
display: flex;
|
|
922
|
-
align-items:
|
|
437
|
+
align-items: flex-start;
|
|
923
438
|
justify-content: space-between;
|
|
924
439
|
gap: 24px;
|
|
925
|
-
|
|
440
|
+
margin-bottom: 24px;
|
|
926
441
|
}
|
|
927
442
|
|
|
928
|
-
.
|
|
443
|
+
.section-head h3,
|
|
929
444
|
.section-label {
|
|
930
445
|
margin: 0 0 6px;
|
|
931
446
|
color: #233545;
|
|
@@ -933,83 +448,16 @@ function showMessage(text: string, color: string) {
|
|
|
933
448
|
font-weight: 700;
|
|
934
449
|
}
|
|
935
450
|
|
|
936
|
-
.
|
|
937
|
-
.section-description {
|
|
451
|
+
.section-head p {
|
|
938
452
|
margin: 0;
|
|
939
453
|
color: #677280;
|
|
940
454
|
font-size: 12px;
|
|
941
|
-
line-height: 1.5;
|
|
942
455
|
}
|
|
943
456
|
|
|
944
457
|
.form-section {
|
|
945
458
|
padding: 16px 0;
|
|
946
459
|
}
|
|
947
460
|
|
|
948
|
-
.identification-section {
|
|
949
|
-
padding-bottom: 20px;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
.methods-grid {
|
|
953
|
-
display: grid;
|
|
954
|
-
grid-template-columns: repeat(6, minmax(120px, 1fr));
|
|
955
|
-
gap: 18px;
|
|
956
|
-
margin-top: 16px;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
.method-control {
|
|
960
|
-
display: flex;
|
|
961
|
-
align-items: center;
|
|
962
|
-
justify-content: space-between;
|
|
963
|
-
min-width: 0;
|
|
964
|
-
min-height: 42px;
|
|
965
|
-
padding: 0 10px 0 14px;
|
|
966
|
-
border: 1px solid #dce2e7;
|
|
967
|
-
border-radius: 6px;
|
|
968
|
-
color: #334454;
|
|
969
|
-
font-size: 12px;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
.method-control span {
|
|
973
|
-
overflow: hidden;
|
|
974
|
-
text-overflow: ellipsis;
|
|
975
|
-
white-space: nowrap;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
.method-control--disabled {
|
|
979
|
-
background-color: #f5f6f7;
|
|
980
|
-
color: #8b9297;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
.method-control :deep(.v-switch) {
|
|
984
|
-
flex: 0 0 auto;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
.method-control :deep(.v-selection-control) {
|
|
988
|
-
min-height: 32px;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
.hid-config-switch :deep(.v-switch__track) {
|
|
992
|
-
background-color: #8b9297;
|
|
993
|
-
opacity: 1;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
.hid-config-switch :deep(.v-selection-control--dirty .v-switch__track) {
|
|
997
|
-
background-color: #2eaf55 !important;
|
|
998
|
-
color: #2eaf55 !important;
|
|
999
|
-
opacity: 1;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
.hid-config-switch :deep(.v-switch__thumb) {
|
|
1003
|
-
background-color: #fff !important;
|
|
1004
|
-
color: #fff !important;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
.hid-config-switch.v-input--disabled :deep(.v-switch__track) {
|
|
1008
|
-
background-color: #aeb4b8 !important;
|
|
1009
|
-
color: #aeb4b8 !important;
|
|
1010
|
-
opacity: 1;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
461
|
.printer-grid {
|
|
1014
462
|
display: grid;
|
|
1015
463
|
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr);
|
|
@@ -1035,231 +483,6 @@ function showMessage(text: string, color: string) {
|
|
|
1035
483
|
align-items: center;
|
|
1036
484
|
}
|
|
1037
485
|
|
|
1038
|
-
.permission-section {
|
|
1039
|
-
padding-top: 2px;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
.permission-location {
|
|
1043
|
-
display: grid;
|
|
1044
|
-
grid-template-columns: minmax(260px, 1fr) minmax(320px, 1.35fr);
|
|
1045
|
-
gap: 36px;
|
|
1046
|
-
align-items: center;
|
|
1047
|
-
padding: 18px 0;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
.permission-location__select {
|
|
1051
|
-
width: 100%;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
.permission-row {
|
|
1055
|
-
display: grid;
|
|
1056
|
-
grid-template-columns: minmax(260px, 1fr) minmax(520px, 1.35fr);
|
|
1057
|
-
gap: 36px;
|
|
1058
|
-
align-items: center;
|
|
1059
|
-
padding: 18px 0;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
.permission-row--intercom {
|
|
1063
|
-
padding-bottom: 4px;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
.permission-copy {
|
|
1067
|
-
min-width: 0;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
.permission-actions {
|
|
1071
|
-
display: grid;
|
|
1072
|
-
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
|
1073
|
-
gap: 12px;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
.permission-actions--single {
|
|
1077
|
-
grid-template-columns: minmax(180px, 240px);
|
|
1078
|
-
justify-content: end;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
.permission-button {
|
|
1082
|
-
justify-content: flex-start;
|
|
1083
|
-
min-height: 42px;
|
|
1084
|
-
padding-inline: 12px;
|
|
1085
|
-
border-color: #dce2e7;
|
|
1086
|
-
color: #334454;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
.permission-count {
|
|
1090
|
-
display: inline-grid;
|
|
1091
|
-
place-items: center;
|
|
1092
|
-
width: 20px;
|
|
1093
|
-
height: 20px;
|
|
1094
|
-
margin-right: 8px;
|
|
1095
|
-
border-radius: 50%;
|
|
1096
|
-
background: #dce2e7;
|
|
1097
|
-
color: #526170;
|
|
1098
|
-
font-size: 11px;
|
|
1099
|
-
font-weight: 700;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
.permission-button:not(:disabled):hover {
|
|
1103
|
-
border-color: #1473c9;
|
|
1104
|
-
color: #0a5da7;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
.permission-dialog {
|
|
1108
|
-
overflow: hidden;
|
|
1109
|
-
border-radius: 6px;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
.permission-dialog__header {
|
|
1113
|
-
display: flex;
|
|
1114
|
-
align-items: flex-start;
|
|
1115
|
-
justify-content: space-between;
|
|
1116
|
-
gap: 16px;
|
|
1117
|
-
padding: 16px 18px 14px;
|
|
1118
|
-
border-bottom: 1px solid #e5e9ed;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
.permission-dialog__title {
|
|
1122
|
-
color: #172534;
|
|
1123
|
-
font-size: 18px;
|
|
1124
|
-
font-weight: 700;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
.permission-dialog__subtitle {
|
|
1128
|
-
margin-top: 4px;
|
|
1129
|
-
color: #687583;
|
|
1130
|
-
font-size: 12px;
|
|
1131
|
-
white-space: normal;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
.permission-dialog__body {
|
|
1135
|
-
display: grid;
|
|
1136
|
-
gap: 10px;
|
|
1137
|
-
padding: 14px 18px 16px;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
.permission-search :deep(.v-field) {
|
|
1141
|
-
min-height: 44px;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
.permission-list-summary {
|
|
1145
|
-
display: flex;
|
|
1146
|
-
align-items: center;
|
|
1147
|
-
justify-content: space-between;
|
|
1148
|
-
min-height: 22px;
|
|
1149
|
-
color: #778391;
|
|
1150
|
-
font-size: 11px;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
.permission-list-summary__selected {
|
|
1154
|
-
color: #286c3c;
|
|
1155
|
-
font-weight: 700;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
.permission-list {
|
|
1159
|
-
min-height: 180px;
|
|
1160
|
-
max-height: min(360px, 48vh);
|
|
1161
|
-
overflow-y: auto;
|
|
1162
|
-
border: 1px solid #dfe5ea;
|
|
1163
|
-
border-radius: 6px;
|
|
1164
|
-
background: #ffffff;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
.permission-list--loading {
|
|
1168
|
-
display: grid;
|
|
1169
|
-
place-items: center;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
.permission-list-item {
|
|
1173
|
-
display: grid;
|
|
1174
|
-
grid-template-columns: minmax(0, 1fr) max-content 32px;
|
|
1175
|
-
gap: 12px;
|
|
1176
|
-
align-items: center;
|
|
1177
|
-
min-height: 56px;
|
|
1178
|
-
padding: 7px 12px;
|
|
1179
|
-
border-bottom: 1px solid #edf0f3;
|
|
1180
|
-
cursor: pointer;
|
|
1181
|
-
transition: background-color 120ms ease;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
.permission-list-item:last-child {
|
|
1185
|
-
border-bottom: 0;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
.permission-list-item:hover {
|
|
1189
|
-
background: #f7fafc;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
.permission-list-item--selected {
|
|
1193
|
-
background: #f1f8f3;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
.permission-list-item--selected:hover {
|
|
1197
|
-
background: #eaf5ed;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
.permission-list-item__content {
|
|
1201
|
-
display: grid;
|
|
1202
|
-
gap: 2px;
|
|
1203
|
-
min-width: 0;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
.permission-list-item__content strong,
|
|
1207
|
-
.permission-list-item__content small {
|
|
1208
|
-
overflow: hidden;
|
|
1209
|
-
text-overflow: ellipsis;
|
|
1210
|
-
white-space: nowrap;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
.permission-list-item__content strong {
|
|
1214
|
-
color: #243443;
|
|
1215
|
-
font-size: 13px;
|
|
1216
|
-
line-height: 1.35;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
.permission-list-item__content small,
|
|
1220
|
-
.permission-list-item__group {
|
|
1221
|
-
color: #778391;
|
|
1222
|
-
font-size: 11px;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
.permission-list-item__group {
|
|
1226
|
-
max-width: 170px;
|
|
1227
|
-
overflow: hidden;
|
|
1228
|
-
padding: 4px 9px;
|
|
1229
|
-
border-radius: 4px;
|
|
1230
|
-
background: #eef2f5;
|
|
1231
|
-
text-overflow: ellipsis;
|
|
1232
|
-
white-space: nowrap;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
.permission-list-item__checkbox {
|
|
1236
|
-
justify-self: end;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
.permission-list-item__checkbox :deep(.v-selection-control) {
|
|
1240
|
-
min-height: 32px;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
.permission-empty {
|
|
1244
|
-
display: flex;
|
|
1245
|
-
flex-direction: column;
|
|
1246
|
-
align-items: center;
|
|
1247
|
-
justify-content: center;
|
|
1248
|
-
gap: 10px;
|
|
1249
|
-
min-height: 180px;
|
|
1250
|
-
padding: 24px;
|
|
1251
|
-
color: #75818c;
|
|
1252
|
-
font-size: 13px;
|
|
1253
|
-
text-align: center;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
.permission-dialog__actions {
|
|
1257
|
-
justify-content: flex-end;
|
|
1258
|
-
gap: 8px;
|
|
1259
|
-
padding: 12px 18px;
|
|
1260
|
-
border-top: 1px solid #e5e9ed;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
486
|
.save-button {
|
|
1264
487
|
background: #082e43;
|
|
1265
488
|
color: #ffffff;
|
|
@@ -1369,21 +592,11 @@ function showMessage(text: string, color: string) {
|
|
|
1369
592
|
}
|
|
1370
593
|
|
|
1371
594
|
@media (max-width: 1100px) {
|
|
1372
|
-
.methods-grid {
|
|
1373
|
-
grid-template-columns: repeat(3, minmax(140px, 1fr));
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
595
|
.printer-grid,
|
|
1377
596
|
.template-grid,
|
|
1378
|
-
.validity-grid
|
|
1379
|
-
.permission-location,
|
|
1380
|
-
.permission-row {
|
|
597
|
+
.validity-grid {
|
|
1381
598
|
grid-template-columns: 1fr 1fr;
|
|
1382
599
|
}
|
|
1383
|
-
|
|
1384
|
-
.permission-actions {
|
|
1385
|
-
grid-template-columns: 1fr;
|
|
1386
|
-
}
|
|
1387
600
|
}
|
|
1388
601
|
|
|
1389
602
|
@media (max-width: 700px) {
|
|
@@ -1391,40 +604,15 @@ function showMessage(text: string, color: string) {
|
|
|
1391
604
|
padding: 22px 16px;
|
|
1392
605
|
}
|
|
1393
606
|
|
|
1394
|
-
.
|
|
607
|
+
.section-head,
|
|
1395
608
|
.printer-grid,
|
|
1396
609
|
.template-grid,
|
|
1397
|
-
.validity-grid
|
|
1398
|
-
.methods-grid,
|
|
1399
|
-
.permission-location,
|
|
1400
|
-
.permission-row,
|
|
1401
|
-
.permission-actions {
|
|
610
|
+
.validity-grid {
|
|
1402
611
|
grid-template-columns: 1fr;
|
|
1403
612
|
}
|
|
1404
613
|
|
|
1405
|
-
.
|
|
614
|
+
.section-head {
|
|
1406
615
|
display: grid;
|
|
1407
616
|
}
|
|
1408
|
-
|
|
1409
|
-
.permission-actions--single {
|
|
1410
|
-
justify-content: stretch;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
.permission-list-item {
|
|
1414
|
-
grid-template-columns: minmax(0, 1fr) 32px;
|
|
1415
|
-
gap: 8px;
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
.permission-list-item__group {
|
|
1419
|
-
grid-column: 1;
|
|
1420
|
-
grid-row: 2;
|
|
1421
|
-
justify-self: start;
|
|
1422
|
-
max-width: 100%;
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
.permission-list-item__checkbox {
|
|
1426
|
-
grid-column: 2;
|
|
1427
|
-
grid-row: 1 / span 2;
|
|
1428
|
-
}
|
|
1429
617
|
}
|
|
1430
618
|
</style>
|