@7365admin1/layer-common 1.11.54 → 2.0.0

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - f4ee57d: Refactor Building Management Module
8
+
3
9
  ## 1.11.54
4
10
 
5
11
  ### Patch Changes
@@ -3,7 +3,7 @@
3
3
  <v-toolbar>
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
5
  <span class="font-weight-bold text-h5 text-capitalize">
6
- {{ prop.mode }} Block
6
+ {{ prop.mode }} Block {{ (prop.mode === "edit" && building.block) ? `- ${building.block}` : "" }}
7
7
  </span>
8
8
  </v-row>
9
9
  </v-toolbar>
@@ -12,21 +12,21 @@
12
12
  <v-row no-gutters class="px-6 pt-4">
13
13
  <v-col cols="12" class="mt-2">
14
14
  <v-row no-gutters>
15
- <InputLabel class="text-capitalize font-weight-bold" title="Name" required />
15
+ <InputLabel class="text-capitalize font-weight-bold" title="Block Name" required />
16
16
  <v-col cols="12">
17
17
  <v-text-field v-model="building.name" density="comfortable" :rules="[requiredRule]"></v-text-field>
18
18
  </v-col>
19
19
  </v-row>
20
20
  </v-col>
21
21
 
22
- <v-col cols="12">
22
+ <v-col v-if="prop.mode === 'add'" cols="12">
23
23
  <v-row>
24
24
  <v-col cols="6" class="mt-2">
25
25
  <v-row no-gutters>
26
- <InputLabel class="text-capitalize font-weight-bold" title="Block" required />
26
+ <InputLabel class="text-capitalize font-weight-bold" title="Block No." required />
27
27
  <v-col cols="12">
28
28
  <v-select v-model.number="building.block" :items="blocks" item-title="label" item-value="value"
29
- density="comfortable" :disabled="prop.mode === 'edit'" :rules="[
29
+ density="comfortable" :rules="[
30
30
  requiredRule,
31
31
  () =>
32
32
  (building.block && building.block > 0) ||
@@ -43,7 +43,7 @@
43
43
 
44
44
  <v-col cols="6" class="mt-2">
45
45
  <v-row no-gutters>
46
- <InputLabel class="text-capitalize font-weight-bold" title="Levels" required />
46
+ <InputLabel class="text-capitalize font-weight-bold" title="No. of Levels" required />
47
47
  <v-col cols="12">
48
48
  <v-text-field v-model.number="buildingLevels" density="comfortable" :rules="[requiredRule]"
49
49
  type="number" @update:model-value="setBuildingLevels()"></v-text-field>
@@ -53,24 +53,33 @@
53
53
  </v-row>
54
54
  </v-col>
55
55
 
56
- <v-col v-if="buildingLevels" cols="12">
56
+ <!-- <v-col v-if="buildingLevels &&" cols="12">
57
57
  <v-row justify="center">
58
58
  <v-col cols="6">
59
59
  <v-btn block color="primary" variant="text" class="text-none font-weight-bold" text="Set level labels"
60
60
  @click="show = !show"></v-btn>
61
61
  </v-col>
62
62
  </v-row>
63
- </v-col>
63
+ </v-col> -->
64
+ </v-row>
65
+
66
+ <v-row no-gutters v-if="building.levels.length > 0 || newLevelsArray.length > 0" class="mt-2">
67
+ <InputLabel class="text-capitalize font-weight-bold px-6 mb-2" title="Levels" />
64
68
  </v-row>
65
69
 
66
70
  <v-expand-transition>
67
- <v-row v-show="show" no-gutters class="px-6">
71
+ <v-row v-if="prop.mode === 'edit'" no-gutters class="px-6">
68
72
  <template v-for="(level, levelIndex) in building.levels" :key="levelIndex">
69
73
  <v-col cols="12">
70
74
  <v-row no-gutters>
71
- <InputLabel class="text-capitalize font-weight-bold" :title="`Level ${levelIndex + 1}`" />
72
- <v-col cols="12">
73
- <v-text-field v-model.trim="building.levels[levelIndex]" density="comfortable"></v-text-field>
75
+ <v-col :cols="prop.mode === 'edit' ? 10 : 12">
76
+ <v-text-field v-model.trim="building.levels[levelIndex].name" density="comfortable"></v-text-field>
77
+ </v-col>
78
+ <v-col v-if="prop.mode === 'edit'" cols="2" class="d-flex align-center pb-5 justify-end">
79
+ <v-btn icon variant="text" color="error" :disabled="disable"
80
+ @click="openDeleteLevelPrompt(levelIndex)">
81
+ <v-icon icon="mdi-trash-can-outline"></v-icon>
82
+ </v-btn>
74
83
  </v-col>
75
84
  </v-row>
76
85
  </v-col>
@@ -78,9 +87,43 @@
78
87
  </v-row>
79
88
  </v-expand-transition>
80
89
 
90
+
91
+
92
+ <v-row no-gutters class="px-6 w-100">
93
+ <v-row no-gutters v-for="(newLevelsItem, index) in newLevelsArray" :key="index" :cols="12" class="w-100">
94
+ <v-text-field v-model.trim="newLevelsArray[index]" density="comfortable"></v-text-field>
95
+ <v-col cols="2" class="d-flex align-center pb-5 justify-end">
96
+ <v-btn icon variant="text" color="error" @click="newLevelsArray.splice(index, 1)">
97
+ <v-icon icon="mdi-trash-can-outline"></v-icon>
98
+ </v-btn>
99
+ </v-col>
100
+ </v-row>
101
+ </v-row>
102
+
103
+ <v-row v-if="prop.mode === 'edit'" no-gutters class="px-6 w-100">
104
+ <v-col cols="10">
105
+ <v-text-field v-model.trim="activeNewLevelInput" density="comfortable" class="w-100"
106
+ placeholder="Add new level" @click="addNewLevelErrorMessage = ''"></v-text-field>
107
+ </v-col>
108
+ <v-col cols="2" class="d-flex align-center pb-5 justify-end">
109
+ <v-btn icon variant="text" @click="addNewLevel()">
110
+ <v-icon icon="mdi-plus"></v-icon>
111
+ </v-btn>
112
+ </v-col>
113
+ </v-row>
114
+ <v-row v-if="prop.mode === 'edit'" no-gutters class="px-6 mb-2">
115
+ <span class="text-caption text-error">
116
+ {{ addNewLevelErrorMessage }}
117
+ </span>
118
+ </v-row>
119
+
120
+
121
+
122
+
81
123
  <v-col cols="12" class="px-6">
82
- <InputLabel class="text-capitalize" title="Building Floor plan" />
83
- <InputFileV2 v-model="building.buildingFiles" :multiple="true" accept="*" :max-length="10" title="Upload Files" />
124
+ <InputLabel class="text-capitalize font-weight-bold" title="Building Floor plan" />
125
+ <InputFileV2 v-model="building.buildingFiles" :multiple="true" accept="*" :max-length="10"
126
+ title="Upload Files" />
84
127
  </v-col>
85
128
 
86
129
  <v-col cols="12" class="mt-2">
@@ -121,10 +164,40 @@
121
164
  </v-col>
122
165
  </v-row>
123
166
  </v-toolbar>
167
+
168
+ <ConfirmDialog v-model="confirmDeleteLevelDialog" :loading="loading.deletingLevel" width="450" persistent>
169
+ <template #title>
170
+ Delete Level
171
+ </template>
172
+
173
+ <template #description>
174
+ Are you sure you want to delete {{ levelPendingDeleteName || "this level" }}?
175
+ </template>
176
+
177
+ <template #footer>
178
+ <v-row no-gutters class="w-100">
179
+ <v-col cols="6">
180
+ <v-btn tile block size="48" variant="text" class="text-none" :disabled="loading.deletingLevel"
181
+ @click="closeDeleteLevelPrompt">
182
+ Cancel
183
+ </v-btn>
184
+ </v-col>
185
+
186
+ <v-col cols="6">
187
+ <v-btn tile block size="48" variant="flat" color="error" class="text-none" :loading="loading.deletingLevel"
188
+ :disabled="loading.deletingLevel" @click="confirmDeleteLevel">
189
+ Delete
190
+ </v-btn>
191
+ </v-col>
192
+ </v-row>
193
+ </template>
194
+ </ConfirmDialog>
124
195
  </v-card>
125
196
  </template>
126
197
 
127
198
  <script setup lang="ts">
199
+ import InputLabel from './InputLabel.vue';
200
+
128
201
  const prop = defineProps({
129
202
  site: {
130
203
  type: String,
@@ -152,6 +225,7 @@ const prop = defineProps({
152
225
 
153
226
  const { getSiteById } = useSite();
154
227
  const { getFileById } = useFile();
228
+ const { deleteById, batchUpdateLevels } = useBuildingLevel();
155
229
 
156
230
  const site = ref<TSite | null>(null);
157
231
 
@@ -184,21 +258,64 @@ const blocks = computed(() => {
184
258
  return formattedArray;
185
259
  });
186
260
 
187
- const emit = defineEmits(["cancel", "success", "success:create-more"]);
261
+ const emit = defineEmits(["cancel", "success", "success:create-more", "refresh"]);
188
262
 
189
263
  const validForm = ref(false);
190
264
 
191
265
  const buildingLevels = ref(0);
266
+ const newLevelsArray = ref<string[]>([])
267
+ const activeNewLevelInput = ref<string>("")
268
+ const addNewLevelErrorMessage = ref("");
269
+
270
+ const loading = reactive({
271
+ deletingLevel: false,
272
+ });
192
273
 
193
274
  const show = ref(false);
275
+ const confirmDeleteLevelDialog = ref(false);
276
+ const levelPendingDeleteIndex = ref<number | null>(null);
277
+ const levelActionLoading = ref(false);
278
+
279
+ const levelPendingDeleteName = computed(() => {
280
+ if (levelPendingDeleteIndex.value === null) return "";
281
+ return building.value.levels[levelPendingDeleteIndex.value]?.name ?? "";
282
+ });
194
283
 
195
284
  function setBuildingLevels() {
196
- building.value.levels.length = 0;
285
+ newLevelsArray.value = [];
197
286
  for (let index = 0; index < buildingLevels.value; index++) {
198
- building.value.levels.push(`Lvl${index + 1}`);
287
+ newLevelsArray.value.push(`Lvl${index + 1}`);
199
288
  }
200
289
  }
201
290
 
291
+ // function addNewLevels() {
292
+ // const levelsToAdd = newLevelsInput.value
293
+ // .split(/\r?\n|,/)
294
+ // .map((item) => item.trim())
295
+ // .filter(Boolean);
296
+
297
+ // if (!levelsToAdd.length) return;
298
+ // buildingLevels.value = building.value.levels.length;
299
+ // newLevelsInput.value = "";
300
+ // }
301
+
302
+ function openDeleteLevelPrompt(levelIndex: number) {
303
+ levelPendingDeleteIndex.value = levelIndex;
304
+ confirmDeleteLevelDialog.value = true;
305
+ }
306
+
307
+ function closeDeleteLevelPrompt() {
308
+ confirmDeleteLevelDialog.value = false;
309
+ levelPendingDeleteIndex.value = null;
310
+ }
311
+
312
+ async function deleteLevelPlaceholder(levelName: string, levelIndex: number) {
313
+ // Placeholder integration for future delete-level API call.
314
+ return { success: true, levelName, levelIndex };
315
+ }
316
+
317
+
318
+
202
319
  const building = ref<TBuilding>({
203
320
  site: "",
204
321
  name: "",
@@ -207,13 +324,17 @@ const building = ref<TBuilding>({
207
324
  buildingFiles: []
208
325
  });
209
326
 
327
+ const initialBuildingLevels = ref([])
328
+
210
329
  // Normalize buildingFiles: extract IDs for InputFileV2, keep names in a separate map
211
330
  const buildingFilesNames = ref<Record<string, string>>({});
212
331
 
213
332
  building.value.site = prop.site;
214
333
  if (prop.mode === "edit") {
215
334
  building.value = JSON.parse(JSON.stringify(prop.building));
335
+ building.value.levels = building.value.levels.map((level: any) => ({ _id: level._id, name: level.name })); //temporary
216
336
  buildingLevels.value = building.value.levels.length;
337
+ initialBuildingLevels.value = JSON.parse(JSON.stringify(building.value.levels));
217
338
 
218
339
  const rawFiles = building.value.buildingFiles as any[];
219
340
  if (rawFiles?.length && typeof rawFiles[0] === 'object') {
@@ -256,16 +377,34 @@ async function submit() {
256
377
  try {
257
378
  if (prop.mode === "add") {
258
379
  const buildingPayload = { ...building.value };
259
- buildingPayload.buildingFiles = (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" }));
260
- await createBuilding(buildingPayload);
380
+
381
+ let payload: TBuildingPayload = {
382
+ site: buildingPayload.site,
383
+ name: buildingPayload.name,
384
+ block: buildingPayload.block,
385
+ levels: [...newLevelsArray.value],
386
+ buildingFiles: (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" }))
387
+ };
388
+
389
+ await createBuilding(payload);
261
390
  }
262
391
 
263
392
  if (prop.mode === "edit") {
393
+
394
+ console.log('Updating building with levels:', building.value.levels);
395
+
396
+ if(JSON.stringify(initialBuildingLevels.value) !== JSON.stringify(building.value.levels)) {
397
+ await batchUpdateLevels(building.value.levels as { _id: string; name: string }[]);
398
+ }
399
+
400
+ const newLevels = [...newLevelsArray.value, ...(activeNewLevelInput.value.trim() ? [activeNewLevelInput.value.trim()] : [])];
401
+
264
402
  await updateById(building.value._id ?? "", {
265
403
  name: building.value.name,
266
404
  block: building.value.block,
267
- levels: building.value.levels,
268
- buildingFiles: (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" }))
405
+ // levels: building.value.levels,
406
+ buildingFiles: (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" })),
407
+ ...(newLevels.length > 0 ? { levels: newLevels } : {})
269
408
  });
270
409
  }
271
410
 
@@ -288,6 +427,49 @@ async function submit() {
288
427
  }
289
428
  }
290
429
 
430
+
431
+ const addNewLevel = () => {
432
+ if (!activeNewLevelInput.value.trim()) {
433
+ addNewLevelErrorMessage.value = "Please fill in the current new level before adding another.";
434
+ return;
435
+ }
436
+ newLevelsArray.value.push(activeNewLevelInput.value);
437
+ activeNewLevelInput.value = "";
438
+ }
439
+
440
+ const confirmDeleteLevel = async () => {
441
+ const levelIndex = levelPendingDeleteIndex.value;
442
+ if (levelIndex === null) {
443
+ console.error("No level selected for deletion.");
444
+ return;
445
+ }
446
+ const levelId = building.value.levels[levelIndex]._id;
447
+ if (!levelId) {
448
+ console.error("Level ID is missing. Cannot delete level.");
449
+ return;
450
+ }
451
+
452
+ try {
453
+ loading.deletingLevel = true;
454
+ await deleteById(levelId);
455
+ const index = building.value.levels.findIndex(level => level._id === levelId);
456
+ closeDeleteLevelPrompt();
457
+ if (index !== -1) {
458
+ building.value.levels.splice(index, 1);
459
+ buildingLevels.value = building.value.levels.length;
460
+ }
461
+ emit('refresh')
462
+ } catch (error: any) {
463
+ console.error("Failed to delete level:", error);
464
+ const message = error.response?._data?.message || "Failed to delete level";
465
+ message.value = message;
466
+ } finally {
467
+ loading.deletingLevel = false;
468
+ }
469
+
470
+
471
+ }
472
+
291
473
  watch(buildingLevels, (newVal) => {
292
474
  setTimeout(() => {
293
475
  show.value = newVal > 0 && !!newVal;
@@ -43,12 +43,12 @@
43
43
  <v-dialog v-model="createDialog" width="450" persistent>
44
44
  <BuildingForm :site="site" :blocks="items" @cancel="createDialog = false" @success="successCreate()"
45
45
  @success:create-more="getBuildings()" />
46
- </v-dialog>
46
+ </v-dialog>
47
47
 
48
48
  <!-- Edit Dialog -->
49
49
  <v-dialog v-model="editDialog" width="450" persistent>
50
50
  <BuildingForm :site="site" :blocks="items" mode="edit" @cancel="editDialog = false" @success="successUpdate()"
51
- :building="selectedBuilding" />
51
+ :building="selectedBuilding" @refresh="getBuildings()" />
52
52
  </v-dialog>
53
53
 
54
54
  <!-- Preview Dialog -->
@@ -103,7 +103,7 @@ const props = defineProps({
103
103
  },
104
104
  {
105
105
  title: "Level",
106
- value: "level",
106
+ value: "level.name",
107
107
  },
108
108
  {
109
109
  title: "Block",
@@ -31,12 +31,12 @@
31
31
  item-value="_id" v-model:search="searchBuilding" :hide-no-data="false" density="comfortable"
32
32
  :rules="[requiredRule]" variant="outlined">
33
33
  <template v-slot:item="{ props: itemProps, item }">
34
- <v-list-item v-bind="itemProps" :title="`Block ${item?.raw?.block} (${item?.raw?.name})`">
34
+ <v-list-item v-bind="itemProps" :title="item?.raw?.name">
35
35
 
36
36
  </v-list-item>
37
37
  </template>
38
38
  <template v-slot:selection="{ item }">
39
- {{ buildingUnit.building && `Block ${item?.raw?.block} (${item?.raw?.name})` }}
39
+ {{ buildingUnit.building && `${item?.raw?.name}` }}
40
40
  </template>
41
41
  </v-autocomplete>
42
42
  </v-col>
@@ -47,7 +47,7 @@
47
47
  <v-row no-gutters>
48
48
  <InputLabel class="text-capitalize" title="Level" required />
49
49
  <v-col cols="12">
50
- <v-select v-model="buildingUnit.level" :items="buildingLevels" density="comfortable"
50
+ <v-select v-model="buildingUnit.level" :items="buildingLevels" item-title="name" item-value="_id" density="comfortable"
51
51
  :rules="[requiredRule]"></v-select>
52
52
  </v-col>
53
53
  </v-row>
@@ -275,7 +275,7 @@ watchEffect(() => {
275
275
 
276
276
  const buildingUnitQty = ref(1);
277
277
 
278
- const buildingUnit = ref<TBuildingUnit>({
278
+ const buildingUnit = ref<TBuildingUnitPayload>({
279
279
  site: "",
280
280
  name: "Unit",
281
281
  building: "",
@@ -29,10 +29,11 @@
29
29
 
30
30
  <v-col cols="12" md="6" lg="4" class="mt-2">
31
31
  <v-row no-gutters>
32
- <InputLabel class="text-capitalize font-weight-bold" title="Level" required />
32
+ <InputLabel class="text-capitalize font-weight-bold" title="Level"/>
33
33
  <v-col cols="12">
34
- <v-select v-model="buildingUnit.level" :items="buildingLevels" density="comfortable"
35
- :rules="[requiredRule]"></v-select>
34
+ <!-- <v-select v-model="buildingUnit.level" :items="buildingLevels" readonly item-title="name" class="no-pointer" item-value="_id" density="comfortable"
35
+ :rules="[requiredRule]"></v-select> -->
36
+ <v-text-field :model-value="buildingUnit.level.name" density="comfortable" readonly class="no-pointer" />
36
37
  </v-col>
37
38
  </v-row>
38
39
  </v-col>
@@ -43,7 +44,7 @@
43
44
  <v-row no-gutters>
44
45
  <InputLabel class="text-capitalize font-weight-bold" title="Block Name" />
45
46
  <v-col cols="12">
46
- <v-text-field :model-value="`Block ${buildingUnit.block} (${buildingUnit.buildingName})`"
47
+ <v-text-field :model-value="buildingUnit.buildingName"
47
48
  density="comfortable" readonly class="no-pointer"></v-text-field>
48
49
  </v-col>
49
50
  </v-row>
@@ -328,7 +329,10 @@ const buildingUnit = ref({
328
329
  ownerName: "",
329
330
  building: "",
330
331
  buildingName: "",
331
- level: 0,
332
+ level: {
333
+ _id: "",
334
+ name: "",
335
+ },
332
336
  // category: "",
333
337
  block: null,
334
338
  status: "active",
@@ -457,13 +461,13 @@ const selectedBuilding = computed(() => {
457
461
  return buildings.value.find((b) => b.value === buildingUnit.value.building);
458
462
  });
459
463
 
460
- const buildingLevels = computed(() => {
461
- const levels = selectedBuilding.value?.levels ?? [];
462
- return levels.map((level: string) => ({
463
- title: level,
464
- value: level,
465
- }))
466
- });
464
+ // const buildingLevels = computed(() => {
465
+ // const levels = selectedBuilding.value?.levels ?? [];
466
+ // return levels.map((level: { name: string; _id: string }) => ({
467
+ // name: level.name,
468
+ // value: level._id,
469
+ // }))
470
+ // });
467
471
 
468
472
  const disable = ref(false);
469
473
 
@@ -500,7 +504,7 @@ async function submit() {
500
504
 
501
505
  await updateById(buildingUnit.value._id ?? "", {
502
506
  name: buildingUnit.value.name,
503
- level: buildingUnit.value.level,
507
+ level: buildingUnit.value.level._id,
504
508
  // category: buildingUnit.value.category,
505
509
  buildingUnitFiles: (buildingUnit.value.buildingUnitFiles as string[] || []).map((id) => ({ id, name: buildingUnitFilesNames.value[id] ?? "" })),
506
510
  companyName: buildingUnit.value.companyName,
@@ -267,7 +267,7 @@ const { create: _add, updateById: _update, getAll: _getAll } = useOnlineFormConf
267
267
 
268
268
  const { data: existingFormsData } = useLazyAsyncData(
269
269
  `online-form-configurations-filter-${siteId}-${Date.now()}`,
270
- () => _getAll({ site: siteId, org: orgId, limit: 100 })
270
+ () => _getAll({ site: siteId, org: orgId, status: "active", limit: 100 })
271
271
  );
272
272
 
273
273
  const availableTemplateNames = computed(() => {
@@ -32,6 +32,16 @@
32
32
  >
33
33
  <v-icon>mdi-refresh</v-icon>
34
34
  </v-btn>
35
+ <v-text-field
36
+ v-model="search"
37
+ density="compact"
38
+ variant="outlined"
39
+ placeholder="Search..."
40
+ prepend-inner-icon="mdi-magnify"
41
+ clearable
42
+ hide-details
43
+ style="width: 220px; margin-left: 12px;"
44
+ />
35
45
  </template>
36
46
 
37
47
  <template #append>
@@ -263,6 +273,8 @@
263
273
  <script setup lang="ts">
264
274
  import OnlineFormConfigurationForm from "./OnlineFormConfigurationForm.vue";
265
275
  import OnlineFormRenderPreview from "./OnlineFormRenderPreview.vue";
276
+ import useFile from "../composables/useFile";
277
+ import useOnlineFormConfiguration from "../composables/useOnlineFormConfiguration";
266
278
 
267
279
  definePageMeta({
268
280
  middleware: ["01-auth", "02-org"],
@@ -306,13 +318,13 @@ const props = defineProps({
306
318
  },
307
319
  });
308
320
 
309
- const { headerSearch } = useLocal();
310
321
  const { getAll: _getAll, deleteById: _deleteById } = useOnlineFormConfiguration();
311
322
  const { getFileUrl } = useFile();
312
323
 
313
324
  const page = ref(1);
314
325
  const pages = ref(0);
315
326
  const pageRange = ref("-- - -- of --");
327
+ const search = ref("");
316
328
 
317
329
  const message = ref("");
318
330
  const messageSnackbar = ref(false);
@@ -333,7 +345,8 @@ const {
333
345
  } = useLazyAsyncData("get-all-online-form-configuration", () =>
334
346
  _getAll({
335
347
  page: page.value,
336
- search: headerSearch.value,
348
+ search: search.value,
349
+ status: "active",
337
350
  org: orgId,
338
351
  site: siteId,
339
352
  })
@@ -341,6 +354,15 @@ const {
341
354
 
342
355
  const loading = computed(() => getAllReqStatus.value === "pending");
343
356
 
357
+ let searchDebounce: ReturnType<typeof setTimeout> | null = null;
358
+ watch(search, () => {
359
+ if (searchDebounce) clearTimeout(searchDebounce);
360
+ searchDebounce = setTimeout(() => {
361
+ page.value = 1;
362
+ getOnlineFormConfigurations();
363
+ }, 400);
364
+ });
365
+
344
366
  watchEffect(() => {
345
367
  if (getOnlineFormConfigurationReq.value) {
346
368
  items.value = getOnlineFormConfigurationReq.value.items;
@@ -34,20 +34,25 @@
34
34
 
35
35
  <v-col v-if="shouldShowField('block')" cols="12">
36
36
  <InputLabel class="text-capitalize" title="Block" required />
37
- <v-autocomplete v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
38
- @update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" />
37
+ <v-autocomplete v-model="vehicle.block" :items="blocksArray" autocomplete="off"
38
+ :key="blockListDataPending ? 'block-list-pending' : 'block-list-ready'" :loading="blockListDataPending"
39
+ item-value="value" item-title="title" @update:model-value="handleChangeBlock" density="comfortable"
40
+ :rules="[requiredRule]" />
39
41
  </v-col>
40
42
 
41
43
  <v-col v-if="shouldShowField('level')" cols="12">
42
44
  <InputLabel class="text-capitalize" title="Level" required />
43
- <v-autocomplete v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block"
44
- @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
45
+ <v-autocomplete v-model="vehicle.level" :items="levelsArray" autocomplete="off"
46
+ :key="levelListDataPending ? 'level-list-pending' : 'level-list-ready'" :loading="levelListDataPending"
47
+ density="comfortable" :disabled="!vehicle.block" @update:model-value="handleChangeLevel"
48
+ :rules="[requiredRule]" />
45
49
  </v-col>
46
50
 
47
51
  <v-col v-if="shouldShowField('unit')" cols="12">
48
52
  <InputLabel class="text-capitalize" title="Unit" required />
49
- <v-autocomplete v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level"
50
- :rules="[requiredRule]" />
53
+ <v-autocomplete v-model="vehicle.unit" :items="unitsArray" autocomplete="off"
54
+ :key="unitListDataPending ? 'unit-list-pending' : 'unit-list-ready'" :loading="unitListDataPending"
55
+ density="comfortable" :disabled="!vehicle.level" :rules="[requiredRule]" />
51
56
  </v-col>
52
57
 
53
58
  <v-col v-if="shouldShowField('nric')" cols="12">
@@ -278,6 +283,7 @@ const { requiredRule, formatDateISO8601, debounce } = useUtils();
278
283
  const { addVehicle, getCustomSeasonPassTypes, updateVehicle, formatVehicleStatus } = useVehicle();
279
284
  const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
280
285
  const { findPersonByNRICMultipleResult, getPeopleByUnit } = usePeople();
286
+ const { getBlocksSelection, getLevelsSelection, getUnitSelection } = useBuilding();
281
287
 
282
288
  const emit = defineEmits(['back', 'select', 'done', 'error', 'close', 'close:all']);
283
289
 
@@ -313,6 +319,7 @@ const seasonPassTypeArray = ref<{ title: string, value: string }[]>([]);
313
319
 
314
320
  const matchingPeople = ref<Partial<TPeople>[]>([]);
315
321
  const showMatchingPeopleDialog = ref(false);
322
+ const prefillingRecords = ref(false);
316
323
  const checkingNRIC = ref(false);
317
324
  const checkingUnit = ref(false);
318
325
 
@@ -367,62 +374,80 @@ if (prop.vehicleData) {
367
374
  }
368
375
 
369
376
 
370
- const { data: siteData, refresh: refreshSiteData } = useLazyAsyncData(
371
- `fetch-site-data-${prop.site}`,
372
- async () => await getSiteById(prop.site));
373
377
 
374
- const { data: levelsData, refresh: refreshLevelsData } = useLazyAsyncData(
375
- `fetch-levels-data-${prop.site}-${vehicle.block}`,
378
+ const {
379
+ data: blocksListData,
380
+ refresh: refreshBlockListData,
381
+ status: blocksListStatus,
382
+ pending: blockListDataPending,
383
+ } = useAsyncData(`vehicle-fetch-block-list-data-${prop.site}`, () =>
384
+ getBlocksSelection({ siteId: prop.site }), { watch: [() => prop.site], deep: true }
385
+ );
386
+
387
+
388
+ const {
389
+ data: levelsData,
390
+ refresh: refreshLevelsData,
391
+ status: levelsStatus,
392
+ pending: levelListDataPending,
393
+ } = useAsyncData(
394
+ `vehicle-fetch-levels-data-${prop.site}-${vehicle.block}`,
376
395
  async () => {
377
- if (!vehicle.block) return Promise.resolve(null);;
378
- return await getSiteLevels(prop.site, { block: Number(vehicle.block) })
379
- });
396
+ if (!vehicle.block) return Promise.resolve(null);
397
+ // return await getSiteWithLevels(prop.site, { block: Number(vehicle.block) });
398
+ return await getLevelsSelection({ siteId: prop.site, blockId: vehicle.block._id ? vehicle.block._id : vehicle.block });
399
+ }, { watch: [() => vehicle.block], deep: true }
400
+ );
380
401
 
381
- const { data: unitsData, refresh: refreshUnitsData } = useLazyAsyncData(
382
- `fetch-units-data-${prop.site}-${vehicle.level}`,
402
+ const {
403
+ data: unitsData,
404
+ refresh: refreshUnitsData,
405
+ status: unitsStatus,
406
+ pending: unitListDataPending,
407
+ } = useAsyncData(
408
+ `vehicle-fetch-units-data-${prop.site}-${vehicle.level}`,
383
409
  async () => {
384
- if (!vehicle.level) return Promise.resolve(null);;
385
- return await getSiteUnits(prop.site, Number(vehicle.block), vehicle.level)
386
- });
410
+ if (!vehicle.level) return Promise.resolve(null);
411
+ return await getUnitSelection({ siteId: prop.site, block: vehicle.block._id ? vehicle.block._id : vehicle.block, level: vehicle.level._id ? vehicle.level._id : vehicle.level });
412
+ }, { watch: [() => vehicle.level], deep: true }
413
+ );
387
414
 
388
415
 
389
416
  const { data: seasonPassTypeData, refresh: refreshSeasonPassTypeData } = useLazyAsyncData(
390
- `fetch-season-pass-type-data-${prop.site}`,
417
+ `vehicle-fetch-season-pass-type-data-${prop.site}`,
391
418
  async () => await getCustomSeasonPassTypes(prop.site));
392
419
 
393
420
 
394
421
  watch(
395
- siteData,
396
- (newVal) => {
397
- const siteDataValue = newVal as any;
398
- if (siteDataValue) {
399
- const numberOfBlocks = siteDataValue.metadata?.block || 0;
400
- for (let i = 1; i <= numberOfBlocks; i++) {
401
- blocksArray.value.push({
402
- title: `Block ${i}`,
403
- value: i
404
- });
405
- }
406
-
407
- } else {
422
+ [blocksListData],
423
+ () => {
424
+ if (blocksListStatus.value === 'pending') {
408
425
  blocksArray.value = [];
426
+ return;
409
427
  }
410
- }, { immediate: true });
411
428
 
429
+ const arr = Array.isArray(blocksListData.value?.data) ? blocksListData.value?.data : [];
430
+ blocksArray.value = arr.map((block: any) => ({
431
+ title: block.name,
432
+ value: block._id,
433
+ }));
434
+ },
435
+ { immediate: true, deep: true }
436
+ )
412
437
 
413
- watch(
414
- levelsData,
415
- (newVal: any) => {
416
- if (newVal) {
417
- const arr = newVal.levels || [];
418
- levelsArray.value = arr?.map((level: any) => ({
419
- title: level,
420
- value: level
421
- }));
422
- } else {
423
- levelsArray.value = [];
424
- }
425
- }, { immediate: true });
438
+ watch([levelsData], () => {
439
+ if (levelsStatus.value === 'pending') {
440
+ levelsArray.value = [];
441
+ return;
442
+ }
443
+
444
+ const arr = Array.isArray(levelsData.value?.data) ? levelsData.value?.data : [];
445
+ const mappedLevels = arr.map((level: any) => ({
446
+ title: level.name,
447
+ value: level._id,
448
+ }));
449
+ levelsArray.value = mappedLevels;
450
+ }, { immediate: true, deep: true })
426
451
 
427
452
  watch(
428
453
  unitsData,
@@ -710,7 +735,7 @@ function selectPeopleRecord(record: TPeople) {
710
735
 
711
736
 
712
737
  async function checkPeopleById() {
713
- if (!vehicle.unit || prop.mode === 'edit') return;
738
+ if (!vehicle.unit || prop.mode === 'edit' || prefillingRecords.value) return;
714
739
 
715
740
  checkingUnit.value = true;
716
741
  showMatchingPeopleDialog.value = true;
@@ -743,6 +768,8 @@ const debounceedCheckUnit = debounce(checkPeopleById, 500);
743
768
  watch(
744
769
  () => vehicle.unit,
745
770
  async (newUnit) => {
771
+ if (prefillingRecords.value) return;
772
+
746
773
  resetVehicleDetails();
747
774
  matchingPeople.value = [];
748
775
  if (!newUnit) return;
@@ -778,13 +805,25 @@ onMounted(() => {
778
805
  vehicle.start = prop.vehicleData.start || '';
779
806
  vehicle.plateNumber = prop.vehicleData.plateNumber
780
807
  vehicle.peopleId = prop.vehicleData.peopleId || '';
781
- vehicle.block = prop.vehicleData.block === 0 ? null : prop.vehicleData.block || null;
808
+ vehicle.block = prop.vehicleData.block || null;
782
809
 
783
- if(prop.type === "seasonpass"){
810
+ if (prop.type === "seasonpass") {
784
811
  showSubscriptionDateOptions.value = true;
785
812
  }
786
813
  }
787
814
  }, 300);
815
+
816
+ if (prop.mode === 'add' && prop.type !== 'blocklist') {
817
+ // For add mode, we want to check for matching people records if the type is not blocklist, as blocklist records are typically not linked to people records.
818
+ prefillingRecords.value = true;
819
+ vehicle.block = prop.vehicleData?.block?._id ? prop.vehicleData?.block?._id : '';
820
+ vehicle.level = prop.vehicleData?.level?._id ? prop.vehicleData?.level?._id : '';
821
+ vehicle.unit = prop.vehicleData?.unit?._id ? prop.vehicleData?.unit?._id : '';
822
+ }
823
+
824
+ nextTick(() => {
825
+ prefillingRecords.value = false;
826
+ })
788
827
  })
789
828
 
790
829
 
@@ -170,9 +170,9 @@ const headers = [
170
170
  { title: "Name", value: "name" },
171
171
  { title: "Vehicle Numbers", value: "plates" },
172
172
  { title: "NRIC", value: "nric" },
173
- { title: "Block", value: "block" },
174
- { title: "Floor", value: "level" },
175
- { title: "Unit", value: "unitName" },
173
+ { title: "Block", value: "block.name" },
174
+ { title: "Floor", value: "level.name" },
175
+ { title: "Unit", value: "unit.name" },
176
176
  { title: "Category", value: "category" },
177
177
  // { title: "Type", value: "type" },
178
178
  // { title: "Status", value: "status" },
@@ -247,7 +247,7 @@ const formattedFields: Partial<Record<keyof TVehicle, string>> = {
247
247
  nric: "NRIC",
248
248
  block: "Block",
249
249
  level: "Level",
250
- unitName: "Unit",
250
+ unit: "Unit",
251
251
  start: "Start",
252
252
  end: "End",
253
253
  category: "Category",
@@ -259,6 +259,12 @@ const formattedFields: Partial<Record<keyof TVehicle, string>> = {
259
259
  function formatValues(key: string, value: any) {
260
260
  if (!value) return ""
261
261
  switch (key) {
262
+ case "unit":
263
+ return value.name || "";
264
+ case "level":
265
+ return value.name || "";
266
+ case "block":
267
+ return value.name || "";
262
268
  case "start":
263
269
  return formatDate(value)
264
270
  case "end":
@@ -152,27 +152,27 @@
152
152
 
153
153
  <v-col v-if="shouldShowField('block')" cols="12">
154
154
  <InputLabel class="text-capitalize" title="Block" required />
155
- <v-autocomplete v-model="visitor.block" :items="blocksArray" item-value="value" item-title="title"
156
- :loading="blockStatus === 'pending'" @update:model-value="handleChangeBlock" density="comfortable"
155
+ <v-autocomplete v-model="visitor.block" :items="blocksArray" item-value="value" autocomplete="off" item-title="title" :key="blockListDataPending ? 'block-list-pending' : 'block-list-ready'"
156
+ :loading="blockListDataPending" @update:model-value="handleChangeBlock" density="comfortable"
157
157
  :rules="[requiredRule]" />
158
158
  </v-col>
159
159
 
160
160
  <v-col v-if="shouldShowField('level')" cols="12">
161
161
  <InputLabel class="text-capitalize" title="Level" required />
162
- <v-autocomplete v-model="visitor.level" :items="levelsArray" density="comfortable" :disabled="!visitor.block"
163
- :loading="levelsStatus === 'pending'" @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
162
+ <v-autocomplete v-model="visitor.level" :items="levelsArray" density="comfortable" autocomplete="off"
163
+ :disabled="!visitor.block" :key="levelListDataPending ? 'level-list-pending' : 'level-list-ready'" :loading="levelsDataPending" @update:model-value="handleChangeLevel"
164
+ :rules="[requiredRule]" />
164
165
  </v-col>
165
-
166
-
167
166
  <v-col v-if="shouldShowField('unit')" cols="12">
168
167
  <InputLabel class="text-capitalize" title="Unit" required />
169
- <v-autocomplete v-model="visitor.unit" :items="unitsArray" density="comfortable" item-title="title"
170
- item-value="value" :disabled="!visitor.level" :loading="unitsStatus === 'pending'" :rules="[requiredRule]"
168
+ <v-autocomplete v-model="visitor.unit" :items="unitsArray" density="comfortable" autocomplete="off" item-title="title"
169
+ item-value="value" :disabled="!visitor.level" :key="unitListDataPending ? 'unit-list-pending' : 'unit-list-ready'" :loading="unitsDataPending" :rules="[requiredRule]"
171
170
  @update:model-value="handleUpdateUnit" />
172
171
  </v-col>
173
172
 
174
173
  <v-col v-if="shouldShowField('unit')" cols="12">
175
- <InputLabel class="text-capitalize" title="Registered Unit Company Name" required :key="'unit-key' + visitor.level" />
174
+ <InputLabel class="text-capitalize" title="Registered Unit Company Name" required
175
+ :key="'unit-key' + visitor.level" />
176
176
  <v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable"
177
177
  :loading="buildingUnitDataPending" readonly class="no-pointer" />
178
178
  </v-col>
@@ -315,7 +315,8 @@ const currentAutofillSource = ref<AutofillSource>(null);
315
315
 
316
316
 
317
317
  const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
318
- const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
318
+ const { getSiteById } = useSiteSettings();
319
+ const { getBlocksSelection, getLevelsSelection, getUnitSelection } = useBuilding();
319
320
  const { createVisitor, typeFieldMap, contractorTypes, getVisitors, updateVisitor } = useVisitor();
320
321
  const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
321
322
  const { createVisitorPass, signQr } = useAccessManagement();
@@ -683,78 +684,101 @@ const {
683
684
  const {
684
685
  data: siteData,
685
686
  refresh: refreshSiteData,
686
- status: blockStatus,
687
+ status: siteStatus,
687
688
  pending: siteDataPending,
688
- } = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
689
+ } = useLazyAsyncData(`visitor-fetch-site-data-${prop.site}`, () =>
689
690
  getSiteById(prop.site)
690
691
  );
691
692
 
693
+
694
+ const {
695
+ data: blocksListData,
696
+ refresh: refreshBlockListData,
697
+ status: blocksListStatus,
698
+ pending: blockListDataPending,
699
+ } = useLazyAsyncData(`visitor-fetch-block-list-data-${prop.site}`, () =>
700
+ getBlocksSelection({ siteId: prop.site }), {watch: [() => prop.site], deep: true }
701
+ );
702
+
703
+
704
+
692
705
  const {
693
706
  data: levelsData,
694
707
  refresh: refreshLevelsData,
695
708
  status: levelsStatus,
709
+ pending: levelsDataPending,
696
710
  } = useLazyAsyncData(
697
- `fetch-levels-data-${prop.site}-${visitor.block}`,
711
+ `visitor-fetch-levels-data-${prop.site}-${visitor.block}`,
698
712
  async () => {
699
713
  if (!visitor.block) return Promise.resolve(null);
700
- return await getSiteLevels(prop.site, { block: Number(visitor.block) });
701
- }, { watch: [() => visitor.block] }
714
+ // return await getSiteWithLevels(prop.site, { block: Number(visitor.block) });
715
+ return await getLevelsSelection({ siteId: prop.site, blockId: visitor.block });
716
+ }, { watch: [() => visitor.block], deep: true }
702
717
  );
703
718
 
704
719
  const {
705
720
  data: unitsData,
706
721
  refresh: refreshUnitsData,
707
722
  status: unitsStatus,
723
+ pending: unitsDataPending,
708
724
  } = useLazyAsyncData(
709
- `fetch-units-data-${prop.site}-${visitor.level}`,
725
+ `visitor-fetch-units-data-${prop.site}-${visitor.level}`,
710
726
  async () => {
711
727
  if (!visitor.level) return Promise.resolve(null);
712
- return await getSiteUnits(prop.site, Number(visitor.block), visitor.level);
713
- }, { watch: [() => visitor.level] }
728
+ return await getUnitSelection({ siteId: prop.site, block: visitor.block, level: visitor.level });
729
+ },{ watch: [() => visitor.level], deep: true }
714
730
  );
715
731
 
732
+
716
733
  watch(
717
734
  siteData,
718
735
  (newVal) => {
719
736
  const siteDataValue = newVal as TSite;
720
737
  if (siteDataValue) {
721
- const numberOfBlocks = siteDataValue.metadata?.block || 0;
722
- for (let i = 1; i <= numberOfBlocks; i++) {
723
- blocksArray.value.push({
724
- title: `Block ${i}`,
725
- value: i,
726
- });
727
- }
728
738
 
729
739
  deliveryCompanyList.value = Array.isArray(siteDataValue?.deliveryCompanyList) ? siteDataValue.deliveryCompanyList : [];
730
740
 
731
- } else {
732
- blocksArray.value = [];
733
- }
741
+ }
734
742
  },
735
743
  { immediate: true }
736
744
  );
737
745
 
738
746
  watch(
739
- levelsData,
740
- (newVal: any) => {
741
- if (newVal) {
742
- const arr = newVal.levels || [];
743
- levelsArray.value = arr?.map((level: any) => ({
744
- title: level,
745
- value: level,
746
- }));
747
- } else {
748
- levelsArray.value = [];
747
+ [blocksListData],
748
+ () => {
749
+ if (blocksListStatus.value === 'pending') {
750
+ blocksArray.value = [];
751
+ return;
749
752
  }
753
+
754
+ const arr = Array.isArray(blocksListData.value?.data) ? blocksListData.value?.data : [];
755
+ blocksArray.value = arr.map((block: any) => ({
756
+ title: block.name,
757
+ value: block._id,
758
+ }));
750
759
  },
751
- { immediate: true }
752
- );
760
+ { immediate: true, deep: true }
761
+ )
762
+
763
+
764
+ watch([levelsData], () => {
765
+ if (levelsStatus.value === 'pending') {
766
+ levelsArray.value = [];
767
+ return;
768
+ }
769
+
770
+ const arr = Array.isArray(levelsData.value?.data) ? levelsData.value?.data : [];
771
+ const mappedLevels = arr.map((level: any) => ({
772
+ title: level.name,
773
+ value: level._id,
774
+ }));
775
+ levelsArray.value = mappedLevels;
776
+ }, { immediate: true, deep: true})
753
777
 
754
778
  watch(
755
779
  unitsData,
756
780
  (newVal: any) => {
757
- if (!newVal) {
781
+ if (!newVal || !Array.isArray(newVal)) {
758
782
  unitsArray.value = []
759
783
  return
760
784
  }
@@ -777,7 +801,7 @@ watch(
777
801
 
778
802
  unitsArray.value = mapped
779
803
  },
780
- { immediate: true }
804
+ { immediate: true, deep: true }
781
805
  )
782
806
 
783
807
 
@@ -925,7 +949,7 @@ async function submit() {
925
949
  }
926
950
 
927
951
  // add register status when registering unregistered visitor
928
- if (prop.mode === 'register'){
952
+ if (prop.mode === 'register') {
929
953
  payload.status = "registered"
930
954
  }
931
955
 
@@ -96,7 +96,7 @@
96
96
  formatLocation({
97
97
  block: item.block,
98
98
  level: item.level,
99
- unit: item.unitName,
99
+ unit: item.unit,
100
100
  })
101
101
  }}</span>
102
102
  </span>
@@ -852,7 +852,7 @@ const formattedFields = {
852
852
  company: "Company",
853
853
  block: "Block",
854
854
  level: "Level",
855
- unitName: "Unit",
855
+ unit: "Unit",
856
856
  checkIn: "Check In",
857
857
  snapshotEntryImage: "Entry Image",
858
858
  checkOut: "Check Out",
@@ -973,10 +973,10 @@ const selectedVisitorObject = computed<Record<string, any>>(() => {
973
973
 
974
974
  let locationString = "N/A";
975
975
 
976
- if (obj?.block || obj?.level || obj?.unitName) {
977
- locationString = `${obj.block} / ${obj.level} / ${obj.unitName}`;
978
- } else if (obj?.block) {
979
- locationString = obj.block;
976
+ if (obj?.block?.name || obj?.level?.name || obj?.unitName) {
977
+ locationString = `${obj.block?.name} / ${obj.level?.name} / ${obj.unitName}`;
978
+ } else if (obj?.block?.name) {
979
+ locationString = obj.block?.name;
980
980
  } else {
981
981
  locationString = "N/A";
982
982
  }
@@ -994,6 +994,10 @@ function formatValues(key: string, value: any) {
994
994
  switch (key) {
995
995
  case "unit":
996
996
  return value?.name;
997
+ case "block":
998
+ return value?.name;
999
+ case "level":
1000
+ return value?.name;
997
1001
  case "checkIn":
998
1002
  return formatDate(value);
999
1003
  case "checkOut":
@@ -206,7 +206,7 @@ export default function useBuilding() {
206
206
  });
207
207
  }
208
208
 
209
- function createBuilding(data: TBuilding) {
209
+ function createBuilding(data: TBuildingPayload) {
210
210
  return useNuxtApp().$api("/api/buildings", {
211
211
  method: "POST",
212
212
  body: data,
@@ -222,7 +222,7 @@ export default function useBuilding() {
222
222
 
223
223
  function updateById(
224
224
  id: string,
225
- data: Pick<TBuilding, "name" | "block" | "levels" | "buildingFloorPlan">
225
+ data: TBuildingUpdatePayload
226
226
  ) {
227
227
  return useNuxtApp().$api(`/api/buildings/id/${id}`, {
228
228
  method: "PATCH",
@@ -236,6 +236,32 @@ export default function useBuilding() {
236
236
  });
237
237
  }
238
238
 
239
+
240
+ function getBlocksSelection({siteId} : { siteId: string; }) {
241
+ return useNuxtApp().$api(`/api/buildings/list/site/${siteId}`, {
242
+ method: "GET",
243
+ });
244
+ }
245
+
246
+ function getLevelsSelection({siteId, blockId} : { siteId: string; blockId: string }) {
247
+ return useNuxtApp().$api(`/api/building-levels/list/site/${siteId}`, {
248
+ method: "GET",
249
+ query: {
250
+ block: blockId
251
+ }
252
+ });
253
+ }
254
+
255
+ async function getUnitSelection({siteId, block, level} : { siteId: string; block: string; level: string }) {
256
+ return await useNuxtApp().$api<Record<string, any>>(
257
+ `/api/building-units/site/${siteId}/block/${block}/level/${level}`,
258
+ {
259
+ method: "GET",
260
+ }
261
+ );
262
+ }
263
+
264
+
239
265
  return {
240
266
  categories,
241
267
  types,
@@ -246,5 +272,8 @@ export default function useBuilding() {
246
272
  updateBuildingField,
247
273
  deleteById,
248
274
  updateById,
249
- };
275
+ getBlocksSelection,
276
+ getLevelsSelection,
277
+ getUnitSelection
278
+ }
250
279
  }
@@ -0,0 +1,31 @@
1
+ export default function useBuildingLevel() {
2
+
3
+
4
+ function batchUpdateLevels(levels: { _id: string; name: string }[]) {
5
+
6
+ const formattedPayloadLevels = levels.map(level => ({
7
+ _id: level._id,
8
+ value: {
9
+ name: level.name
10
+ }
11
+ }));
12
+
13
+ return useNuxtApp().$api(`/api/building-levels/batch`, {
14
+ method: "PATCH",
15
+ body:
16
+ [...formattedPayloadLevels]
17
+ });
18
+ }
19
+
20
+ function deleteById(id: string) {
21
+ return useNuxtApp().$api(`/api/building-levels/${id}`, {
22
+ method: "PUT",
23
+ });
24
+ }
25
+
26
+
27
+ return {
28
+ deleteById,
29
+ batchUpdateLevels
30
+ };
31
+ }
@@ -3,9 +3,9 @@ export default function(){
3
3
  const formatLocation = ({block, level, unit}: {block: number, level: string, unit: string | TBuildingUnit}) => {
4
4
  const parts = [];
5
5
 
6
- if (block) parts.push(`Block ${block}`);
7
- if (level) parts.push(level);
8
- if (unit) parts.push(unit);
6
+ if (block?.name) parts.push(`${block.name}`);
7
+ if (level?.name) parts.push(`${level.name}`);
8
+ if (unit?.name) parts.push(unit?.name);
9
9
 
10
10
  return parts.join(" / ");
11
11
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.11.54",
5
+ "version": "2.0.0",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -3,7 +3,7 @@ declare type TBuilding = {
3
3
  site: string;
4
4
  name: string;
5
5
  block: number | null;
6
- levels: string[];
6
+ levels: { _id: string; name: string }[];
7
7
  buildingFiles: { id: string, name: string }[] | string[];
8
8
  };
9
9
 
@@ -16,7 +16,7 @@ declare type TBuildingUnit = {
16
16
  building: string;
17
17
  buildingName?: string;
18
18
  block: number | null;
19
- level: string | null;
19
+ level: { _id: string; name: string } | null;
20
20
  category: string;
21
21
  status: string;
22
22
  buildingUnitFiles: string[];
@@ -25,3 +25,15 @@ declare type TBuildingUnit = {
25
25
  leaseStart: string;
26
26
  leaseEnd: string;
27
27
  };
28
+
29
+
30
+ declare type TBuildingPayload = Pick<TBuilding, "site" | "name" | "block" | "buildingFiles"> & {
31
+ levels: string[];
32
+ };
33
+
34
+
35
+ declare type TBuildingUpdatePayload = Pick<TBuilding, "name" | "buildingFiles"> & {
36
+ levels: string[];
37
+ };
38
+
39
+ declare type TBuildingUnitPayload = Pick<TBuildingUnit, "site" | "name" | "building" | "buildingName" | "block" | "category" | "status" | "buildingUnitFiles" | "companyName" | "companyRegistrationNumber" | "leaseStart" | "leaseEnd"> & { level?: string}
@@ -6,8 +6,14 @@ declare type TVehicle = {
6
6
  direction: "entry" | "exit" | "both" | "none";
7
7
  name: string;
8
8
  phoneNumber?: string;
9
- block: number | "";
10
- level: string;
9
+ block: {
10
+ _id: string;
11
+ name: string;
12
+ }
13
+ level: {
14
+ _id: string;
15
+ name: string;
16
+ }
11
17
  unit: string;
12
18
  unitName?: string; // For display purposes, the API will return the unit name if available
13
19
  nric?: string | null;
@@ -33,8 +39,6 @@ declare type TVehiclePayload = Pick<
33
39
  | "direction"
34
40
  | "name"
35
41
  | "phoneNumber"
36
- | "block"
37
- | "level"
38
42
  | "unit"
39
43
  | "nric"
40
44
  | "remarks"
@@ -44,4 +48,4 @@ declare type TVehiclePayload = Pick<
44
48
  | "site"
45
49
  | "org"
46
50
  | "peopleId"
47
- > & { plateNumber?: string | string[] };
51
+ > & { plateNumber?: string | string[], block?: string, level?: string };