@7365admin1/layer-common 1.10.5 → 1.10.7

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AccessCardDetailsDialog.vue +144 -0
  3. package/components/AccessCardPreviewDialog.vue +7 -2
  4. package/components/AccessCardQrTagging.vue +314 -34
  5. package/components/AccessCardQrTaggingPrintQr.vue +75 -0
  6. package/components/AccessManagement.vue +5 -1
  7. package/components/AreaChecklistHistoryLogs.vue +9 -0
  8. package/components/BuildingForm.vue +36 -5
  9. package/components/BuildingManagement/buildings.vue +18 -9
  10. package/components/BuildingManagement/units.vue +12 -114
  11. package/components/BuildingUnitFormAdd.vue +39 -30
  12. package/components/BuildingUnitFormEdit.vue +265 -116
  13. package/components/CleaningScheduleMain.vue +60 -13
  14. package/components/Dialog/DeleteConfirmation.vue +2 -2
  15. package/components/Dialog/UpdateMoreAction.vue +2 -2
  16. package/components/EntryPassInformation.vue +215 -0
  17. package/components/Input/InputPhoneNumberV2.vue +11 -0
  18. package/components/ManageChecklistMain.vue +29 -3
  19. package/components/PlateNumberDisplay.vue +9 -1
  20. package/components/ScheduleAreaMain.vue +56 -0
  21. package/components/TableHygiene.vue +265 -113
  22. package/components/UnitPersonCard.vue +63 -0
  23. package/components/VehicleAddSelection.vue +2 -2
  24. package/components/VehicleForm.vue +169 -47
  25. package/components/VehicleManagement.vue +169 -43
  26. package/components/VisitorForm.vue +19 -0
  27. package/components/VisitorManagement.vue +1 -1
  28. package/components/WorkOrder/Main.vue +2 -1
  29. package/composables/useAccessManagement.ts +63 -0
  30. package/composables/useFeedback.ts +2 -2
  31. package/composables/usePeople.ts +14 -3
  32. package/composables/useVehicle.ts +15 -1
  33. package/composables/useWorkOrder.ts +2 -2
  34. package/package.json +3 -2
  35. package/types/cleaner-schedule.d.ts +1 -0
  36. package/types/html2pdf.d.ts +19 -0
  37. package/types/people.d.ts +4 -0
  38. package/types/site.d.ts +8 -0
  39. package/types/vehicle.d.ts +3 -4
  40. package/.playground/app.vue +0 -41
  41. package/.playground/eslint.config.mjs +0 -6
  42. package/.playground/nuxt.config.ts +0 -22
  43. package/.playground/pages/feedback.vue +0 -30
  44. package/components/AccessCardHistoryDialog.vue +0 -133
@@ -32,47 +32,87 @@
32
32
  </v-combobox>
33
33
  </v-col>
34
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
+
35
40
  <v-col v-if="shouldShowField('name')" cols="12">
36
41
  <v-row>
37
42
  <v-col cols="12">
38
43
  <InputLabel class="text-capitalize" title="Full Name" required />
39
- <v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" />
44
+ <v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" disabled />
40
45
  </v-col>
41
46
  </v-row>
42
47
  </v-col>
43
48
 
44
- <v-col v-if="shouldShowField('nric')" cols="12">
45
- <InputLabel class="text-capitalize" title="NRIC" required />
46
- <InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]" />
47
- </v-col>
49
+
48
50
 
49
51
  <v-col v-if="shouldShowField('phone')" cols="12">
50
52
  <InputLabel class="text-capitalize" title="Phone Number" required />
51
- <InputPhoneNumberV2 v-model="vehicle.phoneNumber" density="comfortable" :rules="[requiredRule]" />
53
+ <InputPhoneNumberV2 v-model="vehicle.phoneNumber" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
52
54
  </v-col>
53
55
 
54
56
  <v-col v-if="shouldShowField('block')" cols="12">
55
57
  <InputLabel class="text-capitalize" title="Block" required />
56
58
  <v-select v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
57
- @update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" />
59
+ @update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
58
60
  </v-col>
59
61
 
60
62
  <v-col v-if="shouldShowField('level')" cols="12">
61
63
  <InputLabel class="text-capitalize" title="Level" required />
62
- <v-select v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block"
64
+ <v-select v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block || disablePrefilledInputs"
63
65
  @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
64
66
  </v-col>
65
67
 
66
68
  <v-col v-if="shouldShowField('unit')" cols="12">
67
69
  <InputLabel class="text-capitalize" title="Unit" required />
68
- <v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level"
70
+ <v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level || disablePrefilledInputs"
69
71
  :rules="[requiredRule]" />
70
72
  </v-col>
71
73
 
72
74
  <v-col v-if="shouldShowField('plateNumber')" cols="12">
73
- <InputLabel class="text-capitalize" title="Vehicle Number" required />
75
+ <InputLabel class="text-capitalize" title="Vehicle Numbers" required />
74
76
  <!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
75
- <InputVehicleNumber 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" readonly>
79
+ <template #append-inner>
80
+ <v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{ formatVehicleStatus(plate?.status)?.label }}</v-chip>
81
+ </template>
82
+ </v-text-field>
83
+ </template>
84
+
85
+ <template v-for="(plate, index) in newPlateNumbers" :key="index">
86
+ <v-row class="" :class="`${index === newPlateNumbers.length - 1 ? 'mb-1' : 'mb-5'}`" no-gutters>
87
+ <v-col :cols="newPlateNumbers.length > 1 ? '11' : '12'">
88
+ <InputVehicleNumber
89
+ v-model="newPlateNumbers[index]"
90
+ density="comfortable"
91
+ placeholder="New Vehicle Number"
92
+ :rules="[requiredRule]"
93
+ />
94
+ </v-col>
95
+
96
+ <v-col :cols="newPlateNumbers.length > 1 ? '1' : '0'" class="d-flex align-start">
97
+ <v-btn
98
+ icon="mdi-delete-outline"
99
+ variant="text"
100
+ color="error"
101
+ v-if="newPlateNumbers.length > 1"
102
+ @click="removePlate(index)"
103
+ />
104
+ </v-col>
105
+ </v-row>
106
+ </template>
107
+
108
+ <v-btn
109
+ prepend-icon="mdi-plus"
110
+ variant="outlined"
111
+ class="text-none"
112
+ @click="addPlate"
113
+ >
114
+ Add Plate
115
+ </v-btn>
76
116
  </v-col>
77
117
 
78
118
  <v-col v-if="shouldShowField('remarks')" cols="12">
@@ -149,6 +189,59 @@
149
189
  </v-col>
150
190
  </v-row>
151
191
  </v-toolbar>
192
+
193
+ <v-dialog v-model="showMatchingPeopleDialog" max-width="700">
194
+ <v-card :loading="checkingNRIC">
195
+ <v-toolbar>
196
+ <v-toolbar-title>
197
+ <v-row no-gutters class="d-flex align-center justify-space-between">
198
+ <span class="font-weight-bold">
199
+ Matching Records for NRIC: {{ vehicle.nric }}
200
+ </span>
201
+ <span>
202
+ <v-btn icon="mdi-close" variant="text" @click="showMatchingPeopleDialog = false" />
203
+ </span>
204
+ </v-row>
205
+ </v-toolbar-title>
206
+ </v-toolbar>
207
+
208
+ <v-card-text>
209
+
210
+ <v-list lines="three">
211
+ <v-list-item v-if="matchingPeople.length > 0 || checkingNRIC" v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
212
+ <v-list-item-title>
213
+ {{ v.name }}
214
+ </v-list-item-title>
215
+
216
+ <v-list-item-subtitle>
217
+ Block {{ v.block }} - {{ v.level }} - {{ v.unitName }}
218
+ </v-list-item-subtitle>
219
+
220
+ <div class="mt-1">
221
+ <v-chip v-for="p in v.plates" :key="p?.plateNumber" size="small" class="mr-1">
222
+ {{ p?.plateNumber }}
223
+ </v-chip>
224
+ </div>
225
+
226
+ <template #append>
227
+ <v-btn variant="flat" color="primary" @click="selectNRICRecord(v)">Select</v-btn>
228
+ </template>
229
+
230
+ </v-list-item>
231
+ <v-empty-state v-else
232
+ icon="mdi-account-off"
233
+ title="No matching records found"
234
+ subtitle="The NRIC you entered does not match any existing records.">
235
+ <template #actions>
236
+ <v-btn variant="flat" color="primary" @click="showMatchingPeopleDialog = false">Close</v-btn>
237
+ </template>
238
+ </v-empty-state>
239
+ </v-list>
240
+
241
+ </v-card-text>
242
+ </v-card>
243
+ </v-dialog>
244
+
152
245
  </v-card>
153
246
  </template>
154
247
 
@@ -158,7 +251,7 @@
158
251
 
159
252
  const prop = defineProps({
160
253
  type: {
161
- type: String as PropType<TVehicleType>,
254
+ type: String as PropType<TVehicleType | 'seasonpass'>,
162
255
  required: true
163
256
  },
164
257
  org: {
@@ -180,14 +273,15 @@ const prop = defineProps({
180
273
  });
181
274
 
182
275
  const { requiredRule, formatDateISO8601, debounce } = useUtils();
183
- const { addVehicle, getCustomSeasonPassTypes, updateVehicle, getVehicleByNRIC } = useVehicle();
276
+ const { addVehicle, getCustomSeasonPassTypes, updateVehicle, formatVehicleStatus } = useVehicle();
184
277
  const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
278
+ const { findPersonByNRICMultipleResult } = usePeople();
185
279
 
186
280
  const emit = defineEmits(['back', 'select', 'done', 'error', 'close', 'close:all']);
187
281
 
188
282
 
189
- const vehicle = reactive<Partial<TVehicle>>({
190
- plateNumber: '',
283
+ const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicleType | 'seasonpass' }>({
284
+ plates: [],
191
285
  type: prop.type,
192
286
  category: "resident",
193
287
  name: '',
@@ -205,15 +299,16 @@ const vehicle = reactive<Partial<TVehicle>>({
205
299
  _id: '',
206
300
  });
207
301
 
208
-
302
+ const newPlateNumbers = ref<string[]>(['']);
303
+ const disablePrefilledInputs = ref(true);
209
304
 
210
305
  const blocksArray = ref<TDefaultOptionObj[]>([]);
211
306
  const levelsArray = ref<TDefaultOptionObj[]>([]);
212
307
  const unitsArray = ref<TDefaultOptionObj[]>([]);
213
308
  const seasonPassTypeArray = ref<{ title: string, value: string }[]>([]);
214
309
 
215
- const matchingVehicles = ref<TVehicle[]>([]);
216
- const showVehicleMatchDialog = ref(false);
310
+ const matchingPeople = ref<Partial<TPeople>[]>([]);
311
+ const showMatchingPeopleDialog = ref(false);
217
312
  const checkingNRIC = ref(false);
218
313
 
219
314
  const defaultSeasonPassTypeArray = computed(() => {
@@ -231,7 +326,7 @@ const defaultSeasonPassTypeArray = computed(() => {
231
326
  });
232
327
 
233
328
 
234
- const typeFieldMap: Record<TVehicleType, string[]> = {
329
+ const typeFieldMap: Record<TVehicleType | 'seasonpass', string[]> = {
235
330
  seasonpass: ['seasonPassType', 'name', 'phone', 'plateNumber', 'block', 'level', 'unit', 'start', 'end'],
236
331
  blocklist: ['name', 'nric', 'phone', 'plateNumber', 'remarks'],
237
332
  whitelist: ['name', 'nric', 'phone', 'plateNumber', 'block', 'level', 'unit']
@@ -256,7 +351,7 @@ const errorMessage = ref('');
256
351
 
257
352
 
258
353
  // fetch existing vehicle data if in edit mode
259
- if (prop.mode === 'edit') {
354
+ if (prop.vehicleData) {
260
355
  const existingVehicleData = JSON.parse(JSON.stringify(prop.vehicleData || {}));
261
356
  Object.assign(vehicle, existingVehicleData);
262
357
  }
@@ -370,7 +465,14 @@ function handleChangeLevel(value: any) {
370
465
  refreshUnitsData();
371
466
  }
372
467
 
468
+ function addPlate() {
469
+ newPlateNumbers.value.push('');
470
+ }
471
+
373
472
 
473
+ function removePlate(index: number) {
474
+ newPlateNumbers.value.splice(index, 1);
475
+ }
374
476
 
375
477
  function back() {
376
478
  emit("back");
@@ -386,12 +488,13 @@ function close() {
386
488
  selectedSubscriptionDuration.value = null;
387
489
  }
388
490
 
389
- function formatVehicleType(type: TVehicleType): string {
491
+ function formatVehicleType(type: TVehicleType | 'seasonpass'): string {
390
492
  switch (type) {
391
493
  case 'whitelist':
392
494
  return 'Whitelist';
393
495
  case 'blocklist':
394
496
  return 'Blocklist';
497
+
395
498
  case 'seasonpass':
396
499
  return 'Season Pass';
397
500
  default:
@@ -417,15 +520,16 @@ async function submit() {
417
520
  const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org } = vehicle
418
521
 
419
522
  let payload: Partial<TVehiclePayload> = {
420
- plateNumber,
523
+ plateNumber: newPlateNumbers.value,
421
524
  name,
422
525
  phoneNumber,
526
+ type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
527
+ nric
423
528
  };
424
529
 
425
530
  if (prop.mode === 'add') {
426
531
  payload = {
427
532
  ...payload,
428
- type,
429
533
  category,
430
534
  site,
431
535
  org
@@ -452,7 +556,6 @@ async function submit() {
452
556
  } else if (vehicle.type === 'blocklist') {
453
557
  payload = {
454
558
  ...payload,
455
- nric,
456
559
  remarks,
457
560
  };
458
561
  }
@@ -541,23 +644,41 @@ watch([() => vehicle.end, () => vehicle.start], () => {
541
644
  });
542
645
 
543
646
 
647
+ function selectNRICRecord(record: TPeople) {
648
+
649
+ vehicle.name = record.name;
650
+ vehicle.phoneNumber = record.contact;
651
+ vehicle.block = Number(record.block);
652
+ vehicle.level = record.level;
653
+ vehicle.unit = record.unit;
654
+
655
+ vehicle.plates = record.plates || [];
656
+
657
+ disablePrefilledInputs.value = true;
658
+ showMatchingPeopleDialog.value = false;
659
+
660
+ refreshLevelsData();
661
+ refreshUnitsData();
662
+ }
663
+
664
+
544
665
  async function checkNRIC() {
545
666
  if (!vehicle.nric || vehicle.nric.length < 5) return;
546
667
 
547
668
  checkingNRIC.value = true;
669
+ showMatchingPeopleDialog.value = true;
548
670
  try {
549
- const res = await getVehicleByNRIC(vehicle.nric, prop.site);
550
- if (res && res.vehicles && res.vehicles.length > 0) {
551
- matchingVehicles.value = res.vehicles;
552
- showVehicleMatchDialog.value = true;
671
+ const res = await findPersonByNRICMultipleResult(vehicle.nric, prop.site) as { items: TPeople[] } | null;
672
+
673
+ if (res?.items && res.items.length > 0) {
674
+ matchingPeople.value = res.items || []
553
675
  } else {
554
- showVehicleMatchDialog.value = false;
555
- matchingVehicles.value = [];
676
+ matchingPeople.value = [];
556
677
  }
678
+
679
+
557
680
  } catch (error) {
558
- console.error('Error checking NRIC:', error);
559
- showVehicleMatchDialog.value = false;
560
- matchingVehicles.value = [];
681
+ console.error("NRIC search failed:", error);
561
682
  } finally {
562
683
  checkingNRIC.value = false;
563
684
  }
@@ -568,26 +689,27 @@ const debounceedCheckNRIC = debounce(checkNRIC, 500);
568
689
  watch(
569
690
  () => vehicle.nric,
570
691
  async (newNRIC) => {
571
- if (!newNRIC || newNRIC.length < 5) return;
692
+ resetVehicleDetails();
693
+ matchingPeople.value = [];
694
+ if (!newNRIC || newNRIC.length < 3) return;
572
695
 
573
696
  debounceedCheckNRIC();
574
697
  }
575
698
  );
576
699
 
577
- watch(
578
- () => vehicle.plateNumber,
579
- (newData) => {
580
-
581
- const plateNumberExistsInNRICMatches = matchingVehicles.value.some(v => v.plateNumber === newData);
582
- if (plateNumberExistsInNRICMatches) {
583
- errorMessage.value = 'The vehicle number you entered is already associated with the NRIC provided. Please double check.';
584
- showVehicleMatchDialog.value = true;
585
- } else {
586
- errorMessage.value = '';
587
- showVehicleMatchDialog.value = false;
588
- }
700
+
701
+ const resetVehicleDetails = () => {
702
+ vehicle.name = '';
703
+ vehicle.phoneNumber = '';
704
+ vehicle.block = '';
705
+ vehicle.level = '';
706
+ vehicle.unit = '';
707
+ vehicle.plates = [];
708
+ disablePrefilledInputs.value = false;
589
709
  }
590
- );
710
+
711
+ watch()
712
+
591
713
 
592
714
 
593
715