@7365admin1/layer-common 1.11.36 → 1.11.38
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 +12 -0
- package/components/ClientMain.vue +210 -101
- package/components/InvitationClientForm.vue +40 -22
- package/components/ScheduleTaskForm.vue +168 -18
- package/components/ScheduleTaskMain.vue +10 -4
- package/components/VehicleForm.vue +83 -50
- package/components/VehicleManagement.vue +67 -27
- package/components/VisitorManagement.vue +4 -4
- package/composables/useCustomerSite.ts +11 -0
- package/composables/useMember.ts +16 -0
- package/composables/useUser.ts +26 -1
- package/composables/useWorkOrder.ts +13 -7
- package/package.json +1 -1
- package/types/org.d.ts +1 -0
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</v-row>
|
|
21
21
|
</v-toolbar>
|
|
22
22
|
|
|
23
|
-
<v-card-text class="pa-
|
|
23
|
+
<v-card-text class="pa-4 flex-grow-1" style="overflow-y: auto">
|
|
24
24
|
<v-form ref="formRef" v-model="valid">
|
|
25
25
|
<v-row dense>
|
|
26
26
|
<v-col cols="12">
|
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
<div class="text-caption text-medium-emphasis mb-2">
|
|
53
53
|
{{ selectedDates.length }} date(s) selected
|
|
54
54
|
</div>
|
|
55
|
-
<div
|
|
55
|
+
<div
|
|
56
|
+
v-if="selectedDates.length > 0"
|
|
57
|
+
class="d-flex flex-wrap ga-2"
|
|
58
|
+
>
|
|
56
59
|
<v-chip
|
|
57
60
|
v-for="date in selectedDates"
|
|
58
61
|
:key="date"
|
|
@@ -121,6 +124,108 @@
|
|
|
121
124
|
</v-select>
|
|
122
125
|
</v-col>
|
|
123
126
|
|
|
127
|
+
<v-col v-if="selectedAreas.length > 0" cols="12">
|
|
128
|
+
<InputLabel title="Units Under Selected Areas" />
|
|
129
|
+
<v-card variant="outlined" rounded class="mb-4">
|
|
130
|
+
<v-list density="compact" nav>
|
|
131
|
+
<template
|
|
132
|
+
v-for="(area, idx) in selectedAreas"
|
|
133
|
+
:key="area.value || area._id"
|
|
134
|
+
>
|
|
135
|
+
<v-divider v-if="idx > 0" />
|
|
136
|
+
|
|
137
|
+
<v-list-item
|
|
138
|
+
rounded="0"
|
|
139
|
+
class="px-3"
|
|
140
|
+
@click="toggleArea(area.value || area._id)"
|
|
141
|
+
>
|
|
142
|
+
<template #prepend>
|
|
143
|
+
<v-icon size="18" color="medium-emphasis">
|
|
144
|
+
{{
|
|
145
|
+
openAreas.includes(area.value || area._id)
|
|
146
|
+
? "mdi-chevron-down"
|
|
147
|
+
: "mdi-chevron-right"
|
|
148
|
+
}}
|
|
149
|
+
</v-icon>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<v-list-item-title class="text-body-2 font-weight-medium">
|
|
153
|
+
{{ area.name }}
|
|
154
|
+
</v-list-item-title>
|
|
155
|
+
|
|
156
|
+
<template #append>
|
|
157
|
+
<v-progress-circular
|
|
158
|
+
v-if="
|
|
159
|
+
loadingUnits &&
|
|
160
|
+
!(area.value || area._id in fetchedAreaUnits)
|
|
161
|
+
"
|
|
162
|
+
size="14"
|
|
163
|
+
width="2"
|
|
164
|
+
indeterminate
|
|
165
|
+
color="primary"
|
|
166
|
+
/>
|
|
167
|
+
<v-chip
|
|
168
|
+
v-else
|
|
169
|
+
size="x-small"
|
|
170
|
+
variant="tonal"
|
|
171
|
+
color="primary"
|
|
172
|
+
>
|
|
173
|
+
{{
|
|
174
|
+
(fetchedAreaUnits[area.value || area._id] || [])
|
|
175
|
+
.length
|
|
176
|
+
}}
|
|
177
|
+
</v-chip>
|
|
178
|
+
</template>
|
|
179
|
+
</v-list-item>
|
|
180
|
+
|
|
181
|
+
<v-expand-transition>
|
|
182
|
+
<div v-show="openAreas.includes(area.value || area._id)">
|
|
183
|
+
<v-card-text class="pt-1 pb-3 px-3">
|
|
184
|
+
<div
|
|
185
|
+
v-if="
|
|
186
|
+
loadingUnits &&
|
|
187
|
+
!(area.value || area._id in fetchedAreaUnits)
|
|
188
|
+
"
|
|
189
|
+
class="d-flex flex-wrap ga-2"
|
|
190
|
+
>
|
|
191
|
+
<v-skeleton-loader
|
|
192
|
+
v-for="n in 4"
|
|
193
|
+
:key="n"
|
|
194
|
+
type="chip"
|
|
195
|
+
width="80"
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div
|
|
200
|
+
v-else-if="
|
|
201
|
+
(fetchedAreaUnits[area.value || area._id] || [])
|
|
202
|
+
.length > 0
|
|
203
|
+
"
|
|
204
|
+
class="d-flex flex-wrap ga-1"
|
|
205
|
+
>
|
|
206
|
+
<v-chip
|
|
207
|
+
v-for="unit in fetchedAreaUnits[
|
|
208
|
+
area.value || area._id
|
|
209
|
+
]"
|
|
210
|
+
:key="unit.unit"
|
|
211
|
+
size="small"
|
|
212
|
+
variant="tonal"
|
|
213
|
+
>
|
|
214
|
+
{{ unit.name }}
|
|
215
|
+
</v-chip>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<span v-else class="text-caption text-disabled">
|
|
219
|
+
No units configured
|
|
220
|
+
</span>
|
|
221
|
+
</v-card-text>
|
|
222
|
+
</div>
|
|
223
|
+
</v-expand-transition>
|
|
224
|
+
</template>
|
|
225
|
+
</v-list>
|
|
226
|
+
</v-card>
|
|
227
|
+
</v-col>
|
|
228
|
+
|
|
124
229
|
<v-col cols="12">
|
|
125
230
|
<InputLabel for="description" title="Task Description" />
|
|
126
231
|
<v-textarea
|
|
@@ -138,18 +243,16 @@
|
|
|
138
243
|
</v-form>
|
|
139
244
|
</v-card-text>
|
|
140
245
|
|
|
141
|
-
<v-
|
|
142
|
-
|
|
143
|
-
<v-card-actions class="pa-0">
|
|
144
|
-
<v-row no-gutters class="fill-width">
|
|
246
|
+
<v-toolbar class="pa-0" density="compact">
|
|
247
|
+
<v-row no-gutters>
|
|
145
248
|
<v-col cols="6" class="pa-0">
|
|
146
249
|
<v-btn
|
|
147
250
|
block
|
|
148
251
|
variant="text"
|
|
149
|
-
class="text-none
|
|
150
|
-
size="
|
|
252
|
+
class="text-none"
|
|
253
|
+
size="large"
|
|
151
254
|
@click="close"
|
|
152
|
-
height="
|
|
255
|
+
height="48"
|
|
153
256
|
:disabled="submitting"
|
|
154
257
|
>
|
|
155
258
|
Cancel
|
|
@@ -161,7 +264,7 @@
|
|
|
161
264
|
variant="flat"
|
|
162
265
|
color="primary"
|
|
163
266
|
class="text-none font-weight-bold rounded-0"
|
|
164
|
-
height="
|
|
267
|
+
height="48"
|
|
165
268
|
@click="submitTask"
|
|
166
269
|
:loading="submitting"
|
|
167
270
|
>
|
|
@@ -169,7 +272,7 @@
|
|
|
169
272
|
</v-btn>
|
|
170
273
|
</v-col>
|
|
171
274
|
</v-row>
|
|
172
|
-
</v-
|
|
275
|
+
</v-toolbar>
|
|
173
276
|
</v-card>
|
|
174
277
|
</v-dialog>
|
|
175
278
|
|
|
@@ -202,7 +305,7 @@ const props = defineProps({
|
|
|
202
305
|
const show = defineModel("show", { type: Boolean, default: false });
|
|
203
306
|
const emit = defineEmits(["saved", "close", "refresh"]);
|
|
204
307
|
|
|
205
|
-
const { getAreas } = useAreas();
|
|
308
|
+
const { getAreas, getAreaById } = useAreas();
|
|
206
309
|
const { createScheduleTask, updateScheduleTask, getScheduleTaskById } =
|
|
207
310
|
useScheduleTask();
|
|
208
311
|
|
|
@@ -215,19 +318,52 @@ const subject = ref("");
|
|
|
215
318
|
const description = ref("");
|
|
216
319
|
const scheduleTime = ref("08:00");
|
|
217
320
|
const pickerDates = ref<Date[]>([]);
|
|
218
|
-
// Derived from pickerDates — single source of truth, always in sync
|
|
219
321
|
const selectedDates = computed(() =>
|
|
220
322
|
pickerDates.value.map((d) => toLocalISO(d)).sort()
|
|
221
323
|
);
|
|
222
324
|
|
|
223
325
|
const selectedAreas = ref<any[]>([]);
|
|
326
|
+
const loadingUnits = ref(false);
|
|
327
|
+
const fetchedAreaUnits = ref<Record<string, { name: string; unit: string }[]>>(
|
|
328
|
+
{}
|
|
329
|
+
);
|
|
330
|
+
const openAreas = ref<string[]>([]);
|
|
331
|
+
|
|
332
|
+
const selectedAreaIds = computed(() =>
|
|
333
|
+
selectedAreas.value.map((a: any) => a.value || a._id)
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
watch(selectedAreaIds, async (ids, prevIds) => {
|
|
337
|
+
openAreas.value = ids.slice();
|
|
338
|
+
|
|
339
|
+
const toFetch = ids.filter(
|
|
340
|
+
(id) => !prevIds?.includes(id) && !(id in fetchedAreaUnits.value)
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!toFetch.length) return;
|
|
344
|
+
|
|
345
|
+
loadingUnits.value = true;
|
|
346
|
+
try {
|
|
347
|
+
await Promise.all(
|
|
348
|
+
toFetch.map(async (id) => {
|
|
349
|
+
try {
|
|
350
|
+
const result = await getAreaById(id);
|
|
351
|
+
fetchedAreaUnits.value[id] = result?.units || [];
|
|
352
|
+
} catch {
|
|
353
|
+
fetchedAreaUnits.value[id] = [];
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
);
|
|
357
|
+
} finally {
|
|
358
|
+
loadingUnits.value = false;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
224
361
|
|
|
225
362
|
const message = ref("");
|
|
226
363
|
const messageSnackbar = ref(false);
|
|
227
364
|
const messageColor = ref("success");
|
|
228
365
|
const areasList = ref<any[]>([]);
|
|
229
366
|
|
|
230
|
-
// Helper function to generate all dates between start and end
|
|
231
367
|
const generateDateRange = (start: Date, end: Date): Date[] => {
|
|
232
368
|
const dates: Date[] = [];
|
|
233
369
|
const current = new Date(start);
|
|
@@ -240,7 +376,6 @@ const generateDateRange = (start: Date, end: Date): Date[] => {
|
|
|
240
376
|
return dates;
|
|
241
377
|
};
|
|
242
378
|
|
|
243
|
-
// Helper: convert a Date to a local YYYY-MM-DD string (avoids UTC-shift issues)
|
|
244
379
|
const toLocalISO = (d: Date): string => {
|
|
245
380
|
const y = d.getFullYear();
|
|
246
381
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -248,7 +383,6 @@ const toLocalISO = (d: Date): string => {
|
|
|
248
383
|
return `${y}-${m}-${day}`;
|
|
249
384
|
};
|
|
250
385
|
|
|
251
|
-
// Remove a single date from the selection via chip close
|
|
252
386
|
const removeDate = (dateISO: string) => {
|
|
253
387
|
pickerDates.value = pickerDates.value.filter(
|
|
254
388
|
(d) => toLocalISO(d) !== dateISO
|
|
@@ -261,12 +395,23 @@ const showMessage = (text: string, color: string = "success") => {
|
|
|
261
395
|
messageSnackbar.value = true;
|
|
262
396
|
};
|
|
263
397
|
|
|
398
|
+
const toggleArea = (id: string) => {
|
|
399
|
+
const idx = openAreas.value.indexOf(id);
|
|
400
|
+
if (idx === -1) {
|
|
401
|
+
openAreas.value.push(id);
|
|
402
|
+
} else {
|
|
403
|
+
openAreas.value.splice(idx, 1);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
264
407
|
const resetForm = () => {
|
|
265
408
|
subject.value = "";
|
|
266
409
|
description.value = "";
|
|
267
410
|
scheduleTime.value = "08:00";
|
|
268
411
|
pickerDates.value = [];
|
|
269
412
|
selectedAreas.value = [];
|
|
413
|
+
fetchedAreaUnits.value = {};
|
|
414
|
+
openAreas.value = [];
|
|
270
415
|
};
|
|
271
416
|
|
|
272
417
|
const setFormFromTask = (task: any) => {
|
|
@@ -310,7 +455,8 @@ const setFormFromTask = (task: any) => {
|
|
|
310
455
|
|
|
311
456
|
const { data: getAreasReq, pending: loadingAreas } = await useLazyAsyncData(
|
|
312
457
|
`get-areas-${props.site}`,
|
|
313
|
-
() =>
|
|
458
|
+
() =>
|
|
459
|
+
getAreas({ site: props.site, limit: 1000, serviceType: props.serviceType }),
|
|
314
460
|
{
|
|
315
461
|
watch: [show, () => props.site],
|
|
316
462
|
immediate: false,
|
|
@@ -377,7 +523,11 @@ const submitTask = async () => {
|
|
|
377
523
|
(props.taskData as any)?._id || (props.taskData as any)?.id;
|
|
378
524
|
response = await updateScheduleTask(taskId, payload);
|
|
379
525
|
} else {
|
|
380
|
-
response = await createScheduleTask({
|
|
526
|
+
response = await createScheduleTask({
|
|
527
|
+
...payload,
|
|
528
|
+
site: props.site,
|
|
529
|
+
serviceType: props.serviceType,
|
|
530
|
+
});
|
|
381
531
|
}
|
|
382
532
|
|
|
383
533
|
showMessage(
|
|
@@ -205,12 +205,14 @@
|
|
|
205
205
|
<v-toolbar>
|
|
206
206
|
<v-row no-gutters class="fill-height px-6" align="center">
|
|
207
207
|
<span class="font-weight-bold text-h6 text-capitalize">
|
|
208
|
-
Delete {{ selectedTask?.title ||
|
|
208
|
+
Delete {{ selectedTask?.title || "Schedule Task" }}
|
|
209
209
|
</span>
|
|
210
210
|
</v-row>
|
|
211
211
|
</v-toolbar>
|
|
212
212
|
<v-card-text>
|
|
213
|
-
<span class="text-subtitle-2"
|
|
213
|
+
<span class="text-subtitle-2"
|
|
214
|
+
>Are you sure you want to delete this schedule task?</span
|
|
215
|
+
>
|
|
214
216
|
</v-card-text>
|
|
215
217
|
|
|
216
218
|
<v-toolbar class="pa-0" density="compact">
|
|
@@ -257,6 +259,7 @@
|
|
|
257
259
|
|
|
258
260
|
<script lang="ts" setup>
|
|
259
261
|
import useScheduleTask from "../composables/useScheduleTask";
|
|
262
|
+
import useUtils from "../composables/useUtils";
|
|
260
263
|
|
|
261
264
|
const props = defineProps({
|
|
262
265
|
orgId: { type: String, required: true },
|
|
@@ -271,13 +274,16 @@ const props = defineProps({
|
|
|
271
274
|
});
|
|
272
275
|
|
|
273
276
|
const { formatDate, debounce } = useUtils();
|
|
274
|
-
const { getScheduleTasks, getScheduleTaskById, deleteScheduleTask } =
|
|
277
|
+
const { getScheduleTasks, getScheduleTaskById, deleteScheduleTask } =
|
|
278
|
+
useScheduleTask();
|
|
275
279
|
|
|
276
280
|
const canViewScheduleTasks = computed(() => props.canViewScheduleTasks);
|
|
277
281
|
const canCreateScheduleTask = computed(() => props.canCreateScheduleTask);
|
|
278
282
|
const canUpdateScheduleTask = computed(() => props.canUpdateScheduleTask);
|
|
279
283
|
const canDeleteScheduleTask = computed(() => props.canDeleteScheduleTask);
|
|
280
|
-
const canViewScheduleTaskDetails = computed(
|
|
284
|
+
const canViewScheduleTaskDetails = computed(
|
|
285
|
+
() => props.canViewScheduleTaskDetails
|
|
286
|
+
);
|
|
281
287
|
|
|
282
288
|
const page = ref(1);
|
|
283
289
|
const pages = ref(0);
|
|
@@ -35,34 +35,32 @@
|
|
|
35
35
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
36
36
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
37
37
|
<v-select v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
|
|
38
|
-
@update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]"
|
|
39
|
-
/>
|
|
38
|
+
@update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" />
|
|
40
39
|
</v-col>
|
|
41
40
|
|
|
42
41
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
43
42
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
44
|
-
<v-select v-model="vehicle.level" :items="levelsArray" density="comfortable"
|
|
45
|
-
|
|
46
|
-
:rules="[requiredRule]" />
|
|
43
|
+
<v-select v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block"
|
|
44
|
+
@update:model-value="handleChangeLevel" :rules="[requiredRule]" />
|
|
47
45
|
</v-col>
|
|
48
46
|
|
|
49
47
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
50
48
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
51
|
-
<v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable"
|
|
52
|
-
:
|
|
49
|
+
<v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level"
|
|
50
|
+
:rules="[requiredRule]" />
|
|
53
51
|
</v-col>
|
|
54
52
|
|
|
55
53
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
56
54
|
<InputLabel class="text-capitalize" title="NRIC" required />
|
|
57
|
-
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
55
|
+
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
56
|
+
:disabled="disablePrefilledInputs && type !== 'blocklist'" />
|
|
58
57
|
</v-col>
|
|
59
58
|
|
|
60
59
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
61
60
|
<v-row>
|
|
62
61
|
<v-col cols="12">
|
|
63
62
|
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
64
|
-
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]"
|
|
65
|
-
/>
|
|
63
|
+
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" />
|
|
66
64
|
</v-col>
|
|
67
65
|
</v-row>
|
|
68
66
|
</v-col>
|
|
@@ -75,42 +73,49 @@
|
|
|
75
73
|
</v-col>
|
|
76
74
|
|
|
77
75
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
78
|
-
|
|
79
|
-
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
80
|
-
<template v-for="plate in vehicle.plates" :key="plate.plateNumber">
|
|
81
|
-
<InputLabel class="text-capitalize" title="Existing Vehicle Numbers" required />
|
|
82
|
-
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]" class="mb-2"
|
|
83
|
-
readonly disabled>
|
|
84
|
-
<template #append-inner>
|
|
85
|
-
<v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{
|
|
86
|
-
formatVehicleStatus(plate?.status)?.label }}</v-chip>
|
|
87
|
-
</template>
|
|
88
|
-
</v-text-field>
|
|
89
|
-
</template>
|
|
90
76
|
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
<v-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
<template v-if="prop.mode === 'add'">
|
|
78
|
+
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
79
|
+
<template v-for="plate in vehicle.plates" :key="plate.plateNumber">
|
|
80
|
+
<InputLabel class="text-capitalize" title="Existing Vehicle Numbers" required />
|
|
81
|
+
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]"
|
|
82
|
+
class="mb-2 class-readonly" readonly>
|
|
83
|
+
<template #append-inner>
|
|
84
|
+
<v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{
|
|
85
|
+
formatVehicleStatus(plate?.status)?.label }}</v-chip>
|
|
86
|
+
</template>
|
|
87
|
+
</v-text-field>
|
|
88
|
+
</template>
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
<InputLabel class="text-capitalize" title="New Vehicle Numbers" required />
|
|
91
|
+
<template v-for="(plate, index) in newPlateNumbers" :key="index">
|
|
92
|
+
<v-row class="" :class="`${index === newPlateNumbers.length - 1 ? 'mb-1' : 'mb-5'}`" no-gutters>
|
|
93
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '11' : '12'">
|
|
94
|
+
<InputVehicleNumber v-model="newPlateNumbers[index]" density="comfortable"
|
|
95
|
+
placeholder="New Vehicle Number" :rules="[requiredRule]" />
|
|
96
|
+
</v-col>
|
|
97
|
+
|
|
98
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '1' : '0'" class="d-flex align-start">
|
|
99
|
+
<v-btn icon="mdi-delete-outline" variant="text" color="error" v-if="newPlateNumbers.length > 1"
|
|
100
|
+
@click="removePlate(index)" />
|
|
101
|
+
</v-col>
|
|
102
|
+
</v-row>
|
|
103
|
+
|
|
104
|
+
<v-btn prepend-icon="mdi-plus" variant="outlined" class="text-none" @click="addPlate">
|
|
105
|
+
Add Plate
|
|
106
|
+
</v-btn>
|
|
107
|
+
</template>
|
|
104
108
|
</template>
|
|
105
109
|
|
|
106
|
-
<v-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
<template v-else-if="prop.mode === 'edit'">
|
|
111
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" required />
|
|
112
|
+
<InputVehicleNumber v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" />
|
|
113
|
+
</template>
|
|
109
114
|
</v-col>
|
|
110
115
|
|
|
111
116
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
112
|
-
<InputLabel class="text-capitalize" title="Remarks"
|
|
113
|
-
<v-textarea v-model="vehicle.remarks" density="comfortable" :rows="3" no-resize
|
|
117
|
+
<InputLabel class="text-capitalize" title="Remarks" />
|
|
118
|
+
<v-textarea v-model="vehicle.remarks" density="comfortable" :rows="3" no-resize />
|
|
114
119
|
</v-col>
|
|
115
120
|
|
|
116
121
|
<v-col v-if="shouldShowField('start')" cols="12">
|
|
@@ -192,8 +197,7 @@
|
|
|
192
197
|
Matching Records for "{{ searchUnitName ?? "-" }}"
|
|
193
198
|
</span>
|
|
194
199
|
<span>
|
|
195
|
-
<v-btn icon="mdi-close" variant="text"
|
|
196
|
-
@click="handleCloseMatchDialog" />
|
|
200
|
+
<v-btn icon="mdi-close" variant="text" @click="handleCloseMatchDialog" />
|
|
197
201
|
</span>
|
|
198
202
|
</v-row>
|
|
199
203
|
</v-toolbar-title>
|
|
@@ -209,7 +213,7 @@
|
|
|
209
213
|
</v-list-item-title>
|
|
210
214
|
|
|
211
215
|
<v-list-item-subtitle>
|
|
212
|
-
|
|
216
|
+
NRIC: {{ v.nric || "-" }}
|
|
213
217
|
</v-list-item-subtitle>
|
|
214
218
|
|
|
215
219
|
<div class="mt-1">
|
|
@@ -262,6 +266,10 @@ const prop = defineProps({
|
|
|
262
266
|
vehicleData: {
|
|
263
267
|
type: Object as PropType<Partial<TVehicle> | null>,
|
|
264
268
|
default: null
|
|
269
|
+
},
|
|
270
|
+
plateNumberId: {
|
|
271
|
+
type: String,
|
|
272
|
+
default: ''
|
|
265
273
|
}
|
|
266
274
|
});
|
|
267
275
|
|
|
@@ -291,6 +299,7 @@ const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicl
|
|
|
291
299
|
org: prop.org,
|
|
292
300
|
_id: '',
|
|
293
301
|
peopleId: '',
|
|
302
|
+
plateNumber: "",
|
|
294
303
|
});
|
|
295
304
|
|
|
296
305
|
const newPlateNumbers = ref<string[]>(['']);
|
|
@@ -520,7 +529,6 @@ async function submit() {
|
|
|
520
529
|
const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org, peopleId } = vehicle
|
|
521
530
|
|
|
522
531
|
let payload: Partial<TVehiclePayload> = {
|
|
523
|
-
plateNumber: newPlateNumbers.value,
|
|
524
532
|
name,
|
|
525
533
|
phoneNumber,
|
|
526
534
|
type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
|
|
@@ -533,7 +541,16 @@ async function submit() {
|
|
|
533
541
|
...payload,
|
|
534
542
|
category,
|
|
535
543
|
site,
|
|
536
|
-
org
|
|
544
|
+
org,
|
|
545
|
+
plateNumber: newPlateNumbers.value
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (prop.mode === 'edit' && prop.plateNumberId) {
|
|
550
|
+
payload = {
|
|
551
|
+
...payload,
|
|
552
|
+
site: prop.site,
|
|
553
|
+
plateNumber
|
|
537
554
|
}
|
|
538
555
|
}
|
|
539
556
|
|
|
@@ -564,8 +581,8 @@ async function submit() {
|
|
|
564
581
|
if (prop.mode === 'add') {
|
|
565
582
|
await addVehicle(payload);
|
|
566
583
|
} else if (prop.mode === 'edit') {
|
|
567
|
-
const
|
|
568
|
-
await updateVehicle(
|
|
584
|
+
const plateNumberId = prop.plateNumberId as string;
|
|
585
|
+
await updateVehicle(plateNumberId, payload);
|
|
569
586
|
}
|
|
570
587
|
emit("done");
|
|
571
588
|
|
|
@@ -692,14 +709,14 @@ function selectPeopleRecord(record: TPeople) {
|
|
|
692
709
|
|
|
693
710
|
|
|
694
711
|
async function checkPeopleById() {
|
|
695
|
-
if (!vehicle.unit) return;
|
|
712
|
+
if (!vehicle.unit || prop.mode === 'edit') return;
|
|
696
713
|
|
|
697
714
|
checkingUnit.value = true;
|
|
698
715
|
showMatchingPeopleDialog.value = true;
|
|
699
716
|
// Lock editable fields while we verify if this NRIC has existing records.
|
|
700
717
|
disablePrefilledInputs.value = true;
|
|
701
718
|
try {
|
|
702
|
-
const res = await getPeopleByUnit(vehicle.unit, { status: 'active', type: 'resident,tenant' }) as TPeople[]| null;
|
|
719
|
+
const res = await getPeopleByUnit(vehicle.unit, { status: 'active', type: 'resident,tenant' }) as TPeople[] | null;
|
|
703
720
|
|
|
704
721
|
if (Array.isArray(res) && res.length > 0) {
|
|
705
722
|
matchingPeople.value = res || []
|
|
@@ -745,12 +762,24 @@ const resetVehicleDetails = () => {
|
|
|
745
762
|
|
|
746
763
|
function handleCloseMatchDialog() {
|
|
747
764
|
showMatchingPeopleDialog.value = false;
|
|
748
|
-
if(matchingPeople.value.length > 0) {
|
|
765
|
+
if (matchingPeople.value.length > 0) {
|
|
749
766
|
vehicle.unit = null
|
|
750
767
|
}
|
|
751
768
|
}
|
|
752
769
|
|
|
753
|
-
|
|
770
|
+
onMounted(() => {
|
|
771
|
+
setTimeout(() => {
|
|
772
|
+
console.log("VehicleForm mounted with props:", prop);
|
|
773
|
+
if (prop.mode === 'edit' && prop.vehicleData) {
|
|
774
|
+
// In edit mode, we only want to check for matching people records if there is a pre-filled unit value. If there is no unit value, we can assume that this vehicle record is not linked to any existing people record and skip the check.
|
|
775
|
+
vehicle.seasonPassType = prop.vehicleData.seasonPassType || '';
|
|
776
|
+
vehicle.end = prop.vehicleData.end || '';
|
|
777
|
+
vehicle.start = prop.vehicleData.start || '';
|
|
778
|
+
vehicle.plateNumber = prop.vehicleData.plates?.find((p) => p._id === prop.plateNumberId)?.plateNumber || '';
|
|
779
|
+
vehicle.peopleId = prop.vehicleData.peopleId || '';
|
|
780
|
+
}
|
|
781
|
+
}, 300);
|
|
782
|
+
})
|
|
754
783
|
|
|
755
784
|
|
|
756
785
|
</script>
|
|
@@ -758,4 +787,8 @@ function handleCloseMatchDialog() {
|
|
|
758
787
|
.button-outline-class {
|
|
759
788
|
border: 1px solid rgba(var(--v-theme-primary));
|
|
760
789
|
}
|
|
790
|
+
|
|
791
|
+
.class-readonly {
|
|
792
|
+
pointer-events: none;
|
|
793
|
+
}
|
|
761
794
|
</style>
|