@7365admin1/layer-common 3.0.6 → 3.0.8
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 +13 -0
- package/components/BulletinBoardForm.vue +423 -333
- package/components/CameraMain.vue +17 -1
- package/components/DashboardMain.vue +200 -150
- package/components/MemberInformation.vue +23 -15
- package/components/OnlineFormConfigurationForm.vue +2 -2
- package/components/OnlineFormFill.vue +227 -51
- package/components/PassInformation.vue +73 -34
- package/components/SiteSettings.vue +42 -121
- package/components/VehicleManagement.vue +6 -2
- package/components/VisitorForm.vue +623 -289
- package/components/VisitorManagement.vue +652 -205
- package/composables/useCommonPermission.ts +59 -0
- package/composables/useDashboard.ts +43 -1
- package/composables/useOnlineForm.ts +25 -13
- package/composables/useOnlineFormPages.ts +2 -2
- package/composables/useSettingsPermission.ts +138 -0
- package/package.json +2 -2
- package/types/dashboard.d.ts +1 -1
- package/types/visitor.d.ts +4 -2
|
@@ -14,6 +14,39 @@
|
|
|
14
14
|
</v-row>
|
|
15
15
|
</v-toolbar>
|
|
16
16
|
|
|
17
|
+
<!-- Submission metadata bar (readonly only) -->
|
|
18
|
+
<div
|
|
19
|
+
v-if="readonly && (submissionStatus)"
|
|
20
|
+
class="px-6 py-2 d-flex align-center ga-4"
|
|
21
|
+
style="background: #f5f5f5; border-bottom: 1px solid #e0e0e0; flex-wrap: wrap"
|
|
22
|
+
>
|
|
23
|
+
<v-chip
|
|
24
|
+
v-if="submissionStatus"
|
|
25
|
+
:color="submissionStatusColor"
|
|
26
|
+
variant="flat"
|
|
27
|
+
size="x-small"
|
|
28
|
+
class="text-capitalize"
|
|
29
|
+
>{{ submissionStatus }}</v-chip>
|
|
30
|
+
<template v-if="requiresPayment">
|
|
31
|
+
<v-chip
|
|
32
|
+
v-if="initialManagementValues?.paymentStatus"
|
|
33
|
+
color="green"
|
|
34
|
+
variant="flat"
|
|
35
|
+
size="x-small"
|
|
36
|
+
>Payment {{ initialManagementValues.paymentStatus }}</v-chip>
|
|
37
|
+
<v-chip
|
|
38
|
+
v-else
|
|
39
|
+
color="orange"
|
|
40
|
+
variant="flat"
|
|
41
|
+
size="x-small"
|
|
42
|
+
>Payment Not Paid</v-chip>
|
|
43
|
+
<span
|
|
44
|
+
v-if="initialManagementValues?.paymentOrderId"
|
|
45
|
+
class="text-caption text-medium-emphasis"
|
|
46
|
+
>Order: {{ initialManagementValues.paymentOrderId }}</span>
|
|
47
|
+
</template>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
17
50
|
<!-- Scrollable body -->
|
|
18
51
|
<v-card-text
|
|
19
52
|
style="max-height: 76vh; overflow-y: auto"
|
|
@@ -31,12 +64,12 @@
|
|
|
31
64
|
<v-row no-gutters>
|
|
32
65
|
<v-col cols="5">
|
|
33
66
|
<span class="text-body-2">
|
|
34
|
-
<strong>Date : </strong>{{ todayDate }}
|
|
67
|
+
<strong>Date : </strong>{{ (initialValues as any).formDate || submissionDate || todayDate }}
|
|
35
68
|
</span>
|
|
36
69
|
</v-col>
|
|
37
70
|
<v-col cols="7">
|
|
38
71
|
<span class="text-body-2">
|
|
39
|
-
<strong>To : </strong>{{ resolveText(block.to) }}
|
|
72
|
+
<strong>To : </strong>{{ (initialValues as any).formTo || resolveText(block.to) }}
|
|
40
73
|
</span>
|
|
41
74
|
</v-col>
|
|
42
75
|
</v-row>
|
|
@@ -220,12 +253,28 @@
|
|
|
220
253
|
</div>
|
|
221
254
|
<div
|
|
222
255
|
v-else-if="block.type === 'signature-row' && readonly"
|
|
223
|
-
class="mt-
|
|
256
|
+
class="mt-3 mb-5"
|
|
224
257
|
>
|
|
258
|
+
<span class="text-caption text-medium-emphasis d-block mb-1">{{ block.label }}</span>
|
|
225
259
|
<div
|
|
260
|
+
v-if="initialValues[block.key]"
|
|
261
|
+
style="
|
|
262
|
+
display: inline-block;
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
background: #111111;
|
|
265
|
+
padding: 8px;
|
|
266
|
+
max-width: 100%;
|
|
267
|
+
"
|
|
268
|
+
>
|
|
269
|
+
<img
|
|
270
|
+
:src="initialValues[block.key]"
|
|
271
|
+
style="display: block; max-width: 100%; max-height: 130px; object-fit: contain;"
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
<div
|
|
275
|
+
v-else
|
|
226
276
|
style="border-bottom: 1px dashed #bbb; height: 56px; background: #fafafa; border-radius: 4px"
|
|
227
277
|
/>
|
|
228
|
-
<span class="text-caption text-medium-emphasis">{{ block.label }}</span>
|
|
229
278
|
</div>
|
|
230
279
|
|
|
231
280
|
<!-- ACKNOWLEDGEMENT -->
|
|
@@ -237,27 +286,38 @@
|
|
|
237
286
|
<span class="text-body-2 font-weight-medium">{{ resolveText(block.text) }}</span>
|
|
238
287
|
</div>
|
|
239
288
|
|
|
240
|
-
<!-- MANAGEMENT USE
|
|
289
|
+
<!-- MANAGEMENT USE -->
|
|
241
290
|
<div
|
|
242
291
|
v-else-if="block.type === 'management-use'"
|
|
243
292
|
class="pa-4 mt-2"
|
|
244
|
-
style="
|
|
293
|
+
:style="approvalMode && selectedAction === 'approved'
|
|
294
|
+
? 'background: #e8f5e9; border-radius: 6px; border: 1px solid #a5d6a7'
|
|
295
|
+
: 'background: #f0f0f0; border-radius: 6px; opacity: 0.75'"
|
|
245
296
|
>
|
|
246
297
|
<v-row no-gutters class="mb-2">
|
|
247
298
|
<span class="text-caption font-weight-bold text-medium-emphasis">
|
|
248
299
|
FOR MANAGEMENT USE ONLY
|
|
249
300
|
</span>
|
|
250
301
|
</v-row>
|
|
251
|
-
<v-row no-gutters style="gap: 16px">
|
|
302
|
+
<v-row no-gutters style="gap: 16px; flex-wrap: wrap">
|
|
252
303
|
<div
|
|
253
304
|
v-for="(item, idx) in block.items"
|
|
254
305
|
:key="idx"
|
|
255
|
-
style="min-width: 160px"
|
|
306
|
+
style="min-width: 160px; flex: 1"
|
|
256
307
|
>
|
|
257
308
|
<div class="text-caption text-medium-emphasis mb-1">{{ item }}</div>
|
|
258
|
-
<
|
|
259
|
-
|
|
309
|
+
<v-text-field
|
|
310
|
+
v-if="approvalMode && selectedAction === 'approved'"
|
|
311
|
+
v-model="managementValues[item]"
|
|
312
|
+
density="compact"
|
|
313
|
+
variant="underlined"
|
|
314
|
+
:error="!(managementValues[item] ?? '').trim()"
|
|
315
|
+
:error-messages="!(managementValues[item] ?? '').trim() ? 'Required' : ''"
|
|
260
316
|
/>
|
|
317
|
+
<div
|
|
318
|
+
v-else
|
|
319
|
+
style="border-bottom: 1px solid #aaa; min-width: 120px; height: 22px; font-size: 13px; padding-top: 2px"
|
|
320
|
+
>{{ managementValues[item] || '' }}</div>
|
|
261
321
|
</div>
|
|
262
322
|
</v-row>
|
|
263
323
|
</div>
|
|
@@ -271,53 +331,70 @@
|
|
|
271
331
|
</v-form>
|
|
272
332
|
</v-card-text>
|
|
273
333
|
|
|
274
|
-
<!--
|
|
275
|
-
<
|
|
334
|
+
<!-- Approval action panel -->
|
|
335
|
+
<div
|
|
336
|
+
v-if="readonly && approvalMode"
|
|
337
|
+
style="border-top: 1px solid #e0e0e0; padding: 12px 16px; background: #fafafa; display: flex; flex-direction: column; gap: 10px"
|
|
338
|
+
>
|
|
339
|
+
<v-text-field
|
|
340
|
+
v-model="adminRemarks"
|
|
341
|
+
label="Remarks"
|
|
342
|
+
density="compact"
|
|
343
|
+
variant="outlined"
|
|
344
|
+
:hide-details="adminRemarks.trim().length > 0"
|
|
345
|
+
:error="remarksRequired && adminRemarks.trim().length === 0"
|
|
346
|
+
:error-messages="remarksRequired && adminRemarks.trim().length === 0 ? 'Remarks are required' : ''"
|
|
347
|
+
/>
|
|
348
|
+
<div class="d-flex align-center ga-2 justify-end">
|
|
349
|
+
<v-btn variant="text" class="text-none" size="small" @click="emit('cancel')">Close</v-btn>
|
|
350
|
+
<v-btn
|
|
351
|
+
:variant="selectedAction === 'resubmission' ? 'flat' : 'tonal'"
|
|
352
|
+
color="orange-darken-1"
|
|
353
|
+
class="text-none"
|
|
354
|
+
size="small"
|
|
355
|
+
@click="selectAction('resubmission')"
|
|
356
|
+
>Resubmit</v-btn>
|
|
357
|
+
<v-btn
|
|
358
|
+
:variant="selectedAction === 'rejected' ? 'flat' : 'tonal'"
|
|
359
|
+
color="red"
|
|
360
|
+
class="text-none"
|
|
361
|
+
size="small"
|
|
362
|
+
@click="selectAction('rejected')"
|
|
363
|
+
>Reject</v-btn>
|
|
364
|
+
<v-btn
|
|
365
|
+
:variant="selectedAction === 'approved' ? 'flat' : 'tonal'"
|
|
366
|
+
color="green"
|
|
367
|
+
class="text-none"
|
|
368
|
+
size="small"
|
|
369
|
+
@click="selectAction('approved')"
|
|
370
|
+
>Approve</v-btn>
|
|
371
|
+
<v-btn
|
|
372
|
+
v-if="selectedAction"
|
|
373
|
+
variant="flat"
|
|
374
|
+
color="grey-darken-3"
|
|
375
|
+
class="text-none"
|
|
376
|
+
size="small"
|
|
377
|
+
:disabled="!canConfirm"
|
|
378
|
+
:loading="updatingStatus"
|
|
379
|
+
@click="confirmStatusUpdate"
|
|
380
|
+
>Confirm</v-btn>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<!-- Normal action bar -->
|
|
385
|
+
<v-toolbar v-else density="compact">
|
|
276
386
|
<v-row no-gutters>
|
|
277
|
-
<!-- Readonly: single Close button -->
|
|
278
387
|
<template v-if="readonly">
|
|
279
388
|
<v-col cols="12">
|
|
280
|
-
<v-btn
|
|
281
|
-
tile
|
|
282
|
-
block
|
|
283
|
-
variant="text"
|
|
284
|
-
class="text-none"
|
|
285
|
-
size="48"
|
|
286
|
-
@click="emit('cancel')"
|
|
287
|
-
>
|
|
288
|
-
Close
|
|
289
|
-
</v-btn>
|
|
389
|
+
<v-btn tile block variant="text" class="text-none" size="48" @click="emit('cancel')">Close</v-btn>
|
|
290
390
|
</v-col>
|
|
291
391
|
</template>
|
|
292
|
-
<!-- Interactive: Cancel + Submit -->
|
|
293
392
|
<template v-else>
|
|
294
393
|
<v-col cols="6">
|
|
295
|
-
<v-btn
|
|
296
|
-
tile
|
|
297
|
-
block
|
|
298
|
-
variant="text"
|
|
299
|
-
class="text-none"
|
|
300
|
-
size="48"
|
|
301
|
-
:disabled="submitting"
|
|
302
|
-
@click="emit('cancel')"
|
|
303
|
-
>
|
|
304
|
-
Cancel
|
|
305
|
-
</v-btn>
|
|
394
|
+
<v-btn tile block variant="text" class="text-none" size="48" :disabled="submitting" @click="emit('cancel')">Cancel</v-btn>
|
|
306
395
|
</v-col>
|
|
307
396
|
<v-col cols="6">
|
|
308
|
-
<v-btn
|
|
309
|
-
tile
|
|
310
|
-
block
|
|
311
|
-
variant="flat"
|
|
312
|
-
color="black"
|
|
313
|
-
class="text-none"
|
|
314
|
-
size="48"
|
|
315
|
-
:disabled="!validForm || submitting"
|
|
316
|
-
:loading="submitting"
|
|
317
|
-
@click="submit"
|
|
318
|
-
>
|
|
319
|
-
Submit
|
|
320
|
-
</v-btn>
|
|
397
|
+
<v-btn tile block variant="flat" color="black" class="text-none" size="48" :disabled="!validForm || submitting" :loading="submitting" @click="submit">Submit</v-btn>
|
|
321
398
|
</v-col>
|
|
322
399
|
</template>
|
|
323
400
|
</v-row>
|
|
@@ -339,6 +416,13 @@ const props = defineProps({
|
|
|
339
416
|
orgId: { type: String, required: true },
|
|
340
417
|
siteName: { type: String, default: "" },
|
|
341
418
|
readonly: { type: Boolean, default: false },
|
|
419
|
+
initialValues: { type: Object as PropType<Record<string, any>>, default: () => ({}) },
|
|
420
|
+
submissionDate: { type: String, default: "" },
|
|
421
|
+
submissionStatus: { type: String, default: "" },
|
|
422
|
+
submissionUnit: { type: String, default: "" },
|
|
423
|
+
approvalMode: { type: Boolean, default: false },
|
|
424
|
+
initialManagementValues: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
|
|
425
|
+
formType: { type: String, default: "" },
|
|
342
426
|
/** Optional custom submit handler. Receives the collected form values.
|
|
343
427
|
* If omitted the component submits to /api/online-forms automatically. */
|
|
344
428
|
submitFn: {
|
|
@@ -347,7 +431,7 @@ const props = defineProps({
|
|
|
347
431
|
},
|
|
348
432
|
});
|
|
349
433
|
|
|
350
|
-
const emit = defineEmits(["cancel", "success"]);
|
|
434
|
+
const emit = defineEmits(["cancel", "success", "update-status"]);
|
|
351
435
|
|
|
352
436
|
function resolveText(text: string): string {
|
|
353
437
|
return text.replace(/\{siteName\}/g, props.siteName || "the Management");
|
|
@@ -361,8 +445,100 @@ const submitting = ref(false);
|
|
|
361
445
|
const message = ref("");
|
|
362
446
|
const values = ref<Record<string, any>>({});
|
|
363
447
|
|
|
448
|
+
const selectedAction = ref("");
|
|
449
|
+
const adminRemarks = ref("");
|
|
450
|
+
const updatingStatus = ref(false);
|
|
451
|
+
const managementValues = ref<Record<string, string>>({});
|
|
452
|
+
|
|
453
|
+
const managementUseItems = computed<string[]>(() => {
|
|
454
|
+
const result: string[] = [];
|
|
455
|
+
for (const block of props.definition.blocks) {
|
|
456
|
+
if (block.type === "management-use") result.push(...block.items);
|
|
457
|
+
}
|
|
458
|
+
return result;
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Load previously saved management values (saved keys have colons stripped; remap to match item labels)
|
|
462
|
+
watch(() => props.initialManagementValues, (saved) => {
|
|
463
|
+
if (!saved || !Object.keys(saved).length) return;
|
|
464
|
+
const loaded: Record<string, string> = {};
|
|
465
|
+
for (const item of managementUseItems.value) {
|
|
466
|
+
const stripped = item.replace(/:+\s*$/, "").trim();
|
|
467
|
+
const value = saved[stripped] ?? saved[item] ?? "";
|
|
468
|
+
if (value) loaded[item] = value;
|
|
469
|
+
}
|
|
470
|
+
managementValues.value = { ...loaded, ...managementValues.value };
|
|
471
|
+
}, { immediate: true });
|
|
472
|
+
|
|
473
|
+
const remarksRequired = computed(() => !!selectedAction.value);
|
|
474
|
+
|
|
475
|
+
const managementComplete = computed(() => {
|
|
476
|
+
if (selectedAction.value !== "approved") return true;
|
|
477
|
+
return managementUseItems.value.every(item => (managementValues.value[item] ?? "").trim().length > 0);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const canConfirm = computed(() =>
|
|
481
|
+
!!selectedAction.value &&
|
|
482
|
+
adminRemarks.value.trim().length > 0 &&
|
|
483
|
+
managementComplete.value
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
function prePopulateManagement() {
|
|
487
|
+
const today = new Date().toLocaleDateString("en-SG", { day: "2-digit", month: "short", year: "numeric" });
|
|
488
|
+
const userName = currentUser.value?.name ?? "";
|
|
489
|
+
for (const item of managementUseItems.value) {
|
|
490
|
+
if (managementValues.value[item]) continue;
|
|
491
|
+
const lower = item.toLowerCase();
|
|
492
|
+
if (lower.includes("by:") || lower.endsWith(" by")) {
|
|
493
|
+
managementValues.value[item] = userName;
|
|
494
|
+
} else if (lower.includes("date")) {
|
|
495
|
+
managementValues.value[item] = today;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function selectAction(action: string) {
|
|
501
|
+
selectedAction.value = selectedAction.value === action ? "" : action;
|
|
502
|
+
if (selectedAction.value === "approved") prePopulateManagement();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function confirmStatusUpdate() {
|
|
506
|
+
if (!canConfirm.value) return;
|
|
507
|
+
updatingStatus.value = true;
|
|
508
|
+
emit("update-status", {
|
|
509
|
+
status: selectedAction.value,
|
|
510
|
+
remarks: adminRemarks.value,
|
|
511
|
+
managementValues: managementValues.value,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
364
515
|
const requiredRule = (v: any) => (v !== null && v !== undefined && v !== "") || "This field is required";
|
|
365
516
|
|
|
517
|
+
const submissionStatusColor = computed(() => {
|
|
518
|
+
switch (props.submissionStatus?.toLowerCase()) {
|
|
519
|
+
case "approved": return "green";
|
|
520
|
+
case "pending": return "orange";
|
|
521
|
+
case "resubmission": return "orange";
|
|
522
|
+
case "rejected": return "red";
|
|
523
|
+
default: return "grey";
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const PAYMENT_REQUIRED_FORM_TYPES = new Set([
|
|
528
|
+
"Application for Electronic Access Card",
|
|
529
|
+
"Application for Booking of BBQ Pit",
|
|
530
|
+
"Application for Moving In/Out & Delivery",
|
|
531
|
+
"Application for Main Entrance RFID Transponder",
|
|
532
|
+
]);
|
|
533
|
+
|
|
534
|
+
const requiresPayment = computed(() => {
|
|
535
|
+
if (!PAYMENT_REQUIRED_FORM_TYPES.has(props.formType)) return false;
|
|
536
|
+
if (props.formType === "Application for Main Entrance RFID Transponder") {
|
|
537
|
+
return !!(props.initialValues.reasonLost || props.initialValues.reasonDamaged);
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
540
|
+
});
|
|
541
|
+
|
|
366
542
|
// Today's date formatted DD MMM YYYY
|
|
367
543
|
const todayDate = computed(() => {
|
|
368
544
|
const d = new Date();
|
|
@@ -390,7 +566,7 @@ function initValues(def: FormDefinition) {
|
|
|
390
566
|
return init;
|
|
391
567
|
}
|
|
392
568
|
|
|
393
|
-
values.value = initValues(props.definition);
|
|
569
|
+
values.value = { ...initValues(props.definition), ...props.initialValues };
|
|
394
570
|
|
|
395
571
|
const NAME_LABELS = new Set([
|
|
396
572
|
"name",
|
|
@@ -421,7 +597,7 @@ function prePopulate(u: any) {
|
|
|
421
597
|
}
|
|
422
598
|
}
|
|
423
599
|
|
|
424
|
-
watch(currentUser, (u) => { if (u) prePopulate(u); }, { immediate: true });
|
|
600
|
+
watch(currentUser, (u) => { if (u && !props.readonly) prePopulate(u); }, { immediate: true });
|
|
425
601
|
|
|
426
602
|
async function submit() {
|
|
427
603
|
submitting.value = true;
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
persistent-hint
|
|
28
28
|
small-chips
|
|
29
29
|
:clearable="props.clearable"
|
|
30
|
+
@click="fetchPasses"
|
|
31
|
+
:loading="isPassListLoading"
|
|
30
32
|
>
|
|
31
33
|
<template v-slot:chip="{ props, item }">
|
|
32
34
|
<v-chip
|
|
@@ -56,6 +58,8 @@
|
|
|
56
58
|
persistent-hint
|
|
57
59
|
small-chips
|
|
58
60
|
:clearable="props.clearable"
|
|
61
|
+
@click="fetchKeys"
|
|
62
|
+
:loading="isKeyListLoading"
|
|
59
63
|
>
|
|
60
64
|
<template v-slot:chip="{ props, item }">
|
|
61
65
|
<v-chip
|
|
@@ -83,6 +87,7 @@
|
|
|
83
87
|
</v-card-text>
|
|
84
88
|
</v-card>
|
|
85
89
|
</v-row>
|
|
90
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
86
91
|
</template>
|
|
87
92
|
|
|
88
93
|
<script setup lang="ts">
|
|
@@ -90,6 +95,7 @@ import type { PropType } from "vue";
|
|
|
90
95
|
import type { ValidationRule } from "vuetify/lib/types.mjs";
|
|
91
96
|
import usePassKey from "../composables/usePassKey";
|
|
92
97
|
import useKey from "../composables/useKey";
|
|
98
|
+
import { string } from "zod/v4";
|
|
93
99
|
|
|
94
100
|
const props = defineProps({
|
|
95
101
|
passRules: {
|
|
@@ -112,6 +118,10 @@ const props = defineProps({
|
|
|
112
118
|
type: String,
|
|
113
119
|
default: "",
|
|
114
120
|
},
|
|
121
|
+
passKeys: {
|
|
122
|
+
type: Array,
|
|
123
|
+
default: [],
|
|
124
|
+
},
|
|
115
125
|
hideKeys: {
|
|
116
126
|
type: Boolean,
|
|
117
127
|
default: false,
|
|
@@ -158,43 +168,72 @@ const passTypesComputed = computed(() => {
|
|
|
158
168
|
}
|
|
159
169
|
});
|
|
160
170
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
pending: fetchPassesPending,
|
|
165
|
-
} = await useLazyAsyncData("get-pass-keys", () => {
|
|
166
|
-
return getPassKeysByPageSearch({
|
|
167
|
-
search: passInput.value,
|
|
168
|
-
page: 1,
|
|
169
|
-
limit: 500,
|
|
170
|
-
passTypes: passTypesComputed.value,
|
|
171
|
-
sites: [props.site],
|
|
172
|
-
statuses: ["Available"],
|
|
173
|
-
});
|
|
174
|
-
});
|
|
171
|
+
const message = ref("");
|
|
172
|
+
const messageColor = ref("");
|
|
173
|
+
const messageSnackbar = ref(false);
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
function showMessage(msg: string, color: string) {
|
|
176
|
+
message.value = msg;
|
|
177
|
+
messageColor.value = color;
|
|
178
|
+
messageSnackbar.value = true;
|
|
179
|
+
}
|
|
179
180
|
|
|
180
|
-
const
|
|
181
|
-
data: keysData,
|
|
182
|
-
refresh: refreshKeysData,
|
|
183
|
-
pending: fetchKeysPending,
|
|
184
|
-
} = await useLazyAsyncData("get-keys", () => {
|
|
185
|
-
return getPassKeysByPageSearch({
|
|
186
|
-
search: keyInput.value,
|
|
187
|
-
statuses: ["Available"],
|
|
188
|
-
passTypes: ["pass-key"],
|
|
189
|
-
page: 1,
|
|
190
|
-
limit: 500,
|
|
191
|
-
sites: [props.site],
|
|
192
|
-
});
|
|
193
|
-
});
|
|
181
|
+
const isPassListLoading = ref(false);
|
|
194
182
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
async function fetchPasses() {
|
|
184
|
+
// load pass list
|
|
185
|
+
passItems.value = [];
|
|
186
|
+
isPassListLoading.value = true;
|
|
187
|
+
try {
|
|
188
|
+
const result = await getPassKeysByPageSearch({
|
|
189
|
+
page: 1,
|
|
190
|
+
sites: [props.site],
|
|
191
|
+
limit: 10000,
|
|
192
|
+
passTypes: passTypesComputed.value,
|
|
193
|
+
statuses: ["Available"],
|
|
194
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
195
|
+
});
|
|
196
|
+
passItems.value = result?.items;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
showMessage(errorConverter(error as string), "error");
|
|
199
|
+
} finally {
|
|
200
|
+
isPassListLoading.value = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const isKeyListLoading = ref(false);
|
|
205
|
+
|
|
206
|
+
async function fetchKeys() {
|
|
207
|
+
keyItems.value = [];
|
|
208
|
+
isKeyListLoading.value = true;
|
|
209
|
+
try {
|
|
210
|
+
const keys: any = await getPassKeysByPageSearch({
|
|
211
|
+
page: 1,
|
|
212
|
+
sites: [props.site],
|
|
213
|
+
limit: 10000,
|
|
214
|
+
passTypes: ["pass-key"],
|
|
215
|
+
statuses: ["Available"],
|
|
216
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
217
|
+
});
|
|
218
|
+
const selectedKeys = keyItems.value.filter((item: any) => {
|
|
219
|
+
if (Array.isArray(props?.passKeys) && props.passKeys.length) {
|
|
220
|
+
return props.passKeys.includes(item._id);
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
});
|
|
224
|
+
const filteredArray = keys.items.filter((item: any) => {
|
|
225
|
+
if (Array.isArray(props?.passKeys) && props.passKeys.length) {
|
|
226
|
+
return !props.passKeys.includes(item._id);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
keyItems.value = [...selectedKeys, ...filteredArray];
|
|
231
|
+
} catch (error) {
|
|
232
|
+
showMessage(errorConverter(error as string), "error");
|
|
233
|
+
} finally {
|
|
234
|
+
isKeyListLoading.value = false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
198
237
|
|
|
199
238
|
watch(selectedPass, (newVal) => {
|
|
200
239
|
pass.value = [{ keyId: newVal }];
|