@7365admin1/layer-common 3.1.4-staging.53 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/components/CreateSubsciptionPlan.vue +376 -0
- package/components/DashboardMain.vue +16 -38
- package/components/EntryPassInformation.vue +4 -4
- package/components/HidAccessLogDashboard.vue +8 -53
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidIntercomManagement.vue +174 -621
- package/components/HidQrCodeConfiguration.vue +94 -742
- package/components/HidServiceSettingsPanel.vue +1 -20
- package/components/HidUserEnrollment.vue +0 -2
- package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
- package/components/PlanActionsMenu.vue +33 -0
- package/components/PlanStatusBadge.vue +26 -0
- package/components/QrTemplate/CreditCardLandscape.vue +12 -19
- package/components/QrTemplate/PrintDialog.vue +196 -209
- package/components/SubscriptionPlanTable.vue +362 -0
- package/components/VisitorForm.vue +78 -201
- package/components/VisitorManagement.vue +1 -0
- package/composables/useEquipmentManagementPermission.ts +1 -1
- package/composables/useEquipmentPermission.ts +1 -1
- package/composables/useHidAmico.ts +0 -66
- package/composables/useHidNavigation.ts +7 -0
- package/composables/useWebUsb.ts +37 -127
- package/package.json +1 -2
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
- package/plugins/secure-member.client.ts +56 -21
- package/types/site.d.ts +0 -65
- package/types/subscription.ts +51 -0
- package/composables/useSipWebPhone.ts +0 -236
|
@@ -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,54 +184,6 @@
|
|
|
175
184
|
</v-btn>
|
|
176
185
|
</div>
|
|
177
186
|
</div>
|
|
178
|
-
|
|
179
|
-
<v-divider />
|
|
180
|
-
|
|
181
|
-
<div class="permission-section">
|
|
182
|
-
<div class="permission-row">
|
|
183
|
-
<div class="permission-copy">
|
|
184
|
-
<div class="section-label">Access Permission</div>
|
|
185
|
-
<p class="section-description">
|
|
186
|
-
Select who can use the enabled HID identification methods at this site.
|
|
187
|
-
</p>
|
|
188
|
-
</div>
|
|
189
|
-
<div class="permission-actions">
|
|
190
|
-
<v-btn
|
|
191
|
-
v-for="option in accessPermissionOptions"
|
|
192
|
-
:key="option.category"
|
|
193
|
-
class="permission-button text-none"
|
|
194
|
-
variant="outlined"
|
|
195
|
-
:disabled="!form.onlineMode"
|
|
196
|
-
@click="openPermissionDialog(option.category)"
|
|
197
|
-
>
|
|
198
|
-
<span class="permission-count">{{ permissionCount(option.category) }}</span>
|
|
199
|
-
<span>{{ option.label }}</span>
|
|
200
|
-
</v-btn>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
<v-divider />
|
|
205
|
-
|
|
206
|
-
<div class="permission-row permission-row--intercom">
|
|
207
|
-
<div class="permission-copy">
|
|
208
|
-
<div class="section-label">Access Intercom</div>
|
|
209
|
-
<p class="section-description">
|
|
210
|
-
Choose which authorized people and providers may use HID intercom access.
|
|
211
|
-
</p>
|
|
212
|
-
</div>
|
|
213
|
-
<div class="permission-actions permission-actions--single">
|
|
214
|
-
<v-btn
|
|
215
|
-
class="permission-button text-none"
|
|
216
|
-
variant="outlined"
|
|
217
|
-
:disabled="!form.onlineMode || permissionState.assignments.length === 0"
|
|
218
|
-
@click="openPermissionDialog('intercom')"
|
|
219
|
-
>
|
|
220
|
-
<span class="permission-count">{{ permissionState.counts.intercom }}</span>
|
|
221
|
-
<span>Intercom Permissions</span>
|
|
222
|
-
</v-btn>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
187
|
</div>
|
|
227
188
|
</v-card>
|
|
228
189
|
|
|
@@ -261,109 +222,24 @@
|
|
|
261
222
|
</v-card>
|
|
262
223
|
</v-dialog>
|
|
263
224
|
|
|
264
|
-
<v-dialog v-model="permissionDialog" max-width="640" persistent>
|
|
265
|
-
<v-card class="permission-dialog">
|
|
266
|
-
<v-card-title class="permission-dialog__header">
|
|
267
|
-
<div>
|
|
268
|
-
<div class="permission-dialog__title">{{ permissionDialogTitle }}</div>
|
|
269
|
-
<div class="permission-dialog__subtitle">{{ permissionDialogDescription }}</div>
|
|
270
|
-
</div>
|
|
271
|
-
<v-btn
|
|
272
|
-
icon="mdi-close"
|
|
273
|
-
variant="text"
|
|
274
|
-
density="comfortable"
|
|
275
|
-
aria-label="Close permission dialog"
|
|
276
|
-
@click="closePermissionDialog"
|
|
277
|
-
/>
|
|
278
|
-
</v-card-title>
|
|
279
|
-
|
|
280
|
-
<v-card-text class="permission-dialog__body">
|
|
281
|
-
<v-text-field
|
|
282
|
-
v-model="permissionSearch"
|
|
283
|
-
prepend-inner-icon="mdi-magnify"
|
|
284
|
-
placeholder="Search by name or details"
|
|
285
|
-
variant="outlined"
|
|
286
|
-
density="compact"
|
|
287
|
-
hide-details
|
|
288
|
-
clearable
|
|
289
|
-
/>
|
|
290
|
-
|
|
291
|
-
<div class="permission-list" :class="{ 'permission-list--loading': loadingPermissionCandidates }">
|
|
292
|
-
<div v-if="loadingPermissionCandidates" class="permission-empty">
|
|
293
|
-
<v-progress-circular indeterminate color="primary" size="28" />
|
|
294
|
-
<span>Loading permissions...</span>
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<template v-else-if="visiblePermissionCandidates.length">
|
|
298
|
-
<label
|
|
299
|
-
v-for="candidate in visiblePermissionCandidates"
|
|
300
|
-
:key="`${candidate.category}:${candidate.subjectId}`"
|
|
301
|
-
class="permission-list-item"
|
|
302
|
-
>
|
|
303
|
-
<v-checkbox-btn
|
|
304
|
-
:model-value="selectedPermissionIds.has(candidate.subjectId)"
|
|
305
|
-
color="success"
|
|
306
|
-
@update:model-value="togglePermissionCandidate(candidate.subjectId, Boolean($event))"
|
|
307
|
-
/>
|
|
308
|
-
<span class="permission-list-item__content">
|
|
309
|
-
<strong>{{ candidate.name }}</strong>
|
|
310
|
-
<small>{{ candidate.subtitle || permissionCategoryLabel(candidate.category) }}</small>
|
|
311
|
-
</span>
|
|
312
|
-
<span class="permission-list-item__group">{{ permissionCategoryLabel(candidate.category) }}</span>
|
|
313
|
-
</label>
|
|
314
|
-
</template>
|
|
315
|
-
|
|
316
|
-
<div v-else class="permission-empty">
|
|
317
|
-
<v-icon icon="mdi-account-search-outline" size="30" />
|
|
318
|
-
<span>{{ permissionEmptyMessage }}</span>
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
</v-card-text>
|
|
322
|
-
|
|
323
|
-
<v-card-actions class="permission-dialog__actions">
|
|
324
|
-
<v-btn class="text-none" variant="text" @click="closePermissionDialog">Cancel</v-btn>
|
|
325
|
-
<v-btn
|
|
326
|
-
class="text-none save-button"
|
|
327
|
-
variant="flat"
|
|
328
|
-
min-width="140"
|
|
329
|
-
:loading="savingPermissions"
|
|
330
|
-
:disabled="loadingPermissionCandidates"
|
|
331
|
-
@click="savePermissions"
|
|
332
|
-
>
|
|
333
|
-
Save Permissions
|
|
334
|
-
</v-btn>
|
|
335
|
-
</v-card-actions>
|
|
336
|
-
</v-card>
|
|
337
|
-
</v-dialog>
|
|
338
|
-
|
|
339
225
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
340
226
|
</section>
|
|
341
227
|
</template>
|
|
342
228
|
|
|
343
229
|
<script setup lang="ts">
|
|
344
|
-
type
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
_id: string;
|
|
356
|
-
name?: string;
|
|
357
|
-
deviceId?: string;
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
type HidReaderListResponse = {
|
|
361
|
-
items?: HidReaderOption[];
|
|
362
|
-
readers?: HidReaderOption[];
|
|
363
|
-
data?: {
|
|
364
|
-
items?: HidReaderOption[];
|
|
365
|
-
readers?: HidReaderOption[];
|
|
230
|
+
type HidQrConfig = {
|
|
231
|
+
enabled: boolean;
|
|
232
|
+
readerId: string;
|
|
233
|
+
qrFormat: "0" | "1" | "2";
|
|
234
|
+
printer: {
|
|
235
|
+
vendorId: string;
|
|
236
|
+
productId: string;
|
|
237
|
+
};
|
|
238
|
+
template: {
|
|
239
|
+
header: string;
|
|
240
|
+
subtext: string;
|
|
366
241
|
};
|
|
242
|
+
validityMinutes: number | null;
|
|
367
243
|
};
|
|
368
244
|
|
|
369
245
|
const props = defineProps<{
|
|
@@ -371,32 +247,12 @@ const props = defineProps<{
|
|
|
371
247
|
}>();
|
|
372
248
|
|
|
373
249
|
const { getSiteById, updateSitebyId } = useSiteSettings();
|
|
374
|
-
const {
|
|
375
|
-
getReaders,
|
|
376
|
-
getReaderConfiguration,
|
|
377
|
-
setReaderConfiguration,
|
|
378
|
-
getSitePermissions,
|
|
379
|
-
getPermissionCandidates,
|
|
380
|
-
updateSitePermissions,
|
|
381
|
-
} = useHidAmico();
|
|
382
|
-
const {
|
|
383
|
-
requestDevice: requestPrinterDevice,
|
|
384
|
-
testConnection: testPrinterConnection,
|
|
385
|
-
} = useWebUsb();
|
|
250
|
+
const { getReaders, setReaderConfiguration } = useHidAmico();
|
|
386
251
|
|
|
387
252
|
const defaultConfig: HidQrConfig = {
|
|
388
253
|
enabled: false,
|
|
389
|
-
onlineMode: false,
|
|
390
254
|
readerId: "",
|
|
391
255
|
qrFormat: "0",
|
|
392
|
-
identificationMethods: {
|
|
393
|
-
facial: true,
|
|
394
|
-
card: true,
|
|
395
|
-
qrCode: false,
|
|
396
|
-
idPassword: true,
|
|
397
|
-
pin: false,
|
|
398
|
-
bluetooth: false,
|
|
399
|
-
},
|
|
400
256
|
printer: {
|
|
401
257
|
vendorId: "",
|
|
402
258
|
productId: "",
|
|
@@ -408,54 +264,26 @@ const defaultConfig: HidQrConfig = {
|
|
|
408
264
|
validityMinutes: null,
|
|
409
265
|
};
|
|
410
266
|
|
|
411
|
-
const siteData = ref<
|
|
412
|
-
const readers = ref<
|
|
267
|
+
const siteData = ref<Record<string, any> | null>(null);
|
|
268
|
+
const readers = ref<Record<string, any>[]>([]);
|
|
413
269
|
const form = reactive<HidQrConfig>(structuredClone(defaultConfig));
|
|
414
270
|
const previewDialog = ref(false);
|
|
415
271
|
const savingSection = ref("");
|
|
416
|
-
const testingPrinter = ref(false);
|
|
417
272
|
const message = ref("");
|
|
418
273
|
const messageColor = ref("success");
|
|
419
274
|
const messageSnackbar = ref(false);
|
|
420
|
-
const permissionState = reactive<THidSitePermissions>({
|
|
421
|
-
site: props.site,
|
|
422
|
-
assignments: [],
|
|
423
|
-
counts: {
|
|
424
|
-
resident: 0,
|
|
425
|
-
propertyManagement: 0,
|
|
426
|
-
serviceProvider: 0,
|
|
427
|
-
intercom: 0,
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
const permissionDialog = ref(false);
|
|
431
|
-
const permissionDialogCategory = ref<THidPermissionCategory | "intercom">("resident");
|
|
432
|
-
const permissionCandidates = ref<THidPermissionCandidate[]>([]);
|
|
433
|
-
const selectedPermissionIds = ref(new Set<string>());
|
|
434
|
-
const permissionSearch = ref("");
|
|
435
|
-
const loadingPermissionCandidates = ref(false);
|
|
436
|
-
const savingPermissions = ref(false);
|
|
437
275
|
|
|
438
276
|
const hasPrinterConfig = computed(() =>
|
|
439
277
|
Boolean(form.printer.vendorId && form.printer.productId),
|
|
440
278
|
);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
{
|
|
447
|
-
{
|
|
448
|
-
{
|
|
449
|
-
{ key: "pin", label: "PIN" },
|
|
450
|
-
{ key: "bluetooth", label: "Bluetooth" },
|
|
451
|
-
];
|
|
452
|
-
const accessPermissionOptions: Array<{
|
|
453
|
-
category: THidPermissionCategory;
|
|
454
|
-
label: string;
|
|
455
|
-
}> = [
|
|
456
|
-
{ category: "resident", label: "Resident" },
|
|
457
|
-
{ category: "property_management", label: "Property Management" },
|
|
458
|
-
{ 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" },
|
|
459
287
|
];
|
|
460
288
|
|
|
461
289
|
const siteName = computed(() => siteData.value?.name || "Site");
|
|
@@ -463,29 +291,6 @@ const siteLabel = computed(() => siteData.value?.code || siteData.value?.block |
|
|
|
463
291
|
const validityText = computed(() =>
|
|
464
292
|
form.validityMinutes ? `${form.validityMinutes} minutes` : "Not set",
|
|
465
293
|
);
|
|
466
|
-
const visiblePermissionCandidates = computed(() => {
|
|
467
|
-
const search = permissionSearch.value.trim().toLowerCase();
|
|
468
|
-
if (!search) return permissionCandidates.value;
|
|
469
|
-
return permissionCandidates.value.filter((candidate) => (
|
|
470
|
-
candidate.name.toLowerCase().includes(search)
|
|
471
|
-
|| candidate.subtitle?.toLowerCase().includes(search)
|
|
472
|
-
));
|
|
473
|
-
});
|
|
474
|
-
const permissionDialogTitle = computed(() => (
|
|
475
|
-
permissionDialogCategory.value === "intercom"
|
|
476
|
-
? "Intercom Permissions"
|
|
477
|
-
: `${permissionCategoryLabel(permissionDialogCategory.value)} Access Permission`
|
|
478
|
-
));
|
|
479
|
-
const permissionDialogDescription = computed(() => (
|
|
480
|
-
permissionDialogCategory.value === "intercom"
|
|
481
|
-
? "Select from people and providers who already have HID access permission."
|
|
482
|
-
: `Select the ${permissionCategoryLabel(permissionDialogCategory.value).toLowerCase()} entries allowed to use HID access at this site.`
|
|
483
|
-
));
|
|
484
|
-
const permissionEmptyMessage = computed(() => (
|
|
485
|
-
permissionDialogCategory.value === "intercom" && permissionState.assignments.length === 0
|
|
486
|
-
? "Add an access permission before assigning intercom access."
|
|
487
|
-
: "No matching entries found."
|
|
488
|
-
));
|
|
489
294
|
|
|
490
295
|
watch(
|
|
491
296
|
() => props.site,
|
|
@@ -500,24 +305,13 @@ async function loadConfig() {
|
|
|
500
305
|
getSiteById(props.site),
|
|
501
306
|
getReaders({ site: props.site, page: 1, limit: 100 }),
|
|
502
307
|
]);
|
|
503
|
-
siteData.value = site
|
|
504
|
-
|
|
505
|
-
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 ?? [];
|
|
506
310
|
const config = siteData.value?.metadata?.hidQrCodePass || {};
|
|
507
|
-
const configuredMethods = config.identificationMethods;
|
|
508
311
|
Object.assign(form, {
|
|
509
312
|
enabled: Boolean(config.enabled),
|
|
510
|
-
onlineMode: Boolean(config.onlineMode),
|
|
511
313
|
readerId: String(config.readerId || readers.value[0]?._id || ""),
|
|
512
314
|
qrFormat: ["0", "1", "2"].includes(String(config.qrFormat)) ? String(config.qrFormat) as "0" | "1" | "2" : "0",
|
|
513
|
-
identificationMethods: {
|
|
514
|
-
facial: configuredMethods?.facial ?? true,
|
|
515
|
-
card: configuredMethods?.card ?? true,
|
|
516
|
-
qrCode: configuredMethods?.qrCode ?? Boolean(config.enabled),
|
|
517
|
-
idPassword: configuredMethods?.idPassword ?? true,
|
|
518
|
-
pin: configuredMethods?.pin ?? false,
|
|
519
|
-
bluetooth: configuredMethods?.bluetooth ?? false,
|
|
520
|
-
},
|
|
521
315
|
printer: {
|
|
522
316
|
vendorId: String(config.printer?.vendorId || ""),
|
|
523
317
|
productId: String(config.printer?.productId || ""),
|
|
@@ -528,150 +322,15 @@ async function loadConfig() {
|
|
|
528
322
|
},
|
|
529
323
|
validityMinutes: config.validityMinutes ? Number(config.validityMinutes) : null,
|
|
530
324
|
});
|
|
531
|
-
|
|
532
|
-
await Promise.all([loadReaderConfiguration(), loadPermissions()]);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function loadPermissions() {
|
|
536
|
-
if (!props.site) return;
|
|
537
|
-
try {
|
|
538
|
-
const response = await getSitePermissions(props.site);
|
|
539
|
-
Object.assign(permissionState, response.data);
|
|
540
|
-
} catch (error: unknown) {
|
|
541
|
-
console.error("Unable to load HID permissions:", errorConverter(error));
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function permissionCategoryLabel(category: THidPermissionCategory) {
|
|
546
|
-
return accessPermissionOptions.find((option) => option.category === category)?.label || "Permission";
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function permissionCount(category: THidPermissionCategory) {
|
|
550
|
-
if (category === "property_management") return permissionState.counts.propertyManagement;
|
|
551
|
-
if (category === "service_provider") return permissionState.counts.serviceProvider;
|
|
552
|
-
return permissionState.counts.resident;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
async function openPermissionDialog(category: THidPermissionCategory | "intercom") {
|
|
556
|
-
permissionDialogCategory.value = category;
|
|
557
|
-
permissionSearch.value = "";
|
|
558
|
-
permissionCandidates.value = [];
|
|
559
|
-
selectedPermissionIds.value = new Set<string>();
|
|
560
|
-
permissionDialog.value = true;
|
|
561
|
-
loadingPermissionCandidates.value = true;
|
|
562
|
-
|
|
563
|
-
try {
|
|
564
|
-
const response = await getPermissionCandidates(props.site, {
|
|
565
|
-
category,
|
|
566
|
-
page: 1,
|
|
567
|
-
limit: 500,
|
|
568
|
-
});
|
|
569
|
-
permissionCandidates.value = response.items || [];
|
|
570
|
-
selectedPermissionIds.value = new Set(
|
|
571
|
-
permissionCandidates.value
|
|
572
|
-
.filter((candidate) => category === "intercom" ? candidate.intercom : candidate.selected)
|
|
573
|
-
.map((candidate) => candidate.subjectId),
|
|
574
|
-
);
|
|
575
|
-
} catch (error: unknown) {
|
|
576
|
-
showMessage(errorConverter(error), "error");
|
|
577
|
-
} finally {
|
|
578
|
-
loadingPermissionCandidates.value = false;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function closePermissionDialog() {
|
|
583
|
-
if (savingPermissions.value) return;
|
|
584
|
-
permissionDialog.value = false;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function togglePermissionCandidate(subjectId: string, selected: boolean) {
|
|
588
|
-
const next = new Set(selectedPermissionIds.value);
|
|
589
|
-
if (selected) next.add(subjectId);
|
|
590
|
-
else next.delete(subjectId);
|
|
591
|
-
selectedPermissionIds.value = next;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
async function savePermissions() {
|
|
595
|
-
const category = permissionDialogCategory.value;
|
|
596
|
-
let assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>;
|
|
597
|
-
|
|
598
|
-
if (category === "intercom") {
|
|
599
|
-
assignments = permissionState.assignments.map((assignment) => ({
|
|
600
|
-
subjectId: assignment.subjectId,
|
|
601
|
-
category: assignment.category,
|
|
602
|
-
intercom: selectedPermissionIds.value.has(assignment.subjectId),
|
|
603
|
-
}));
|
|
604
|
-
} else {
|
|
605
|
-
const retained = permissionState.assignments
|
|
606
|
-
.filter((assignment) => assignment.category !== category)
|
|
607
|
-
.map((assignment) => ({
|
|
608
|
-
subjectId: assignment.subjectId,
|
|
609
|
-
category: assignment.category,
|
|
610
|
-
intercom: assignment.intercom,
|
|
611
|
-
}));
|
|
612
|
-
const selected = permissionCandidates.value
|
|
613
|
-
.filter((candidate) => selectedPermissionIds.value.has(candidate.subjectId))
|
|
614
|
-
.map((candidate) => ({
|
|
615
|
-
subjectId: candidate.subjectId,
|
|
616
|
-
category: candidate.category,
|
|
617
|
-
intercom: candidate.intercom,
|
|
618
|
-
}));
|
|
619
|
-
assignments = [...retained, ...selected];
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
savingPermissions.value = true;
|
|
623
|
-
try {
|
|
624
|
-
const response = await updateSitePermissions(props.site, assignments);
|
|
625
|
-
Object.assign(permissionState, response.data);
|
|
626
|
-
permissionDialog.value = false;
|
|
627
|
-
showMessage("HID permissions updated.", "success");
|
|
628
|
-
} catch (error: unknown) {
|
|
629
|
-
showMessage(errorConverter(error), "error");
|
|
630
|
-
} finally {
|
|
631
|
-
savingPermissions.value = false;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
636
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function getConfigurationData(response: unknown) {
|
|
640
|
-
if (!isUnknownRecord(response)) return {};
|
|
641
|
-
return isUnknownRecord(response.data) ? response.data : response;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function isEnabledConfiguration(value: unknown) {
|
|
645
|
-
return value === true || value === 1 || value === "1";
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
async function loadReaderConfiguration() {
|
|
649
|
-
if (!form.readerId) return;
|
|
650
|
-
|
|
651
|
-
try {
|
|
652
|
-
const response = await getReaderConfiguration(form.readerId, {
|
|
653
|
-
general: ["online"],
|
|
654
|
-
});
|
|
655
|
-
const configuration = getConfigurationData(response);
|
|
656
|
-
const general = isUnknownRecord(configuration.general) ? configuration.general : {};
|
|
657
|
-
|
|
658
|
-
if (general.online !== undefined) {
|
|
659
|
-
form.onlineMode = isEnabledConfiguration(general.online);
|
|
660
|
-
}
|
|
661
|
-
} catch (error: unknown) {
|
|
662
|
-
console.warn("Unable to load HID reader configuration:", errorConverter(error));
|
|
663
|
-
}
|
|
664
325
|
}
|
|
665
326
|
|
|
666
327
|
function buildPayload() {
|
|
667
328
|
const metadata = {
|
|
668
329
|
...(siteData.value?.metadata || {}),
|
|
669
330
|
hidQrCodePass: {
|
|
670
|
-
enabled: form.
|
|
671
|
-
onlineMode: form.onlineMode,
|
|
331
|
+
enabled: form.enabled,
|
|
672
332
|
readerId: form.readerId,
|
|
673
333
|
qrFormat: form.qrFormat,
|
|
674
|
-
identificationMethods: { ...form.identificationMethods },
|
|
675
334
|
printer: {
|
|
676
335
|
vendorId: form.printer.vendorId.trim(),
|
|
677
336
|
productId: form.printer.productId.trim(),
|
|
@@ -693,45 +352,17 @@ async function saveSection(section: string) {
|
|
|
693
352
|
|
|
694
353
|
savingSection.value = section;
|
|
695
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
|
+
}
|
|
696
362
|
await updateSitebyId(props.site, buildPayload());
|
|
697
363
|
await loadConfig();
|
|
698
|
-
showMessage("HID
|
|
699
|
-
} catch (error:
|
|
700
|
-
showMessage(errorConverter(error), "error");
|
|
701
|
-
} finally {
|
|
702
|
-
savingSection.value = "";
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
async function saveOnlineMode() {
|
|
707
|
-
if (!props.site || !form.readerId) return;
|
|
708
|
-
|
|
709
|
-
savingSection.value = "online-mode";
|
|
710
|
-
try {
|
|
711
|
-
await setReaderConfiguration(form.readerId, {
|
|
712
|
-
general: { online: form.onlineMode ? "1" : "0" },
|
|
713
|
-
});
|
|
714
|
-
await updateSitebyId(props.site, buildPayload());
|
|
715
|
-
showMessage("Online mode updated.", "success");
|
|
716
|
-
} catch (error: unknown) {
|
|
717
|
-
await loadConfig();
|
|
718
|
-
showMessage(errorConverter(error), "error");
|
|
719
|
-
} finally {
|
|
720
|
-
savingSection.value = "";
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
async function saveIdentificationMethod(method: HidIdentificationMethod) {
|
|
725
|
-
if (!props.site) return;
|
|
726
|
-
|
|
727
|
-
form.enabled = form.identificationMethods.qrCode;
|
|
728
|
-
|
|
729
|
-
savingSection.value = `method-${method}`;
|
|
730
|
-
try {
|
|
731
|
-
await updateSitebyId(props.site, buildPayload());
|
|
732
|
-
showMessage(`${identificationMethodOptions.find((item) => item.key === method)?.label || "Identification method"} updated.`, "success");
|
|
733
|
-
} catch (error: unknown) {
|
|
734
|
-
await loadConfig();
|
|
364
|
+
showMessage("HID QR code settings saved.", "success");
|
|
365
|
+
} catch (error: any) {
|
|
735
366
|
showMessage(errorConverter(error), "error");
|
|
736
367
|
} finally {
|
|
737
368
|
savingSection.value = "";
|
|
@@ -745,36 +376,19 @@ async function scanPrinter() {
|
|
|
745
376
|
}
|
|
746
377
|
|
|
747
378
|
try {
|
|
748
|
-
const device = await
|
|
379
|
+
const device = await (navigator as any).usb.requestDevice({ filters: [] });
|
|
749
380
|
form.printer.vendorId = `0x${Number(device.vendorId).toString(16).padStart(4, "0")}`;
|
|
750
381
|
form.printer.productId = `0x${Number(device.productId).toString(16).padStart(4, "0")}`;
|
|
751
382
|
showMessage("Printer selected. Save settings to keep it.", "success");
|
|
752
|
-
} catch (error:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
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");
|
|
756
386
|
}
|
|
757
387
|
}
|
|
758
388
|
}
|
|
759
389
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const productId = Number(form.printer.productId);
|
|
763
|
-
if (!vendorId || !productId) {
|
|
764
|
-
showMessage("Please scan a valid USB printer first.", "error");
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
testingPrinter.value = true;
|
|
769
|
-
try {
|
|
770
|
-
const result = await testPrinterConnection(vendorId, productId);
|
|
771
|
-
showMessage(
|
|
772
|
-
result.success ? result.message : result.error || result.message,
|
|
773
|
-
result.success ? "success" : "error",
|
|
774
|
-
);
|
|
775
|
-
} finally {
|
|
776
|
-
testingPrinter.value = false;
|
|
777
|
-
}
|
|
390
|
+
function testPrint() {
|
|
391
|
+
showMessage("Printer settings are ready. Connect print command when HID QR payload generation is available.", "success");
|
|
778
392
|
}
|
|
779
393
|
|
|
780
394
|
function showMessage(text: string, color: string) {
|
|
@@ -818,15 +432,15 @@ function showMessage(text: string, color: string) {
|
|
|
818
432
|
padding: 30px 38px;
|
|
819
433
|
}
|
|
820
434
|
|
|
821
|
-
.
|
|
435
|
+
.section-head {
|
|
822
436
|
display: flex;
|
|
823
|
-
align-items:
|
|
437
|
+
align-items: flex-start;
|
|
824
438
|
justify-content: space-between;
|
|
825
439
|
gap: 24px;
|
|
826
|
-
|
|
440
|
+
margin-bottom: 24px;
|
|
827
441
|
}
|
|
828
442
|
|
|
829
|
-
.
|
|
443
|
+
.section-head h3,
|
|
830
444
|
.section-label {
|
|
831
445
|
margin: 0 0 6px;
|
|
832
446
|
color: #233545;
|
|
@@ -834,83 +448,16 @@ function showMessage(text: string, color: string) {
|
|
|
834
448
|
font-weight: 700;
|
|
835
449
|
}
|
|
836
450
|
|
|
837
|
-
.
|
|
838
|
-
.section-description {
|
|
451
|
+
.section-head p {
|
|
839
452
|
margin: 0;
|
|
840
453
|
color: #677280;
|
|
841
454
|
font-size: 12px;
|
|
842
|
-
line-height: 1.5;
|
|
843
455
|
}
|
|
844
456
|
|
|
845
457
|
.form-section {
|
|
846
458
|
padding: 16px 0;
|
|
847
459
|
}
|
|
848
460
|
|
|
849
|
-
.identification-section {
|
|
850
|
-
padding-bottom: 20px;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
.methods-grid {
|
|
854
|
-
display: grid;
|
|
855
|
-
grid-template-columns: repeat(6, minmax(120px, 1fr));
|
|
856
|
-
gap: 18px;
|
|
857
|
-
margin-top: 16px;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
.method-control {
|
|
861
|
-
display: flex;
|
|
862
|
-
align-items: center;
|
|
863
|
-
justify-content: space-between;
|
|
864
|
-
min-width: 0;
|
|
865
|
-
min-height: 42px;
|
|
866
|
-
padding: 0 10px 0 14px;
|
|
867
|
-
border: 1px solid #dce2e7;
|
|
868
|
-
border-radius: 6px;
|
|
869
|
-
color: #334454;
|
|
870
|
-
font-size: 12px;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
.method-control span {
|
|
874
|
-
overflow: hidden;
|
|
875
|
-
text-overflow: ellipsis;
|
|
876
|
-
white-space: nowrap;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
.method-control--disabled {
|
|
880
|
-
background-color: #f5f6f7;
|
|
881
|
-
color: #8b9297;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
.method-control :deep(.v-switch) {
|
|
885
|
-
flex: 0 0 auto;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
.method-control :deep(.v-selection-control) {
|
|
889
|
-
min-height: 32px;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
.hid-config-switch :deep(.v-switch__track) {
|
|
893
|
-
background-color: #8b9297;
|
|
894
|
-
opacity: 1;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
.hid-config-switch :deep(.v-selection-control--dirty .v-switch__track) {
|
|
898
|
-
background-color: #2eaf55 !important;
|
|
899
|
-
color: #2eaf55 !important;
|
|
900
|
-
opacity: 1;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
.hid-config-switch :deep(.v-switch__thumb) {
|
|
904
|
-
background-color: #fff !important;
|
|
905
|
-
color: #fff !important;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
.hid-config-switch.v-input--disabled :deep(.v-switch__track) {
|
|
909
|
-
background-color: #aeb4b8 !important;
|
|
910
|
-
color: #aeb4b8 !important;
|
|
911
|
-
opacity: 1;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
461
|
.printer-grid {
|
|
915
462
|
display: grid;
|
|
916
463
|
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr);
|
|
@@ -936,177 +483,6 @@ function showMessage(text: string, color: string) {
|
|
|
936
483
|
align-items: center;
|
|
937
484
|
}
|
|
938
485
|
|
|
939
|
-
.permission-section {
|
|
940
|
-
padding-top: 2px;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
.permission-row {
|
|
944
|
-
display: grid;
|
|
945
|
-
grid-template-columns: minmax(260px, 1fr) minmax(520px, 1.35fr);
|
|
946
|
-
gap: 36px;
|
|
947
|
-
align-items: center;
|
|
948
|
-
padding: 18px 0;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
.permission-row--intercom {
|
|
952
|
-
padding-bottom: 4px;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
.permission-copy {
|
|
956
|
-
min-width: 0;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
.permission-actions {
|
|
960
|
-
display: grid;
|
|
961
|
-
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
|
962
|
-
gap: 12px;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
.permission-actions--single {
|
|
966
|
-
grid-template-columns: minmax(180px, 240px);
|
|
967
|
-
justify-content: end;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
.permission-button {
|
|
971
|
-
justify-content: flex-start;
|
|
972
|
-
min-height: 42px;
|
|
973
|
-
padding-inline: 12px;
|
|
974
|
-
border-color: #dce2e7;
|
|
975
|
-
color: #334454;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
.permission-count {
|
|
979
|
-
display: inline-grid;
|
|
980
|
-
place-items: center;
|
|
981
|
-
width: 20px;
|
|
982
|
-
height: 20px;
|
|
983
|
-
margin-right: 8px;
|
|
984
|
-
border-radius: 50%;
|
|
985
|
-
background: #dce2e7;
|
|
986
|
-
color: #526170;
|
|
987
|
-
font-size: 11px;
|
|
988
|
-
font-weight: 700;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
.permission-button:not(:disabled):hover {
|
|
992
|
-
border-color: #1473c9;
|
|
993
|
-
color: #0a5da7;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
.permission-dialog {
|
|
997
|
-
overflow: hidden;
|
|
998
|
-
border-radius: 6px;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.permission-dialog__header {
|
|
1002
|
-
display: flex;
|
|
1003
|
-
align-items: flex-start;
|
|
1004
|
-
justify-content: space-between;
|
|
1005
|
-
gap: 20px;
|
|
1006
|
-
padding: 20px 22px 16px;
|
|
1007
|
-
border-bottom: 1px solid #e5e9ed;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
.permission-dialog__title {
|
|
1011
|
-
color: #172534;
|
|
1012
|
-
font-size: 18px;
|
|
1013
|
-
font-weight: 700;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
.permission-dialog__subtitle {
|
|
1017
|
-
margin-top: 4px;
|
|
1018
|
-
color: #687583;
|
|
1019
|
-
font-size: 12px;
|
|
1020
|
-
white-space: normal;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
.permission-dialog__body {
|
|
1024
|
-
display: grid;
|
|
1025
|
-
gap: 16px;
|
|
1026
|
-
padding: 18px 22px;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
.permission-list {
|
|
1030
|
-
min-height: 240px;
|
|
1031
|
-
max-height: 390px;
|
|
1032
|
-
overflow-y: auto;
|
|
1033
|
-
border: 1px solid #dfe5ea;
|
|
1034
|
-
border-radius: 6px;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
.permission-list--loading {
|
|
1038
|
-
display: grid;
|
|
1039
|
-
place-items: center;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
.permission-list-item {
|
|
1043
|
-
display: grid;
|
|
1044
|
-
grid-template-columns: 34px minmax(0, 1fr) auto;
|
|
1045
|
-
gap: 10px;
|
|
1046
|
-
align-items: center;
|
|
1047
|
-
min-height: 62px;
|
|
1048
|
-
padding: 8px 14px 8px 8px;
|
|
1049
|
-
border-bottom: 1px solid #edf0f3;
|
|
1050
|
-
cursor: pointer;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
.permission-list-item:last-child {
|
|
1054
|
-
border-bottom: 0;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
.permission-list-item:hover {
|
|
1058
|
-
background: #f7fafc;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
.permission-list-item__content {
|
|
1062
|
-
display: grid;
|
|
1063
|
-
min-width: 0;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
.permission-list-item__content strong,
|
|
1067
|
-
.permission-list-item__content small {
|
|
1068
|
-
overflow: hidden;
|
|
1069
|
-
text-overflow: ellipsis;
|
|
1070
|
-
white-space: nowrap;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
.permission-list-item__content strong {
|
|
1074
|
-
color: #243443;
|
|
1075
|
-
font-size: 13px;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
.permission-list-item__content small,
|
|
1079
|
-
.permission-list-item__group {
|
|
1080
|
-
color: #778391;
|
|
1081
|
-
font-size: 11px;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
.permission-list-item__group {
|
|
1085
|
-
padding: 4px 8px;
|
|
1086
|
-
border-radius: 4px;
|
|
1087
|
-
background: #eef2f5;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
.permission-empty {
|
|
1091
|
-
display: flex;
|
|
1092
|
-
flex-direction: column;
|
|
1093
|
-
align-items: center;
|
|
1094
|
-
justify-content: center;
|
|
1095
|
-
gap: 10px;
|
|
1096
|
-
min-height: 240px;
|
|
1097
|
-
padding: 24px;
|
|
1098
|
-
color: #75818c;
|
|
1099
|
-
font-size: 13px;
|
|
1100
|
-
text-align: center;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
.permission-dialog__actions {
|
|
1104
|
-
justify-content: flex-end;
|
|
1105
|
-
gap: 8px;
|
|
1106
|
-
padding: 14px 22px;
|
|
1107
|
-
border-top: 1px solid #e5e9ed;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
486
|
.save-button {
|
|
1111
487
|
background: #082e43;
|
|
1112
488
|
color: #ffffff;
|
|
@@ -1216,20 +592,11 @@ function showMessage(text: string, color: string) {
|
|
|
1216
592
|
}
|
|
1217
593
|
|
|
1218
594
|
@media (max-width: 1100px) {
|
|
1219
|
-
.methods-grid {
|
|
1220
|
-
grid-template-columns: repeat(3, minmax(140px, 1fr));
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
595
|
.printer-grid,
|
|
1224
596
|
.template-grid,
|
|
1225
|
-
.validity-grid
|
|
1226
|
-
.permission-row {
|
|
597
|
+
.validity-grid {
|
|
1227
598
|
grid-template-columns: 1fr 1fr;
|
|
1228
599
|
}
|
|
1229
|
-
|
|
1230
|
-
.permission-actions {
|
|
1231
|
-
grid-template-columns: 1fr;
|
|
1232
|
-
}
|
|
1233
600
|
}
|
|
1234
601
|
|
|
1235
602
|
@media (max-width: 700px) {
|
|
@@ -1237,30 +604,15 @@ function showMessage(text: string, color: string) {
|
|
|
1237
604
|
padding: 22px 16px;
|
|
1238
605
|
}
|
|
1239
606
|
|
|
1240
|
-
.
|
|
607
|
+
.section-head,
|
|
1241
608
|
.printer-grid,
|
|
1242
609
|
.template-grid,
|
|
1243
|
-
.validity-grid
|
|
1244
|
-
.methods-grid,
|
|
1245
|
-
.permission-row,
|
|
1246
|
-
.permission-actions {
|
|
610
|
+
.validity-grid {
|
|
1247
611
|
grid-template-columns: 1fr;
|
|
1248
612
|
}
|
|
1249
613
|
|
|
1250
|
-
.
|
|
614
|
+
.section-head {
|
|
1251
615
|
display: grid;
|
|
1252
616
|
}
|
|
1253
|
-
|
|
1254
|
-
.permission-actions--single {
|
|
1255
|
-
justify-content: stretch;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
.permission-list-item {
|
|
1259
|
-
grid-template-columns: 34px minmax(0, 1fr);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
.permission-list-item__group {
|
|
1263
|
-
display: none;
|
|
1264
|
-
}
|
|
1265
617
|
}
|
|
1266
618
|
</style>
|