@7365admin1/layer-common 1.10.4 → 1.10.6

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.
@@ -0,0 +1,663 @@
1
+ <template>
2
+ <v-card width="100%" :loading="processing">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6 d-flex ga-2 justify-space-between" align="center">
5
+ <span class="font-weight-bold text-h5 text-capitalize">
6
+ {{ prop.mode }} Vehicle <span>({{ formatVehicleType(type) }})</span>
7
+ </span>
8
+ <span>
9
+ <ButtonClose @click="emit('close:all')" icon-only />
10
+ </span>
11
+ </v-row>
12
+ </v-toolbar>
13
+
14
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-3">
15
+ <span class="text-subtitle-1 w-100 font-weight-bold mb-3">General Information</span>
16
+ <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
17
+ <v-row no-gutters class="pt-4">
18
+ <v-col v-if="shouldShowField('seasonPassType')" cols="12">
19
+ <InputLabel class="text-capitalize" title="Season Pass Type" required />
20
+ <v-combobox v-model.trim="vehicle.seasonPassType" v-model:search="seasonPassTypeInput" :hide-no-data="false"
21
+ @update:modelValue="onSeasonPassTypeSelected" :items="seasonPassTypeArray" :rules="[requiredRule]"
22
+ item-title="title" item-value="value" variant="outlined" density="comfortable" persistent-hint
23
+ small-chips>
24
+ <template v-slot:no-data>
25
+ <v-list-item>
26
+ <v-list-item-title>
27
+ No results matching "<strong>{{ seasonPassTypeInput }}</strong>". This value will be added as new
28
+ option.
29
+ </v-list-item-title>
30
+ </v-list-item>
31
+ </template>
32
+ </v-combobox>
33
+ </v-col>
34
+
35
+ <v-col v-if="shouldShowField('nric')" cols="12">
36
+ <InputLabel class="text-capitalize" title="NRIC" required />
37
+ <InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]" />
38
+ </v-col>
39
+
40
+ <v-col v-if="shouldShowField('name')" cols="12">
41
+ <v-row>
42
+ <v-col cols="12">
43
+ <InputLabel class="text-capitalize" title="Full Name" required />
44
+ <v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
45
+ </v-col>
46
+ </v-row>
47
+ </v-col>
48
+
49
+
50
+
51
+ <v-col v-if="shouldShowField('phone')" cols="12">
52
+ <InputLabel class="text-capitalize" title="Phone Number" required />
53
+ <InputPhoneNumberV2 v-model="vehicle.phoneNumber" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
54
+ </v-col>
55
+
56
+ <v-col v-if="shouldShowField('block')" cols="12">
57
+ <InputLabel class="text-capitalize" title="Block" required />
58
+ <v-select v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
59
+ @update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
60
+ </v-col>
61
+
62
+ <v-col v-if="shouldShowField('level')" cols="12">
63
+ <InputLabel class="text-capitalize" title="Level" required />
64
+ <v-select v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block || disablePrefilledInputs"
65
+ @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
66
+ </v-col>
67
+
68
+ <v-col v-if="shouldShowField('unit')" cols="12">
69
+ <InputLabel class="text-capitalize" title="Unit" required />
70
+ <v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level || disablePrefilledInputs"
71
+ :rules="[requiredRule]" />
72
+ </v-col>
73
+
74
+ <v-col v-if="shouldShowField('plateNumber')" cols="12">
75
+ <InputLabel class="text-capitalize" title="Vehicle Numbers" required />
76
+ <!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
77
+ <template v-for="plate in vehicle.plates" :key="plate.plateNumber">
78
+ <v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]" class="mb-2" read-only />
79
+ </template>
80
+
81
+ <InputVehicleNumber v-model="newPlateNumber" density="comfortable" :rules="[requiredRule]" />
82
+ </v-col>
83
+
84
+ <v-col v-if="shouldShowField('remarks')" cols="12">
85
+ <InputLabel class="text-capitalize" title="Remarks" required />
86
+ <v-textarea v-model="vehicle.remarks" density="comfortable" :rows="3" no-resize :rules="[requiredRule]" />
87
+ </v-col>
88
+
89
+ <v-col v-if="shouldShowField('start')" cols="12">
90
+ <v-expand-transition v-if="showSubscriptionDateOptions">
91
+ <v-row no-gutters>
92
+ <v-row dense justify="space-between">
93
+ <template v-for="option in subscriptionOptions" :key="option.value">
94
+ <v-col cols="4">
95
+ <v-btn :text="option.label" class="text-capitalize" min-width="120" :ripple="false"
96
+ :class="[option.value === selectedSubscriptionDuration ? 'button-outline-class' : '']"
97
+ @click="selectedSubscriptionDuration = option.value" />
98
+ </v-col>
99
+
100
+ </template>
101
+ </v-row>
102
+
103
+ <v-row no-gutters class="mt-5">
104
+ <v-col cols="12">
105
+ <InputLabel class="text-capitalize" title="Start Date" required />
106
+ <InputDateTimePicker ref="startDateRef" v-model="vehicle.start" :rules="[validStartDateRule]" />
107
+ </v-col>
108
+
109
+ <v-col cols="12">
110
+ <InputLabel class="text-capitalize" title="Expiry Date" required />
111
+ <InputDateTimePicker ref="expiryDateRef" v-model="vehicle.end" :rules="[validExpiryDateRule]" />
112
+ </v-col>
113
+ </v-row>
114
+ </v-row>
115
+
116
+
117
+ </v-expand-transition>
118
+
119
+ <v-col cols="12" :class="[showSubscriptionDateOptions && 'mt-5']">
120
+ <v-row justify="center">
121
+ <v-col cols="6">
122
+ <v-btn block color="primary" variant="text" class="text-none font-weight-bold"
123
+ :text="showSubscriptionDateOptions ? 'Hide Subscription Options' : 'Show Subscription Options'"
124
+ @click="showSubscriptionDateOptions = !showSubscriptionDateOptions"></v-btn>
125
+ </v-col>
126
+ </v-row>
127
+ </v-col>
128
+ </v-col>
129
+
130
+ <v-col cols="12">
131
+ <v-row no-gutters>
132
+ <v-col cols="12" class="text-center">
133
+ <span class="text-none text-subtitle-2 font-weight-medium text-error">
134
+ {{ message }}
135
+ </span>
136
+ </v-col>
137
+ </v-row>
138
+ </v-col>
139
+ </v-row>
140
+ </v-form>
141
+ </v-card-text>
142
+ <v-row no-gutters class="w-100" v-if="errorMessage">
143
+ <p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
144
+ </v-row>
145
+ <v-toolbar density="compact">
146
+ <v-row no-gutters>
147
+ <v-col cols="6">
148
+ <v-btn v-if="prop.mode === 'add'" tile block variant="text" class="text-none" size="48" @click="back"
149
+ text="Back to Selection" />
150
+ <v-btn v-else tile block variant="text" class="text-none" size="48" @click="close" text="Close" />
151
+ </v-col>
152
+ <v-col cols="6">
153
+ <v-btn tile block variant="flat" color="black" class="text-none" size="48"
154
+ :disabled="!validForm || processing" @click="submit" :text="prop.mode == 'add' ? 'Submit' : 'Update'" />
155
+ </v-col>
156
+ </v-row>
157
+ </v-toolbar>
158
+
159
+ <v-dialog v-model="showMatchingPeopleDialog" max-width="700">
160
+ <v-card>
161
+ <v-toolbar>
162
+ <v-toolbar-title>
163
+ Existing Records Found
164
+ </v-toolbar-title>
165
+ </v-toolbar>
166
+
167
+ <v-card-text>
168
+
169
+ <v-list lines="three">
170
+ <v-list-item v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
171
+ <v-list-item-title>
172
+ {{ v.name }}
173
+ </v-list-item-title>
174
+
175
+ <v-list-item-subtitle>
176
+ Block {{ v.block }} - {{ v.level }} - {{ v.unitName }}
177
+ </v-list-item-subtitle>
178
+
179
+ <div class="mt-1">
180
+ <v-chip v-for="p in v.plates" :key="p?.plateNumber" size="small" class="mr-1">
181
+ {{ p?.plateNumber }}
182
+ </v-chip>
183
+ </div>
184
+
185
+ <template #append>
186
+ <v-btn variant="flat" color="primary" @click="selectNRICRecord(v)">Select</v-btn>
187
+ </template>
188
+
189
+ </v-list-item>
190
+ </v-list>
191
+
192
+ </v-card-text>
193
+ </v-card>
194
+ </v-dialog>
195
+
196
+ </v-card>
197
+ </template>
198
+
199
+ <script setup lang="ts">
200
+
201
+
202
+
203
+ const prop = defineProps({
204
+ type: {
205
+ type: String as PropType<TVehicleType>,
206
+ required: true
207
+ },
208
+ org: {
209
+ type: String,
210
+ required: true
211
+ },
212
+ site: {
213
+ type: String,
214
+ required: true
215
+ },
216
+ mode: {
217
+ type: String as PropType<'add' | 'edit'>,
218
+ default: 'add'
219
+ },
220
+ vehicleData: {
221
+ type: Object as PropType<Partial<TVehicle> | null>,
222
+ default: null
223
+ }
224
+ });
225
+
226
+ const { requiredRule, formatDateISO8601, debounce } = useUtils();
227
+ const { addVehicle, getCustomSeasonPassTypes, updateVehicle, getVehicleByNRIC } = useVehicle();
228
+ const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
229
+ const { findPersonByNRICMultipleResult } = usePeople();
230
+
231
+ const emit = defineEmits(['back', 'select', 'done', 'error', 'close', 'close:all']);
232
+
233
+
234
+ const vehicle = reactive<Partial<TVehicle>>({
235
+ plates: [],
236
+ type: prop.type,
237
+ category: "resident",
238
+ name: '',
239
+ phoneNumber: '',
240
+ block: '',
241
+ level: '',
242
+ unit: '',
243
+ nric: '',
244
+ remarks: '',
245
+ seasonPassType: '',
246
+ start: '',
247
+ end: '',
248
+ site: prop.site,
249
+ org: prop.org,
250
+ _id: '',
251
+ });
252
+
253
+ const newPlateNumber = ref('');
254
+ const disablePrefilledInputs = ref(true);
255
+
256
+ const blocksArray = ref<TDefaultOptionObj[]>([]);
257
+ const levelsArray = ref<TDefaultOptionObj[]>([]);
258
+ const unitsArray = ref<TDefaultOptionObj[]>([]);
259
+ const seasonPassTypeArray = ref<{ title: string, value: string }[]>([]);
260
+
261
+ const matchingPeople = ref<Partial<TPeople>[]>([]);
262
+ const showMatchingPeopleDialog = ref(false);
263
+ const checkingNRIC = ref(false);
264
+
265
+ const defaultSeasonPassTypeArray = computed(() => {
266
+ return [
267
+ 'Resident Season Pass',
268
+ 'Tenant/Leaseholder Pass',
269
+ 'VIP/Preferred Parking Pass',
270
+ 'Visitor Season Pass',
271
+ 'Staff & Service Provider Pass',
272
+ 'Business Tenant Pass',
273
+ ].map((item) => ({
274
+ title: item,
275
+ value: item
276
+ }))
277
+ });
278
+
279
+
280
+ const typeFieldMap: Record<TVehicleType, string[]> = {
281
+ seasonpass: ['seasonPassType', 'name', 'phone', 'plateNumber', 'block', 'level', 'unit', 'start', 'end'],
282
+ blocklist: ['name', 'nric', 'phone', 'plateNumber', 'remarks'],
283
+ whitelist: ['name', 'nric', 'phone', 'plateNumber', 'block', 'level', 'unit']
284
+ };
285
+
286
+ const shouldShowField = (fieldKey: string): boolean => {
287
+ const visibleFields = typeFieldMap[prop.type as TVehicleType];
288
+ return visibleFields?.includes(fieldKey);
289
+ };
290
+
291
+
292
+ const validForm = ref(false);
293
+ const seasonPassTypeInput = ref('')
294
+ const formRef = ref<HTMLFormElement | null>(null);
295
+ const startDateRef = ref<HTMLInputElement | null>(null)
296
+ const expiryDateRef = ref<HTMLInputElement | null>(null)
297
+ const processing = ref(false);
298
+ const message = ref('');
299
+ const selectedSubscriptionDuration = ref<string | null>('custom')
300
+ const showSubscriptionDateOptions = ref(false)
301
+ const errorMessage = ref('');
302
+
303
+
304
+ // fetch existing vehicle data if in edit mode
305
+ if (prop.mode === 'edit') {
306
+ const existingVehicleData = JSON.parse(JSON.stringify(prop.vehicleData || {}));
307
+ Object.assign(vehicle, existingVehicleData);
308
+ }
309
+
310
+
311
+ const { data: siteData, refresh: refreshSiteData } = useLazyAsyncData(
312
+ `fetch-site-data-${prop.site}`,
313
+ async () => await getSiteById(prop.site));
314
+
315
+ const { data: levelsData, refresh: refreshLevelsData } = useLazyAsyncData(
316
+ `fetch-levels-data-${prop.site}-${vehicle.block}`,
317
+ async () => {
318
+ if (!vehicle.block) return Promise.resolve(null);;
319
+ return await getSiteLevels(prop.site, { block: Number(vehicle.block) })
320
+ });
321
+
322
+ const { data: unitsData, refresh: refreshUnitsData } = useLazyAsyncData(
323
+ `fetch-units-data-${prop.site}-${vehicle.level}`,
324
+ async () => {
325
+ if (!vehicle.level) return Promise.resolve(null);;
326
+ return await getSiteUnits(prop.site, Number(vehicle.block), vehicle.level)
327
+ });
328
+
329
+
330
+ const { data: seasonPassTypeData, refresh: refreshSeasonPassTypeData } = useLazyAsyncData(
331
+ `fetch-season-pass-type-data-${prop.site}`,
332
+ async () => await getCustomSeasonPassTypes(prop.site));
333
+
334
+
335
+ watch(
336
+ siteData,
337
+ (newVal) => {
338
+ const siteDataValue = newVal as any;
339
+ if (siteDataValue) {
340
+ const numberOfBlocks = siteDataValue.metadata?.block || 0;
341
+ for (let i = 1; i <= numberOfBlocks; i++) {
342
+ blocksArray.value.push({
343
+ title: `Block ${i}`,
344
+ value: i
345
+ });
346
+ }
347
+
348
+ } else {
349
+ blocksArray.value = [];
350
+ }
351
+ }, { immediate: true });
352
+
353
+
354
+ watch(
355
+ levelsData,
356
+ (newVal: any) => {
357
+ if (newVal) {
358
+ const arr = newVal.levels || [];
359
+ levelsArray.value = arr?.map((level: any) => ({
360
+ title: level,
361
+ value: level
362
+ }));
363
+ } else {
364
+ levelsArray.value = [];
365
+ }
366
+ }, { immediate: true });
367
+
368
+ watch(
369
+ unitsData,
370
+ (newVal: any) => {
371
+ if (newVal && Array.isArray(newVal)) {
372
+ const arr = newVal || [];
373
+ unitsArray.value = arr?.map((unit: any) => ({
374
+ title: unit?.name,
375
+ value: unit?._id
376
+ }));
377
+ } else {
378
+ unitsArray.value = [];
379
+ }
380
+ }, { immediate: true });
381
+
382
+
383
+ watch(
384
+ seasonPassTypeData,
385
+ (newVal) => {
386
+ if (Array.isArray(newVal)) {
387
+ const filteredArr = newVal.filter((item: any) => item.title && item.value);
388
+ seasonPassTypeArray.value = [...defaultSeasonPassTypeArray.value, ...filteredArr];
389
+ } else {
390
+ seasonPassTypeArray.value = defaultSeasonPassTypeArray.value;
391
+ }
392
+ }, { immediate: true });
393
+
394
+
395
+
396
+
397
+ const subscriptionOptions = computed(() => {
398
+ return [
399
+ { label: 'Custom', value: 'custom' },
400
+ { label: '1 Week', value: '1week' },
401
+ { label: '2 Weeks', value: '2weeks' },
402
+ { label: '3 Weeks', value: '3weeks' },
403
+ { label: '4 Weeks', value: '4weeks' },
404
+ { label: '5 Weeks', value: '5weeks' },
405
+ ];
406
+ })
407
+
408
+ function handleChangeBlock(value: any) {
409
+ vehicle.level = '';
410
+ vehicle.unit = '';
411
+ refreshLevelsData();
412
+ }
413
+
414
+ function handleChangeLevel(value: any) {
415
+ vehicle.unit = '';
416
+ refreshUnitsData();
417
+ }
418
+
419
+
420
+
421
+ function back() {
422
+ emit("back");
423
+ message.value = '';
424
+ showSubscriptionDateOptions.value = false;
425
+ selectedSubscriptionDuration.value = null;
426
+ }
427
+
428
+ function close() {
429
+ emit("close");
430
+ message.value = '';
431
+ showSubscriptionDateOptions.value = false;
432
+ selectedSubscriptionDuration.value = null;
433
+ }
434
+
435
+ function formatVehicleType(type: TVehicleType): string {
436
+ switch (type) {
437
+ case 'whitelist':
438
+ return 'Whitelist';
439
+ case 'blocklist':
440
+ return 'Blocklist';
441
+ case 'seasonpass':
442
+ return 'Season Pass';
443
+ default:
444
+ return '';
445
+ }
446
+ }
447
+
448
+ function onSeasonPassTypeSelected(val: string | { title: string, value: string }) {
449
+ vehicle.seasonPassType = typeof val === 'object' ? val?.value : val;
450
+ }
451
+
452
+
453
+ async function submit() {
454
+ errorMessage.value = '';
455
+ processing.value = true;
456
+ try {
457
+ const SPTVal = vehicle?.seasonPassType as string;
458
+ if (SPTVal) {
459
+ vehicle.seasonPassType = SPTVal?.charAt(0)?.toUpperCase() + SPTVal?.slice(1)?.toLowerCase();
460
+ }
461
+
462
+
463
+ const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org } = vehicle
464
+
465
+ let payload: Partial<TVehiclePayload> = {
466
+ plateNumber,
467
+ name,
468
+ phoneNumber,
469
+ };
470
+
471
+ if (prop.mode === 'add') {
472
+ payload = {
473
+ ...payload,
474
+ type,
475
+ category,
476
+ site,
477
+ org
478
+ }
479
+ }
480
+
481
+ if (vehicle.type === 'whitelist') {
482
+ payload = {
483
+ ...payload,
484
+ block,
485
+ level,
486
+ unit,
487
+ };
488
+ } else if (vehicle.type === 'seasonpass') {
489
+ payload = {
490
+ ...payload,
491
+ seasonPassType,
492
+ block,
493
+ level,
494
+ unit,
495
+ start,
496
+ end
497
+ };
498
+ } else if (vehicle.type === 'blocklist') {
499
+ payload = {
500
+ ...payload,
501
+ nric,
502
+ remarks,
503
+ };
504
+ }
505
+
506
+ if (prop.mode === 'add') {
507
+ await addVehicle(payload);
508
+ } else if (prop.mode === 'edit') {
509
+ const vehicleId = vehicle?._id as string;
510
+ await updateVehicle(vehicleId, payload);
511
+ }
512
+ emit("done");
513
+
514
+
515
+ } catch (error: any) {
516
+ const err = error?.data?.message
517
+ errorMessage.value = err || `Failed to ${prop.mode === 'add' ? 'add' : 'update'} vehicle. Please try again.`;
518
+ } finally {
519
+ processing.value = false;
520
+ }
521
+ }
522
+
523
+ // const showComponent = (value: TVehicleType[]) => {
524
+ // return value.includes(prop.type);
525
+
526
+ // }
527
+
528
+ watch(selectedSubscriptionDuration, (duration) => {
529
+
530
+
531
+ const dateNowISO = formatDateISO8601(new Date())
532
+ const weeksLaterISO = (week: number) => {
533
+ const now = new Date()
534
+ const dateWeekLater = new Date(now.getTime() + (week * 7) * 24 * 60 * 60 * 1000)
535
+ return formatDateISO8601(dateWeekLater)
536
+ }
537
+
538
+ if (duration === 'custom') {
539
+ vehicle.start = '';
540
+ vehicle.end = '';
541
+ } else if (duration === '1week') {
542
+ vehicle.start = dateNowISO
543
+ vehicle.end = weeksLaterISO(1)
544
+ } else if (duration === '2weeks') {
545
+ vehicle.start = dateNowISO
546
+ vehicle.end = weeksLaterISO(2)
547
+ } else if (duration === '3weeks') {
548
+ vehicle.start = dateNowISO
549
+ vehicle.end = weeksLaterISO(3)
550
+ } else if (duration === '4weeks') {
551
+ vehicle.start = dateNowISO
552
+ vehicle.end = weeksLaterISO(4)
553
+ } else if (duration === '5weeks') {
554
+ vehicle.start = dateNowISO
555
+ vehicle.end = weeksLaterISO(5)
556
+ }
557
+ })
558
+
559
+ function validStartDateRule(value: string) {
560
+ const expiryDateISO = vehicle.end;
561
+ if (!value && expiryDateISO) {
562
+ return 'Start Date is required';
563
+ }
564
+
565
+ return true;
566
+ }
567
+
568
+ function validExpiryDateRule(value: string) {
569
+ const startDateISO = vehicle.start;
570
+
571
+ if (!value && startDateISO) {
572
+ return 'Expiry Date is required';
573
+ }
574
+
575
+ if (value && startDateISO) {
576
+ const expiry = new Date(value);
577
+ const start = new Date(startDateISO as string);
578
+ return expiry > start || 'Expiry date must be later than start date';
579
+ }
580
+
581
+ return true;
582
+ }
583
+
584
+ watch([() => vehicle.end, () => vehicle.start], () => {
585
+ (expiryDateRef.value as any)?.validate();
586
+ (startDateRef.value as any)?.validate();
587
+ });
588
+
589
+
590
+ function selectNRICRecord(record: TPeople) {
591
+
592
+ vehicle.name = record.name;
593
+ vehicle.phoneNumber = record.contact;
594
+ vehicle.block = Number(record.block);
595
+ vehicle.level = record.level;
596
+ vehicle.unit = record.unit;
597
+
598
+ vehicle.plates = record.plates || [];
599
+
600
+ disablePrefilledInputs.value = true;
601
+ showMatchingPeopleDialog.value = false;
602
+
603
+ refreshLevelsData();
604
+ refreshUnitsData();
605
+ }
606
+
607
+
608
+ async function checkNRIC() {
609
+ if (!vehicle.nric || vehicle.nric.length < 5) return;
610
+
611
+ checkingNRIC.value = true;
612
+
613
+ try {
614
+ const res = await findPersonByNRICMultipleResult(vehicle.nric, prop.site) as { items: TPeople[] } | null;
615
+
616
+ if (res?.items && res.items.length > 0) {
617
+ matchingPeople.value = res.items || []
618
+ showMatchingPeopleDialog.value = true;
619
+ } else {
620
+ matchingPeople.value = [];
621
+ showMatchingPeopleDialog.value = false;
622
+ }
623
+
624
+ } catch (error) {
625
+ console.error("NRIC search failed:", error);
626
+ } finally {
627
+ checkingNRIC.value = false;
628
+ }
629
+ }
630
+
631
+ const debounceedCheckNRIC = debounce(checkNRIC, 500);
632
+
633
+ watch(
634
+ () => vehicle.nric,
635
+ async (newNRIC) => {
636
+ resetVehicleDetails();
637
+ if (!newNRIC || newNRIC.length < 3) return;
638
+
639
+ debounceedCheckNRIC();
640
+ }
641
+ );
642
+
643
+
644
+ const resetVehicleDetails = () => {
645
+ vehicle.name = '';
646
+ vehicle.phoneNumber = '';
647
+ vehicle.block = '';
648
+ vehicle.level = '';
649
+ vehicle.unit = '';
650
+ vehicle.plates = [];
651
+ disablePrefilledInputs.value = false;
652
+ }
653
+
654
+
655
+
656
+
657
+
658
+ </script>
659
+ <style scoped>
660
+ .button-outline-class {
661
+ border: 1px solid rgba(var(--v-theme-primary));
662
+ }
663
+ </style>