@7365admin1/layer-common 3.0.28 → 3.0.29
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/AccessCardDetailsDialog.vue +24 -11
- package/components/AccessCardPreviewDialog.vue +1 -0
- package/components/VehicleForm.vue +570 -285
- package/components/VisitorForm.vue +201 -79
- package/components/VisitorManagement.vue +43 -11
- package/composables/useAccessManagement.ts +1 -1
- package/composables/useVisitor.ts +34 -0
- package/package.json +1 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-card width="100%" :loading="processing">
|
|
3
3
|
<v-toolbar>
|
|
4
|
-
<v-row
|
|
4
|
+
<v-row
|
|
5
|
+
no-gutters
|
|
6
|
+
class="fill-height px-6 d-flex ga-2 justify-space-between"
|
|
7
|
+
align="center"
|
|
8
|
+
>
|
|
5
9
|
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
10
|
{{ prop.mode }} Vehicle <span>({{ formatVehicleType(type) }})</span>
|
|
7
11
|
</span>
|
|
@@ -12,20 +16,43 @@
|
|
|
12
16
|
</v-toolbar>
|
|
13
17
|
|
|
14
18
|
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-3">
|
|
15
|
-
<span class="text-subtitle-1 w-100 font-weight-bold mb-3"
|
|
16
|
-
|
|
19
|
+
<span class="text-subtitle-1 w-100 font-weight-bold mb-3"
|
|
20
|
+
>General Information</span
|
|
21
|
+
>
|
|
22
|
+
<v-form
|
|
23
|
+
ref="formRef"
|
|
24
|
+
v-model="validForm"
|
|
25
|
+
:disabled="processing"
|
|
26
|
+
@click="errorMessage = ''"
|
|
27
|
+
>
|
|
17
28
|
<v-row no-gutters class="pt-4">
|
|
18
29
|
<v-col v-if="shouldShowField('seasonPassType')" cols="12">
|
|
19
|
-
<InputLabel
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
<InputLabel
|
|
31
|
+
class="text-capitalize"
|
|
32
|
+
title="Season Pass Type"
|
|
33
|
+
required
|
|
34
|
+
/>
|
|
35
|
+
<v-combobox
|
|
36
|
+
v-model.trim="vehicle.seasonPassType"
|
|
37
|
+
v-model:search="seasonPassTypeInput"
|
|
38
|
+
:hide-no-data="false"
|
|
39
|
+
@update:modelValue="onSeasonPassTypeSelected"
|
|
40
|
+
:items="seasonPassTypeArray"
|
|
41
|
+
:rules="[requiredRule]"
|
|
42
|
+
item-title="title"
|
|
43
|
+
item-value="value"
|
|
44
|
+
variant="outlined"
|
|
45
|
+
density="comfortable"
|
|
46
|
+
persistent-hint
|
|
47
|
+
small-chips
|
|
48
|
+
>
|
|
24
49
|
<template v-slot:no-data>
|
|
25
50
|
<v-list-item>
|
|
26
51
|
<v-list-item-title>
|
|
27
|
-
No results matching "<strong>{{
|
|
28
|
-
|
|
52
|
+
No results matching "<strong>{{
|
|
53
|
+
seasonPassTypeInput
|
|
54
|
+
}}</strong
|
|
55
|
+
>". This value will be added as new option.
|
|
29
56
|
</v-list-item-title>
|
|
30
57
|
</v-list-item>
|
|
31
58
|
</template>
|
|
@@ -34,94 +61,197 @@
|
|
|
34
61
|
|
|
35
62
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
36
63
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
37
|
-
<v-autocomplete
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
<v-autocomplete
|
|
65
|
+
v-model="vehicle.block"
|
|
66
|
+
:items="blocksArray"
|
|
67
|
+
autocomplete="off"
|
|
68
|
+
:key="
|
|
69
|
+
blockListDataPending ? 'block-list-pending' : 'block-list-ready'
|
|
70
|
+
"
|
|
71
|
+
:loading="blockListDataPending"
|
|
72
|
+
item-value="value"
|
|
73
|
+
item-title="title"
|
|
74
|
+
@update:model-value="handleChangeBlock"
|
|
75
|
+
density="comfortable"
|
|
76
|
+
:rules="[requiredRule]"
|
|
77
|
+
/>
|
|
41
78
|
</v-col>
|
|
42
79
|
|
|
43
80
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
44
81
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
45
|
-
<v-autocomplete
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
<v-autocomplete
|
|
83
|
+
v-model="vehicle.level"
|
|
84
|
+
:items="levelsArray"
|
|
85
|
+
autocomplete="off"
|
|
86
|
+
:key="
|
|
87
|
+
levelListDataPending ? 'level-list-pending' : 'level-list-ready'
|
|
88
|
+
"
|
|
89
|
+
:loading="levelListDataPending"
|
|
90
|
+
density="comfortable"
|
|
91
|
+
:disabled="!vehicle.block"
|
|
92
|
+
@update:model-value="handleChangeLevel"
|
|
93
|
+
:rules="[requiredRule]"
|
|
94
|
+
/>
|
|
49
95
|
</v-col>
|
|
50
96
|
|
|
51
97
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
52
98
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
53
|
-
<v-autocomplete
|
|
54
|
-
|
|
55
|
-
|
|
99
|
+
<v-autocomplete
|
|
100
|
+
v-model="vehicle.unit"
|
|
101
|
+
:items="unitsArray"
|
|
102
|
+
autocomplete="off"
|
|
103
|
+
:key="
|
|
104
|
+
unitListDataPending ? 'unit-list-pending' : 'unit-list-ready'
|
|
105
|
+
"
|
|
106
|
+
:loading="unitListDataPending"
|
|
107
|
+
density="comfortable"
|
|
108
|
+
:disabled="!vehicle.level"
|
|
109
|
+
:rules="[requiredRule]"
|
|
110
|
+
/>
|
|
56
111
|
</v-col>
|
|
57
112
|
|
|
58
113
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
59
|
-
<InputLabel class="text-capitalize" title="NRIC"/>
|
|
60
|
-
<InputNRICNumber
|
|
61
|
-
|
|
114
|
+
<InputLabel class="text-capitalize" title="NRIC" />
|
|
115
|
+
<InputNRICNumber
|
|
116
|
+
v-model="vehicle.nric"
|
|
117
|
+
density="comfortable"
|
|
118
|
+
:disabled="
|
|
119
|
+
disablePrefilledInputs &&
|
|
120
|
+
type !== 'blocklist' &&
|
|
121
|
+
prop.mode !== 'edit'
|
|
122
|
+
"
|
|
123
|
+
/>
|
|
62
124
|
</v-col>
|
|
63
125
|
|
|
64
126
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
65
127
|
<v-row>
|
|
66
128
|
<v-col cols="12">
|
|
67
|
-
<InputLabel
|
|
68
|
-
|
|
69
|
-
|
|
129
|
+
<InputLabel
|
|
130
|
+
class="text-capitalize"
|
|
131
|
+
title="Full Name"
|
|
132
|
+
required
|
|
133
|
+
/>
|
|
134
|
+
<v-text-field
|
|
135
|
+
v-model.trim="vehicle.name"
|
|
136
|
+
density="comfortable"
|
|
137
|
+
:rules="[requiredRule]"
|
|
138
|
+
:disabled="
|
|
139
|
+
disablePrefilledInputs &&
|
|
140
|
+
type !== 'blocklist' &&
|
|
141
|
+
prop.mode !== 'edit'
|
|
142
|
+
"
|
|
143
|
+
/>
|
|
70
144
|
</v-col>
|
|
71
145
|
</v-row>
|
|
72
146
|
</v-col>
|
|
73
147
|
|
|
74
|
-
|
|
75
|
-
|
|
76
148
|
<v-col v-if="shouldShowField('phone')" cols="12">
|
|
77
|
-
<InputLabel class="text-capitalize" title="Phone Number"/>
|
|
78
|
-
<InputPhoneNumberV2
|
|
149
|
+
<InputLabel class="text-capitalize" title="Phone Number" />
|
|
150
|
+
<InputPhoneNumberV2
|
|
151
|
+
v-model="vehicle.phoneNumber"
|
|
152
|
+
density="comfortable"
|
|
153
|
+
/>
|
|
79
154
|
</v-col>
|
|
80
155
|
|
|
81
156
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
82
|
-
|
|
83
157
|
<template v-if="prop.mode === 'add'">
|
|
84
158
|
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
85
|
-
<template
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
159
|
+
<template
|
|
160
|
+
v-for="plate in vehicle.plates"
|
|
161
|
+
:key="plate.plateNumber"
|
|
162
|
+
>
|
|
163
|
+
<InputLabel
|
|
164
|
+
class="text-capitalize"
|
|
165
|
+
title="Existing Vehicle Numbers"
|
|
166
|
+
required
|
|
167
|
+
/>
|
|
168
|
+
<v-text-field
|
|
169
|
+
v-model="plate.plateNumber"
|
|
170
|
+
density="comfortable"
|
|
171
|
+
:rules="[requiredRule]"
|
|
172
|
+
class="mb-2 class-readonly"
|
|
173
|
+
readonly
|
|
174
|
+
>
|
|
89
175
|
<template #append-inner>
|
|
90
|
-
<v-chip
|
|
91
|
-
|
|
176
|
+
<v-chip
|
|
177
|
+
size="small"
|
|
178
|
+
class="ma-0"
|
|
179
|
+
:color="formatVehicleStatus(plate?.status)?.color as string"
|
|
180
|
+
>{{ formatVehicleStatus(plate?.status)?.label }}</v-chip
|
|
181
|
+
>
|
|
92
182
|
</template>
|
|
93
183
|
</v-text-field>
|
|
94
184
|
</template>
|
|
95
185
|
|
|
96
|
-
<InputLabel
|
|
186
|
+
<InputLabel
|
|
187
|
+
class="text-capitalize"
|
|
188
|
+
title="New Vehicle Numbers"
|
|
189
|
+
required
|
|
190
|
+
/>
|
|
97
191
|
<template v-for="(plate, index) in newPlateNumbers" :key="index">
|
|
98
|
-
<v-row
|
|
192
|
+
<v-row
|
|
193
|
+
class=""
|
|
194
|
+
:class="`${
|
|
195
|
+
index === newPlateNumbers.length - 1 ? 'mb-1' : 'mb-5'
|
|
196
|
+
}`"
|
|
197
|
+
no-gutters
|
|
198
|
+
>
|
|
99
199
|
<v-col :cols="newPlateNumbers.length > 1 ? '11' : '12'">
|
|
100
|
-
<InputVehicleNumber
|
|
101
|
-
|
|
200
|
+
<InputVehicleNumber
|
|
201
|
+
v-model="newPlateNumbers[index]"
|
|
202
|
+
density="comfortable"
|
|
203
|
+
placeholder="New Vehicle Number"
|
|
204
|
+
:rules="[requiredRule]"
|
|
205
|
+
/>
|
|
102
206
|
</v-col>
|
|
103
207
|
|
|
104
|
-
<v-col
|
|
105
|
-
|
|
106
|
-
|
|
208
|
+
<v-col
|
|
209
|
+
:cols="newPlateNumbers.length > 1 ? '1' : '0'"
|
|
210
|
+
class="d-flex align-start"
|
|
211
|
+
>
|
|
212
|
+
<v-btn
|
|
213
|
+
icon="mdi-delete-outline"
|
|
214
|
+
variant="text"
|
|
215
|
+
color="error"
|
|
216
|
+
v-if="newPlateNumbers.length > 1"
|
|
217
|
+
@click="removePlate(index)"
|
|
218
|
+
/>
|
|
107
219
|
</v-col>
|
|
108
220
|
</v-row>
|
|
109
221
|
|
|
110
|
-
<v-btn
|
|
222
|
+
<v-btn
|
|
223
|
+
prepend-icon="mdi-plus"
|
|
224
|
+
variant="outlined"
|
|
225
|
+
class="text-none"
|
|
226
|
+
@click="addPlate"
|
|
227
|
+
>
|
|
111
228
|
Add Plate
|
|
112
229
|
</v-btn>
|
|
113
230
|
</template>
|
|
114
231
|
</template>
|
|
115
232
|
|
|
116
233
|
<template v-else-if="prop.mode === 'edit'">
|
|
117
|
-
<InputLabel
|
|
118
|
-
|
|
234
|
+
<InputLabel
|
|
235
|
+
class="text-capitalize"
|
|
236
|
+
title="Vehicle Number"
|
|
237
|
+
required
|
|
238
|
+
/>
|
|
239
|
+
<InputVehicleNumber
|
|
240
|
+
v-model="vehicle.plateNumber"
|
|
241
|
+
density="comfortable"
|
|
242
|
+
:rules="[requiredRule]"
|
|
243
|
+
/>
|
|
119
244
|
</template>
|
|
120
245
|
</v-col>
|
|
121
246
|
|
|
122
247
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
123
248
|
<InputLabel class="text-capitalize" title="Remarks" />
|
|
124
|
-
<v-textarea
|
|
249
|
+
<v-textarea
|
|
250
|
+
v-model="vehicle.remarks"
|
|
251
|
+
density="comfortable"
|
|
252
|
+
:rows="3"
|
|
253
|
+
no-resize
|
|
254
|
+
/>
|
|
125
255
|
</v-col>
|
|
126
256
|
|
|
127
257
|
<v-col v-if="shouldShowField('start')" cols="12">
|
|
@@ -140,26 +270,51 @@
|
|
|
140
270
|
|
|
141
271
|
<v-row no-gutters class="mt-5">
|
|
142
272
|
<v-col cols="12">
|
|
143
|
-
<InputLabel
|
|
144
|
-
|
|
273
|
+
<InputLabel
|
|
274
|
+
class="text-capitalize"
|
|
275
|
+
title="Start Date"
|
|
276
|
+
required
|
|
277
|
+
/>
|
|
278
|
+
<InputDateTimePicker
|
|
279
|
+
ref="startDateRef"
|
|
280
|
+
v-model="vehicle.start"
|
|
281
|
+
:rules="[validStartDateRule]"
|
|
282
|
+
/>
|
|
145
283
|
</v-col>
|
|
146
284
|
|
|
147
285
|
<v-col cols="12">
|
|
148
|
-
<InputLabel
|
|
149
|
-
|
|
286
|
+
<InputLabel
|
|
287
|
+
class="text-capitalize"
|
|
288
|
+
title="Expiry Date"
|
|
289
|
+
required
|
|
290
|
+
/>
|
|
291
|
+
<InputDateTimePicker
|
|
292
|
+
ref="expiryDateRef"
|
|
293
|
+
v-model="vehicle.end"
|
|
294
|
+
:rules="[validExpiryDateRule]"
|
|
295
|
+
/>
|
|
150
296
|
</v-col>
|
|
151
297
|
</v-row>
|
|
152
298
|
</v-row>
|
|
153
|
-
|
|
154
|
-
|
|
155
299
|
</v-expand-transition>
|
|
156
300
|
|
|
157
301
|
<v-col cols="12" :class="[showSubscriptionDateOptions && 'mt-5']">
|
|
158
302
|
<v-row justify="center">
|
|
159
303
|
<v-col cols="6">
|
|
160
|
-
<v-btn
|
|
161
|
-
|
|
162
|
-
|
|
304
|
+
<v-btn
|
|
305
|
+
block
|
|
306
|
+
color="primary"
|
|
307
|
+
variant="text"
|
|
308
|
+
class="text-none font-weight-bold"
|
|
309
|
+
:text="
|
|
310
|
+
showSubscriptionDateOptions
|
|
311
|
+
? 'Hide Subscription Options'
|
|
312
|
+
: 'Show Subscription Options'
|
|
313
|
+
"
|
|
314
|
+
@click="
|
|
315
|
+
showSubscriptionDateOptions = !showSubscriptionDateOptions
|
|
316
|
+
"
|
|
317
|
+
></v-btn>
|
|
163
318
|
</v-col>
|
|
164
319
|
</v-row>
|
|
165
320
|
</v-col>
|
|
@@ -168,7 +323,9 @@
|
|
|
168
323
|
<v-col cols="12">
|
|
169
324
|
<v-row no-gutters>
|
|
170
325
|
<v-col cols="12" class="text-center">
|
|
171
|
-
<span
|
|
326
|
+
<span
|
|
327
|
+
class="text-none text-subtitle-2 font-weight-medium text-error"
|
|
328
|
+
>
|
|
172
329
|
{{ message }}
|
|
173
330
|
</span>
|
|
174
331
|
</v-col>
|
|
@@ -178,18 +335,46 @@
|
|
|
178
335
|
</v-form>
|
|
179
336
|
</v-card-text>
|
|
180
337
|
<v-row no-gutters class="w-100" v-if="errorMessage">
|
|
181
|
-
<p class="text-error w-100 text-center text-subtitle-2">
|
|
338
|
+
<p class="text-error w-100 text-center text-subtitle-2">
|
|
339
|
+
{{ errorMessage }}
|
|
340
|
+
</p>
|
|
182
341
|
</v-row>
|
|
183
342
|
<v-toolbar density="compact">
|
|
184
343
|
<v-row no-gutters>
|
|
185
344
|
<v-col cols="6">
|
|
186
|
-
<v-btn
|
|
187
|
-
|
|
188
|
-
|
|
345
|
+
<v-btn
|
|
346
|
+
v-if="prop.mode === 'add'"
|
|
347
|
+
tile
|
|
348
|
+
block
|
|
349
|
+
variant="text"
|
|
350
|
+
class="text-none"
|
|
351
|
+
size="48"
|
|
352
|
+
@click="back"
|
|
353
|
+
text="Back to Selection"
|
|
354
|
+
/>
|
|
355
|
+
<v-btn
|
|
356
|
+
v-else
|
|
357
|
+
tile
|
|
358
|
+
block
|
|
359
|
+
variant="text"
|
|
360
|
+
class="text-none"
|
|
361
|
+
size="48"
|
|
362
|
+
@click="close"
|
|
363
|
+
text="Close"
|
|
364
|
+
/>
|
|
189
365
|
</v-col>
|
|
190
366
|
<v-col cols="6">
|
|
191
|
-
<v-btn
|
|
192
|
-
|
|
367
|
+
<v-btn
|
|
368
|
+
tile
|
|
369
|
+
block
|
|
370
|
+
variant="flat"
|
|
371
|
+
color="black"
|
|
372
|
+
class="text-none"
|
|
373
|
+
size="48"
|
|
374
|
+
:disabled="!validForm || processing"
|
|
375
|
+
@click="submit"
|
|
376
|
+
:text="prop.mode == 'add' ? 'Submit' : 'Update'"
|
|
377
|
+
/>
|
|
193
378
|
</v-col>
|
|
194
379
|
</v-row>
|
|
195
380
|
</v-toolbar>
|
|
@@ -203,17 +388,24 @@
|
|
|
203
388
|
Matching Records for "{{ searchUnitName ?? "-" }}"
|
|
204
389
|
</span>
|
|
205
390
|
<span>
|
|
206
|
-
<v-btn
|
|
391
|
+
<v-btn
|
|
392
|
+
icon="mdi-close"
|
|
393
|
+
variant="text"
|
|
394
|
+
@click="handleCloseMatchDialog"
|
|
395
|
+
/>
|
|
207
396
|
</span>
|
|
208
397
|
</v-row>
|
|
209
398
|
</v-toolbar-title>
|
|
210
399
|
</v-toolbar>
|
|
211
400
|
|
|
212
401
|
<v-card-text max-height="400" style="overflow-y: auto">
|
|
213
|
-
|
|
214
402
|
<v-list lines="three">
|
|
215
|
-
<v-list-item
|
|
216
|
-
|
|
403
|
+
<v-list-item
|
|
404
|
+
v-if="matchingPeople.length > 0 || checkingUnit"
|
|
405
|
+
v-for="v in matchingPeople"
|
|
406
|
+
:key="v._id"
|
|
407
|
+
class="cursor-pointer"
|
|
408
|
+
>
|
|
217
409
|
<v-list-item-title>
|
|
218
410
|
{{ v.name }}
|
|
219
411
|
</v-list-item-title>
|
|
@@ -223,99 +415,132 @@
|
|
|
223
415
|
</v-list-item-subtitle>
|
|
224
416
|
|
|
225
417
|
<div class="mt-1">
|
|
226
|
-
<v-chip
|
|
418
|
+
<v-chip
|
|
419
|
+
v-for="p in v.plates"
|
|
420
|
+
:key="p?.plateNumber"
|
|
421
|
+
size="small"
|
|
422
|
+
class="mr-1"
|
|
423
|
+
>
|
|
227
424
|
{{ p?.plateNumber }}
|
|
228
425
|
</v-chip>
|
|
229
426
|
</div>
|
|
230
427
|
|
|
231
428
|
<template #append>
|
|
232
|
-
<v-btn
|
|
429
|
+
<v-btn
|
|
430
|
+
variant="flat"
|
|
431
|
+
color="primary"
|
|
432
|
+
@click="selectPeopleRecord(v)"
|
|
433
|
+
>Select</v-btn
|
|
434
|
+
>
|
|
233
435
|
</template>
|
|
234
|
-
|
|
235
436
|
</v-list-item>
|
|
236
|
-
<v-empty-state
|
|
237
|
-
|
|
437
|
+
<v-empty-state
|
|
438
|
+
v-else
|
|
439
|
+
icon="mdi-account-off"
|
|
440
|
+
title="No matching records found"
|
|
441
|
+
subtitle="The NRIC you entered does not match any existing records."
|
|
442
|
+
>
|
|
238
443
|
<template #actions>
|
|
239
|
-
<v-btn
|
|
444
|
+
<v-btn
|
|
445
|
+
variant="flat"
|
|
446
|
+
color="primary"
|
|
447
|
+
@click="showMatchingPeopleDialog = false"
|
|
448
|
+
>Close</v-btn
|
|
449
|
+
>
|
|
240
450
|
</template>
|
|
241
451
|
</v-empty-state>
|
|
242
452
|
</v-list>
|
|
243
|
-
|
|
244
453
|
</v-card-text>
|
|
245
454
|
</v-card>
|
|
246
455
|
</v-dialog>
|
|
247
|
-
|
|
248
456
|
</v-card>
|
|
457
|
+
|
|
458
|
+
<Snackbar
|
|
459
|
+
v-model="messageSnackbar"
|
|
460
|
+
:text="snackbarMessage"
|
|
461
|
+
:color="messageColor"
|
|
462
|
+
/>
|
|
249
463
|
</template>
|
|
250
464
|
|
|
251
465
|
<script setup lang="ts">
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
466
|
const prop = defineProps({
|
|
256
467
|
type: {
|
|
257
|
-
type: String as PropType<TVehicleType |
|
|
258
|
-
required: true
|
|
468
|
+
type: String as PropType<TVehicleType | "seasonpass">,
|
|
469
|
+
required: true,
|
|
259
470
|
},
|
|
260
471
|
org: {
|
|
261
472
|
type: String,
|
|
262
|
-
required: true
|
|
473
|
+
required: true,
|
|
263
474
|
},
|
|
264
475
|
site: {
|
|
265
476
|
type: String,
|
|
266
|
-
required: true
|
|
477
|
+
required: true,
|
|
267
478
|
},
|
|
268
479
|
mode: {
|
|
269
|
-
type: String as PropType<
|
|
270
|
-
default:
|
|
480
|
+
type: String as PropType<"add" | "edit">,
|
|
481
|
+
default: "add",
|
|
271
482
|
},
|
|
272
483
|
vehicleData: {
|
|
273
484
|
type: Object as PropType<Partial<TVehicle> | null>,
|
|
274
|
-
default: null
|
|
485
|
+
default: null,
|
|
275
486
|
},
|
|
276
487
|
plateNumberId: {
|
|
277
488
|
type: String,
|
|
278
|
-
default:
|
|
279
|
-
}
|
|
489
|
+
default: "",
|
|
490
|
+
},
|
|
280
491
|
});
|
|
281
492
|
|
|
282
493
|
const { requiredRule, formatDateISO8601, debounce } = useUtils();
|
|
283
|
-
const {
|
|
494
|
+
const {
|
|
495
|
+
addVehicle,
|
|
496
|
+
getCustomSeasonPassTypes,
|
|
497
|
+
updateVehicle,
|
|
498
|
+
formatVehicleStatus,
|
|
499
|
+
} = useVehicle();
|
|
284
500
|
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
285
501
|
const { findPersonByNRICMultipleResult, getPeopleByUnit } = usePeople();
|
|
286
|
-
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
502
|
+
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
503
|
+
useBuilding();
|
|
504
|
+
|
|
505
|
+
const emit = defineEmits([
|
|
506
|
+
"back",
|
|
507
|
+
"select",
|
|
508
|
+
"done",
|
|
509
|
+
"error",
|
|
510
|
+
"close",
|
|
511
|
+
"close:all",
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
const vehicle = reactive<
|
|
515
|
+
Omit<ExceptPartial<TVehicle>, "type"> & { type: TVehicleType | "seasonpass" }
|
|
516
|
+
>({
|
|
292
517
|
plates: [],
|
|
293
518
|
type: prop.type,
|
|
294
519
|
category: "resident",
|
|
295
|
-
name:
|
|
296
|
-
phoneNumber:
|
|
297
|
-
block:
|
|
298
|
-
level:
|
|
299
|
-
unit:
|
|
300
|
-
nric:
|
|
301
|
-
remarks:
|
|
302
|
-
seasonPassType:
|
|
303
|
-
start:
|
|
304
|
-
end:
|
|
520
|
+
name: "",
|
|
521
|
+
phoneNumber: "",
|
|
522
|
+
block: "",
|
|
523
|
+
level: "",
|
|
524
|
+
unit: "",
|
|
525
|
+
nric: "",
|
|
526
|
+
remarks: "",
|
|
527
|
+
seasonPassType: "",
|
|
528
|
+
start: "",
|
|
529
|
+
end: "",
|
|
305
530
|
site: prop.site,
|
|
306
531
|
org: prop.org,
|
|
307
|
-
_id:
|
|
308
|
-
peopleId:
|
|
532
|
+
_id: "",
|
|
533
|
+
peopleId: "",
|
|
309
534
|
plateNumber: "",
|
|
310
535
|
});
|
|
311
536
|
|
|
312
|
-
const newPlateNumbers = ref<string[]>([
|
|
537
|
+
const newPlateNumbers = ref<string[]>([""]);
|
|
313
538
|
const disablePrefilledInputs = ref(true);
|
|
314
539
|
|
|
315
540
|
const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
316
541
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
317
542
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
318
|
-
const seasonPassTypeArray = ref<{ title: string
|
|
543
|
+
const seasonPassTypeArray = ref<{ title: string; value: string }[]>([]);
|
|
319
544
|
|
|
320
545
|
const matchingPeople = ref<Partial<TPeople>[]>([]);
|
|
321
546
|
const showMatchingPeopleDialog = ref(false);
|
|
@@ -325,23 +550,32 @@ const checkingUnit = ref(false);
|
|
|
325
550
|
|
|
326
551
|
const defaultSeasonPassTypeArray = computed(() => {
|
|
327
552
|
return [
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
553
|
+
"Resident Season Pass",
|
|
554
|
+
"Tenant/Leaseholder Pass",
|
|
555
|
+
"VIP/Preferred Parking Pass",
|
|
556
|
+
"Visitor Season Pass",
|
|
557
|
+
"Staff & Service Provider Pass",
|
|
558
|
+
"Business Tenant Pass",
|
|
334
559
|
].map((item) => ({
|
|
335
560
|
title: item,
|
|
336
|
-
value: item
|
|
337
|
-
}))
|
|
561
|
+
value: item,
|
|
562
|
+
}));
|
|
338
563
|
});
|
|
339
564
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
565
|
+
const typeFieldMap: Record<TVehicleType | "seasonpass", string[]> = {
|
|
566
|
+
seasonpass: [
|
|
567
|
+
"seasonPassType",
|
|
568
|
+
"name",
|
|
569
|
+
"phone",
|
|
570
|
+
"plateNumber",
|
|
571
|
+
"block",
|
|
572
|
+
"level",
|
|
573
|
+
"unit",
|
|
574
|
+
"start",
|
|
575
|
+
"end",
|
|
576
|
+
],
|
|
577
|
+
blocklist: ["name", "nric", "phone", "plateNumber", "remarks"],
|
|
578
|
+
whitelist: ["name", "nric", "phone", "plateNumber", "block", "level", "unit"],
|
|
345
579
|
};
|
|
346
580
|
|
|
347
581
|
const shouldShowField = (fieldKey: string): boolean => {
|
|
@@ -349,42 +583,43 @@ const shouldShowField = (fieldKey: string): boolean => {
|
|
|
349
583
|
return visibleFields?.includes(fieldKey);
|
|
350
584
|
};
|
|
351
585
|
|
|
352
|
-
|
|
353
586
|
const searchUnitName = computed(() => {
|
|
354
|
-
return
|
|
355
|
-
|
|
356
|
-
|
|
587
|
+
return (
|
|
588
|
+
unitsArray.value.find((u) => u.value === vehicle.unit)?.title ||
|
|
589
|
+
vehicle.unit
|
|
590
|
+
);
|
|
591
|
+
});
|
|
357
592
|
|
|
358
593
|
const validForm = ref(false);
|
|
359
|
-
const seasonPassTypeInput = ref(
|
|
594
|
+
const seasonPassTypeInput = ref("");
|
|
360
595
|
const formRef = ref<HTMLFormElement | null>(null);
|
|
361
|
-
const startDateRef = ref<HTMLInputElement | null>(null)
|
|
362
|
-
const expiryDateRef = ref<HTMLInputElement | null>(null)
|
|
596
|
+
const startDateRef = ref<HTMLInputElement | null>(null);
|
|
597
|
+
const expiryDateRef = ref<HTMLInputElement | null>(null);
|
|
363
598
|
const processing = ref(false);
|
|
364
|
-
const message = ref(
|
|
365
|
-
const selectedSubscriptionDuration = ref<string | null>(
|
|
366
|
-
const showSubscriptionDateOptions = ref(false)
|
|
367
|
-
const errorMessage = ref(
|
|
368
|
-
|
|
599
|
+
const message = ref("");
|
|
600
|
+
const selectedSubscriptionDuration = ref<string | null>("custom");
|
|
601
|
+
const showSubscriptionDateOptions = ref(false);
|
|
602
|
+
const errorMessage = ref("");
|
|
369
603
|
|
|
370
604
|
// fetch existing vehicle data if in edit mode
|
|
371
605
|
if (prop.vehicleData) {
|
|
372
|
-
const existingVehicleData = JSON.parse(
|
|
606
|
+
const existingVehicleData = JSON.parse(
|
|
607
|
+
JSON.stringify(prop.vehicleData || {})
|
|
608
|
+
);
|
|
373
609
|
Object.assign(vehicle, existingVehicleData);
|
|
374
610
|
}
|
|
375
611
|
|
|
376
|
-
|
|
377
|
-
|
|
378
612
|
const {
|
|
379
613
|
data: blocksListData,
|
|
380
614
|
refresh: refreshBlockListData,
|
|
381
615
|
status: blocksListStatus,
|
|
382
616
|
pending: blockListDataPending,
|
|
383
|
-
} = useAsyncData(
|
|
384
|
-
|
|
617
|
+
} = useAsyncData(
|
|
618
|
+
`vehicle-fetch-block-list-data-${prop.site}`,
|
|
619
|
+
() => getBlocksSelection({ siteId: prop.site }),
|
|
620
|
+
{ watch: [() => prop.site], deep: true }
|
|
385
621
|
);
|
|
386
622
|
|
|
387
|
-
|
|
388
623
|
const {
|
|
389
624
|
data: levelsData,
|
|
390
625
|
refresh: refreshLevelsData,
|
|
@@ -395,8 +630,12 @@ const {
|
|
|
395
630
|
async () => {
|
|
396
631
|
if (!vehicle.block) return Promise.resolve(null);
|
|
397
632
|
// return await getSiteWithLevels(prop.site, { block: Number(vehicle.block) });
|
|
398
|
-
return await getLevelsSelection({
|
|
399
|
-
|
|
633
|
+
return await getLevelsSelection({
|
|
634
|
+
siteId: prop.site,
|
|
635
|
+
blockId: vehicle.block._id ? vehicle.block._id : vehicle.block,
|
|
636
|
+
});
|
|
637
|
+
},
|
|
638
|
+
{ watch: [() => vehicle.block], deep: true }
|
|
400
639
|
);
|
|
401
640
|
|
|
402
641
|
const {
|
|
@@ -408,46 +647,59 @@ const {
|
|
|
408
647
|
`vehicle-fetch-units-data-${prop.site}-${vehicle.level}`,
|
|
409
648
|
async () => {
|
|
410
649
|
if (!vehicle.level) return Promise.resolve(null);
|
|
411
|
-
return await getUnitSelection({
|
|
412
|
-
|
|
650
|
+
return await getUnitSelection({
|
|
651
|
+
siteId: prop.site,
|
|
652
|
+
block: vehicle.block._id ? vehicle.block._id : vehicle.block,
|
|
653
|
+
level: vehicle.level._id ? vehicle.level._id : vehicle.level,
|
|
654
|
+
});
|
|
655
|
+
},
|
|
656
|
+
{ watch: [() => vehicle.level], deep: true }
|
|
413
657
|
);
|
|
414
658
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
659
|
+
const { data: seasonPassTypeData, refresh: refreshSeasonPassTypeData } =
|
|
660
|
+
useLazyAsyncData(
|
|
661
|
+
`vehicle-fetch-season-pass-type-data-${prop.site}`,
|
|
662
|
+
async () => await getCustomSeasonPassTypes(prop.site)
|
|
663
|
+
);
|
|
420
664
|
|
|
421
665
|
watch(
|
|
422
666
|
[blocksListData],
|
|
423
667
|
() => {
|
|
424
|
-
if (blocksListStatus.value ===
|
|
668
|
+
if (blocksListStatus.value === "pending") {
|
|
425
669
|
blocksArray.value = [];
|
|
426
670
|
return;
|
|
427
671
|
}
|
|
428
672
|
|
|
429
|
-
const arr = Array.isArray(blocksListData.value?.data)
|
|
673
|
+
const arr = Array.isArray(blocksListData.value?.data)
|
|
674
|
+
? blocksListData.value?.data
|
|
675
|
+
: [];
|
|
430
676
|
blocksArray.value = arr.map((block: any) => ({
|
|
431
677
|
title: block.name,
|
|
432
678
|
value: block._id,
|
|
433
679
|
}));
|
|
434
680
|
},
|
|
435
681
|
{ immediate: true, deep: true }
|
|
436
|
-
)
|
|
682
|
+
);
|
|
437
683
|
|
|
438
|
-
watch(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
684
|
+
watch(
|
|
685
|
+
[levelsData],
|
|
686
|
+
() => {
|
|
687
|
+
if (levelsStatus.value === "pending") {
|
|
688
|
+
levelsArray.value = [];
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
443
691
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
692
|
+
const arr = Array.isArray(levelsData.value?.data)
|
|
693
|
+
? levelsData.value?.data
|
|
694
|
+
: [];
|
|
695
|
+
const mappedLevels = arr.map((level: any) => ({
|
|
696
|
+
title: level.name,
|
|
697
|
+
value: level._id,
|
|
698
|
+
}));
|
|
699
|
+
levelsArray.value = mappedLevels;
|
|
700
|
+
},
|
|
701
|
+
{ immediate: true, deep: true }
|
|
702
|
+
);
|
|
451
703
|
|
|
452
704
|
watch(
|
|
453
705
|
unitsData,
|
|
@@ -456,138 +708,170 @@ watch(
|
|
|
456
708
|
const arr = newVal || [];
|
|
457
709
|
unitsArray.value = arr?.map((unit: any) => ({
|
|
458
710
|
title: unit?.name,
|
|
459
|
-
value: unit?._id
|
|
711
|
+
value: unit?._id,
|
|
460
712
|
}));
|
|
461
713
|
} else {
|
|
462
714
|
unitsArray.value = [];
|
|
463
715
|
}
|
|
464
|
-
},
|
|
465
|
-
|
|
716
|
+
},
|
|
717
|
+
{ immediate: true }
|
|
718
|
+
);
|
|
466
719
|
|
|
467
720
|
watch(
|
|
468
721
|
seasonPassTypeData,
|
|
469
722
|
(newVal) => {
|
|
470
723
|
if (Array.isArray(newVal)) {
|
|
471
|
-
const filteredArr = newVal.filter(
|
|
472
|
-
|
|
724
|
+
const filteredArr = newVal.filter(
|
|
725
|
+
(item: any) => item.title && item.value
|
|
726
|
+
);
|
|
727
|
+
seasonPassTypeArray.value = [
|
|
728
|
+
...defaultSeasonPassTypeArray.value,
|
|
729
|
+
...filteredArr,
|
|
730
|
+
];
|
|
473
731
|
} else {
|
|
474
732
|
seasonPassTypeArray.value = defaultSeasonPassTypeArray.value;
|
|
475
733
|
}
|
|
476
|
-
},
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
734
|
+
},
|
|
735
|
+
{ immediate: true }
|
|
736
|
+
);
|
|
480
737
|
|
|
481
738
|
const subscriptionOptions = computed(() => {
|
|
482
739
|
return [
|
|
483
|
-
{ label:
|
|
484
|
-
{ label:
|
|
485
|
-
{ label:
|
|
486
|
-
{ label:
|
|
487
|
-
{ label:
|
|
488
|
-
{ label:
|
|
740
|
+
{ label: "Custom", value: "custom" },
|
|
741
|
+
{ label: "1 Week", value: "1week" },
|
|
742
|
+
{ label: "2 Weeks", value: "2weeks" },
|
|
743
|
+
{ label: "3 Weeks", value: "3weeks" },
|
|
744
|
+
{ label: "4 Weeks", value: "4weeks" },
|
|
745
|
+
{ label: "5 Weeks", value: "5weeks" },
|
|
489
746
|
];
|
|
490
|
-
})
|
|
747
|
+
});
|
|
491
748
|
|
|
492
749
|
function handleChangeBlock(value: any) {
|
|
493
|
-
vehicle.level =
|
|
494
|
-
vehicle.unit =
|
|
750
|
+
vehicle.level = "";
|
|
751
|
+
vehicle.unit = "";
|
|
495
752
|
refreshLevelsData();
|
|
496
753
|
}
|
|
497
754
|
|
|
498
755
|
function handleChangeLevel(value: any) {
|
|
499
|
-
vehicle.unit =
|
|
756
|
+
vehicle.unit = "";
|
|
500
757
|
refreshUnitsData();
|
|
501
758
|
}
|
|
502
759
|
|
|
503
760
|
function addPlate() {
|
|
504
|
-
newPlateNumbers.value.push(
|
|
761
|
+
newPlateNumbers.value.push("");
|
|
505
762
|
}
|
|
506
763
|
|
|
507
|
-
|
|
508
764
|
function removePlate(index: number) {
|
|
509
765
|
newPlateNumbers.value.splice(index, 1);
|
|
510
766
|
}
|
|
511
767
|
|
|
512
768
|
function back() {
|
|
513
769
|
emit("back");
|
|
514
|
-
message.value =
|
|
770
|
+
message.value = "";
|
|
515
771
|
showSubscriptionDateOptions.value = false;
|
|
516
772
|
selectedSubscriptionDuration.value = null;
|
|
517
773
|
}
|
|
518
774
|
|
|
519
775
|
function close() {
|
|
520
776
|
emit("close");
|
|
521
|
-
message.value =
|
|
777
|
+
message.value = "";
|
|
522
778
|
showSubscriptionDateOptions.value = false;
|
|
523
779
|
selectedSubscriptionDuration.value = null;
|
|
524
780
|
}
|
|
525
781
|
|
|
526
|
-
function formatVehicleType(type: TVehicleType |
|
|
782
|
+
function formatVehicleType(type: TVehicleType | "seasonpass"): string {
|
|
527
783
|
switch (type) {
|
|
528
|
-
case
|
|
529
|
-
return
|
|
530
|
-
case
|
|
531
|
-
return
|
|
784
|
+
case "whitelist":
|
|
785
|
+
return "Whitelist";
|
|
786
|
+
case "blocklist":
|
|
787
|
+
return "Blocklist";
|
|
532
788
|
|
|
533
|
-
case
|
|
534
|
-
return
|
|
789
|
+
case "seasonpass":
|
|
790
|
+
return "Season Pass";
|
|
535
791
|
default:
|
|
536
|
-
return
|
|
792
|
+
return "";
|
|
537
793
|
}
|
|
538
794
|
}
|
|
539
795
|
|
|
540
|
-
function onSeasonPassTypeSelected(
|
|
541
|
-
|
|
796
|
+
function onSeasonPassTypeSelected(
|
|
797
|
+
val: string | { title: string; value: string }
|
|
798
|
+
) {
|
|
799
|
+
vehicle.seasonPassType = typeof val === "object" ? val?.value : val;
|
|
542
800
|
}
|
|
543
801
|
|
|
802
|
+
const snackbarMessage = ref("");
|
|
803
|
+
const messageColor = ref("");
|
|
804
|
+
const messageSnackbar = ref(false);
|
|
805
|
+
|
|
806
|
+
function showMessage(msg: string, color: string) {
|
|
807
|
+
snackbarMessage.value = msg;
|
|
808
|
+
messageColor.value = color;
|
|
809
|
+
messageSnackbar.value = true;
|
|
810
|
+
}
|
|
544
811
|
|
|
545
812
|
async function submit() {
|
|
546
|
-
errorMessage.value =
|
|
813
|
+
errorMessage.value = "";
|
|
547
814
|
processing.value = true;
|
|
548
815
|
try {
|
|
549
816
|
const SPTVal = vehicle?.seasonPassType as string;
|
|
550
817
|
if (SPTVal) {
|
|
551
|
-
vehicle.seasonPassType =
|
|
818
|
+
vehicle.seasonPassType =
|
|
819
|
+
SPTVal?.charAt(0)?.toUpperCase() + SPTVal?.slice(1)?.toLowerCase();
|
|
552
820
|
}
|
|
553
821
|
|
|
554
|
-
|
|
555
|
-
|
|
822
|
+
const {
|
|
823
|
+
plateNumber,
|
|
824
|
+
type,
|
|
825
|
+
category,
|
|
826
|
+
name,
|
|
827
|
+
phoneNumber,
|
|
828
|
+
block,
|
|
829
|
+
level,
|
|
830
|
+
unit,
|
|
831
|
+
nric,
|
|
832
|
+
remarks,
|
|
833
|
+
seasonPassType,
|
|
834
|
+
start,
|
|
835
|
+
end,
|
|
836
|
+
site,
|
|
837
|
+
org,
|
|
838
|
+
peopleId,
|
|
839
|
+
} = vehicle;
|
|
556
840
|
|
|
557
841
|
let payload: Partial<TVehiclePayload> = {
|
|
558
842
|
name,
|
|
559
843
|
phoneNumber,
|
|
560
|
-
type: type ===
|
|
844
|
+
type: type === "seasonpass" ? "whitelist" : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
|
|
561
845
|
nric,
|
|
562
|
-
peopleId
|
|
846
|
+
peopleId,
|
|
563
847
|
};
|
|
564
848
|
|
|
565
|
-
if (prop.mode ===
|
|
849
|
+
if (prop.mode === "add") {
|
|
566
850
|
payload = {
|
|
567
851
|
...payload,
|
|
568
852
|
category,
|
|
569
853
|
site,
|
|
570
854
|
org,
|
|
571
|
-
plateNumber: newPlateNumbers.value
|
|
572
|
-
}
|
|
855
|
+
plateNumber: newPlateNumbers.value,
|
|
856
|
+
};
|
|
573
857
|
}
|
|
574
858
|
|
|
575
|
-
if (prop.mode ===
|
|
859
|
+
if (prop.mode === "edit" && prop.plateNumberId) {
|
|
576
860
|
payload = {
|
|
577
861
|
...payload,
|
|
578
862
|
site: prop.site,
|
|
579
|
-
plateNumber
|
|
580
|
-
}
|
|
863
|
+
plateNumber,
|
|
864
|
+
};
|
|
581
865
|
}
|
|
582
866
|
|
|
583
|
-
if (vehicle.type ===
|
|
867
|
+
if (vehicle.type === "whitelist") {
|
|
584
868
|
payload = {
|
|
585
869
|
...payload,
|
|
586
870
|
block,
|
|
587
871
|
level,
|
|
588
872
|
unit,
|
|
589
873
|
};
|
|
590
|
-
} else if (vehicle.type ===
|
|
874
|
+
} else if (vehicle.type === "seasonpass") {
|
|
591
875
|
payload = {
|
|
592
876
|
...payload,
|
|
593
877
|
seasonPassType,
|
|
@@ -595,27 +879,31 @@ async function submit() {
|
|
|
595
879
|
level,
|
|
596
880
|
unit,
|
|
597
881
|
start,
|
|
598
|
-
end
|
|
882
|
+
end,
|
|
599
883
|
};
|
|
600
|
-
} else if (vehicle.type ===
|
|
884
|
+
} else if (vehicle.type === "blocklist") {
|
|
601
885
|
payload = {
|
|
602
886
|
...payload,
|
|
603
887
|
remarks,
|
|
604
888
|
};
|
|
605
889
|
}
|
|
606
890
|
|
|
607
|
-
if (prop.mode ===
|
|
891
|
+
if (prop.mode === "add") {
|
|
608
892
|
await addVehicle(payload);
|
|
609
|
-
|
|
893
|
+
showMessage("Vehicle added successfully.", "success");
|
|
894
|
+
} else if (prop.mode === "edit") {
|
|
610
895
|
const plateNumberId = prop.plateNumberId as string;
|
|
611
896
|
await updateVehicle(plateNumberId, payload);
|
|
897
|
+
showMessage("Vehicle updated successfully.", "success");
|
|
612
898
|
}
|
|
613
899
|
emit("done");
|
|
614
|
-
|
|
615
|
-
|
|
616
900
|
} catch (error: any) {
|
|
617
|
-
const err = error?.data?.message
|
|
618
|
-
errorMessage.value =
|
|
901
|
+
const err = error?.data?.message;
|
|
902
|
+
errorMessage.value =
|
|
903
|
+
err ||
|
|
904
|
+
`Failed to ${
|
|
905
|
+
prop.mode === "add" ? "add" : "update"
|
|
906
|
+
} vehicle. Please try again.`;
|
|
619
907
|
} finally {
|
|
620
908
|
processing.value = false;
|
|
621
909
|
}
|
|
@@ -627,40 +915,40 @@ async function submit() {
|
|
|
627
915
|
// }
|
|
628
916
|
|
|
629
917
|
watch(selectedSubscriptionDuration, (duration) => {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const dateNowISO = formatDateISO8601(new Date())
|
|
918
|
+
const dateNowISO = formatDateISO8601(new Date());
|
|
633
919
|
const weeksLaterISO = (week: number) => {
|
|
634
|
-
const now = new Date()
|
|
635
|
-
const dateWeekLater = new Date(
|
|
636
|
-
|
|
920
|
+
const now = new Date();
|
|
921
|
+
const dateWeekLater = new Date(
|
|
922
|
+
now.getTime() + week * 7 * 24 * 60 * 60 * 1000
|
|
923
|
+
);
|
|
924
|
+
return formatDateISO8601(dateWeekLater);
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
if (duration === "custom") {
|
|
928
|
+
vehicle.start = "";
|
|
929
|
+
vehicle.end = "";
|
|
930
|
+
} else if (duration === "1week") {
|
|
931
|
+
vehicle.start = dateNowISO;
|
|
932
|
+
vehicle.end = weeksLaterISO(1);
|
|
933
|
+
} else if (duration === "2weeks") {
|
|
934
|
+
vehicle.start = dateNowISO;
|
|
935
|
+
vehicle.end = weeksLaterISO(2);
|
|
936
|
+
} else if (duration === "3weeks") {
|
|
937
|
+
vehicle.start = dateNowISO;
|
|
938
|
+
vehicle.end = weeksLaterISO(3);
|
|
939
|
+
} else if (duration === "4weeks") {
|
|
940
|
+
vehicle.start = dateNowISO;
|
|
941
|
+
vehicle.end = weeksLaterISO(4);
|
|
942
|
+
} else if (duration === "5weeks") {
|
|
943
|
+
vehicle.start = dateNowISO;
|
|
944
|
+
vehicle.end = weeksLaterISO(5);
|
|
637
945
|
}
|
|
638
|
-
|
|
639
|
-
if (duration === 'custom') {
|
|
640
|
-
vehicle.start = '';
|
|
641
|
-
vehicle.end = '';
|
|
642
|
-
} else if (duration === '1week') {
|
|
643
|
-
vehicle.start = dateNowISO
|
|
644
|
-
vehicle.end = weeksLaterISO(1)
|
|
645
|
-
} else if (duration === '2weeks') {
|
|
646
|
-
vehicle.start = dateNowISO
|
|
647
|
-
vehicle.end = weeksLaterISO(2)
|
|
648
|
-
} else if (duration === '3weeks') {
|
|
649
|
-
vehicle.start = dateNowISO
|
|
650
|
-
vehicle.end = weeksLaterISO(3)
|
|
651
|
-
} else if (duration === '4weeks') {
|
|
652
|
-
vehicle.start = dateNowISO
|
|
653
|
-
vehicle.end = weeksLaterISO(4)
|
|
654
|
-
} else if (duration === '5weeks') {
|
|
655
|
-
vehicle.start = dateNowISO
|
|
656
|
-
vehicle.end = weeksLaterISO(5)
|
|
657
|
-
}
|
|
658
|
-
})
|
|
946
|
+
});
|
|
659
947
|
|
|
660
948
|
function validStartDateRule(value: string) {
|
|
661
949
|
const expiryDateISO = vehicle.end;
|
|
662
950
|
if (!value && expiryDateISO) {
|
|
663
|
-
return
|
|
951
|
+
return "Start Date is required";
|
|
664
952
|
}
|
|
665
953
|
|
|
666
954
|
return true;
|
|
@@ -671,13 +959,13 @@ function validExpiryDateRule() {
|
|
|
671
959
|
const endDateISO = vehicle.end;
|
|
672
960
|
|
|
673
961
|
if (!endDateISO && startDateISO) {
|
|
674
|
-
return
|
|
962
|
+
return "Expiry Date is required";
|
|
675
963
|
}
|
|
676
964
|
|
|
677
965
|
if (endDateISO && startDateISO) {
|
|
678
966
|
const expiry = new Date(endDateISO);
|
|
679
967
|
const start = new Date(startDateISO as string);
|
|
680
|
-
return expiry > start ||
|
|
968
|
+
return expiry > start || "Expiry date must be later than start date";
|
|
681
969
|
}
|
|
682
970
|
|
|
683
971
|
return true;
|
|
@@ -688,19 +976,16 @@ watch([() => vehicle.end, () => vehicle.start], () => {
|
|
|
688
976
|
(startDateRef.value as any)?.validate();
|
|
689
977
|
});
|
|
690
978
|
|
|
691
|
-
|
|
692
979
|
function selectPeopleRecord(record: TPeople) {
|
|
693
|
-
|
|
694
980
|
vehicle.name = record.name;
|
|
695
981
|
vehicle.phoneNumber = record.contact;
|
|
696
982
|
vehicle.nric = record.nric;
|
|
697
983
|
vehicle.plates = record.plates || [];
|
|
698
984
|
disablePrefilledInputs.value = true;
|
|
699
985
|
showMatchingPeopleDialog.value = false;
|
|
700
|
-
vehicle.peopleId = record._id ||
|
|
986
|
+
vehicle.peopleId = record._id || "";
|
|
701
987
|
}
|
|
702
988
|
|
|
703
|
-
|
|
704
989
|
// async function checkNRIC() {
|
|
705
990
|
// if (!vehicle.nric || vehicle.nric.length < 5) return;
|
|
706
991
|
|
|
@@ -721,7 +1006,6 @@ function selectPeopleRecord(record: TPeople) {
|
|
|
721
1006
|
// disablePrefilledInputs.value = false;
|
|
722
1007
|
// }
|
|
723
1008
|
|
|
724
|
-
|
|
725
1009
|
// } catch (error) {
|
|
726
1010
|
// console.error("NRIC search failed:", error);
|
|
727
1011
|
// disablePrefilledInputs.value = false;
|
|
@@ -732,20 +1016,21 @@ function selectPeopleRecord(record: TPeople) {
|
|
|
732
1016
|
|
|
733
1017
|
// const debounceedCheckNRIC = debounce(checkNRIC, 500);
|
|
734
1018
|
|
|
735
|
-
|
|
736
|
-
|
|
737
1019
|
async function checkPeopleById() {
|
|
738
|
-
if (!vehicle.unit || prop.mode ===
|
|
1020
|
+
if (!vehicle.unit || prop.mode === "edit" || prefillingRecords.value) return;
|
|
739
1021
|
|
|
740
1022
|
checkingUnit.value = true;
|
|
741
1023
|
showMatchingPeopleDialog.value = true;
|
|
742
1024
|
// Lock editable fields while we verify if this NRIC has existing records.
|
|
743
1025
|
disablePrefilledInputs.value = true;
|
|
744
1026
|
try {
|
|
745
|
-
const res = await getPeopleByUnit(vehicle.unit, {
|
|
1027
|
+
const res = (await getPeopleByUnit(vehicle.unit, {
|
|
1028
|
+
status: "active",
|
|
1029
|
+
type: "resident,tenant",
|
|
1030
|
+
})) as TPeople[] | null;
|
|
746
1031
|
|
|
747
1032
|
if (Array.isArray(res) && res.length > 0) {
|
|
748
|
-
matchingPeople.value = res || []
|
|
1033
|
+
matchingPeople.value = res || [];
|
|
749
1034
|
// Force selection from matched records.
|
|
750
1035
|
disablePrefilledInputs.value = true;
|
|
751
1036
|
} else {
|
|
@@ -753,8 +1038,6 @@ async function checkPeopleById() {
|
|
|
753
1038
|
// No match found, allow manual input.
|
|
754
1039
|
disablePrefilledInputs.value = false;
|
|
755
1040
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
1041
|
} catch (error) {
|
|
759
1042
|
console.error("Unit search failed:", error);
|
|
760
1043
|
// disablePrefilledInputs.value = false;
|
|
@@ -770,7 +1053,7 @@ watch(
|
|
|
770
1053
|
async (newUnit) => {
|
|
771
1054
|
if (prefillingRecords.value) return;
|
|
772
1055
|
|
|
773
|
-
if(prop.mode ===
|
|
1056
|
+
if (prop.mode === "edit") return;
|
|
774
1057
|
|
|
775
1058
|
resetVehicleDetails();
|
|
776
1059
|
matchingPeople.value = [];
|
|
@@ -780,33 +1063,31 @@ watch(
|
|
|
780
1063
|
}
|
|
781
1064
|
);
|
|
782
1065
|
|
|
783
|
-
|
|
784
1066
|
const resetVehicleDetails = () => {
|
|
785
|
-
vehicle.name =
|
|
786
|
-
vehicle.phoneNumber =
|
|
1067
|
+
vehicle.name = "";
|
|
1068
|
+
vehicle.phoneNumber = "";
|
|
787
1069
|
vehicle.plates = [];
|
|
788
|
-
vehicle.nric =
|
|
789
|
-
vehicle.peopleId =
|
|
790
|
-
}
|
|
791
|
-
|
|
1070
|
+
vehicle.nric = "";
|
|
1071
|
+
vehicle.peopleId = "";
|
|
1072
|
+
};
|
|
792
1073
|
|
|
793
1074
|
function handleCloseMatchDialog() {
|
|
794
1075
|
showMatchingPeopleDialog.value = false;
|
|
795
1076
|
if (matchingPeople.value.length > 0) {
|
|
796
|
-
vehicle.unit = null
|
|
1077
|
+
vehicle.unit = null;
|
|
797
1078
|
}
|
|
798
1079
|
}
|
|
799
1080
|
|
|
800
1081
|
onMounted(() => {
|
|
801
1082
|
setTimeout(() => {
|
|
802
1083
|
console.log("VehicleForm mounted with props:", prop);
|
|
803
|
-
if (prop.mode ===
|
|
1084
|
+
if (prop.mode === "edit" && prop.vehicleData) {
|
|
804
1085
|
// 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.
|
|
805
|
-
vehicle.seasonPassType = prop.vehicleData.seasonPassType ||
|
|
806
|
-
vehicle.end = prop.vehicleData.end ||
|
|
807
|
-
vehicle.start = prop.vehicleData.start ||
|
|
808
|
-
vehicle.plateNumber = prop.vehicleData.plateNumber
|
|
809
|
-
vehicle.peopleId = prop.vehicleData.peopleId ||
|
|
1086
|
+
vehicle.seasonPassType = prop.vehicleData.seasonPassType || "";
|
|
1087
|
+
vehicle.end = prop.vehicleData.end || "";
|
|
1088
|
+
vehicle.start = prop.vehicleData.start || "";
|
|
1089
|
+
vehicle.plateNumber = prop.vehicleData.plateNumber;
|
|
1090
|
+
vehicle.peopleId = prop.vehicleData.peopleId || "";
|
|
810
1091
|
vehicle.block = prop.vehicleData.block || null;
|
|
811
1092
|
|
|
812
1093
|
if (prop.type === "seasonpass") {
|
|
@@ -815,20 +1096,24 @@ onMounted(() => {
|
|
|
815
1096
|
}
|
|
816
1097
|
}, 300);
|
|
817
1098
|
|
|
818
|
-
if (prop.mode ===
|
|
1099
|
+
if (prop.mode === "add" && prop.type !== "blocklist") {
|
|
819
1100
|
// 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.
|
|
820
1101
|
prefillingRecords.value = true;
|
|
821
|
-
vehicle.block = prop.vehicleData?.block?._id
|
|
822
|
-
|
|
823
|
-
|
|
1102
|
+
vehicle.block = prop.vehicleData?.block?._id
|
|
1103
|
+
? prop.vehicleData?.block?._id
|
|
1104
|
+
: "";
|
|
1105
|
+
vehicle.level = prop.vehicleData?.level?._id
|
|
1106
|
+
? prop.vehicleData?.level?._id
|
|
1107
|
+
: "";
|
|
1108
|
+
vehicle.unit = prop.vehicleData?.unit?._id
|
|
1109
|
+
? prop.vehicleData?.unit?._id
|
|
1110
|
+
: "";
|
|
824
1111
|
}
|
|
825
1112
|
|
|
826
1113
|
nextTick(() => {
|
|
827
1114
|
prefillingRecords.value = false;
|
|
828
|
-
})
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
832
1117
|
</script>
|
|
833
1118
|
<style scoped>
|
|
834
1119
|
.button-outline-class {
|