@7365admin1/layer-common 3.0.7 → 3.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/components/CameraMain.vue +17 -1
- package/components/DashboardMain.vue +38 -82
- package/components/MemberInformation.vue +23 -15
- package/components/OnlineFormFill.vue +34 -0
- package/components/PassInformation.vue +73 -34
- package/components/SiteSettings.vue +42 -121
- package/components/VisitorForm.vue +623 -289
- package/components/VisitorManagement.vue +652 -205
- package/composables/useCommonPermission.ts +59 -0
- package/composables/useDashboard.ts +18 -0
- package/composables/useSettingsPermission.ts +138 -0
- package/package.json +2 -2
- package/types/visitor.d.ts +4 -2
|
@@ -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 justify-space-between align-center"
|
|
7
|
+
align="center"
|
|
8
|
+
>
|
|
5
9
|
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
10
|
{{ prop.mode }} {{ formatVisitorType(type) }}
|
|
7
11
|
</span>
|
|
@@ -12,25 +16,54 @@
|
|
|
12
16
|
<div class="w-100 d-flex justify-space-between ga-2">
|
|
13
17
|
<span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
|
|
14
18
|
formTitle
|
|
15
|
-
|
|
16
|
-
<span
|
|
17
|
-
|
|
19
|
+
}}</span>
|
|
20
|
+
<span
|
|
21
|
+
v-if="withStep2 || withStep3"
|
|
22
|
+
class="text-subtitle-2 font-weight-bold"
|
|
23
|
+
style="text-wrap: nowrap"
|
|
24
|
+
>Step <span class="text-primary-button">{{ contractorStep }}</span
|
|
25
|
+
>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span
|
|
26
|
+
>
|
|
18
27
|
</div>
|
|
19
|
-
<v-form
|
|
28
|
+
<v-form
|
|
29
|
+
ref="formRef"
|
|
30
|
+
v-model="validForm"
|
|
31
|
+
:disabled="processing"
|
|
32
|
+
@click="errorMessage = ''"
|
|
33
|
+
>
|
|
20
34
|
<v-row no-gutters class="pt-4">
|
|
21
35
|
<v-col v-if="shouldShowField('contractorType')" cols="12">
|
|
22
|
-
<InputLabel
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
<InputLabel
|
|
37
|
+
class="text-capitalize"
|
|
38
|
+
title="Contractor Type"
|
|
39
|
+
required
|
|
40
|
+
/>
|
|
41
|
+
<v-combobox
|
|
42
|
+
v-model="contractorTypeObj"
|
|
43
|
+
v-model:search="contractorTypeInput"
|
|
44
|
+
ref="contractorTypeCombo"
|
|
45
|
+
autocomplete="off"
|
|
46
|
+
:hide-no-data="false"
|
|
47
|
+
@update:focused="handleFocusedContractorType"
|
|
48
|
+
:items="contractorTypes"
|
|
49
|
+
:rules="[requiredRule]"
|
|
50
|
+
item-value="value"
|
|
51
|
+
@update:model-value="handleSelectContractorType"
|
|
52
|
+
variant="outlined"
|
|
53
|
+
density="comfortable"
|
|
54
|
+
persistent-hint
|
|
55
|
+
small-chips
|
|
56
|
+
>
|
|
28
57
|
<template v-slot:no-data>
|
|
29
58
|
<v-list-item>
|
|
30
|
-
<v-list-item-title
|
|
59
|
+
<v-list-item-title
|
|
60
|
+
@click.stop="handleAddNewContractorType"
|
|
61
|
+
class="d-flex align-center ga-1"
|
|
62
|
+
>
|
|
31
63
|
<span><v-icon icon="mdi-plus" /></span> Add "<strong>{{
|
|
32
64
|
contractorTypeInput
|
|
33
|
-
|
|
65
|
+
}}</strong
|
|
66
|
+
>" as custom contractor type.
|
|
34
67
|
</v-list-item-title>
|
|
35
68
|
</v-list-item>
|
|
36
69
|
</template>
|
|
@@ -39,14 +72,26 @@
|
|
|
39
72
|
|
|
40
73
|
<v-col v-if="shouldShowField('attachments')" class="w-100">
|
|
41
74
|
<InputLabel class="text-capitalize" title="Attach Image" />
|
|
42
|
-
<InputFileV2
|
|
75
|
+
<InputFileV2
|
|
76
|
+
v-model="visitor.attachments"
|
|
77
|
+
multiple
|
|
78
|
+
:max-length="3"
|
|
79
|
+
/>
|
|
43
80
|
</v-col>
|
|
44
81
|
|
|
45
82
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
46
83
|
<v-row>
|
|
47
84
|
<v-col cols="12">
|
|
48
|
-
<InputLabel
|
|
49
|
-
|
|
85
|
+
<InputLabel
|
|
86
|
+
class="text-capitalize"
|
|
87
|
+
title="Full Name"
|
|
88
|
+
required
|
|
89
|
+
/>
|
|
90
|
+
<v-text-field
|
|
91
|
+
v-model.trim="visitor.name"
|
|
92
|
+
density="comfortable"
|
|
93
|
+
:rules="[requiredRule]"
|
|
94
|
+
/>
|
|
50
95
|
</v-col>
|
|
51
96
|
</v-row>
|
|
52
97
|
</v-col>
|
|
@@ -54,25 +99,44 @@
|
|
|
54
99
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
55
100
|
<v-row>
|
|
56
101
|
<v-col cols="12">
|
|
57
|
-
<InputLabel
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
102
|
+
<InputLabel
|
|
103
|
+
class="text-capitalize"
|
|
104
|
+
title="NRIC"
|
|
105
|
+
:required="requireNRIC"
|
|
106
|
+
/>
|
|
107
|
+
<InputNRICNumber
|
|
108
|
+
v-model="visitor.nric"
|
|
109
|
+
density="comfortable"
|
|
110
|
+
:rules="[requireNRIC ? requiredRule : () => true]"
|
|
111
|
+
:key="currentAutofillSource + 'nric-key'"
|
|
112
|
+
@update:model-value="handleUpdateNRIC"
|
|
113
|
+
:loading="fetchPersonByNRICPending"
|
|
114
|
+
/>
|
|
61
115
|
</v-col>
|
|
62
116
|
</v-row>
|
|
63
117
|
</v-col>
|
|
64
118
|
|
|
65
119
|
<v-col v-if="shouldShowField('contact')" cols="12">
|
|
66
120
|
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
67
|
-
<InputPhoneNumberV2
|
|
68
|
-
|
|
69
|
-
|
|
121
|
+
<InputPhoneNumberV2
|
|
122
|
+
v-model="visitor.contact"
|
|
123
|
+
:rules="[requiredRule]"
|
|
124
|
+
density="comfortable"
|
|
125
|
+
:key="currentAutofillSource + 'phone-key'"
|
|
126
|
+
:loading="fetchPersonByContactPending"
|
|
127
|
+
@update:model-value="handleUpdateContact"
|
|
128
|
+
/>
|
|
70
129
|
</v-col>
|
|
71
130
|
|
|
72
131
|
<v-col v-if="shouldShowField('deliveryType')" cols="12">
|
|
73
132
|
<v-row>
|
|
74
133
|
<v-col cols="12">
|
|
75
|
-
<v-radio-group
|
|
134
|
+
<v-radio-group
|
|
135
|
+
v-model="visitor.deliveryType"
|
|
136
|
+
inline
|
|
137
|
+
color="primary"
|
|
138
|
+
:rules="[requiredRule]"
|
|
139
|
+
>
|
|
76
140
|
<v-radio label="Food" value="Food"></v-radio>
|
|
77
141
|
<v-radio label="Parcel" value="Parcel"></v-radio>
|
|
78
142
|
</v-radio-group>
|
|
@@ -83,22 +147,44 @@
|
|
|
83
147
|
<template v-if="shouldShowField('company')">
|
|
84
148
|
<v-col cols="12">
|
|
85
149
|
<InputLabel class="text-capitalize" title="Company Name" />
|
|
86
|
-
<v-combobox
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
150
|
+
<v-combobox
|
|
151
|
+
v-model="visitor.company"
|
|
152
|
+
v-model:search="companyNameInput"
|
|
153
|
+
ref="companyCombo"
|
|
154
|
+
autocomplete="off"
|
|
155
|
+
:hide-no-data="false"
|
|
156
|
+
:items="companyNames"
|
|
157
|
+
item-value="value"
|
|
158
|
+
:loading="fetchCompanyListPending"
|
|
159
|
+
@update:model-value="handleSelectCompanyName"
|
|
160
|
+
variant="outlined"
|
|
161
|
+
density="comfortable"
|
|
162
|
+
persistent-hint
|
|
163
|
+
small-chips
|
|
164
|
+
>
|
|
90
165
|
<template v-slot:no-data>
|
|
91
166
|
<v-list-item>
|
|
92
167
|
<v-list-item-title v-if="fetchCompanyListPending">
|
|
93
|
-
<v-progress-circular
|
|
168
|
+
<v-progress-circular
|
|
169
|
+
indeterminate
|
|
170
|
+
size="20"
|
|
171
|
+
class="mr-3"
|
|
172
|
+
/>
|
|
94
173
|
Searching companies…
|
|
95
174
|
</v-list-item-title>
|
|
96
|
-
<v-list-item-title
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
175
|
+
<v-list-item-title
|
|
176
|
+
v-else-if="companyNameInput"
|
|
177
|
+
@click.stop="handleAddNewCompany"
|
|
178
|
+
class="d-flex align-center ga-1"
|
|
179
|
+
>
|
|
180
|
+
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
|
|
181
|
+
companyNameInput
|
|
182
|
+
}}</strong
|
|
183
|
+
>" as new company.
|
|
100
184
|
</v-list-item-title>
|
|
101
|
-
<v-list-item-title
|
|
185
|
+
<v-list-item-title
|
|
186
|
+
v-else-if="!companyNameInput && companyNames.length === 0"
|
|
187
|
+
>
|
|
102
188
|
Start typing to search for companies.
|
|
103
189
|
</v-list-item-title>
|
|
104
190
|
<v-list-item-title v-else>
|
|
@@ -113,21 +199,43 @@
|
|
|
113
199
|
<template v-if="shouldShowField('delivery-company')">
|
|
114
200
|
<v-col cols="12">
|
|
115
201
|
<InputLabel class="text-capitalize" title="Delivery Company" />
|
|
116
|
-
<v-combobox
|
|
117
|
-
|
|
118
|
-
:
|
|
202
|
+
<v-combobox
|
|
203
|
+
v-model="deliveryCompany"
|
|
204
|
+
v-model:search="deliveryCompanyInput"
|
|
205
|
+
ref="companyCombo"
|
|
206
|
+
autocomplete="off"
|
|
207
|
+
:hide-no-data="false"
|
|
208
|
+
:items="deliveryCompanyList"
|
|
209
|
+
item-value="value"
|
|
210
|
+
:loading="siteDataPending"
|
|
211
|
+
variant="outlined"
|
|
212
|
+
density="comfortable"
|
|
213
|
+
persistent-hint
|
|
214
|
+
small-chips
|
|
215
|
+
>
|
|
119
216
|
<template v-slot:no-data>
|
|
120
217
|
<v-list-item>
|
|
121
218
|
<v-list-item-title v-if="fetchCompanyListPending">
|
|
122
|
-
<v-progress-circular
|
|
219
|
+
<v-progress-circular
|
|
220
|
+
indeterminate
|
|
221
|
+
size="20"
|
|
222
|
+
class="mr-3"
|
|
223
|
+
/>
|
|
123
224
|
Searching companies…
|
|
124
225
|
</v-list-item-title>
|
|
125
|
-
<v-list-item-title
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
226
|
+
<v-list-item-title
|
|
227
|
+
v-else-if="companyNameInput"
|
|
228
|
+
@click.stop="handleAddNewCompany"
|
|
229
|
+
class="d-flex align-center ga-1"
|
|
230
|
+
>
|
|
231
|
+
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
|
|
232
|
+
companyNameInput
|
|
233
|
+
}}</strong
|
|
234
|
+
>" as new company.
|
|
129
235
|
</v-list-item-title>
|
|
130
|
-
<v-list-item-title
|
|
236
|
+
<v-list-item-title
|
|
237
|
+
v-else-if="!companyNameInput && companyNames.length === 0"
|
|
238
|
+
>
|
|
131
239
|
Start typing to search for companies.
|
|
132
240
|
</v-list-item-title>
|
|
133
241
|
<v-list-item-title v-else>
|
|
@@ -139,60 +247,135 @@
|
|
|
139
247
|
</v-col>
|
|
140
248
|
</template>
|
|
141
249
|
|
|
142
|
-
|
|
143
250
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
144
251
|
<v-row>
|
|
145
252
|
<v-col cols="12">
|
|
146
|
-
<InputLabel
|
|
253
|
+
<InputLabel
|
|
254
|
+
class="text-capitalize"
|
|
255
|
+
title="Vehicle Number"
|
|
256
|
+
required
|
|
257
|
+
/>
|
|
147
258
|
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
148
|
-
<InputVehicleNumber
|
|
259
|
+
<InputVehicleNumber
|
|
260
|
+
v-model="visitor.plateNumber"
|
|
261
|
+
density="comfortable"
|
|
262
|
+
/>
|
|
149
263
|
</v-col>
|
|
150
264
|
</v-row>
|
|
151
265
|
</v-col>
|
|
152
266
|
|
|
153
267
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
154
268
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
155
|
-
<v-autocomplete
|
|
156
|
-
|
|
157
|
-
:
|
|
269
|
+
<v-autocomplete
|
|
270
|
+
v-model="visitor.block"
|
|
271
|
+
:items="blocksArray"
|
|
272
|
+
item-value="value"
|
|
273
|
+
autocomplete="off"
|
|
274
|
+
item-title="title"
|
|
275
|
+
:key="
|
|
276
|
+
blockListDataPending ? 'block-list-pending' : 'block-list-ready'
|
|
277
|
+
"
|
|
278
|
+
:loading="blockListDataPending"
|
|
279
|
+
@update:model-value="handleChangeBlock"
|
|
280
|
+
density="comfortable"
|
|
281
|
+
:rules="[requiredRule]"
|
|
282
|
+
/>
|
|
158
283
|
</v-col>
|
|
159
284
|
|
|
160
285
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
161
286
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
162
|
-
<v-autocomplete
|
|
163
|
-
|
|
164
|
-
:
|
|
287
|
+
<v-autocomplete
|
|
288
|
+
v-model="visitor.level"
|
|
289
|
+
:items="levelsArray"
|
|
290
|
+
density="comfortable"
|
|
291
|
+
autocomplete="off"
|
|
292
|
+
:disabled="!visitor.block"
|
|
293
|
+
:key="
|
|
294
|
+
levelListDataPending ? 'level-list-pending' : 'level-list-ready'
|
|
295
|
+
"
|
|
296
|
+
:loading="levelsDataPending"
|
|
297
|
+
@update:model-value="handleChangeLevel"
|
|
298
|
+
:rules="[requiredRule]"
|
|
299
|
+
/>
|
|
165
300
|
</v-col>
|
|
166
301
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
167
302
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
168
|
-
<v-autocomplete
|
|
169
|
-
|
|
170
|
-
|
|
303
|
+
<v-autocomplete
|
|
304
|
+
v-model="visitor.unit"
|
|
305
|
+
:items="unitsArray"
|
|
306
|
+
density="comfortable"
|
|
307
|
+
autocomplete="off"
|
|
308
|
+
item-title="title"
|
|
309
|
+
item-value="value"
|
|
310
|
+
:disabled="!visitor.level"
|
|
311
|
+
:key="
|
|
312
|
+
unitListDataPending ? 'unit-list-pending' : 'unit-list-ready'
|
|
313
|
+
"
|
|
314
|
+
:loading="unitsDataPending"
|
|
315
|
+
:rules="[requiredRule]"
|
|
316
|
+
@update:model-value="handleUpdateUnit"
|
|
317
|
+
/>
|
|
171
318
|
</v-col>
|
|
172
319
|
|
|
173
320
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
174
|
-
<InputLabel
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
321
|
+
<InputLabel
|
|
322
|
+
class="text-capitalize"
|
|
323
|
+
title="Registered Unit Company Name"
|
|
324
|
+
required
|
|
325
|
+
:key="'unit-key' + visitor.level"
|
|
326
|
+
/>
|
|
327
|
+
<v-text-field
|
|
328
|
+
v-model.trim="registeredUnitCompanyName"
|
|
329
|
+
density="comfortable"
|
|
330
|
+
:loading="buildingUnitDataPending"
|
|
331
|
+
readonly
|
|
332
|
+
class="no-pointer"
|
|
333
|
+
/>
|
|
178
334
|
</v-col>
|
|
179
335
|
|
|
180
336
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
181
337
|
<InputLabel class="text-capitalize" title="Remarks" />
|
|
182
|
-
<v-textarea
|
|
338
|
+
<v-textarea
|
|
339
|
+
v-model="visitor.remarks"
|
|
340
|
+
density="comfortable"
|
|
341
|
+
:rows="3"
|
|
342
|
+
no-resize
|
|
343
|
+
/>
|
|
183
344
|
</v-col>
|
|
184
345
|
|
|
185
|
-
<v-col
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
346
|
+
<v-col
|
|
347
|
+
v-if="(withStep2 || withStep3) && contractorStep === 2"
|
|
348
|
+
cols="12"
|
|
349
|
+
>
|
|
350
|
+
<PassInformation
|
|
351
|
+
v-model:pass="visitor.visitorPass"
|
|
352
|
+
v-model:keys="visitor.passKeys"
|
|
353
|
+
:site="prop.site"
|
|
354
|
+
:type="prop.type"
|
|
355
|
+
:contractor-type="visitor.contractorType"
|
|
356
|
+
:hide-keys="!showShowKeysField"
|
|
357
|
+
:passKeys="visitor.passKeys"
|
|
358
|
+
/>
|
|
359
|
+
<EntryPassInformation
|
|
360
|
+
v-if="entryPassSettings?.data?.settings?.nfcPass"
|
|
361
|
+
v-model="passType"
|
|
362
|
+
v-model:quantity="passQuantity"
|
|
363
|
+
v-model:cards="passCards"
|
|
364
|
+
:settings="entryPassSettings"
|
|
365
|
+
:loading="entryPassSettingsPending"
|
|
366
|
+
:site-id="prop.site"
|
|
367
|
+
:unit-id="visitor.unit || null"
|
|
368
|
+
/>
|
|
191
369
|
</v-col>
|
|
192
370
|
|
|
193
371
|
<v-col v-if="withStep3 && contractorStep === 3" cols="12">
|
|
194
|
-
<MemberInformation
|
|
195
|
-
|
|
372
|
+
<MemberInformation
|
|
373
|
+
v-model="visitor.members"
|
|
374
|
+
:type="prop.type"
|
|
375
|
+
:contractor-type="visitor.contractorType"
|
|
376
|
+
:site="prop.site"
|
|
377
|
+
:selected-visitor-pass="visitor.visitorPass"
|
|
378
|
+
/>
|
|
196
379
|
</v-col>
|
|
197
380
|
|
|
198
381
|
<v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
|
|
@@ -216,33 +399,89 @@
|
|
|
216
399
|
<v-toolbar density="compact">
|
|
217
400
|
<v-row no-gutters>
|
|
218
401
|
<v-col cols="6">
|
|
219
|
-
<v-btn
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
402
|
+
<v-btn
|
|
403
|
+
v-if="
|
|
404
|
+
prop.mode === 'add' &&
|
|
405
|
+
(withStep2 || withStep3) &&
|
|
406
|
+
contractorStep > 1
|
|
407
|
+
"
|
|
408
|
+
tile
|
|
409
|
+
block
|
|
410
|
+
variant="text"
|
|
411
|
+
class="text-none"
|
|
412
|
+
size="48"
|
|
413
|
+
@click="handleGoToPreviousPage"
|
|
414
|
+
text="Back"
|
|
415
|
+
/>
|
|
416
|
+
<v-btn
|
|
417
|
+
v-else-if="prop.mode === 'add' || prop.mode === 'register'"
|
|
418
|
+
tile
|
|
419
|
+
block
|
|
420
|
+
variant="text"
|
|
421
|
+
class="text-none"
|
|
422
|
+
size="48"
|
|
423
|
+
@click="backToSelection"
|
|
424
|
+
text="Back to Selection"
|
|
425
|
+
/>
|
|
426
|
+
<v-btn
|
|
427
|
+
v-else
|
|
428
|
+
tile
|
|
429
|
+
block
|
|
430
|
+
variant="text"
|
|
431
|
+
class="text-none"
|
|
432
|
+
size="48"
|
|
433
|
+
@click="emit('close:all')"
|
|
434
|
+
text="Close"
|
|
435
|
+
/>
|
|
227
436
|
</v-col>
|
|
228
437
|
<v-col cols="6">
|
|
229
|
-
<v-btn
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
438
|
+
<v-btn
|
|
439
|
+
v-if="showNextButton"
|
|
440
|
+
tile
|
|
441
|
+
block
|
|
442
|
+
variant="flat"
|
|
443
|
+
color="primary-button"
|
|
444
|
+
class="text-none"
|
|
445
|
+
size="48"
|
|
446
|
+
:disabled="processing"
|
|
447
|
+
@click="handleNextPage"
|
|
448
|
+
:text="'Next'"
|
|
449
|
+
/>
|
|
450
|
+
<v-btn
|
|
451
|
+
v-else
|
|
452
|
+
tile
|
|
453
|
+
block
|
|
454
|
+
variant="flat"
|
|
455
|
+
color="black"
|
|
456
|
+
class="text-none"
|
|
457
|
+
size="48"
|
|
458
|
+
:disabled="!validForm || processing || membersFieldIncomplete"
|
|
459
|
+
@click="submit"
|
|
460
|
+
:text="prop.mode == 'add' ? 'Submit' : 'Update'"
|
|
461
|
+
/>
|
|
234
462
|
</v-col>
|
|
235
463
|
</v-row>
|
|
236
464
|
</v-toolbar>
|
|
237
465
|
|
|
238
|
-
<v-dialog
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
466
|
+
<v-dialog
|
|
467
|
+
v-model="dialog.vehicleNumberUsersList"
|
|
468
|
+
v-if="vehicleNumberUserItems.length > 0"
|
|
469
|
+
persistent
|
|
470
|
+
max-width="600"
|
|
471
|
+
>
|
|
472
|
+
<SearchVehicleNumberUser
|
|
473
|
+
:vehicle-number="visitor.plateNumber ?? ''"
|
|
474
|
+
:vehicle-number-users-list="vehicleNumberUserItems"
|
|
475
|
+
@close="dialog.vehicleNumberUsersList = false"
|
|
476
|
+
@update:people="handleAutofillDataViaVehicleNumber"
|
|
477
|
+
/>
|
|
243
478
|
</v-dialog>
|
|
244
479
|
|
|
245
|
-
<v-dialog
|
|
480
|
+
<v-dialog
|
|
481
|
+
v-model="dialog.showNonCheckedOutDialog"
|
|
482
|
+
max-width="700"
|
|
483
|
+
persistent
|
|
484
|
+
>
|
|
246
485
|
<v-card :loading="loading.checkingOut">
|
|
247
486
|
<v-toolbar>
|
|
248
487
|
<v-toolbar-title>
|
|
@@ -255,10 +494,13 @@
|
|
|
255
494
|
</v-toolbar>
|
|
256
495
|
|
|
257
496
|
<v-card-text>
|
|
258
|
-
|
|
259
497
|
<v-list lines="three">
|
|
260
|
-
<v-list-item
|
|
261
|
-
v-
|
|
498
|
+
<v-list-item
|
|
499
|
+
v-if="matchingPlateNumberNonCheckedOutArr.length > 0"
|
|
500
|
+
v-for="v in matchingPlateNumberNonCheckedOutArr"
|
|
501
|
+
:key="v._id"
|
|
502
|
+
class="cursor-pointer"
|
|
503
|
+
>
|
|
262
504
|
<v-list-item-title>
|
|
263
505
|
{{ v.plateNumber }} - {{ v.name }}
|
|
264
506
|
</v-list-item-title>
|
|
@@ -268,14 +510,19 @@
|
|
|
268
510
|
</v-list-item-subtitle>
|
|
269
511
|
|
|
270
512
|
<template #append>
|
|
271
|
-
<v-btn
|
|
272
|
-
|
|
273
|
-
|
|
513
|
+
<v-btn
|
|
514
|
+
size="x-small"
|
|
515
|
+
class="text-capitalize"
|
|
516
|
+
color="red"
|
|
517
|
+
text="Checkout"
|
|
518
|
+
:loading="
|
|
519
|
+
loading.checkingOut && v?._id === prop.visitorData?._id
|
|
520
|
+
"
|
|
521
|
+
@click.stop="handleCheckout(v._id)"
|
|
522
|
+
/>
|
|
274
523
|
</template>
|
|
275
|
-
|
|
276
524
|
</v-list-item>
|
|
277
525
|
</v-list>
|
|
278
|
-
|
|
279
526
|
</v-card-text>
|
|
280
527
|
</v-card>
|
|
281
528
|
</v-dialog>
|
|
@@ -283,9 +530,9 @@
|
|
|
283
530
|
</template>
|
|
284
531
|
|
|
285
532
|
<script setup lang="ts">
|
|
286
|
-
import
|
|
287
|
-
import
|
|
288
|
-
|
|
533
|
+
import { propsFactory } from "vuetify/lib/util/propsFactory.mjs";
|
|
534
|
+
import useBuildingUnit from "../composables/useBuildingUnit";
|
|
535
|
+
import useWebUsb from "../composables/useWebUsb";
|
|
289
536
|
|
|
290
537
|
const prop = defineProps({
|
|
291
538
|
type: {
|
|
@@ -308,21 +555,37 @@ const prop = defineProps({
|
|
|
308
555
|
type: Object as PropType<Partial<TVisitor> | null>,
|
|
309
556
|
default: null,
|
|
310
557
|
},
|
|
558
|
+
currentUser: {
|
|
559
|
+
type: String,
|
|
560
|
+
default: "",
|
|
561
|
+
},
|
|
311
562
|
});
|
|
312
563
|
|
|
313
564
|
type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
|
|
314
565
|
const currentAutofillSource = ref<AutofillSource>(null);
|
|
315
566
|
|
|
316
|
-
|
|
317
567
|
const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
|
|
318
568
|
const { getSiteById } = useSiteSettings();
|
|
319
|
-
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
320
|
-
|
|
321
|
-
const {
|
|
569
|
+
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
570
|
+
useBuilding();
|
|
571
|
+
const {
|
|
572
|
+
createVisitor,
|
|
573
|
+
typeFieldMap,
|
|
574
|
+
contractorTypes,
|
|
575
|
+
getVisitors,
|
|
576
|
+
updateVisitor,
|
|
577
|
+
} = useVisitor();
|
|
578
|
+
const { getBySiteId: getEntryPassSettingsBySiteId } =
|
|
579
|
+
useSiteEntryPassSettings();
|
|
322
580
|
const { createVisitorPass, signQr } = useAccessManagement();
|
|
323
581
|
const { testConnection } = useWebUsb();
|
|
324
|
-
const {
|
|
325
|
-
|
|
582
|
+
const {
|
|
583
|
+
findPersonByNRIC,
|
|
584
|
+
findPersonByContact,
|
|
585
|
+
searchCompanyList,
|
|
586
|
+
findUsersByPlateNumber,
|
|
587
|
+
} = usePeople();
|
|
588
|
+
const { getById: getUnitDataById } = useBuildingUnit();
|
|
326
589
|
|
|
327
590
|
const emit = defineEmits([
|
|
328
591
|
"back",
|
|
@@ -331,7 +594,7 @@ const emit = defineEmits([
|
|
|
331
594
|
"done:more",
|
|
332
595
|
"error",
|
|
333
596
|
"close",
|
|
334
|
-
"close:all"
|
|
597
|
+
"close:all",
|
|
335
598
|
]);
|
|
336
599
|
|
|
337
600
|
const visitor = reactive<Partial<TVisitorPayload>>({
|
|
@@ -350,14 +613,14 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
350
613
|
attachments: [],
|
|
351
614
|
members: [],
|
|
352
615
|
visitorPass: [],
|
|
353
|
-
passKeys: []
|
|
616
|
+
passKeys: [],
|
|
354
617
|
});
|
|
355
618
|
|
|
356
619
|
const passType = ref<string | null>(null);
|
|
357
620
|
const passQuantity = ref<number | null>(1);
|
|
358
621
|
const passCards = ref<{ _id: string; cardNo: string }[]>([]);
|
|
359
622
|
|
|
360
|
-
const registeredUnitCompanyName = ref(
|
|
623
|
+
const registeredUnitCompanyName = ref("N/A");
|
|
361
624
|
|
|
362
625
|
const dialog = reactive({
|
|
363
626
|
vehicleNumberUsersList: false,
|
|
@@ -366,7 +629,7 @@ const dialog = reactive({
|
|
|
366
629
|
|
|
367
630
|
const loading = reactive({
|
|
368
631
|
checkingOut: false,
|
|
369
|
-
})
|
|
632
|
+
});
|
|
370
633
|
|
|
371
634
|
const validForm = ref(false);
|
|
372
635
|
const formRef = ref<HTMLFormElement | null>(null);
|
|
@@ -382,7 +645,6 @@ const contractorStep = ref(1);
|
|
|
382
645
|
const companyNameInput = ref("");
|
|
383
646
|
const companyNameObj = ref<string | null>(null);
|
|
384
647
|
|
|
385
|
-
|
|
386
648
|
const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
387
649
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
388
650
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
@@ -391,13 +653,13 @@ const deliveryCompany = ref("");
|
|
|
391
653
|
const deliveryCompanyInput = ref("");
|
|
392
654
|
const deliveryCompanyList = ref<string[]>([]);
|
|
393
655
|
|
|
394
|
-
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
656
|
+
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([]);
|
|
398
657
|
|
|
658
|
+
const vehicleNumberUserItems = ref<TPeople[]>([]);
|
|
399
659
|
|
|
400
|
-
const shouldShowField = (
|
|
660
|
+
const shouldShowField = (
|
|
661
|
+
fieldKey: keyof TVisitorPayload | "delivery-company"
|
|
662
|
+
) => {
|
|
401
663
|
if ((!withStep2.value && !withStep3.value) || contractorStep.value === 1) {
|
|
402
664
|
const visibleFields = typeFieldMap[prop.type];
|
|
403
665
|
return visibleFields?.includes(fieldKey);
|
|
@@ -407,31 +669,39 @@ const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') =
|
|
|
407
669
|
const withStep2 = computed(() => {
|
|
408
670
|
const step2Types = ["guest", "walk-in"];
|
|
409
671
|
return step2Types.includes(prop.type);
|
|
410
|
-
})
|
|
672
|
+
});
|
|
411
673
|
|
|
412
674
|
const withStep3 = computed(() => {
|
|
413
675
|
const step3Types = ["contractor"];
|
|
414
676
|
return step3Types.includes(prop.type);
|
|
415
|
-
})
|
|
677
|
+
});
|
|
416
678
|
|
|
417
679
|
const showNextButton = computed(() => {
|
|
418
680
|
if (withStep3.value) {
|
|
419
|
-
return contractorStep.value === 1 || contractorStep.value === 2
|
|
681
|
+
return contractorStep.value === 1 || contractorStep.value === 2;
|
|
420
682
|
} else if (withStep2.value) {
|
|
421
|
-
return contractorStep.value === 1
|
|
422
|
-
} else return false
|
|
423
|
-
})
|
|
683
|
+
return contractorStep.value === 1;
|
|
684
|
+
} else return false;
|
|
685
|
+
});
|
|
424
686
|
|
|
425
687
|
const showShowKeysField = computed(() => {
|
|
426
|
-
return prop.type === "contractor"
|
|
427
|
-
})
|
|
688
|
+
return prop.type === "contractor";
|
|
689
|
+
});
|
|
428
690
|
|
|
429
691
|
const requireNRIC = computed(() => {
|
|
430
|
-
return prop.type !==
|
|
431
|
-
})
|
|
692
|
+
return prop.type !== "walk-in" && prop.type !== "guest";
|
|
693
|
+
});
|
|
432
694
|
|
|
433
|
-
const companyNames = ref<string[]>([])
|
|
434
|
-
const companyAutofillDataArray = ref<
|
|
695
|
+
const companyNames = ref<string[]>([]);
|
|
696
|
+
const companyAutofillDataArray = ref<
|
|
697
|
+
{
|
|
698
|
+
companyName: string[];
|
|
699
|
+
unit: string;
|
|
700
|
+
block: number;
|
|
701
|
+
level: string;
|
|
702
|
+
unitName: string;
|
|
703
|
+
}[]
|
|
704
|
+
>([]);
|
|
435
705
|
|
|
436
706
|
const formTitle = computed(() => {
|
|
437
707
|
const isContractorForm = prop.type === "contractor";
|
|
@@ -461,21 +731,21 @@ function handleFocusedContractorType() {
|
|
|
461
731
|
// }
|
|
462
732
|
}
|
|
463
733
|
|
|
464
|
-
const contractorTypeCombo = ref(null)
|
|
734
|
+
const contractorTypeCombo = ref(null);
|
|
465
735
|
|
|
466
736
|
async function handleAddNewContractorType() {
|
|
467
|
-
visitor.contractorType = contractorTypeInput.value
|
|
737
|
+
visitor.contractorType = contractorTypeInput.value;
|
|
468
738
|
|
|
469
|
-
const combo = contractorTypeCombo.value as any
|
|
739
|
+
const combo = contractorTypeCombo.value as any;
|
|
470
740
|
await nextTick();
|
|
471
741
|
|
|
472
742
|
combo.isMenuActive = false;
|
|
473
|
-
combo.$el.querySelector("input")?.blur();
|
|
474
|
-
|
|
743
|
+
combo.$el.querySelector("input")?.blur();
|
|
744
|
+
``;
|
|
475
745
|
}
|
|
476
746
|
|
|
477
747
|
async function handleSelectCompanyName(company: string) {
|
|
478
|
-
visitor.company = company
|
|
748
|
+
visitor.company = company;
|
|
479
749
|
// const selected = companyAutofillDataArray.value.find(x => x.companyName?.includes(company))
|
|
480
750
|
// if (!selected) return
|
|
481
751
|
|
|
@@ -485,48 +755,46 @@ async function handleSelectCompanyName(company: string) {
|
|
|
485
755
|
// visitor.unitName = selected.unitName || ""
|
|
486
756
|
}
|
|
487
757
|
|
|
488
|
-
const companyCombo = ref(null)
|
|
758
|
+
const companyCombo = ref(null);
|
|
489
759
|
|
|
490
760
|
async function handleAddNewCompany() {
|
|
491
|
-
visitor.company = companyNameInput.value
|
|
761
|
+
visitor.company = companyNameInput.value;
|
|
492
762
|
|
|
493
|
-
const combo = companyCombo.value as any
|
|
763
|
+
const combo = companyCombo.value as any;
|
|
494
764
|
await nextTick();
|
|
495
765
|
|
|
496
766
|
combo.isMenuActive = false;
|
|
497
767
|
combo.$el.querySelector("input")?.blur();
|
|
498
768
|
}
|
|
499
769
|
|
|
500
|
-
|
|
501
770
|
const {
|
|
502
771
|
data: fetchPersonByNRICReq,
|
|
503
772
|
refresh: fetchPersonByNRICRefresh,
|
|
504
773
|
pending: fetchPersonByNRICPending,
|
|
505
774
|
} = useLazyAsyncData(`fetch-person`, () => {
|
|
506
775
|
const NRIC = visitor.nric;
|
|
507
|
-
if (!NRIC) return Promise.resolve(null)
|
|
508
|
-
return findPersonByNRIC(NRIC)
|
|
509
|
-
}
|
|
510
|
-
);
|
|
776
|
+
if (!NRIC) return Promise.resolve(null);
|
|
777
|
+
return findPersonByNRIC(NRIC);
|
|
778
|
+
});
|
|
511
779
|
|
|
512
780
|
watch(fetchPersonByNRICReq, (obj) => {
|
|
513
781
|
if (obj) {
|
|
514
782
|
currentAutofillSource.value = "nric";
|
|
515
|
-
companyNames.value = obj.companyName ?? []
|
|
516
|
-
visitor.name = obj.name
|
|
517
|
-
visitor.contact = obj.contact
|
|
783
|
+
companyNames.value = obj.companyName ?? [];
|
|
784
|
+
visitor.name = obj.name;
|
|
785
|
+
visitor.contact = obj.contact;
|
|
518
786
|
if (!visitor.company) {
|
|
519
|
-
visitor.company = companyNames.value?.[0]
|
|
787
|
+
visitor.company = companyNames.value?.[0];
|
|
520
788
|
}
|
|
521
|
-
visitor.block = obj.block ?? ""
|
|
522
|
-
visitor.level = obj.level ?? ""
|
|
523
|
-
visitor.unit = obj.unit ?? ""
|
|
789
|
+
visitor.block = obj.block ?? "";
|
|
790
|
+
visitor.level = obj.level ?? "";
|
|
791
|
+
visitor.unit = obj.unit ?? "";
|
|
524
792
|
|
|
525
793
|
setTimeout(() => {
|
|
526
794
|
currentAutofillSource.value = null;
|
|
527
795
|
}, 1000);
|
|
528
796
|
}
|
|
529
|
-
})
|
|
797
|
+
});
|
|
530
798
|
|
|
531
799
|
const {
|
|
532
800
|
data: fetchPersonByContactReq,
|
|
@@ -534,82 +802,86 @@ const {
|
|
|
534
802
|
pending: fetchPersonByContactPending,
|
|
535
803
|
} = useLazyAsyncData(`fetch-contact`, () => {
|
|
536
804
|
const contact = visitor.contact;
|
|
537
|
-
if (!contact) return Promise.resolve(null)
|
|
538
|
-
return findPersonByContact(contact)
|
|
539
|
-
}
|
|
540
|
-
);
|
|
805
|
+
if (!contact) return Promise.resolve(null);
|
|
806
|
+
return findPersonByContact(contact);
|
|
807
|
+
});
|
|
541
808
|
|
|
542
809
|
watch(fetchPersonByContactReq, (obj: Partial<TPerson>) => {
|
|
543
810
|
if (obj) {
|
|
544
811
|
currentAutofillSource.value = "contact";
|
|
545
|
-
companyNames.value = obj.companyName ?? []
|
|
546
|
-
visitor.name = visitor.name ?? obj.name
|
|
812
|
+
companyNames.value = obj.companyName ?? [];
|
|
813
|
+
visitor.name = visitor.name ?? obj.name;
|
|
547
814
|
if (!visitor.company) {
|
|
548
|
-
visitor.company = companyNames.value?.[0]
|
|
815
|
+
visitor.company = companyNames.value?.[0];
|
|
549
816
|
}
|
|
550
817
|
// visitor.block = visitor.block ?? obj.block ?? ""
|
|
551
818
|
// visitor.level = visitor.level ?? obj.level ?? ""
|
|
552
819
|
// visitor.unit = visitor.unit ?? obj.unit ?? ""
|
|
553
|
-
visitor.nric = visitor.nric ?? obj.nric ?? ""
|
|
820
|
+
visitor.nric = visitor.nric ?? obj.nric ?? "";
|
|
554
821
|
|
|
555
822
|
setTimeout(() => {
|
|
556
823
|
currentAutofillSource.value = null;
|
|
557
824
|
}, 1000);
|
|
558
825
|
}
|
|
559
|
-
})
|
|
826
|
+
});
|
|
560
827
|
|
|
561
828
|
const {
|
|
562
829
|
data: fetchCompanyListReq,
|
|
563
830
|
refresh: fetchCompanyListRefresh,
|
|
564
831
|
pending: fetchCompanyListPending,
|
|
565
|
-
|
|
566
832
|
} = useLazyAsyncData(`fetch-company-list`, () => {
|
|
567
|
-
if (!companyNameInput.value) return Promise.resolve(null)
|
|
568
|
-
return searchCompanyList(companyNameInput.value)
|
|
569
|
-
})
|
|
833
|
+
if (!companyNameInput.value) return Promise.resolve(null);
|
|
834
|
+
return searchCompanyList(companyNameInput.value);
|
|
835
|
+
});
|
|
570
836
|
|
|
571
837
|
watch(fetchCompanyListReq, (arr) => {
|
|
572
838
|
if (Array.isArray(arr)) {
|
|
573
839
|
companyAutofillDataArray.value = arr;
|
|
574
|
-
companyNames.value = arr?.flatMap(x => x?.companyName)
|
|
840
|
+
companyNames.value = arr?.flatMap((x) => x?.companyName);
|
|
575
841
|
}
|
|
576
|
-
})
|
|
842
|
+
});
|
|
577
843
|
const {
|
|
578
844
|
data: fetchVisitorListByVehicleNumberReq,
|
|
579
845
|
refresh: fetchVisitorListByVehicleNumberRefresh,
|
|
580
846
|
pending: fetchVisitorListByVehicleNumberPending,
|
|
581
|
-
|
|
582
847
|
} = useLazyAsyncData(`fetch-visitor-list-by-vehicle-number`, () => {
|
|
583
|
-
if (!visitor.plateNumber) return Promise.resolve(null)
|
|
584
|
-
return getVisitors({
|
|
585
|
-
|
|
848
|
+
if (!visitor.plateNumber) return Promise.resolve(null);
|
|
849
|
+
return getVisitors({
|
|
850
|
+
page: 1,
|
|
851
|
+
limit: 20,
|
|
852
|
+
site: prop.site,
|
|
853
|
+
plateNumber: visitor.plateNumber,
|
|
854
|
+
checkedOut: false,
|
|
855
|
+
status: "registered",
|
|
856
|
+
});
|
|
857
|
+
});
|
|
586
858
|
|
|
587
859
|
watch(fetchVisitorListByVehicleNumberReq, (arr: any) => {
|
|
588
|
-
const itemsArray = arr?.items || []
|
|
589
|
-
const isValidArray = Array.isArray(itemsArray)
|
|
590
|
-
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : []
|
|
860
|
+
const itemsArray = arr?.items || [];
|
|
861
|
+
const isValidArray = Array.isArray(itemsArray);
|
|
862
|
+
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : [];
|
|
591
863
|
if (matchingPlateNumberNonCheckedOutArr.value.length > 0) {
|
|
592
864
|
dialog.showNonCheckedOutDialog = true;
|
|
593
865
|
} else {
|
|
594
866
|
dialog.showNonCheckedOutDialog = false;
|
|
595
867
|
}
|
|
596
|
-
})
|
|
597
|
-
|
|
868
|
+
});
|
|
598
869
|
|
|
599
870
|
const debounceSearchVisitorsByPlateNumbers = debounce(() => {
|
|
600
871
|
if (!visitor.plateNumber) {
|
|
601
|
-
matchingPlateNumberNonCheckedOutArr.value = []
|
|
872
|
+
matchingPlateNumberNonCheckedOutArr.value = [];
|
|
602
873
|
dialog.showNonCheckedOutDialog = false;
|
|
603
874
|
return;
|
|
604
875
|
}
|
|
605
876
|
fetchVisitorListByVehicleNumberRefresh();
|
|
606
877
|
}, 300);
|
|
607
878
|
|
|
608
|
-
watch(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
879
|
+
watch(
|
|
880
|
+
() => visitor.plateNumber,
|
|
881
|
+
(newVal) => {
|
|
882
|
+
debounceSearchVisitorsByPlateNumbers();
|
|
883
|
+
}
|
|
884
|
+
);
|
|
613
885
|
|
|
614
886
|
async function handleCheckout(visitorId: string) {
|
|
615
887
|
if (!visitorId) {
|
|
@@ -624,7 +896,6 @@ async function handleCheckout(visitorId: string) {
|
|
|
624
896
|
});
|
|
625
897
|
if (res) {
|
|
626
898
|
await fetchVisitorListByVehicleNumberRefresh();
|
|
627
|
-
|
|
628
899
|
}
|
|
629
900
|
} catch (error: any) {
|
|
630
901
|
const errorMessage = error?.response?._data?.message;
|
|
@@ -636,50 +907,42 @@ async function handleCheckout(visitorId: string) {
|
|
|
636
907
|
}
|
|
637
908
|
}
|
|
638
909
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
910
|
+
const debounceFetchCompany = debounce(
|
|
911
|
+
async () => fetchCompanyListRefresh(),
|
|
912
|
+
200
|
|
913
|
+
);
|
|
643
914
|
|
|
644
915
|
watch(companyNameInput, async (val) => {
|
|
645
916
|
if (!val) {
|
|
646
|
-
companyNames.value = []
|
|
647
|
-
return
|
|
917
|
+
companyNames.value = [];
|
|
918
|
+
return;
|
|
648
919
|
}
|
|
649
|
-
await debounceFetchCompany()
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
920
|
+
await debounceFetchCompany();
|
|
921
|
+
});
|
|
655
922
|
|
|
656
923
|
function handleAutofillDataViaVehicleNumber(item: TPeople) {
|
|
657
|
-
|
|
658
924
|
currentAutofillSource.value = "vehicleNumber";
|
|
659
925
|
|
|
660
|
-
visitor.name = item.name
|
|
661
|
-
visitor.nric = item.nric
|
|
662
|
-
visitor.contact = item.contact
|
|
663
|
-
companyNames.value = item.companyName ?? []
|
|
926
|
+
visitor.name = item.name;
|
|
927
|
+
visitor.nric = item.nric;
|
|
928
|
+
visitor.contact = item.contact;
|
|
929
|
+
companyNames.value = item.companyName ?? [];
|
|
664
930
|
if (!visitor.company) {
|
|
665
|
-
visitor.company = companyNames.value?.[0]
|
|
931
|
+
visitor.company = companyNames.value?.[0];
|
|
666
932
|
}
|
|
667
|
-
visitor.block = item.block ?? ""
|
|
668
|
-
visitor.level = item.level ?? ""
|
|
669
|
-
visitor.unit = item.unit ?? ""
|
|
933
|
+
visitor.block = item.block ?? "";
|
|
934
|
+
visitor.level = item.level ?? "";
|
|
935
|
+
visitor.unit = item.unit ?? "";
|
|
670
936
|
|
|
671
937
|
setTimeout(() => {
|
|
672
938
|
currentAutofillSource.value = null;
|
|
673
939
|
}, 1000);
|
|
674
940
|
}
|
|
675
941
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
} = useLazyAsyncData(`fetch-entrypass-settings-${prop.site}`, () =>
|
|
681
|
-
getEntryPassSettingsBySiteId(prop.site)
|
|
682
|
-
);
|
|
942
|
+
const { data: entryPassSettings, pending: entryPassSettingsPending } =
|
|
943
|
+
useLazyAsyncData(`fetch-entrypass-settings-${prop.site}`, () =>
|
|
944
|
+
getEntryPassSettingsBySiteId(prop.site)
|
|
945
|
+
);
|
|
683
946
|
|
|
684
947
|
const {
|
|
685
948
|
data: siteData,
|
|
@@ -690,18 +953,17 @@ const {
|
|
|
690
953
|
getSiteById(prop.site)
|
|
691
954
|
);
|
|
692
955
|
|
|
693
|
-
|
|
694
956
|
const {
|
|
695
957
|
data: blocksListData,
|
|
696
958
|
refresh: refreshBlockListData,
|
|
697
959
|
status: blocksListStatus,
|
|
698
960
|
pending: blockListDataPending,
|
|
699
|
-
} = useLazyAsyncData(
|
|
700
|
-
|
|
961
|
+
} = useLazyAsyncData(
|
|
962
|
+
`visitor-fetch-block-list-data-${prop.site}`,
|
|
963
|
+
() => getBlocksSelection({ siteId: prop.site }),
|
|
964
|
+
{ watch: [() => prop.site], deep: true }
|
|
701
965
|
);
|
|
702
966
|
|
|
703
|
-
|
|
704
|
-
|
|
705
967
|
const {
|
|
706
968
|
data: levelsData,
|
|
707
969
|
refresh: refreshLevelsData,
|
|
@@ -712,8 +974,12 @@ const {
|
|
|
712
974
|
async () => {
|
|
713
975
|
if (!visitor.block) return Promise.resolve(null);
|
|
714
976
|
// return await getSiteWithLevels(prop.site, { block: Number(visitor.block) });
|
|
715
|
-
return await getLevelsSelection({
|
|
716
|
-
|
|
977
|
+
return await getLevelsSelection({
|
|
978
|
+
siteId: prop.site,
|
|
979
|
+
blockId: visitor.block,
|
|
980
|
+
});
|
|
981
|
+
},
|
|
982
|
+
{ watch: [() => visitor.block], deep: true }
|
|
717
983
|
);
|
|
718
984
|
|
|
719
985
|
const {
|
|
@@ -725,20 +991,26 @@ const {
|
|
|
725
991
|
`visitor-fetch-units-data-${prop.site}-${visitor.level}`,
|
|
726
992
|
async () => {
|
|
727
993
|
if (!visitor.level) return Promise.resolve(null);
|
|
728
|
-
return await getUnitSelection({
|
|
729
|
-
|
|
994
|
+
return await getUnitSelection({
|
|
995
|
+
siteId: prop.site,
|
|
996
|
+
block: visitor.block,
|
|
997
|
+
level: visitor.level,
|
|
998
|
+
});
|
|
999
|
+
},
|
|
1000
|
+
{ watch: [() => visitor.level], deep: true }
|
|
730
1001
|
);
|
|
731
1002
|
|
|
732
|
-
|
|
733
1003
|
watch(
|
|
734
1004
|
siteData,
|
|
735
1005
|
(newVal) => {
|
|
736
1006
|
const siteDataValue = newVal as TSite;
|
|
737
1007
|
if (siteDataValue) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1008
|
+
deliveryCompanyList.value = Array.isArray(
|
|
1009
|
+
siteDataValue?.deliveryCompanyList
|
|
1010
|
+
)
|
|
1011
|
+
? siteDataValue.deliveryCompanyList
|
|
1012
|
+
: [];
|
|
1013
|
+
}
|
|
742
1014
|
},
|
|
743
1015
|
{ immediate: true }
|
|
744
1016
|
);
|
|
@@ -746,83 +1018,96 @@ watch(
|
|
|
746
1018
|
watch(
|
|
747
1019
|
[blocksListData],
|
|
748
1020
|
() => {
|
|
749
|
-
if (blocksListStatus.value ===
|
|
1021
|
+
if (blocksListStatus.value === "pending") {
|
|
750
1022
|
blocksArray.value = [];
|
|
751
1023
|
return;
|
|
752
1024
|
}
|
|
753
1025
|
|
|
754
|
-
const arr = Array.isArray(blocksListData.value?.data)
|
|
1026
|
+
const arr = Array.isArray(blocksListData.value?.data)
|
|
1027
|
+
? blocksListData.value?.data
|
|
1028
|
+
: [];
|
|
755
1029
|
blocksArray.value = arr.map((block: any) => ({
|
|
756
1030
|
title: block.name,
|
|
757
1031
|
value: block._id,
|
|
758
1032
|
}));
|
|
759
1033
|
},
|
|
760
1034
|
{ immediate: true, deep: true }
|
|
761
|
-
)
|
|
762
|
-
|
|
1035
|
+
);
|
|
763
1036
|
|
|
764
|
-
watch(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1037
|
+
watch(
|
|
1038
|
+
[levelsData],
|
|
1039
|
+
() => {
|
|
1040
|
+
if (levelsStatus.value === "pending") {
|
|
1041
|
+
levelsArray.value = [];
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
769
1044
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
1045
|
+
const arr = Array.isArray(levelsData.value?.data)
|
|
1046
|
+
? levelsData.value?.data
|
|
1047
|
+
: [];
|
|
1048
|
+
const mappedLevels = arr.map((level: any) => ({
|
|
1049
|
+
title: level.name,
|
|
1050
|
+
value: level._id,
|
|
1051
|
+
}));
|
|
1052
|
+
levelsArray.value = mappedLevels;
|
|
1053
|
+
},
|
|
1054
|
+
{ immediate: true, deep: true }
|
|
1055
|
+
);
|
|
777
1056
|
|
|
778
1057
|
watch(
|
|
779
1058
|
unitsData,
|
|
780
1059
|
(newVal: any) => {
|
|
781
1060
|
if (!newVal || !Array.isArray(newVal)) {
|
|
782
|
-
unitsArray.value = []
|
|
783
|
-
return
|
|
1061
|
+
unitsArray.value = [];
|
|
1062
|
+
return;
|
|
784
1063
|
}
|
|
785
1064
|
|
|
786
1065
|
const mapped = newVal.map((unit: any) => ({
|
|
787
1066
|
title: unit.name,
|
|
788
1067
|
value: unit._id,
|
|
789
|
-
}))
|
|
1068
|
+
}));
|
|
790
1069
|
|
|
791
1070
|
// keep custom unit pushed from autofill
|
|
792
|
-
const selected = visitor.unit
|
|
793
|
-
const exists = mapped.some((u: TDefaultOptionObj) => u.value === selected)
|
|
1071
|
+
const selected = visitor.unit;
|
|
1072
|
+
const exists = mapped.some((u: TDefaultOptionObj) => u.value === selected);
|
|
794
1073
|
|
|
795
1074
|
if (!exists && visitor.unitName) {
|
|
796
1075
|
mapped.push({
|
|
797
1076
|
title: visitor.unitName,
|
|
798
1077
|
value: selected,
|
|
799
|
-
})
|
|
1078
|
+
});
|
|
800
1079
|
}
|
|
801
1080
|
|
|
802
|
-
unitsArray.value = mapped
|
|
1081
|
+
unitsArray.value = mapped;
|
|
803
1082
|
},
|
|
804
1083
|
{ immediate: true, deep: true }
|
|
805
|
-
)
|
|
806
|
-
|
|
1084
|
+
);
|
|
807
1085
|
|
|
808
|
-
const {
|
|
1086
|
+
const {
|
|
1087
|
+
data: buildingUnitData,
|
|
1088
|
+
refresh: refreshBuildingUnitData,
|
|
1089
|
+
pending: buildingUnitDataPending,
|
|
1090
|
+
} = useLazyAsyncData(`building-unit-data-${visitor.unit}`, () => {
|
|
809
1091
|
if (!visitor.unit) return Promise.resolve(null);
|
|
810
1092
|
return getUnitDataById(visitor.unit);
|
|
811
|
-
})
|
|
1093
|
+
});
|
|
812
1094
|
|
|
813
1095
|
// trigger refresh of building unit data when unit changes, to get the latest unit name in case there are changes in the backend
|
|
814
|
-
watch(
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1096
|
+
watch(
|
|
1097
|
+
() => visitor.unit,
|
|
1098
|
+
() => {
|
|
1099
|
+
if (visitor.unit) {
|
|
1100
|
+
refreshBuildingUnitData();
|
|
1101
|
+
} else {
|
|
1102
|
+
registeredUnitCompanyName.value = "N/A";
|
|
1103
|
+
}
|
|
819
1104
|
}
|
|
820
|
-
|
|
1105
|
+
);
|
|
821
1106
|
|
|
822
1107
|
watch(buildingUnitData, (newVal: any) => {
|
|
823
|
-
registeredUnitCompanyName.value =
|
|
824
|
-
|
|
825
|
-
|
|
1108
|
+
registeredUnitCompanyName.value =
|
|
1109
|
+
newVal?.data?.buildingUnit?.companyName || "N/A";
|
|
1110
|
+
});
|
|
826
1111
|
|
|
827
1112
|
function handleChangeBlock(value: any) {
|
|
828
1113
|
visitor.level = "";
|
|
@@ -839,17 +1124,19 @@ function handleUpdateUnit(value: any) {
|
|
|
839
1124
|
visitor.unitName = selectedUnit?.title || "";
|
|
840
1125
|
}
|
|
841
1126
|
|
|
842
|
-
const debounceFetchNRIC = debounce(fetchPersonByNRICRefresh, 500)
|
|
843
|
-
const debounceFetchContact = debounce(fetchPersonByContactRefresh, 500)
|
|
1127
|
+
const debounceFetchNRIC = debounce(fetchPersonByNRICRefresh, 500);
|
|
1128
|
+
const debounceFetchContact = debounce(fetchPersonByContactRefresh, 500);
|
|
844
1129
|
|
|
845
1130
|
function handleUpdateNRIC() {
|
|
846
|
-
if (currentAutofillSource.value && currentAutofillSource.value !== "nric")
|
|
847
|
-
|
|
1131
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "nric")
|
|
1132
|
+
return;
|
|
1133
|
+
debounceFetchNRIC();
|
|
848
1134
|
}
|
|
849
1135
|
|
|
850
1136
|
function handleUpdateContact() {
|
|
851
|
-
if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
|
|
852
|
-
|
|
1137
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
|
|
1138
|
+
return;
|
|
1139
|
+
debounceFetchContact();
|
|
853
1140
|
}
|
|
854
1141
|
|
|
855
1142
|
function backToSelection() {
|
|
@@ -908,6 +1195,7 @@ async function submit() {
|
|
|
908
1195
|
type: prop.type,
|
|
909
1196
|
org: prop.org,
|
|
910
1197
|
site: prop.site,
|
|
1198
|
+
createdBy: prop.currentUser,
|
|
911
1199
|
};
|
|
912
1200
|
|
|
913
1201
|
if (prop.mode === "add" || prop.mode === "register") {
|
|
@@ -928,11 +1216,8 @@ async function submit() {
|
|
|
928
1216
|
payload.visitorPass = visitor.visitorPass;
|
|
929
1217
|
payload.passKeys = visitor.passKeys;
|
|
930
1218
|
}
|
|
931
|
-
|
|
932
1219
|
}
|
|
933
1220
|
|
|
934
|
-
|
|
935
|
-
|
|
936
1221
|
if (prop.type === "contractor") {
|
|
937
1222
|
// contractor type logic payload
|
|
938
1223
|
payload = {
|
|
@@ -944,21 +1229,24 @@ async function submit() {
|
|
|
944
1229
|
if (prop.type === "delivery") {
|
|
945
1230
|
payload = {
|
|
946
1231
|
...payload,
|
|
947
|
-
company: deliveryCompany.value
|
|
948
|
-
}
|
|
1232
|
+
company: deliveryCompany.value,
|
|
1233
|
+
};
|
|
949
1234
|
}
|
|
950
1235
|
|
|
951
1236
|
// add register status when registering unregistered visitor
|
|
952
|
-
if (prop.mode ===
|
|
953
|
-
payload.status = "registered"
|
|
1237
|
+
if (prop.mode === "register") {
|
|
1238
|
+
payload.status = "registered";
|
|
954
1239
|
}
|
|
955
1240
|
|
|
956
1241
|
try {
|
|
957
1242
|
let res;
|
|
958
1243
|
if (prop.mode === "add") {
|
|
959
1244
|
res = await createVisitor(payload as TVisitorPayload);
|
|
960
|
-
} else if (
|
|
961
|
-
|
|
1245
|
+
} else if (
|
|
1246
|
+
(prop.mode === "edit" || prop.mode === "register") &&
|
|
1247
|
+
prop.visitorData?._id
|
|
1248
|
+
) {
|
|
1249
|
+
const { unitName, ...rest } = payload;
|
|
962
1250
|
res = await updateVisitor(prop.visitorData._id, rest as TVisitorPayload);
|
|
963
1251
|
}
|
|
964
1252
|
|
|
@@ -967,7 +1255,9 @@ async function submit() {
|
|
|
967
1255
|
const visitorId = res as unknown as string;
|
|
968
1256
|
const rawUrl = entryPassSettings.value?.data?.settings?.url;
|
|
969
1257
|
const { encrypted: acmUrl } = await $fetch<{ encrypted: string }>(
|
|
970
|
-
`/api/encrypt-acm-url${
|
|
1258
|
+
`/api/encrypt-acm-url${
|
|
1259
|
+
rawUrl ? `?url=${encodeURIComponent(rawUrl)}` : ""
|
|
1260
|
+
}`
|
|
971
1261
|
);
|
|
972
1262
|
if (visitorId && acmUrl) {
|
|
973
1263
|
const passRes = await createVisitorPass({
|
|
@@ -982,7 +1272,8 @@ async function submit() {
|
|
|
982
1272
|
|
|
983
1273
|
if (passType.value === "QR") {
|
|
984
1274
|
console.log("[QR Print] passRes:", passRes);
|
|
985
|
-
const accessCards: any[] =
|
|
1275
|
+
const accessCards: any[] =
|
|
1276
|
+
(passRes as any)?.data?.accessCards ?? [];
|
|
986
1277
|
console.log("[QR Print] accessCards:", accessCards);
|
|
987
1278
|
const printer = entryPassSettings.value?.data?.settings?.printer;
|
|
988
1279
|
console.log("[QR Print] printer config:", printer);
|
|
@@ -994,23 +1285,57 @@ async function submit() {
|
|
|
994
1285
|
const vendorId = parseInt(printer.vendorId);
|
|
995
1286
|
const productId = parseInt(printer.productId);
|
|
996
1287
|
const companyName = visitor.company || "";
|
|
997
|
-
const blockLabel =
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1288
|
+
const blockLabel =
|
|
1289
|
+
blocksArray.value.find((b: any) => b.value === visitor.block)
|
|
1290
|
+
?.title || "";
|
|
1291
|
+
const levelLabel =
|
|
1292
|
+
levelsArray.value.find((l: any) => l.value === visitor.level)
|
|
1293
|
+
?.title || "";
|
|
1294
|
+
const unitLabel =
|
|
1295
|
+
unitsArray.value.find((u: any) => u.value === visitor.unit)
|
|
1296
|
+
?.title || "";
|
|
1297
|
+
const address = [blockLabel, levelLabel, unitLabel]
|
|
1298
|
+
.filter(Boolean)
|
|
1299
|
+
.join("/");
|
|
1300
|
+
console.log(
|
|
1301
|
+
"[QR Print] Printing",
|
|
1302
|
+
accessCards.length,
|
|
1303
|
+
"card(s). vendorId:",
|
|
1304
|
+
vendorId,
|
|
1305
|
+
"productId:",
|
|
1306
|
+
productId,
|
|
1307
|
+
"address:",
|
|
1308
|
+
address
|
|
1309
|
+
);
|
|
1002
1310
|
|
|
1003
1311
|
await nextTick();
|
|
1004
1312
|
for (const qrCode of accessCards) {
|
|
1005
1313
|
console.log("[QR Print] Signing QR for card:", qrCode._id);
|
|
1006
|
-
const signed = await signQr({
|
|
1314
|
+
const signed = await signQr({
|
|
1315
|
+
cardId: qrCode._id,
|
|
1316
|
+
purpose: "vms",
|
|
1317
|
+
});
|
|
1007
1318
|
const qrData = signed?.data;
|
|
1008
|
-
console.log(
|
|
1319
|
+
console.log(
|
|
1320
|
+
"[QR Print] signQr result:",
|
|
1321
|
+
signed,
|
|
1322
|
+
"| qrData:",
|
|
1323
|
+
qrData
|
|
1324
|
+
);
|
|
1009
1325
|
if (!qrData) {
|
|
1010
|
-
console.warn(
|
|
1326
|
+
console.warn(
|
|
1327
|
+
"[QR Print] Skipping card",
|
|
1328
|
+
qrCode._id,
|
|
1329
|
+
"— signQr returned no data."
|
|
1330
|
+
);
|
|
1011
1331
|
continue;
|
|
1012
1332
|
}
|
|
1013
|
-
console.log(
|
|
1333
|
+
console.log(
|
|
1334
|
+
"[QR Print] Sending to printer — accessLevel:",
|
|
1335
|
+
qrCode.accessLevel,
|
|
1336
|
+
"liftLevel:",
|
|
1337
|
+
qrCode.liftAccessLevel
|
|
1338
|
+
);
|
|
1014
1339
|
const printResult = await testConnection(
|
|
1015
1340
|
vendorId,
|
|
1016
1341
|
productId,
|
|
@@ -1019,9 +1344,14 @@ async function submit() {
|
|
|
1019
1344
|
qrCode.accessLevel,
|
|
1020
1345
|
qrCode.liftAccessLevel,
|
|
1021
1346
|
companyName,
|
|
1022
|
-
address
|
|
1347
|
+
address
|
|
1348
|
+
);
|
|
1349
|
+
console.log(
|
|
1350
|
+
"[QR Print] Print result for card",
|
|
1351
|
+
qrCode._id,
|
|
1352
|
+
":",
|
|
1353
|
+
printResult
|
|
1023
1354
|
);
|
|
1024
|
-
console.log("[QR Print] Print result for card", qrCode._id, ":", printResult);
|
|
1025
1355
|
}
|
|
1026
1356
|
}
|
|
1027
1357
|
}
|
|
@@ -1035,7 +1365,8 @@ async function submit() {
|
|
|
1035
1365
|
const err = error?.data?.message;
|
|
1036
1366
|
errorMessage.value =
|
|
1037
1367
|
err ||
|
|
1038
|
-
`Failed to ${
|
|
1368
|
+
`Failed to ${
|
|
1369
|
+
prop.mode === "add" ? "add" : "update"
|
|
1039
1370
|
} visitor. Please try again.`;
|
|
1040
1371
|
} finally {
|
|
1041
1372
|
processing.value = false;
|
|
@@ -1065,9 +1396,12 @@ onMounted(() => {
|
|
|
1065
1396
|
contractorStep.value = 1;
|
|
1066
1397
|
currentAutofillSource.value = null;
|
|
1067
1398
|
|
|
1068
|
-
if (prop.mode ===
|
|
1069
|
-
console.log(
|
|
1070
|
-
|
|
1399
|
+
if (prop.mode === "register" && prop.visitorData) {
|
|
1400
|
+
console.log(
|
|
1401
|
+
"Register mode, prefill visitor data",
|
|
1402
|
+
prop.visitorData?.plateNumber
|
|
1403
|
+
);
|
|
1404
|
+
visitor.plateNumber = prop.visitorData?.plateNumber || "";
|
|
1071
1405
|
}
|
|
1072
1406
|
});
|
|
1073
1407
|
</script>
|