@7365admin1/layer-common 1.10.6 → 1.10.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AccessCardQrTagging.vue +314 -34
  3. package/components/AccessCardQrTaggingPrintQr.vue +75 -0
  4. package/components/{AddSupplyForm.vue → AddEqupmentForm.vue} +5 -5
  5. package/components/AreaChecklistHistoryLogs.vue +9 -0
  6. package/components/BuildingForm.vue +36 -5
  7. package/components/BuildingManagement/buildings.vue +18 -9
  8. package/components/BuildingManagement/units.vue +13 -115
  9. package/components/BuildingUnitFormAdd.vue +42 -33
  10. package/components/BuildingUnitFormEdit.vue +334 -139
  11. package/components/CleaningScheduleMain.vue +60 -13
  12. package/components/Dialog/DeleteConfirmation.vue +2 -2
  13. package/components/Dialog/UpdateMoreAction.vue +2 -2
  14. package/components/EntryPassInformation.vue +443 -0
  15. package/components/{CheckoutItemMain.vue → EquipmentItemMain.vue} +88 -85
  16. package/components/{SupplyManagement.vue → EquipmentManagement.vue} +3 -3
  17. package/components/Input/DateTimePicker.vue +17 -11
  18. package/components/Input/InputPhoneNumberV2.vue +8 -0
  19. package/components/ManageChecklistMain.vue +400 -36
  20. package/components/ScheduleAreaMain.vue +56 -0
  21. package/components/TableHygiene.vue +47 -430
  22. package/components/UnitPersonCard.vue +123 -0
  23. package/components/VehicleAddSelection.vue +2 -2
  24. package/components/VehicleForm.vue +78 -19
  25. package/components/VehicleManagement.vue +164 -40
  26. package/components/VisitorForm.vue +95 -20
  27. package/components/VisitorFormSelection.vue +13 -2
  28. package/components/VisitorManagement.vue +83 -55
  29. package/composables/useAccessManagement.ts +52 -0
  30. package/composables/useCleaningPermission.ts +7 -7
  31. package/composables/useDashboardData.ts +2 -2
  32. package/composables/{useSupply.ts → useEquipment.ts} +11 -11
  33. package/composables/{useCheckout.ts → useEquipmentItem.ts} +7 -7
  34. package/composables/{useCheckoutPermission.ts → useEquipmentItemPermission.ts} +13 -13
  35. package/composables/useEquipmentManagementPermission.ts +96 -0
  36. package/composables/{useSupplyPermission.ts → useEquipmentPermission.ts} +9 -9
  37. package/composables/usePeople.ts +4 -3
  38. package/composables/useVehicle.ts +35 -2
  39. package/composables/useVisitor.ts +3 -3
  40. package/composables/useWorkOrder.ts +25 -3
  41. package/package.json +3 -2
  42. package/types/building.d.ts +1 -1
  43. package/types/cleaner-schedule.d.ts +1 -0
  44. package/types/{checkout-item.d.ts → equipment-item.d.ts} +3 -3
  45. package/types/{supply.d.ts → equipment.d.ts} +2 -2
  46. package/types/html2pdf.d.ts +19 -0
  47. package/types/people.d.ts +5 -2
  48. package/types/site.d.ts +8 -0
  49. package/types/vehicle.d.ts +4 -3
  50. package/types/visitor.d.ts +2 -1
  51. package/.playground/app.vue +0 -41
  52. package/.playground/eslint.config.mjs +0 -6
  53. package/.playground/nuxt.config.ts +0 -22
  54. package/.playground/pages/feedback.vue +0 -30
@@ -40,7 +40,7 @@ const prop = defineProps({
40
40
 
41
41
  const emit = defineEmits(['cancel', 'select']);
42
42
 
43
- const selection = computed<{label: string, value: TVehicleType}[]>(() => {
43
+ const selection = computed<{label: string, value: TVehicleType | 'seasonpass'}[]>(() => {
44
44
  return [
45
45
  { label: "Whitelist", value: "whitelist" },
46
46
  { label: "Season Pass", value: "seasonpass" },
@@ -52,7 +52,7 @@ function cancel() {
52
52
  emit("cancel");
53
53
  }
54
54
 
55
- function select(value: string) {
55
+ function select(value: TVehicleType | 'seasonpass') {
56
56
  emit("select", value);
57
57
  }
58
58
  </script>
@@ -41,7 +41,7 @@
41
41
  <v-row>
42
42
  <v-col cols="12">
43
43
  <InputLabel class="text-capitalize" title="Full Name" required />
44
- <v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
44
+ <v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" disabled />
45
45
  </v-col>
46
46
  </v-row>
47
47
  </v-col>
@@ -75,10 +75,44 @@
75
75
  <InputLabel class="text-capitalize" title="Vehicle Numbers" required />
76
76
  <!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
77
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 />
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>
79
83
  </template>
80
84
 
81
- <InputVehicleNumber v-model="newPlateNumber" density="comfortable" :rules="[requiredRule]" />
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>
82
116
  </v-col>
83
117
 
84
118
  <v-col v-if="shouldShowField('remarks')" cols="12">
@@ -157,17 +191,24 @@
157
191
  </v-toolbar>
158
192
 
159
193
  <v-dialog v-model="showMatchingPeopleDialog" max-width="700">
160
- <v-card>
194
+ <v-card :loading="checkingNRIC">
161
195
  <v-toolbar>
162
196
  <v-toolbar-title>
163
- Existing Records Found
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>
164
205
  </v-toolbar-title>
165
206
  </v-toolbar>
166
207
 
167
208
  <v-card-text>
168
209
 
169
210
  <v-list lines="three">
170
- <v-list-item v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
211
+ <v-list-item v-if="matchingPeople.length > 0 || checkingNRIC" v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
171
212
  <v-list-item-title>
172
213
  {{ v.name }}
173
214
  </v-list-item-title>
@@ -187,6 +228,14 @@
187
228
  </template>
188
229
 
189
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>
190
239
  </v-list>
191
240
 
192
241
  </v-card-text>
@@ -202,7 +251,7 @@
202
251
 
203
252
  const prop = defineProps({
204
253
  type: {
205
- type: String as PropType<TVehicleType>,
254
+ type: String as PropType<TVehicleType | 'seasonpass'>,
206
255
  required: true
207
256
  },
208
257
  org: {
@@ -224,14 +273,14 @@ const prop = defineProps({
224
273
  });
225
274
 
226
275
  const { requiredRule, formatDateISO8601, debounce } = useUtils();
227
- const { addVehicle, getCustomSeasonPassTypes, updateVehicle, getVehicleByNRIC } = useVehicle();
276
+ const { addVehicle, getCustomSeasonPassTypes, updateVehicle, formatVehicleStatus } = useVehicle();
228
277
  const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
229
278
  const { findPersonByNRICMultipleResult } = usePeople();
230
279
 
231
280
  const emit = defineEmits(['back', 'select', 'done', 'error', 'close', 'close:all']);
232
281
 
233
282
 
234
- const vehicle = reactive<Partial<TVehicle>>({
283
+ const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicleType | 'seasonpass' }>({
235
284
  plates: [],
236
285
  type: prop.type,
237
286
  category: "resident",
@@ -250,7 +299,7 @@ const vehicle = reactive<Partial<TVehicle>>({
250
299
  _id: '',
251
300
  });
252
301
 
253
- const newPlateNumber = ref('');
302
+ const newPlateNumbers = ref<string[]>(['']);
254
303
  const disablePrefilledInputs = ref(true);
255
304
 
256
305
  const blocksArray = ref<TDefaultOptionObj[]>([]);
@@ -277,7 +326,7 @@ const defaultSeasonPassTypeArray = computed(() => {
277
326
  });
278
327
 
279
328
 
280
- const typeFieldMap: Record<TVehicleType, string[]> = {
329
+ const typeFieldMap: Record<TVehicleType | 'seasonpass', string[]> = {
281
330
  seasonpass: ['seasonPassType', 'name', 'phone', 'plateNumber', 'block', 'level', 'unit', 'start', 'end'],
282
331
  blocklist: ['name', 'nric', 'phone', 'plateNumber', 'remarks'],
283
332
  whitelist: ['name', 'nric', 'phone', 'plateNumber', 'block', 'level', 'unit']
@@ -302,7 +351,7 @@ const errorMessage = ref('');
302
351
 
303
352
 
304
353
  // fetch existing vehicle data if in edit mode
305
- if (prop.mode === 'edit') {
354
+ if (prop.vehicleData) {
306
355
  const existingVehicleData = JSON.parse(JSON.stringify(prop.vehicleData || {}));
307
356
  Object.assign(vehicle, existingVehicleData);
308
357
  }
@@ -416,7 +465,14 @@ function handleChangeLevel(value: any) {
416
465
  refreshUnitsData();
417
466
  }
418
467
 
468
+ function addPlate() {
469
+ newPlateNumbers.value.push('');
470
+ }
471
+
419
472
 
473
+ function removePlate(index: number) {
474
+ newPlateNumbers.value.splice(index, 1);
475
+ }
420
476
 
421
477
  function back() {
422
478
  emit("back");
@@ -432,12 +488,13 @@ function close() {
432
488
  selectedSubscriptionDuration.value = null;
433
489
  }
434
490
 
435
- function formatVehicleType(type: TVehicleType): string {
491
+ function formatVehicleType(type: TVehicleType | 'seasonpass'): string {
436
492
  switch (type) {
437
493
  case 'whitelist':
438
494
  return 'Whitelist';
439
495
  case 'blocklist':
440
496
  return 'Blocklist';
497
+
441
498
  case 'seasonpass':
442
499
  return 'Season Pass';
443
500
  default:
@@ -463,15 +520,16 @@ async function submit() {
463
520
  const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org } = vehicle
464
521
 
465
522
  let payload: Partial<TVehiclePayload> = {
466
- plateNumber,
523
+ plateNumber: newPlateNumbers.value,
467
524
  name,
468
525
  phoneNumber,
526
+ type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
527
+ nric
469
528
  };
470
529
 
471
530
  if (prop.mode === 'add') {
472
531
  payload = {
473
532
  ...payload,
474
- type,
475
533
  category,
476
534
  site,
477
535
  org
@@ -498,7 +556,6 @@ async function submit() {
498
556
  } else if (vehicle.type === 'blocklist') {
499
557
  payload = {
500
558
  ...payload,
501
- nric,
502
559
  remarks,
503
560
  };
504
561
  }
@@ -609,18 +666,17 @@ async function checkNRIC() {
609
666
  if (!vehicle.nric || vehicle.nric.length < 5) return;
610
667
 
611
668
  checkingNRIC.value = true;
612
-
669
+ showMatchingPeopleDialog.value = true;
613
670
  try {
614
671
  const res = await findPersonByNRICMultipleResult(vehicle.nric, prop.site) as { items: TPeople[] } | null;
615
672
 
616
673
  if (res?.items && res.items.length > 0) {
617
674
  matchingPeople.value = res.items || []
618
- showMatchingPeopleDialog.value = true;
619
675
  } else {
620
676
  matchingPeople.value = [];
621
- showMatchingPeopleDialog.value = false;
622
677
  }
623
678
 
679
+
624
680
  } catch (error) {
625
681
  console.error("NRIC search failed:", error);
626
682
  } finally {
@@ -634,6 +690,7 @@ watch(
634
690
  () => vehicle.nric,
635
691
  async (newNRIC) => {
636
692
  resetVehicleDetails();
693
+ matchingPeople.value = [];
637
694
  if (!newNRIC || newNRIC.length < 3) return;
638
695
 
639
696
  debounceedCheckNRIC();
@@ -651,6 +708,8 @@ const resetVehicleDetails = () => {
651
708
  disablePrefilledInputs.value = false;
652
709
  }
653
710
 
711
+ watch()
712
+
654
713
 
655
714
 
656
715
 
@@ -2,7 +2,7 @@
2
2
  <v-row no-gutters>
3
3
  <TableMain :headers="headers" :items="items" :loading="loading || getVehiclesPending" :page="page" :pages="pages"
4
4
  :pageRange="pageRange" :extension-height="70" :canCreate="canCreateVehicle" @refresh="getVehiclesRefresh"
5
- createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="dialog.showSelection = true"
5
+ createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="handleAddVehicleClick"
6
6
  @update:page="handleUpdatePage">
7
7
 
8
8
  <template #extension>
@@ -17,16 +17,16 @@
17
17
  <template #item.block="{ value }">
18
18
  {{ value ? `Blk ${value}` : "" }}
19
19
  </template>
20
- <template #item.status="{ value }">
20
+ <!-- <template #item.status="{ value }">
21
21
  <v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
22
22
  {{ formatVehicleStatus(value).label }}
23
23
  </v-chip>
24
24
 
25
25
 
26
- </template>
26
+ </template> -->
27
27
 
28
28
  <template #item.plates="{ value, item }">
29
- <PlateNumberDisplay :plate-numbers="value" :default-value="item.plateNumber" />
29
+ <PlateNumberDisplay :plate-numbers="value" :default-value="item.plateNumber" />
30
30
  </template>
31
31
  </TableMain>
32
32
 
@@ -37,33 +37,51 @@
37
37
  </v-dialog>
38
38
 
39
39
  <v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
40
- <VehicleForm :type="vehicleType" mode="add" @back="handleBackToSelection" @done="handleAddVehicleComplete"
40
+ <VehicleForm :type="vehicleType" mode="add" :vehicle-data="selectedVehicleObject" @back="handleBackToSelection" @done="handleAddVehicleComplete"
41
41
  :org="org" :site="props.site" @close:all="handleCloseAll" />
42
42
  </v-dialog>
43
43
 
44
- <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
44
+ <!-- <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
45
45
  <VehicleForm :type="vehicleType" mode="edit" :vehicle-data="selectedVehicleObject"
46
46
  @back="dialog.updateVehicle = false" @close="dialog.updateVehicle = false" @done="handleUpdateVehicleComplete"
47
47
  :org="org" :site="site" @close:all="handleCloseAll" />
48
- </v-dialog>
48
+ </v-dialog> -->
49
49
 
50
- <v-dialog v-if="canViewVehicleDetails" v-model="dialog.showMoreActions" width="450" persistent>
51
- <DialogUpdateMoreAction title="Preview" :can-update="canUpdateVehicle" :can-delete="canDeleteVehicle"
52
- @close="dialog.showMoreActions = false" edit-button-label="Edit Vehicle" delete-button-label="Delete Vehicle"
53
- @delete="handleDeleteVehicleAction" @edit="handleEditVehicleAction">
50
+ <v-dialog v-if="canViewVehicleDetails" v-model="dialog.showMoreActions" width="600" persistent>
51
+ <DialogUpdateMoreAction title="Preview" :can-update="false" :can-delete="false"
52
+ @close="dialog.showMoreActions = false">
54
53
  <template v-slot:content>
55
54
  <v-row no-gutters class="ga-1 mb-5">
56
-
55
+
57
56
  <template v-for="(label, key) in formattedFields" :key="key">
58
- <v-col v-if="key === 'plates'" class="d-flex ga-2">
57
+ <v-col v-if="key === 'plates'" class="d-flex flex-column ga-2">
59
58
  <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong></span>
60
- <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" />
59
+ <!-- <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" /> -->
60
+ <v-col cols="12">
61
+ <v-card flat outlined class="pa-2" border="sm black">
62
+ <v-data-table :items="selectedVehicleObject[key]" :headers="plateHeaders" hide-default-footer>
63
+ <template #item.status="{ value }">
64
+ <v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
65
+ {{ formatVehicleStatus(value).label }}
66
+ </v-chip>
67
+ </template>
68
+ <template #item.action="{ item }">
69
+ <v-btn v-if="canDeleteVehicle && (item as TPlateNumber)?.status == 'active'" text="Delete" color="error" flat size="x-small" @click="handleDeleteVehicleAction(item as TPlateNumber)"/>
70
+ <v-btn v-if="props.app === 'property_management_agency' && (item as TPlateNumber)?.status == 'pending'" text="Approve" color="success" flat size="x-small" @click="handleApproveVehicle(item as TPlateNumber)"/>
71
+ <v-btn v-if=" (item as TPlateNumber)?.status == 'deleted'" text="Restore" color="orange" flat size="x-small" @click="handleRestoreVehicle(item as TPlateNumber)"/>
72
+ </template>
73
+ </v-data-table>
74
+ <v-btn text="Add Vehicle Number" class="mt-6 text-capitalize" prepend-icon="mdi-plus"
75
+ @click="addNewPlateNumbers" color="primary" />
76
+ </v-card>
77
+ </v-col>å
78
+
61
79
  </v-col>
62
80
 
63
81
  <v-col v-else-if="selectedVehicleObject[key]" cols="12">
64
82
  <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ formatValues(key,
65
83
  selectedVehicleObject[key])
66
- }}</span>
84
+ }}</span>
67
85
  </v-col>
68
86
  </template>
69
87
  </v-row>
@@ -71,13 +89,22 @@
71
89
  </DialogUpdateMoreAction>
72
90
  </v-dialog>
73
91
  <v-dialog v-model="dialog.deleteVehicle" persistent width="540">
74
- <DialogDeleteConfirmation :message="message" prompt-title="Are you sure want to delete this vehicle?"
92
+ <DialogDeleteConfirmation :message="message" :loading="deletingVehicle" :prompt-title="`Are you sure want to delete this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
75
93
  @delete="submitDelete" @close="closeDeleteDialog" />
76
94
  </v-dialog>
95
+ <v-dialog v-model="dialog.approveVehicle" persistent width="540">
96
+ <DialogReusablePrompt :message="message" :loading="approvingVehicle" :prompt-title="`Are you sure want to approve this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
97
+ @approve="submitApprove" @close="dialog.approveVehicle = false" />
98
+ </v-dialog>
99
+ <v-dialog v-model="dialog.restoreVehicle" persistent width="540">
100
+ <DialogReusablePrompt :message="message" :loading="restoringVehicle" :prompt-title="`Are you sure want to restore this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
101
+ @approve="submitRestore" @close="dialog.restoreVehicle = false" />
102
+ </v-dialog>
77
103
  </v-row>
78
104
  </template>
79
105
 
80
106
  <script lang="ts" setup>
107
+ import useUtils from '../composables/useUtils';
81
108
  import useVehicle from '../composables/useVehicle';
82
109
 
83
110
  definePageMeta({
@@ -93,26 +120,33 @@ const props = defineProps({
93
120
  canViewVehicleDetails: { type: Boolean, default: true },
94
121
  site: { type: String, required: true },
95
122
  org: { type: String, required: true },
123
+ app: { type: String, required: true },
96
124
  });
97
125
 
98
126
 
99
127
  const headers = [
100
- { title: "Name", value: "name" },
101
- // { title: "Building", value: "buildingName" },
102
- { title: "Vehicle Numbers", value: "plates" },
103
- { title: "NRIC", value: "nric" },
104
- { title: "Block", value: "block" },
105
- { title: "Floor", value: "level" },
106
- { title: "Unit", value: "unit" },
107
- { title: "Category", value: "category" },
108
- { title: "Type", value: "type" },
109
- { title: "Status", value: "status" },
110
- ]
128
+ { title: "Name", value: "name" },
129
+ { title: "Vehicle Numbers", value: "plates" },
130
+ { title: "NRIC", value: "nric" },
131
+ { title: "Block", value: "block" },
132
+ { title: "Floor", value: "level" },
133
+ { title: "Unit", value: "unitName" },
134
+ { title: "Category", value: "category" },
135
+ // { title: "Type", value: "type" },
136
+ // { title: "Status", value: "status" },
137
+ ]
138
+
139
+ const plateHeaders = [
140
+ { title: "Plate Number", value: "plateNumber" },
141
+ { title: "Type", value: "type" },
142
+ { title: "Status", value: "status" },
143
+ { title: "", value: "action" },
144
+ ]
111
145
 
112
146
 
113
147
 
114
148
  const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
115
- const { getVehicles, deleteVehicle, formatVehicleStatus } = useVehicle();
149
+ const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle } = useVehicle();
116
150
 
117
151
  const items = ref<Array<Record<string, any>>>([]);
118
152
  const page = ref(1);
@@ -122,9 +156,15 @@ const pageRange = ref("-- - -- of --");
122
156
  const searchInput = ref("")
123
157
 
124
158
  const loading = ref(false);
159
+ const deletingVehicle = ref(false);
160
+ const approvingVehicle = ref(false);
161
+ const restoringVehicle = ref(false);
162
+
125
163
  const selectedVehicleId = ref<string | null>(null)
126
164
  const vehicleType = ref<TVehicleType | null>(null);
127
- const vehicleTypeFilter = ref<TVehicleType | null>(null);
165
+ const vehicleTypeFilter = ref<TVehicleType | null>(null);
166
+
167
+ const selectedPlateNumberObject = ref<TPlateNumber | null>(null);
128
168
 
129
169
  const message = ref("");
130
170
  const messageColor = ref("");
@@ -143,17 +183,18 @@ const dialog = reactive({
143
183
  showSelection: false,
144
184
  deleteVehicle: false,
145
185
  showMoreActions: false,
186
+ approveVehicle: false,
187
+ restoreVehicle: false
146
188
  });
147
189
 
190
+
148
191
  const typeOptions = [
149
192
  { label: "Whitelist", value: "whitelist" },
150
193
  { label: "Blocklist", value: "blocklist" },
151
- { label: "Season Pass", value: "seasonPass" },
152
194
  ]
153
195
 
154
196
  const formattedFields: Partial<Record<keyof TVehicle, string>> = {
155
197
  name: "Name",
156
- plates: "Vehicle Numbers",
157
198
  phoneNumber: "Phone Number",
158
199
  nric: "NRIC",
159
200
  block: "Block",
@@ -164,6 +205,7 @@ const formattedFields: Partial<Record<keyof TVehicle, string>> = {
164
205
  category: "Category",
165
206
  type: "Type",
166
207
  remarks: "Remarks",
208
+ plates: "Vehicle Numbers",
167
209
  }
168
210
 
169
211
  function formatValues(key: string, value: any) {
@@ -212,9 +254,9 @@ const { data: getVehiclesReq, refresh: getVehiclesRefresh, pending: getVehiclesP
212
254
 
213
255
 
214
256
  watch(getVehiclesReq, (newData: any) => {
215
- items.value = newData?.items || [];
216
- pages.value = newData?.pages || 0;
217
- pageRange.value = newData?.pageRange || "-- - -- of --";
257
+ items.value = newData?.items || [];
258
+ pages.value = newData?.pages || 0;
259
+ pageRange.value = newData?.pageRange || "-- - -- of --";
218
260
  })
219
261
 
220
262
  function handleRowClick(data: any) {
@@ -223,17 +265,33 @@ function handleRowClick(data: any) {
223
265
  message.value = "";
224
266
  }
225
267
 
268
+ function addNewPlateNumbers() {
269
+ if (!selectedVehicleId.value) return;
270
+ dialog.showSelection = true;
271
+ }
272
+
226
273
  function handleEditVehicleAction() {
227
274
  vehicleType.value = selectedVehicleObject.value?.type || null;
275
+ console.log("Selected Vehicle Object: ", selectedVehicleObject.value);
228
276
  dialog.showSelection = false;
277
+ dialog.updateVehicle = false;
229
278
  dialog.updateVehicle = true;
230
279
  }
231
280
 
232
- function handleDeleteVehicleAction() {
233
- dialog.showMoreActions = false;
281
+ function handleDeleteVehicleAction(item: TPlateNumber) {
282
+ selectedPlateNumberObject.value = item || null;
234
283
  dialog.deleteVehicle = true;
235
284
  }
236
285
 
286
+ function handleApproveVehicle(item: TPlateNumber) {
287
+ selectedPlateNumberObject.value = item || null;
288
+ dialog.approveVehicle = true;
289
+ }
290
+ function handleRestoreVehicle(item: TPlateNumber) {
291
+ selectedPlateNumberObject.value = item || null;
292
+ dialog.restoreVehicle = true;
293
+ }
294
+
237
295
  function handleSelectVehicleStatus(value: TVehicleType) {
238
296
  vehicleType.value = value;
239
297
  dialog.showSelection = false;
@@ -261,10 +319,16 @@ function handleUpdateVehicleComplete() {
261
319
  getVehiclesRefresh();
262
320
  }
263
321
 
322
+
264
323
  function handleUpdatePage(newPageNum: number) {
265
324
  page.value = newPageNum;
266
325
  }
267
326
 
327
+ function handleAddVehicleClick() {
328
+ dialog.showSelection = true;
329
+ selectedVehicleId.value = null;
330
+ }
331
+
268
332
  const formatPlateNumbers = (value: any) => {
269
333
  if (!value || value.length === 0) return "";
270
334
 
@@ -273,20 +337,80 @@ const formatPlateNumbers = (value: any) => {
273
337
  };
274
338
 
275
339
  async function submitDelete() {
276
- bypass.value = false;
340
+
341
+ const plateNumberId = selectedPlateNumberObject.value?._id;
342
+ const type = selectedPlateNumberObject.value?.type as TVehicleType;
343
+ if (!plateNumberId) {
344
+ showMessage("Invalid plate number selected for deletion.", "error");
345
+ return;
346
+ }
277
347
 
278
348
  try {
279
- const res = await deleteVehicle({ site: props.site, id: selectedVehicleId.value as string, recno: selectedVehicleObject.value.recno, type: selectedVehicleObject.value.type }
349
+ deletingVehicle.value = true;
350
+ const res = await deleteVehicle({ site: props.site, id: plateNumberId as string, recno: selectedVehicleObject.value.recno, type }
280
351
  );
281
352
  dialog.deleteVehicle = false;
282
- dialog.showMoreActions = false;
283
353
  selectedVehicleId.value = null
284
354
  showMessage(res.message, "success");
285
355
  getVehiclesRefresh();
286
356
  } catch (error: any) {
287
357
  console.error("Error deleting vehicle:", error);
288
358
  message.value = error.response._data.message;
289
- bypass.value = true;
359
+ } finally {
360
+ deletingVehicle.value = false;
361
+ }
362
+ }
363
+
364
+
365
+
366
+ async function submitRestore() {
367
+
368
+ const plateNumberId = selectedPlateNumberObject.value?._id;
369
+ if (!plateNumberId) {
370
+ showMessage("Invalid plate number selected for restoration.", "error");
371
+ return;
372
+ }
373
+
374
+ try {
375
+ restoringVehicle.value = true;
376
+ // reactivate and restore will use the same endpoint, just with different payload
377
+ const res = await approveVehicle({ site: props.site, org: props.org, id: plateNumberId as string }
378
+ );
379
+ dialog.restoreVehicle = false;
380
+ selectedVehicleId.value = null
381
+ showMessage(res.message, "success");
382
+ getVehiclesRefresh();
383
+ } catch (error: any) {
384
+ console.error("Error restoring vehicle:", error);
385
+ message.value = error.response._data.message;
386
+ } finally {
387
+ restoringVehicle.value = false;
388
+ }
389
+ }
390
+
391
+
392
+ async function submitApprove() {
393
+
394
+ const plateNumberId = selectedPlateNumberObject.value?._id;
395
+ if (!plateNumberId) {
396
+ showMessage("Invalid plate number selected for approval.", "error");
397
+ return;
398
+ }
399
+
400
+ try {
401
+ approvingVehicle.value = true;
402
+ const res = await approveVehicle({ site: props.site, org: props.org, id: plateNumberId as string }
403
+
404
+ );
405
+ dialog.approveVehicle = false;
406
+ selectedVehicleId.value = null
407
+ showMessage(res.message, "success");
408
+ getVehiclesRefresh();
409
+ } catch (error: any) {
410
+ console.error("Error approving vehicle:", error);
411
+ message.value = error.response._data.message;
412
+ } finally {
413
+ approvingVehicle.value = false;
290
414
  }
291
415
  }
292
416