@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 +6 -0
- package/components/BuildingForm.vue +203 -21
- package/components/BuildingManagement/buildings.vue +2 -2
- package/components/BuildingManagement/units.vue +1 -1
- package/components/BuildingUnitFormAdd.vue +4 -4
- package/components/BuildingUnitFormEdit.vue +17 -13
- package/components/OnlineFormConfigurationForm.vue +1 -1
- package/components/OnlineFormsConfiguration.vue +24 -2
- package/components/VehicleForm.vue +89 -50
- package/components/VehicleManagement.vue +10 -4
- package/components/VisitorForm.vue +67 -43
- package/components/VisitorManagement.vue +10 -6
- package/composables/useBuilding.ts +32 -3
- package/composables/useBuildingLevel.ts +31 -0
- package/composables/useSecurityUtils.ts +3 -3
- package/package.json +1 -1
- package/types/building.d.ts +14 -2
- package/types/vehicle.d.ts +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -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" :
|
|
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-
|
|
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
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
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="*"
|
|
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
|
-
|
|
285
|
+
newLevelsArray.value = [];
|
|
197
286
|
for (let index = 0; index < buildingLevels.value; index++) {
|
|
198
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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 -->
|
|
@@ -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="
|
|
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 &&
|
|
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<
|
|
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"
|
|
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="
|
|
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:
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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:
|
|
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"
|
|
38
|
-
|
|
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"
|
|
44
|
-
|
|
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"
|
|
50
|
-
:
|
|
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 {
|
|
375
|
-
|
|
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
|
|
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 {
|
|
382
|
-
|
|
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
|
|
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
|
-
|
|
396
|
-
(
|
|
397
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
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: "
|
|
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
|
-
|
|
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="
|
|
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"
|
|
163
|
-
:
|
|
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" :
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
701
|
-
|
|
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
|
|
713
|
-
},
|
|
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
|
-
}
|
|
732
|
-
blocksArray.value = [];
|
|
733
|
-
}
|
|
741
|
+
}
|
|
734
742
|
},
|
|
735
743
|
{ immediate: true }
|
|
736
744
|
);
|
|
737
745
|
|
|
738
746
|
watch(
|
|
739
|
-
|
|
740
|
-
(
|
|
741
|
-
if (
|
|
742
|
-
|
|
743
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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
package/types/building.d.ts
CHANGED
|
@@ -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}
|
package/types/vehicle.d.ts
CHANGED
|
@@ -6,8 +6,14 @@ declare type TVehicle = {
|
|
|
6
6
|
direction: "entry" | "exit" | "both" | "none";
|
|
7
7
|
name: string;
|
|
8
8
|
phoneNumber?: string;
|
|
9
|
-
block:
|
|
10
|
-
|
|
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 };
|