@7365admin1/layer-common 3.2.2-staging.124 → 3.2.2-staging.126
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/AccessCardDetailsDialog.vue +125 -79
- package/components/AttendanceMapSearchDialog.vue +42 -44
- package/components/AttendanceSettingsDialog.vue +62 -63
- package/components/DashboardMain.vue +151 -283
- package/components/DashboardPanel.vue +84 -21
- package/components/RolePermissionFormCreate.vue +104 -78
- package/components/RolePermissionFormPreviewUpdate.vue +111 -108
- package/components/VisitorForm.vue +402 -218
- package/package.json +1 -1
- package/utils/status.test.ts +2 -2
- package/utils/status.ts +4 -0
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-card width="100%" :loading="processing">
|
|
3
|
-
<
|
|
4
|
-
<v-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<span
|
|
14
|
-
|
|
15
|
-
}}</span>
|
|
16
|
-
<span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
|
|
17
|
-
<span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
|
|
2
|
+
<v-card width="100%" class="screen-modal visitor-form" :loading="processing">
|
|
3
|
+
<div class="visitor-form__head">
|
|
4
|
+
<v-card-title class="text-capitalize">
|
|
5
|
+
{{ formMode }} {{ formatVisitorType(type) }}
|
|
6
|
+
</v-card-title>
|
|
7
|
+
<AppButton variant="icon" icon="mdi-close" aria-label="Close" @click="emit('close:all')" />
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="screen-modal__body visitor-form__body filter-field">
|
|
11
|
+
<div class="visitor-form__section-row">
|
|
12
|
+
<span class="visitor-form__section">{{ formTitle }}</span>
|
|
13
|
+
<span v-if="withStep2 || withStep3" class="visitor-form__step">Step
|
|
14
|
+
<span class="visitor-form__step-now">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
|
|
18
15
|
</div>
|
|
19
16
|
<v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
|
|
20
17
|
<v-row no-gutters class="pt-4">
|
|
@@ -27,7 +24,8 @@
|
|
|
27
24
|
<v-row>
|
|
28
25
|
<v-col cols="12">
|
|
29
26
|
<InputLabel class="text-capitalize" title="NRIC" :required="requireNRIC" />
|
|
30
|
-
<v-menu v-model="nricSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
27
|
+
<v-menu v-model="nricSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
28
|
+
content-class="visitor-form__menu">
|
|
31
29
|
<template #activator="{ props }">
|
|
32
30
|
<InputNRICNumber v-bind="props" v-model="visitor.nric" density="comfortable"
|
|
33
31
|
@focus="isNricFieldFocused = true" @blur="isNricFieldFocused = false"
|
|
@@ -43,7 +41,8 @@
|
|
|
43
41
|
</v-list-item>
|
|
44
42
|
</div>
|
|
45
43
|
<div class="search-results-close pa-1">
|
|
46
|
-
<v-btn block variant="
|
|
44
|
+
<v-btn block variant="outlined" class="screen-btn-ghost"
|
|
45
|
+
@click.stop="closeAutofillSearchResults('nric')">
|
|
47
46
|
Close
|
|
48
47
|
</v-btn>
|
|
49
48
|
</div>
|
|
@@ -57,7 +56,8 @@
|
|
|
57
56
|
<v-row>
|
|
58
57
|
<v-col cols="12">
|
|
59
58
|
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
60
|
-
<v-menu v-model="phoneSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
59
|
+
<v-menu v-model="phoneSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
60
|
+
content-class="visitor-form__menu">
|
|
61
61
|
<template #activator="{ props }">
|
|
62
62
|
<InputPhoneNumberV2 v-bind="props" v-model="visitor.contact" :rules="[requiredRule]"
|
|
63
63
|
@focus="isPhoneFieldFocused = true" @blur="isPhoneFieldFocused = false" density="comfortable"
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
</v-list-item>
|
|
73
73
|
</div>
|
|
74
74
|
<div class="search-results-close pa-1">
|
|
75
|
-
<v-btn block variant="
|
|
75
|
+
<v-btn block variant="outlined" class="screen-btn-ghost"
|
|
76
|
+
@click.stop="closeAutofillSearchResults('contact')">
|
|
76
77
|
Close
|
|
77
78
|
</v-btn>
|
|
78
79
|
</div>
|
|
@@ -88,7 +89,8 @@
|
|
|
88
89
|
<v-col cols="12">
|
|
89
90
|
<InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
|
|
90
91
|
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
91
|
-
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
92
|
+
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false"
|
|
93
|
+
content-class="visitor-form__menu">
|
|
92
94
|
<template #activator="{ props }">
|
|
93
95
|
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
94
96
|
@update:model-value="handleUpdateVehicleNumber" @focus="handleFocusPlateNumber"
|
|
@@ -103,7 +105,7 @@
|
|
|
103
105
|
</v-list-item>
|
|
104
106
|
</div>
|
|
105
107
|
<div class="search-results-close pa-1">
|
|
106
|
-
<v-btn block variant="
|
|
108
|
+
<v-btn block variant="outlined" class="screen-btn-ghost"
|
|
107
109
|
@click.stop="closeAutofillSearchResults('vehicleNumber')">
|
|
108
110
|
Close
|
|
109
111
|
</v-btn>
|
|
@@ -150,7 +152,15 @@
|
|
|
150
152
|
<v-col v-if="shouldShowField('deliveryType')" cols="12">
|
|
151
153
|
<v-row>
|
|
152
154
|
<v-col cols="12">
|
|
153
|
-
|
|
155
|
+
<!-- The handoff never draws the delivery type, so this takes the
|
|
156
|
+
form's OWN established pattern rather than inventing one: an
|
|
157
|
+
`InputLabel` like every other field, and `accent` - the
|
|
158
|
+
colour the converted NRIC picker's radios already use - in
|
|
159
|
+
place of `primary`, which is the darker link tone. Without
|
|
160
|
+
the label this was the only required field in the form with
|
|
161
|
+
nothing naming it: just a bare Food/Parcel pair. -->
|
|
162
|
+
<InputLabel class="text-capitalize" title="Delivery Type" required />
|
|
163
|
+
<v-radio-group v-model="visitor.deliveryType" inline color="accent" :rules="[requiredRule]">
|
|
154
164
|
<v-radio label="Food" value="Food"></v-radio>
|
|
155
165
|
<v-radio label="Parcel" value="Parcel"></v-radio>
|
|
156
166
|
</v-radio-group>
|
|
@@ -286,34 +296,23 @@
|
|
|
286
296
|
</v-col>
|
|
287
297
|
</v-row>
|
|
288
298
|
</v-form>
|
|
289
|
-
</
|
|
290
|
-
|
|
291
|
-
<v-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
|
|
307
|
-
</v-col>
|
|
308
|
-
<v-col cols="6">
|
|
309
|
-
<v-btn v-if="showNextButton" tile block variant="flat" color="primary-button" class="text-none" size="48"
|
|
310
|
-
:disabled="processing" @click="handleNextPage" :text="'Next'" />
|
|
311
|
-
<v-btn v-else tile block variant="flat" color="black" class="text-none" size="48"
|
|
312
|
-
:disabled="!validForm || processing || membersFieldIncomplete" @click="submit"
|
|
313
|
-
:text="formMode == 'add' ? 'Submit' : 'Update'" />
|
|
314
|
-
</v-col>
|
|
315
|
-
</v-row>
|
|
316
|
-
</v-toolbar>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<p v-if="errorMessage" class="visitor-form__error">
|
|
302
|
+
{{ errorMessage }}
|
|
303
|
+
</p>
|
|
304
|
+
|
|
305
|
+
<div class="visitor-form__footer">
|
|
306
|
+
<AppButton v-if="formMode === 'add' && (withStep2 || withStep3) && contractorStep > 1" variant="ghost"
|
|
307
|
+
@click="handleGoToPreviousPage">Back</AppButton>
|
|
308
|
+
<AppButton v-else-if="formMode === 'add' || formMode === 'register'" variant="ghost" @click="backToSelection">
|
|
309
|
+
Back to Selection</AppButton>
|
|
310
|
+
<AppButton v-else variant="ghost" @click="emit('close:all')">Close</AppButton>
|
|
311
|
+
|
|
312
|
+
<AppButton v-if="showNextButton" :disabled="processing" @click="handleNextPage">Next</AppButton>
|
|
313
|
+
<AppButton v-else :disabled="!validForm || processing || membersFieldIncomplete" @click="submit">
|
|
314
|
+
{{ formMode == 'add' ? 'Submit' : 'Update' }}</AppButton>
|
|
315
|
+
</div>
|
|
317
316
|
|
|
318
317
|
<v-dialog v-model="dialog.vehicleNumberUsersList" v-if="vehicleNumberUserItems.length > 0" persistent
|
|
319
318
|
max-width="600">
|
|
@@ -323,72 +322,73 @@
|
|
|
323
322
|
</v-dialog>
|
|
324
323
|
|
|
325
324
|
<v-dialog v-model="dialog.nricAutofillOptions" max-width="430">
|
|
326
|
-
<v-card v-if="selectedNricSearchResult" class="nric-options-card">
|
|
327
|
-
<
|
|
328
|
-
<v-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
<v-card-text class="pa-4 pt-1">
|
|
332
|
-
<h2 class="nric-options-title font-weight-bold mb-4">Select</h2>
|
|
325
|
+
<v-card v-if="selectedNricSearchResult" class="screen-modal nric-options-card">
|
|
326
|
+
<div class="visitor-form__head">
|
|
327
|
+
<v-card-title>Select</v-card-title>
|
|
328
|
+
<AppButton variant="icon" icon="mdi-close" aria-label="Close" @click="closeNricAutofillOptions" />
|
|
329
|
+
</div>
|
|
333
330
|
|
|
331
|
+
<div class="screen-modal__body nric-options-body">
|
|
334
332
|
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
335
333
|
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
336
334
|
<span v-if="!visitor.nric || currentAutofillSource === 'nric'" class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
337
335
|
</div>
|
|
338
336
|
|
|
339
337
|
<template v-if="(!visitor.contact || currentAutofillSource === 'contact') && selectedNricContactOptions.length >= 1">
|
|
340
|
-
<h3 class="nric-options-section-title
|
|
341
|
-
<v-radio-group v-model="selectedNricContact" hide-details color="
|
|
338
|
+
<h3 class="nric-options-section-title mb-2">Contact</h3>
|
|
339
|
+
<v-radio-group v-model="selectedNricContact" hide-details color="accent" class="nric-option-group">
|
|
342
340
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
343
341
|
class="nric-option-radio" />
|
|
344
342
|
</v-radio-group>
|
|
345
343
|
</template>
|
|
346
344
|
|
|
347
345
|
<template v-if="(!visitor.plateNumber || currentAutofillSource === 'vehicleNumber') && selectedNricPlateOptions.length >= 1">
|
|
348
|
-
<h3 class="nric-options-section-title
|
|
349
|
-
<v-radio-group v-model="selectedNricPlate" hide-details color="
|
|
346
|
+
<h3 class="nric-options-section-title mb-2 mt-5">Vehicle</h3>
|
|
347
|
+
<v-radio-group v-model="selectedNricPlate" hide-details color="accent" class="nric-option-group">
|
|
350
348
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
351
349
|
class="nric-option-radio" />
|
|
352
350
|
</v-radio-group>
|
|
353
351
|
</template>
|
|
354
|
-
</
|
|
352
|
+
</div>
|
|
355
353
|
|
|
356
|
-
<
|
|
357
|
-
<
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
Select
|
|
361
|
-
</v-btn>
|
|
362
|
-
</v-card-actions>
|
|
354
|
+
<div class="visitor-form__footer">
|
|
355
|
+
<AppButton variant="ghost" @click="closeNricAutofillOptions">Cancel</AppButton>
|
|
356
|
+
<AppButton @click="confirmNricAutofillOptions">Select</AppButton>
|
|
357
|
+
</div>
|
|
363
358
|
</v-card>
|
|
364
359
|
</v-dialog>
|
|
365
360
|
|
|
366
361
|
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="1200">
|
|
367
|
-
<v-card
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
<
|
|
373
|
-
<
|
|
362
|
+
<v-card class="screen-modal"
|
|
363
|
+
:loading="loading.checkingOut || loading.searchingNonCheckedOut">
|
|
364
|
+
<!-- No heading: the product gives this dialog none, and inventing copy
|
|
365
|
+
is not this batch's call. The design's modal frame does draw a
|
|
366
|
+
title, so that gap is raised on the PR as a copy decision. -->
|
|
367
|
+
<div class="visitor-form__head visitor-form__head--bare">
|
|
368
|
+
<AppButton variant="icon" icon="mdi-close" aria-label="Close"
|
|
369
|
+
@click="dialog.showNonCheckedOutDialog = false" />
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div class="screen-modal__body checkout-dialog__body">
|
|
373
|
+
<p class="checkout-dialog__lead">
|
|
374
374
|
The following list needs to check-out before proceeding to add related to the NRIC/Phone Number below:
|
|
375
375
|
</p>
|
|
376
376
|
|
|
377
|
-
<div class="
|
|
377
|
+
<div class="checkout-dialog__subject">
|
|
378
378
|
<div v-if="!skipNonCheckedOutNric">NRIC: {{ visitor.nric || "N/A" }}</div>
|
|
379
379
|
<div>Phone Number: {{ visitor.contact || "N/A" }}</div>
|
|
380
380
|
</div>
|
|
381
381
|
|
|
382
|
-
<v-data-table class="
|
|
382
|
+
<v-data-table class="table-card" :headers="nonCheckedOutVisitorHeaders"
|
|
383
383
|
:items="matchingNonCheckedOutVisitors" item-value="_id" :items-per-page="5" hide-default-footer>
|
|
384
384
|
<template #item.name="{ item }">
|
|
385
385
|
<div class="d-flex align-center ga-3">
|
|
386
|
-
<v-avatar
|
|
387
|
-
<span
|
|
386
|
+
<v-avatar class="checkout-dialog__avatar" size="32">
|
|
387
|
+
<span>{{ getVisitorInitial(item.name) }}</span>
|
|
388
388
|
</v-avatar>
|
|
389
389
|
<div>
|
|
390
|
-
<div class="
|
|
391
|
-
<div class="
|
|
390
|
+
<div class="checkout-dialog__name">{{ item.name || "N/A" }}</div>
|
|
391
|
+
<div class="checkout-dialog__meta">
|
|
392
392
|
<v-icon icon="mdi-card-account-details-outline" size="16" class="mr-1" />
|
|
393
393
|
{{ maskNRIC(item.nric) || "N/A" }}
|
|
394
394
|
</div>
|
|
@@ -425,11 +425,11 @@
|
|
|
425
425
|
|
|
426
426
|
<template #item.checkInOut="{ item }">
|
|
427
427
|
<div class="py-2">
|
|
428
|
-
<div class="mb-2">
|
|
429
|
-
<v-icon icon="mdi-clock-check-outline"
|
|
428
|
+
<div class="checkout-dialog__in mb-2">
|
|
429
|
+
<v-icon icon="mdi-clock-check-outline" size="20" class="mr-1" />
|
|
430
430
|
{{ formatNonCheckedOutDate(item.checkIn) }}
|
|
431
431
|
</div>
|
|
432
|
-
<v-btn
|
|
432
|
+
<v-btn class="screen-btn-primary" variant="flat"
|
|
433
433
|
:loading="loading.checkingOut && selectedCheckoutVisitorId === item._id" :disabled="!item._id"
|
|
434
434
|
@click.stop="item._id && handleCheckout(item._id)">
|
|
435
435
|
Check Out
|
|
@@ -437,35 +437,36 @@
|
|
|
437
437
|
</div>
|
|
438
438
|
</template>
|
|
439
439
|
</v-data-table>
|
|
440
|
+
</div>
|
|
440
441
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
</v-card-text>
|
|
442
|
+
<div class="visitor-form__footer" v-if="!skipNonCheckedOutNric">
|
|
443
|
+
<v-btn class="screen-btn-primary" variant="flat" :loading="loading.searchingNonCheckedOut"
|
|
444
|
+
@click="handleSkipNricNonCheckedOut">
|
|
445
|
+
Skip NRIC: {{ visitor.nric || "N/A" }}
|
|
446
|
+
</v-btn>
|
|
447
|
+
</div>
|
|
448
448
|
</v-card>
|
|
449
449
|
</v-dialog>
|
|
450
450
|
|
|
451
451
|
<v-dialog v-if="enableUnregistered" v-model="dialog.showUnregisteredPlateDialog" max-width="900" persistent>
|
|
452
|
-
<v-card
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
<v-card class="screen-modal">
|
|
453
|
+
<!-- No heading in the product; see the note on the check-out dialog. -->
|
|
454
|
+
<div class="visitor-form__head visitor-form__head--bare">
|
|
455
|
+
<AppButton variant="icon" icon="mdi-close" aria-label="Close" @click="closeUnregisteredPlateDialog" />
|
|
456
|
+
</div>
|
|
456
457
|
|
|
457
|
-
<
|
|
458
|
-
<v-icon
|
|
458
|
+
<div class="screen-modal__body unreg-dialog__body">
|
|
459
|
+
<v-icon class="unreg-dialog__alert" size="96">mdi-alert-circle-outline</v-icon>
|
|
459
460
|
|
|
460
|
-
<
|
|
461
|
+
<p class="unreg-dialog__lead">
|
|
461
462
|
Vehicle Number {{ unregisteredPlateSearchText }} is similar to below
|
|
462
463
|
<br />
|
|
463
464
|
Unregistered visitors last 24 hours. Please select to
|
|
464
465
|
<br />
|
|
465
466
|
Update
|
|
466
|
-
</
|
|
467
|
+
</p>
|
|
467
468
|
|
|
468
|
-
<v-table class="
|
|
469
|
+
<v-table class="table-card unreg-dialog__table">
|
|
469
470
|
<thead>
|
|
470
471
|
<tr>
|
|
471
472
|
<th class="text-left">Plate Number</th>
|
|
@@ -475,45 +476,38 @@
|
|
|
475
476
|
</thead>
|
|
476
477
|
<tbody>
|
|
477
478
|
<tr v-for="match in unregisteredPlateMatches" :key="match._id || match.plateNumber">
|
|
478
|
-
<td class="text-left
|
|
479
|
+
<td class="text-left text-uppercase unreg-dialog__plate">
|
|
479
480
|
{{ match.plateNumber || "N/A" }}
|
|
480
481
|
</td>
|
|
481
482
|
<td class="text-left">
|
|
482
483
|
{{ formatUnregisteredCheckIn(match.checkIn) }}
|
|
483
484
|
</td>
|
|
484
485
|
<td class="text-center">
|
|
485
|
-
<
|
|
486
|
-
@click="selectUnregisteredPlateMatch(match)">
|
|
487
|
-
Update
|
|
488
|
-
</v-btn>
|
|
486
|
+
<AppButton @click="selectUnregisteredPlateMatch(match)">Update</AppButton>
|
|
489
487
|
</td>
|
|
490
488
|
</tr>
|
|
491
489
|
</tbody>
|
|
492
490
|
</v-table>
|
|
493
|
-
</
|
|
491
|
+
</div>
|
|
494
492
|
|
|
495
|
-
<
|
|
496
|
-
<
|
|
497
|
-
|
|
498
|
-
</v-btn>
|
|
499
|
-
</v-card-actions>
|
|
493
|
+
<div class="visitor-form__footer">
|
|
494
|
+
<AppButton variant="ghost" @click="closeUnregisteredPlateDialog">Cancel & Add New</AppButton>
|
|
495
|
+
</div>
|
|
500
496
|
</v-card>
|
|
501
497
|
</v-dialog>
|
|
502
498
|
|
|
503
499
|
<v-dialog v-model="hidQrPreviewDialog" max-width="440" persistent>
|
|
504
|
-
<v-card class="hid-qr-preview-card">
|
|
505
|
-
<
|
|
506
|
-
<v-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
<v-card-text class="px-8 pb-6 text-center">
|
|
513
|
-
<div class="text-h6 font-weight-bold">
|
|
500
|
+
<v-card class="screen-modal hid-qr-preview-card">
|
|
501
|
+
<div class="visitor-form__head">
|
|
502
|
+
<v-card-title>Visitor QR Code</v-card-title>
|
|
503
|
+
<AppButton variant="icon" icon="mdi-close" aria-label="Close" @click="closeHidQrPreview" />
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<div class="screen-modal__body hid-qr__body">
|
|
507
|
+
<div class="hid-qr__header">
|
|
514
508
|
{{ hidQrTemplate.header || "Visitor Access Pass" }}
|
|
515
509
|
</div>
|
|
516
|
-
<div class="
|
|
510
|
+
<div class="hid-qr__subtext">
|
|
517
511
|
{{ hidQrTemplate.subtext || "Scan at the HID reader for entry" }}
|
|
518
512
|
</div>
|
|
519
513
|
|
|
@@ -525,38 +519,32 @@
|
|
|
525
519
|
</button>
|
|
526
520
|
</div>
|
|
527
521
|
|
|
528
|
-
<
|
|
529
|
-
variant="
|
|
530
|
-
|
|
531
|
-
class="text-none"
|
|
532
|
-
:prepend-icon="hidQrVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
522
|
+
<AppButton
|
|
523
|
+
variant="ghost"
|
|
524
|
+
:icon="hidQrVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
533
525
|
@click="hidQrVisible = !hidQrVisible"
|
|
534
526
|
>
|
|
535
527
|
{{ hidQrVisible ? "Hide QR code" : "Show QR code" }}
|
|
536
|
-
</
|
|
528
|
+
</AppButton>
|
|
537
529
|
|
|
538
|
-
<div class="
|
|
539
|
-
<div v-if="hidQrReader?.portalName" class="
|
|
530
|
+
<div class="hid-qr__name">{{ visitor.name }}</div>
|
|
531
|
+
<div v-if="hidQrReader?.portalName" class="hid-qr__meta">
|
|
540
532
|
{{ hidQrReader.name || "HID Reader" }} · {{ hidQrReader.portalName }}
|
|
541
533
|
</div>
|
|
542
|
-
<div v-if="hidQrPreview.expiresAt" class="
|
|
534
|
+
<div v-if="hidQrPreview.expiresAt" class="hid-qr__meta">
|
|
543
535
|
Valid until {{ formatHidQrExpiry(hidQrPreview.expiresAt) }}
|
|
544
536
|
</div>
|
|
545
|
-
<
|
|
537
|
+
<StatusChip
|
|
546
538
|
v-if="hidQrPreview.expiresAt"
|
|
547
539
|
class="mt-2"
|
|
548
|
-
|
|
549
|
-
:
|
|
550
|
-
|
|
551
|
-
>
|
|
552
|
-
{{ hidQrExpired ? "Expired" : `Expires in ${formatHidQrCountdown(hidQrRemainingSeconds)}` }}
|
|
553
|
-
</v-chip>
|
|
540
|
+
:tone="hidQrExpired ? 'err' : 'ok'"
|
|
541
|
+
:label="hidQrExpired ? 'Expired' : `Expires in ${formatHidQrCountdown(hidQrRemainingSeconds)}`"
|
|
542
|
+
/>
|
|
554
543
|
|
|
555
544
|
<v-btn
|
|
556
545
|
v-if="hidQrExpired"
|
|
557
546
|
block
|
|
558
|
-
class="mt-4
|
|
559
|
-
color="primary-button"
|
|
547
|
+
class="mt-4 screen-btn-primary"
|
|
560
548
|
variant="flat"
|
|
561
549
|
prepend-icon="mdi-refresh"
|
|
562
550
|
:loading="hidQrRegenerating"
|
|
@@ -568,42 +556,22 @@
|
|
|
568
556
|
<v-alert v-if="hidQrPreviewError" type="error" variant="tonal" density="compact" class="mt-4 text-left">
|
|
569
557
|
{{ hidQrPreviewError }}
|
|
570
558
|
</v-alert>
|
|
571
|
-
</
|
|
559
|
+
</div>
|
|
572
560
|
|
|
573
|
-
<
|
|
574
|
-
|
|
575
|
-
<v-btn
|
|
576
|
-
variant="text"
|
|
577
|
-
height="52"
|
|
578
|
-
class="flex-grow-1 text-none"
|
|
579
|
-
prepend-icon="mdi-download"
|
|
580
|
-
@click="downloadHidQrCode"
|
|
581
|
-
>
|
|
561
|
+
<div class="visitor-form__footer">
|
|
562
|
+
<AppButton variant="ghost" icon="mdi-download" @click="downloadHidQrCode">
|
|
582
563
|
Download
|
|
583
|
-
</
|
|
584
|
-
<
|
|
564
|
+
</AppButton>
|
|
565
|
+
<AppButton
|
|
585
566
|
v-if="hasHidQrPrinterConfig"
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
height="52"
|
|
589
|
-
class="flex-grow-1 text-none"
|
|
590
|
-
prepend-icon="mdi-printer"
|
|
591
|
-
:loading="hidQrPrinting"
|
|
567
|
+
icon="mdi-printer"
|
|
568
|
+
:disabled="hidQrPrinting"
|
|
592
569
|
@click="printHidQrCodePass"
|
|
593
570
|
>
|
|
594
571
|
Print
|
|
595
|
-
</
|
|
596
|
-
<v-
|
|
597
|
-
|
|
598
|
-
variant="flat"
|
|
599
|
-
color="primary-button"
|
|
600
|
-
height="52"
|
|
601
|
-
class="flex-grow-1 text-none"
|
|
602
|
-
@click="closeHidQrPreview"
|
|
603
|
-
>
|
|
604
|
-
Close
|
|
605
|
-
</v-btn>
|
|
606
|
-
</v-card-actions>
|
|
572
|
+
</AppButton>
|
|
573
|
+
<AppButton v-else @click="closeHidQrPreview">Close</AppButton>
|
|
574
|
+
</div>
|
|
607
575
|
</v-card>
|
|
608
576
|
</v-dialog>
|
|
609
577
|
</v-card>
|
|
@@ -891,28 +859,6 @@ type HidQrReaderSummary = {
|
|
|
891
859
|
};
|
|
892
860
|
const hidQrReader = ref<HidQrReaderSummary | null>(null);
|
|
893
861
|
|
|
894
|
-
watch(
|
|
895
|
-
[hidQrReaderId, () => prop.site],
|
|
896
|
-
async ([readerId, site]) => {
|
|
897
|
-
hidQrReader.value = null;
|
|
898
|
-
if (!readerId || !site) return;
|
|
899
|
-
try {
|
|
900
|
-
const response = await getReaders({ site, page: 1, limit: 100 });
|
|
901
|
-
const readers: unknown = response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
|
|
902
|
-
hidQrReader.value = Array.isArray(readers)
|
|
903
|
-
? readers.find((reader: unknown): reader is HidQrReaderSummary => (
|
|
904
|
-
typeof reader === "object"
|
|
905
|
-
&& reader !== null
|
|
906
|
-
&& String((reader as Record<string, unknown>)._id || "") === readerId
|
|
907
|
-
)) || null
|
|
908
|
-
: null;
|
|
909
|
-
} catch {
|
|
910
|
-
hidQrReader.value = null;
|
|
911
|
-
}
|
|
912
|
-
},
|
|
913
|
-
{ immediate: true },
|
|
914
|
-
);
|
|
915
|
-
|
|
916
862
|
const hidQrCodePassAllowed = computed(() => {
|
|
917
863
|
return (
|
|
918
864
|
hidQrCodePassEnabled.value &&
|
|
@@ -1472,6 +1418,33 @@ const {
|
|
|
1472
1418
|
getSiteById(prop.site)
|
|
1473
1419
|
);
|
|
1474
1420
|
|
|
1421
|
+
// Has to sit BELOW `siteData`, not beside `hidQrReaderId` up at the HID block.
|
|
1422
|
+
// `hidQrReaderId` reads `hidQrCodePassConfig`, which reads `siteData`, and Vue
|
|
1423
|
+
// runs a watcher's source getter the moment `watch()` is called - so declared
|
|
1424
|
+
// any earlier this throws `Cannot access 'siteData' before initialization` and
|
|
1425
|
+
// the whole form fails to mount.
|
|
1426
|
+
watch(
|
|
1427
|
+
[hidQrReaderId, () => prop.site],
|
|
1428
|
+
async ([readerId, site]) => {
|
|
1429
|
+
hidQrReader.value = null;
|
|
1430
|
+
if (!readerId || !site) return;
|
|
1431
|
+
try {
|
|
1432
|
+
const response = await getReaders({ site, page: 1, limit: 100 });
|
|
1433
|
+
const readers: unknown = response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
|
|
1434
|
+
hidQrReader.value = Array.isArray(readers)
|
|
1435
|
+
? readers.find((reader: unknown): reader is HidQrReaderSummary => (
|
|
1436
|
+
typeof reader === "object"
|
|
1437
|
+
&& reader !== null
|
|
1438
|
+
&& String((reader as Record<string, unknown>)._id || "") === readerId
|
|
1439
|
+
)) || null
|
|
1440
|
+
: null;
|
|
1441
|
+
} catch {
|
|
1442
|
+
hidQrReader.value = null;
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
{ immediate: true },
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1475
1448
|
const {
|
|
1476
1449
|
data: blocksListData,
|
|
1477
1450
|
refresh: refreshBlockListData,
|
|
@@ -2379,11 +2352,81 @@ onMounted(() => {
|
|
|
2379
2352
|
pointer-events: none;
|
|
2380
2353
|
}
|
|
2381
2354
|
|
|
2355
|
+
.visitor-form__head {
|
|
2356
|
+
display: flex;
|
|
2357
|
+
align-items: center;
|
|
2358
|
+
justify-content: space-between;
|
|
2359
|
+
padding-right: 12px;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/* The card must not scroll as a whole - that takes the Back/Submit footer off
|
|
2363
|
+
screen with it. The BODY scrolls; `.screen-modal` caps the card's height. */
|
|
2364
|
+
.visitor-form__body {
|
|
2365
|
+
padding: 4px 20px 0;
|
|
2366
|
+
max-height: calc(100vh - 160px);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
.visitor-form__section-row {
|
|
2370
|
+
display: flex;
|
|
2371
|
+
align-items: baseline;
|
|
2372
|
+
justify-content: space-between;
|
|
2373
|
+
gap: 8px;
|
|
2374
|
+
margin-bottom: 4px;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
.visitor-form__section {
|
|
2378
|
+
font-size: var(--fs-cell);
|
|
2379
|
+
font-weight: 800;
|
|
2380
|
+
color: var(--text);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/* The handoff draws no step counter, so this takes the established type: the
|
|
2384
|
+
cell size, the accent for the step you are on, `--text2` for the rest. */
|
|
2385
|
+
.visitor-form__step {
|
|
2386
|
+
font-size: var(--fs-cell);
|
|
2387
|
+
font-weight: 700;
|
|
2388
|
+
color: var(--text2);
|
|
2389
|
+
white-space: nowrap;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/* `--accent-text`, not `--accent-strong`: the strong one is a FILL for a white
|
|
2393
|
+
label. Used as ink on `--card` it reads 3.64:1 in dark. `--accent-text` is
|
|
2394
|
+
the accent's text form and `utils/theme.test.ts` asserts it >= 4.5:1 there. */
|
|
2395
|
+
.visitor-form__step-now {
|
|
2396
|
+
color: var(--accent-text);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
.visitor-form__error {
|
|
2400
|
+
margin: 0;
|
|
2401
|
+
padding: 0 20px;
|
|
2402
|
+
font-size: var(--fs-cell);
|
|
2403
|
+
font-weight: 500;
|
|
2404
|
+
color: var(--err);
|
|
2405
|
+
text-align: center;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
/* The design's dialog footer: ghost secondary, then one primary, right-aligned.
|
|
2409
|
+
Deliberately NOT the shared `.screen-modal__footer` - that class is on the
|
|
2410
|
+
batch-D branch (#1150) and not on this base, and adding a second copy of it
|
|
2411
|
+
to `screens.css` here would collide with that PR. */
|
|
2412
|
+
.visitor-form__footer {
|
|
2413
|
+
display: flex;
|
|
2414
|
+
justify-content: flex-end;
|
|
2415
|
+
gap: 8px;
|
|
2416
|
+
padding: 16px 20px 20px;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
/* `!important` only because Vuetify's own is: a `v-list-item` with no `rounded`
|
|
2420
|
+
prop emits `rounded-0`, and that utility is `border-radius: 0 !important`.
|
|
2421
|
+
The original `border-radius: 4px` here lost the same fight and never drew -
|
|
2422
|
+
measured 0px on the glass before this change. Same pattern `.screen-modal`
|
|
2423
|
+
already uses for `--r-modal`. */
|
|
2382
2424
|
.nric-search-item {
|
|
2383
|
-
border: 1px solid
|
|
2384
|
-
border-radius:
|
|
2425
|
+
border: 1px solid var(--border);
|
|
2426
|
+
border-radius: var(--r-inner) !important;
|
|
2385
2427
|
margin: 4px;
|
|
2386
2428
|
min-height: 72px;
|
|
2429
|
+
font-size: var(--fs-cell);
|
|
2387
2430
|
}
|
|
2388
2431
|
|
|
2389
2432
|
.phone-search-results {
|
|
@@ -2391,6 +2434,9 @@ onMounted(() => {
|
|
|
2391
2434
|
flex-direction: column;
|
|
2392
2435
|
max-height: 400px;
|
|
2393
2436
|
overflow: hidden;
|
|
2437
|
+
background: var(--card);
|
|
2438
|
+
border: 1px solid var(--border);
|
|
2439
|
+
box-shadow: var(--shadow-menu);
|
|
2394
2440
|
}
|
|
2395
2441
|
|
|
2396
2442
|
.search-results-scroll {
|
|
@@ -2403,55 +2449,164 @@ onMounted(() => {
|
|
|
2403
2449
|
flex: 0 0 auto;
|
|
2404
2450
|
}
|
|
2405
2451
|
|
|
2406
|
-
.nric-options-
|
|
2407
|
-
|
|
2452
|
+
.nric-options-body {
|
|
2453
|
+
padding: 0 20px;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
.visitor-form__head--bare {
|
|
2457
|
+
justify-content: flex-end;
|
|
2458
|
+
padding-top: 12px;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
.checkout-dialog__body {
|
|
2462
|
+
padding: 0 20px;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
.checkout-dialog__lead {
|
|
2466
|
+
margin-bottom: 20px;
|
|
2467
|
+
font-size: var(--fs-cell);
|
|
2468
|
+
font-weight: 700;
|
|
2469
|
+
color: var(--text);
|
|
2408
2470
|
}
|
|
2409
2471
|
|
|
2410
|
-
.
|
|
2411
|
-
|
|
2412
|
-
font-size:
|
|
2413
|
-
|
|
2472
|
+
.checkout-dialog__subject {
|
|
2473
|
+
margin-bottom: 20px;
|
|
2474
|
+
font-size: var(--fs-cell);
|
|
2475
|
+
color: var(--text2);
|
|
2414
2476
|
}
|
|
2415
2477
|
|
|
2478
|
+
/* The initial badge was a flat `red` avatar with a white letter - 3.99:1 in
|
|
2479
|
+
both themes, and red is the design's ERROR tone, which a person's initial is
|
|
2480
|
+
not. It takes the accent tag: `--accent-soft` fill, `--accent-text` ink. */
|
|
2481
|
+
.checkout-dialog__avatar {
|
|
2482
|
+
background: var(--accent-soft);
|
|
2483
|
+
color: var(--accent-text);
|
|
2484
|
+
font-size: var(--fs-chip);
|
|
2485
|
+
font-weight: var(--fw-chip);
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.checkout-dialog__name {
|
|
2489
|
+
font-size: var(--fs-cell);
|
|
2490
|
+
font-weight: var(--fw-cell);
|
|
2491
|
+
color: var(--text);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
.checkout-dialog__meta {
|
|
2495
|
+
font-size: var(--fs-breadcrumb);
|
|
2496
|
+
color: var(--text2);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
/* Checked-in IS a success state, so this one keeps its green - as the token,
|
|
2500
|
+
not Vuetify's `green`, which does not follow the theme. */
|
|
2501
|
+
.checkout-dialog__in {
|
|
2502
|
+
font-size: var(--fs-cell);
|
|
2503
|
+
color: var(--text);
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
.checkout-dialog__in :deep(.v-icon) {
|
|
2507
|
+
color: var(--ok);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
.unreg-dialog__body {
|
|
2511
|
+
padding: 0 20px;
|
|
2512
|
+
text-align: center;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
/* A warning IS the design's warning tone; Vuetify's `orange` is not it and
|
|
2516
|
+
does not follow the theme. */
|
|
2517
|
+
.unreg-dialog__alert {
|
|
2518
|
+
color: var(--warn);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
.unreg-dialog__lead {
|
|
2522
|
+
margin-top: 20px;
|
|
2523
|
+
font-size: var(--fs-cell);
|
|
2524
|
+
color: var(--text);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
.unreg-dialog__table {
|
|
2528
|
+
margin-top: 20px;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
.unreg-dialog__plate {
|
|
2532
|
+
font-weight: 800;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/* `#06253b` was a literal navy on every line of this dialog - unreadable on a
|
|
2536
|
+
dark card. All of it is `--text` / `--text2` now. */
|
|
2416
2537
|
.nric-options-section-title {
|
|
2417
|
-
|
|
2418
|
-
font-
|
|
2538
|
+
font-size: var(--fs-cell);
|
|
2539
|
+
font-weight: 800;
|
|
2419
2540
|
line-height: 1.25;
|
|
2541
|
+
color: var(--text);
|
|
2420
2542
|
}
|
|
2421
2543
|
|
|
2422
2544
|
.nric-selected-person {
|
|
2423
|
-
border: 1px solid
|
|
2424
|
-
border-radius:
|
|
2425
|
-
color:
|
|
2426
|
-
font-size:
|
|
2545
|
+
border: 1px solid var(--border);
|
|
2546
|
+
border-radius: var(--r-inner-lg);
|
|
2547
|
+
color: var(--text);
|
|
2548
|
+
font-size: var(--fs-cell);
|
|
2427
2549
|
min-height: 52px;
|
|
2428
2550
|
padding: 10px 14px;
|
|
2429
2551
|
}
|
|
2430
2552
|
|
|
2431
2553
|
.nric-option-group {
|
|
2432
|
-
border-top: 1px solid
|
|
2554
|
+
border-top: 1px solid var(--border);
|
|
2433
2555
|
}
|
|
2434
2556
|
|
|
2435
2557
|
.nric-option-radio {
|
|
2436
|
-
border-bottom: 1px solid
|
|
2437
|
-
color:
|
|
2438
|
-
font-size:
|
|
2558
|
+
border-bottom: 1px solid var(--border);
|
|
2559
|
+
color: var(--text);
|
|
2560
|
+
font-size: var(--fs-cell);
|
|
2439
2561
|
min-height: 46px;
|
|
2440
2562
|
}
|
|
2441
2563
|
|
|
2442
2564
|
.nric-option-radio :deep(.v-label) {
|
|
2443
|
-
font-size:
|
|
2565
|
+
font-size: var(--fs-cell);
|
|
2566
|
+
color: var(--text);
|
|
2444
2567
|
opacity: 1;
|
|
2445
2568
|
}
|
|
2446
2569
|
|
|
2447
|
-
.hid-
|
|
2448
|
-
|
|
2570
|
+
.hid-qr__body {
|
|
2571
|
+
padding: 0 20px;
|
|
2572
|
+
text-align: center;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
.hid-qr__header {
|
|
2576
|
+
font-size: var(--fs-card-title);
|
|
2577
|
+
font-weight: var(--fw-card-title);
|
|
2578
|
+
color: var(--text);
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
.hid-qr__subtext {
|
|
2582
|
+
margin-top: 4px;
|
|
2583
|
+
font-size: var(--fs-cell);
|
|
2584
|
+
color: var(--text2);
|
|
2449
2585
|
}
|
|
2450
2586
|
|
|
2587
|
+
.hid-qr__name {
|
|
2588
|
+
margin-top: 12px;
|
|
2589
|
+
font-size: var(--fs-card-title);
|
|
2590
|
+
font-weight: var(--fw-card-title);
|
|
2591
|
+
color: var(--text);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
.hid-qr__meta {
|
|
2595
|
+
margin-top: 4px;
|
|
2596
|
+
font-size: var(--fs-breadcrumb);
|
|
2597
|
+
color: var(--text2);
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
/* The QR itself STAYS on a white plate with dark ink, in both themes, on
|
|
2601
|
+
purpose: a phone camera and an HID reader need the light-on-dark contrast the
|
|
2602
|
+
code is printed with, and inverting it in dark mode would stop it scanning.
|
|
2603
|
+
The frame around it follows the theme; the plate does not. */
|
|
2451
2604
|
.hid-qr-image-shell {
|
|
2452
2605
|
display: inline-flex;
|
|
2606
|
+
margin: 20px 0;
|
|
2453
2607
|
padding: 12px;
|
|
2454
|
-
border: 1px solid
|
|
2608
|
+
border: 1px solid var(--border);
|
|
2609
|
+
border-radius: var(--r-inner);
|
|
2455
2610
|
background: #ffffff;
|
|
2456
2611
|
}
|
|
2457
2612
|
|
|
@@ -2462,11 +2617,40 @@ onMounted(() => {
|
|
|
2462
2617
|
align-items: center;
|
|
2463
2618
|
justify-content: center;
|
|
2464
2619
|
border: 0;
|
|
2465
|
-
|
|
2466
|
-
|
|
2620
|
+
border-radius: var(--r-inner-sm);
|
|
2621
|
+
/* The white plate above is a deliberate exception FOR THE PRINTED CODE. This
|
|
2622
|
+
is the tap-to-reveal button that sits in its place beforehand, so it is
|
|
2623
|
+
ordinary chrome and follows the theme: a literal `#f5f7f9`/`#344054` drew a
|
|
2624
|
+
light slab with dark ink inside the dark modal. `--bg` reads as a recess
|
|
2625
|
+
against the modal's `--card` in both themes. Measured `--text2` on `--bg`:
|
|
2626
|
+
8.88:1 light, 11.21:1 dark. */
|
|
2627
|
+
background: var(--bg);
|
|
2628
|
+
color: var(--text2);
|
|
2467
2629
|
cursor: pointer;
|
|
2468
2630
|
flex-direction: column;
|
|
2469
2631
|
gap: 10px;
|
|
2470
2632
|
font: inherit;
|
|
2633
|
+
font-size: var(--fs-cell);
|
|
2634
|
+
}
|
|
2635
|
+
</style>
|
|
2636
|
+
|
|
2637
|
+
<!--
|
|
2638
|
+
UNSCOPED on purpose, and only for the menu surface.
|
|
2639
|
+
|
|
2640
|
+
A `v-menu`'s panel is TELEPORTED to the overlay container, so it never
|
|
2641
|
+
carries this component's scope attribute - a scoped rule cannot reach it.
|
|
2642
|
+
And Vuetify owns the radius from outside at a specificity a scoped class
|
|
2643
|
+
cannot beat: `.v-menu > .v-overlay__content > .v-list { border-radius:
|
|
2644
|
+
inherit }` is 0,3,0 against a scoped class's 0,2,0, so the list inherits the
|
|
2645
|
+
overlay's 4px however the list itself is styled. Measured: 4px on the glass
|
|
2646
|
+
with the scoped rule in place.
|
|
2647
|
+
|
|
2648
|
+
So the radius is set where Vuetify sets it - on the overlay content, reached
|
|
2649
|
+
by the menu's own `content-class`. The class name is component-prefixed, so
|
|
2650
|
+
nothing else in the layer can pick it up. No `!important`.
|
|
2651
|
+
-->
|
|
2652
|
+
<style>
|
|
2653
|
+
.v-menu > .v-overlay__content.visitor-form__menu {
|
|
2654
|
+
border-radius: var(--r-menu);
|
|
2471
2655
|
}
|
|
2472
2656
|
</style>
|