@7365admin1/layer-common 3.0.7 → 3.0.9
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 +13 -0
- package/components/CameraMain.vue +17 -1
- package/components/DashboardMain.vue +1854 -848
- package/components/EntryPassInformation.vue +60 -0
- package/components/HidAccessLogDashboard.vue +1691 -0
- package/components/HidEnabledGate.vue +51 -0
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidQrCodeConfiguration.vue +618 -0
- package/components/HidReaderForm.vue +246 -0
- package/components/HidReaderManagement.vue +1233 -0
- package/components/HidServiceSettingsPanel.vue +150 -0
- package/components/HidUserEnrollment.vue +1274 -0
- package/components/Layout/NavigationDrawer.vue +31 -1
- package/components/MemberInformation.vue +23 -15
- package/components/NavigationItem.vue +11 -4
- package/components/OnlineFormFill.vue +34 -0
- package/components/PassInformation.vue +73 -34
- package/components/SiteSettings.vue +60 -116
- package/components/VisitorForm.vue +815 -290
- package/components/VisitorFormSelection.vue +12 -2
- package/components/VisitorManagement.vue +652 -205
- package/composables/useCommonPermission.ts +59 -0
- package/composables/useDashboard.ts +46 -1
- package/composables/useHidAmico.ts +152 -0
- package/composables/useHidNavigation.ts +106 -0
- package/composables/useMember.ts +7 -1
- package/composables/useOptionalServices.ts +118 -0
- package/composables/useSettingsPermission.ts +138 -0
- package/composables/useSiteSettings.ts +1 -1
- package/composables/useUser.ts +1 -2
- package/nuxt.config.ts +1 -1
- package/package.json +3 -2
- package/pages/[org]/[site]/access-mgmt/access-logs/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +21 -0
- package/types/local.d.ts +2 -1
- 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,138 @@
|
|
|
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="showEntryPassInformation"
|
|
361
|
+
v-model="passType"
|
|
362
|
+
v-model:quantity="passQuantity"
|
|
363
|
+
v-model:cards="passCards"
|
|
364
|
+
:settings="entryPassSettings"
|
|
365
|
+
:loading="entryPassSettingsPending || siteDataPending"
|
|
366
|
+
:site-id="prop.site"
|
|
367
|
+
:unit-id="visitor.unit || null"
|
|
368
|
+
:visitor-type="prop.type"
|
|
369
|
+
:hid-qr-code-enabled="hidQrCodePassAllowed"
|
|
370
|
+
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
371
|
+
/>
|
|
191
372
|
</v-col>
|
|
192
373
|
|
|
193
374
|
<v-col v-if="withStep3 && contractorStep === 3" cols="12">
|
|
194
|
-
<MemberInformation
|
|
195
|
-
|
|
375
|
+
<MemberInformation
|
|
376
|
+
v-model="visitor.members"
|
|
377
|
+
:type="prop.type"
|
|
378
|
+
:contractor-type="visitor.contractorType"
|
|
379
|
+
:site="prop.site"
|
|
380
|
+
:selected-visitor-pass="visitor.visitorPass"
|
|
381
|
+
/>
|
|
196
382
|
</v-col>
|
|
197
383
|
|
|
198
384
|
<v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
|
|
@@ -216,33 +402,89 @@
|
|
|
216
402
|
<v-toolbar density="compact">
|
|
217
403
|
<v-row no-gutters>
|
|
218
404
|
<v-col cols="6">
|
|
219
|
-
<v-btn
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
405
|
+
<v-btn
|
|
406
|
+
v-if="
|
|
407
|
+
prop.mode === 'add' &&
|
|
408
|
+
(withStep2 || withStep3) &&
|
|
409
|
+
contractorStep > 1
|
|
410
|
+
"
|
|
411
|
+
tile
|
|
412
|
+
block
|
|
413
|
+
variant="text"
|
|
414
|
+
class="text-none"
|
|
415
|
+
size="48"
|
|
416
|
+
@click="handleGoToPreviousPage"
|
|
417
|
+
text="Back"
|
|
418
|
+
/>
|
|
419
|
+
<v-btn
|
|
420
|
+
v-else-if="prop.mode === 'add' || prop.mode === 'register'"
|
|
421
|
+
tile
|
|
422
|
+
block
|
|
423
|
+
variant="text"
|
|
424
|
+
class="text-none"
|
|
425
|
+
size="48"
|
|
426
|
+
@click="backToSelection"
|
|
427
|
+
text="Back to Selection"
|
|
428
|
+
/>
|
|
429
|
+
<v-btn
|
|
430
|
+
v-else
|
|
431
|
+
tile
|
|
432
|
+
block
|
|
433
|
+
variant="text"
|
|
434
|
+
class="text-none"
|
|
435
|
+
size="48"
|
|
436
|
+
@click="emit('close:all')"
|
|
437
|
+
text="Close"
|
|
438
|
+
/>
|
|
227
439
|
</v-col>
|
|
228
440
|
<v-col cols="6">
|
|
229
|
-
<v-btn
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
441
|
+
<v-btn
|
|
442
|
+
v-if="showNextButton"
|
|
443
|
+
tile
|
|
444
|
+
block
|
|
445
|
+
variant="flat"
|
|
446
|
+
color="primary-button"
|
|
447
|
+
class="text-none"
|
|
448
|
+
size="48"
|
|
449
|
+
:disabled="processing"
|
|
450
|
+
@click="handleNextPage"
|
|
451
|
+
:text="'Next'"
|
|
452
|
+
/>
|
|
453
|
+
<v-btn
|
|
454
|
+
v-else
|
|
455
|
+
tile
|
|
456
|
+
block
|
|
457
|
+
variant="flat"
|
|
458
|
+
color="black"
|
|
459
|
+
class="text-none"
|
|
460
|
+
size="48"
|
|
461
|
+
:disabled="!validForm || processing || membersFieldIncomplete"
|
|
462
|
+
@click="submit"
|
|
463
|
+
:text="prop.mode == 'add' ? 'Submit' : 'Update'"
|
|
464
|
+
/>
|
|
234
465
|
</v-col>
|
|
235
466
|
</v-row>
|
|
236
467
|
</v-toolbar>
|
|
237
468
|
|
|
238
|
-
<v-dialog
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
469
|
+
<v-dialog
|
|
470
|
+
v-model="dialog.vehicleNumberUsersList"
|
|
471
|
+
v-if="vehicleNumberUserItems.length > 0"
|
|
472
|
+
persistent
|
|
473
|
+
max-width="600"
|
|
474
|
+
>
|
|
475
|
+
<SearchVehicleNumberUser
|
|
476
|
+
:vehicle-number="visitor.plateNumber ?? ''"
|
|
477
|
+
:vehicle-number-users-list="vehicleNumberUserItems"
|
|
478
|
+
@close="dialog.vehicleNumberUsersList = false"
|
|
479
|
+
@update:people="handleAutofillDataViaVehicleNumber"
|
|
480
|
+
/>
|
|
243
481
|
</v-dialog>
|
|
244
482
|
|
|
245
|
-
<v-dialog
|
|
483
|
+
<v-dialog
|
|
484
|
+
v-model="dialog.showNonCheckedOutDialog"
|
|
485
|
+
max-width="700"
|
|
486
|
+
persistent
|
|
487
|
+
>
|
|
246
488
|
<v-card :loading="loading.checkingOut">
|
|
247
489
|
<v-toolbar>
|
|
248
490
|
<v-toolbar-title>
|
|
@@ -255,10 +497,13 @@
|
|
|
255
497
|
</v-toolbar>
|
|
256
498
|
|
|
257
499
|
<v-card-text>
|
|
258
|
-
|
|
259
500
|
<v-list lines="three">
|
|
260
|
-
<v-list-item
|
|
261
|
-
v-
|
|
501
|
+
<v-list-item
|
|
502
|
+
v-if="matchingPlateNumberNonCheckedOutArr.length > 0"
|
|
503
|
+
v-for="v in matchingPlateNumberNonCheckedOutArr"
|
|
504
|
+
:key="v._id"
|
|
505
|
+
class="cursor-pointer"
|
|
506
|
+
>
|
|
262
507
|
<v-list-item-title>
|
|
263
508
|
{{ v.plateNumber }} - {{ v.name }}
|
|
264
509
|
</v-list-item-title>
|
|
@@ -268,14 +513,19 @@
|
|
|
268
513
|
</v-list-item-subtitle>
|
|
269
514
|
|
|
270
515
|
<template #append>
|
|
271
|
-
<v-btn
|
|
272
|
-
|
|
273
|
-
|
|
516
|
+
<v-btn
|
|
517
|
+
size="x-small"
|
|
518
|
+
class="text-capitalize"
|
|
519
|
+
color="red"
|
|
520
|
+
text="Checkout"
|
|
521
|
+
:loading="
|
|
522
|
+
loading.checkingOut && v?._id === prop.visitorData?._id
|
|
523
|
+
"
|
|
524
|
+
@click.stop="handleCheckout(v._id)"
|
|
525
|
+
/>
|
|
274
526
|
</template>
|
|
275
|
-
|
|
276
527
|
</v-list-item>
|
|
277
528
|
</v-list>
|
|
278
|
-
|
|
279
529
|
</v-card-text>
|
|
280
530
|
</v-card>
|
|
281
531
|
</v-dialog>
|
|
@@ -283,9 +533,9 @@
|
|
|
283
533
|
</template>
|
|
284
534
|
|
|
285
535
|
<script setup lang="ts">
|
|
286
|
-
import
|
|
287
|
-
import
|
|
288
|
-
|
|
536
|
+
import { propsFactory } from "vuetify/lib/util/propsFactory.mjs";
|
|
537
|
+
import useBuildingUnit from "../composables/useBuildingUnit";
|
|
538
|
+
import useWebUsb from "../composables/useWebUsb";
|
|
289
539
|
|
|
290
540
|
const prop = defineProps({
|
|
291
541
|
type: {
|
|
@@ -308,21 +558,37 @@ const prop = defineProps({
|
|
|
308
558
|
type: Object as PropType<Partial<TVisitor> | null>,
|
|
309
559
|
default: null,
|
|
310
560
|
},
|
|
561
|
+
currentUser: {
|
|
562
|
+
type: String,
|
|
563
|
+
default: "",
|
|
564
|
+
},
|
|
311
565
|
});
|
|
312
566
|
|
|
313
567
|
type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
|
|
314
568
|
const currentAutofillSource = ref<AutofillSource>(null);
|
|
315
569
|
|
|
316
|
-
|
|
317
570
|
const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
|
|
318
571
|
const { getSiteById } = useSiteSettings();
|
|
319
|
-
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
320
|
-
|
|
321
|
-
const {
|
|
572
|
+
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
573
|
+
useBuilding();
|
|
574
|
+
const {
|
|
575
|
+
createVisitor,
|
|
576
|
+
typeFieldMap,
|
|
577
|
+
contractorTypes,
|
|
578
|
+
getVisitors,
|
|
579
|
+
updateVisitor,
|
|
580
|
+
} = useVisitor();
|
|
581
|
+
const { getBySiteId: getEntryPassSettingsBySiteId } =
|
|
582
|
+
useSiteEntryPassSettings();
|
|
322
583
|
const { createVisitorPass, signQr } = useAccessManagement();
|
|
323
584
|
const { testConnection } = useWebUsb();
|
|
324
|
-
const {
|
|
325
|
-
|
|
585
|
+
const {
|
|
586
|
+
findPersonByNRIC,
|
|
587
|
+
findPersonByContact,
|
|
588
|
+
searchCompanyList,
|
|
589
|
+
findUsersByPlateNumber,
|
|
590
|
+
} = usePeople();
|
|
591
|
+
const { getById: getUnitDataById } = useBuildingUnit();
|
|
326
592
|
|
|
327
593
|
const emit = defineEmits([
|
|
328
594
|
"back",
|
|
@@ -331,7 +597,7 @@ const emit = defineEmits([
|
|
|
331
597
|
"done:more",
|
|
332
598
|
"error",
|
|
333
599
|
"close",
|
|
334
|
-
"close:all"
|
|
600
|
+
"close:all",
|
|
335
601
|
]);
|
|
336
602
|
|
|
337
603
|
const visitor = reactive<Partial<TVisitorPayload>>({
|
|
@@ -350,14 +616,16 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
350
616
|
attachments: [],
|
|
351
617
|
members: [],
|
|
352
618
|
visitorPass: [],
|
|
353
|
-
passKeys: []
|
|
619
|
+
passKeys: [],
|
|
354
620
|
});
|
|
355
621
|
|
|
356
622
|
const passType = ref<string | null>(null);
|
|
357
623
|
const passQuantity = ref<number | null>(1);
|
|
358
624
|
const passCards = ref<{ _id: string; cardNo: string }[]>([]);
|
|
625
|
+
const hidQrPrintPayload = ref<string | null>(null);
|
|
626
|
+
const hidQrPrinted = ref(false);
|
|
359
627
|
|
|
360
|
-
const registeredUnitCompanyName = ref(
|
|
628
|
+
const registeredUnitCompanyName = ref("N/A");
|
|
361
629
|
|
|
362
630
|
const dialog = reactive({
|
|
363
631
|
vehicleNumberUsersList: false,
|
|
@@ -366,7 +634,7 @@ const dialog = reactive({
|
|
|
366
634
|
|
|
367
635
|
const loading = reactive({
|
|
368
636
|
checkingOut: false,
|
|
369
|
-
})
|
|
637
|
+
});
|
|
370
638
|
|
|
371
639
|
const validForm = ref(false);
|
|
372
640
|
const formRef = ref<HTMLFormElement | null>(null);
|
|
@@ -382,7 +650,6 @@ const contractorStep = ref(1);
|
|
|
382
650
|
const companyNameInput = ref("");
|
|
383
651
|
const companyNameObj = ref<string | null>(null);
|
|
384
652
|
|
|
385
|
-
|
|
386
653
|
const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
387
654
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
388
655
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
@@ -391,13 +658,13 @@ const deliveryCompany = ref("");
|
|
|
391
658
|
const deliveryCompanyInput = ref("");
|
|
392
659
|
const deliveryCompanyList = ref<string[]>([]);
|
|
393
660
|
|
|
394
|
-
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
661
|
+
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([]);
|
|
398
662
|
|
|
663
|
+
const vehicleNumberUserItems = ref<TPeople[]>([]);
|
|
399
664
|
|
|
400
|
-
const shouldShowField = (
|
|
665
|
+
const shouldShowField = (
|
|
666
|
+
fieldKey: keyof TVisitorPayload | "delivery-company"
|
|
667
|
+
) => {
|
|
401
668
|
if ((!withStep2.value && !withStep3.value) || contractorStep.value === 1) {
|
|
402
669
|
const visibleFields = typeFieldMap[prop.type];
|
|
403
670
|
return visibleFields?.includes(fieldKey);
|
|
@@ -407,31 +674,72 @@ const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') =
|
|
|
407
674
|
const withStep2 = computed(() => {
|
|
408
675
|
const step2Types = ["guest", "walk-in"];
|
|
409
676
|
return step2Types.includes(prop.type);
|
|
410
|
-
})
|
|
677
|
+
});
|
|
411
678
|
|
|
412
679
|
const withStep3 = computed(() => {
|
|
413
680
|
const step3Types = ["contractor"];
|
|
414
681
|
return step3Types.includes(prop.type);
|
|
415
|
-
})
|
|
682
|
+
});
|
|
416
683
|
|
|
417
684
|
const showNextButton = computed(() => {
|
|
418
685
|
if (withStep3.value) {
|
|
419
|
-
return contractorStep.value === 1 || contractorStep.value === 2
|
|
686
|
+
return contractorStep.value === 1 || contractorStep.value === 2;
|
|
420
687
|
} else if (withStep2.value) {
|
|
421
|
-
return contractorStep.value === 1
|
|
422
|
-
} else return false
|
|
423
|
-
})
|
|
688
|
+
return contractorStep.value === 1;
|
|
689
|
+
} else return false;
|
|
690
|
+
});
|
|
424
691
|
|
|
425
692
|
const showShowKeysField = computed(() => {
|
|
426
|
-
return
|
|
427
|
-
})
|
|
693
|
+
return ["guest", "contractor", "walk-in"].includes(prop.type);
|
|
694
|
+
});
|
|
428
695
|
|
|
429
696
|
const requireNRIC = computed(() => {
|
|
430
|
-
return prop.type !==
|
|
431
|
-
})
|
|
697
|
+
return prop.type !== "walk-in" && prop.type !== "guest";
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const hidQrCodePassConfig = computed(() => {
|
|
701
|
+
return (siteData.value as any)?.metadata?.hidQrCodePass || {};
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const hidQrCodePassEnabled = computed(() => {
|
|
705
|
+
return Boolean(hidQrCodePassConfig.value?.enabled);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const hidQrPrinterConfig = computed(() => {
|
|
709
|
+
return hidQrCodePassConfig.value?.printer || {};
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
const hasHidQrPrinterConfig = computed(() => {
|
|
713
|
+
return Boolean(hidQrPrinterConfig.value?.vendorId && hidQrPrinterConfig.value?.productId);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
const hidQrTemplate = computed(() => {
|
|
717
|
+
return hidQrCodePassConfig.value?.template || {};
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const hidQrValidityMinutes = computed(() => {
|
|
721
|
+
const value = Number(hidQrCodePassConfig.value?.validityMinutes);
|
|
722
|
+
return Number.isFinite(value) && value > 0 ? value : 60;
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
const hidQrCodePassAllowed = computed(() => {
|
|
726
|
+
return hidQrCodePassEnabled.value && ["guest", "contractor", "walk-in"].includes(prop.type);
|
|
727
|
+
});
|
|
432
728
|
|
|
433
|
-
const
|
|
434
|
-
|
|
729
|
+
const showEntryPassInformation = computed(() => {
|
|
730
|
+
return Boolean(entryPassSettings.value?.data?.settings?.nfcPass || hidQrCodePassAllowed.value);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
const companyNames = ref<string[]>([]);
|
|
734
|
+
const companyAutofillDataArray = ref<
|
|
735
|
+
{
|
|
736
|
+
companyName: string[];
|
|
737
|
+
unit: string;
|
|
738
|
+
block: number;
|
|
739
|
+
level: string;
|
|
740
|
+
unitName: string;
|
|
741
|
+
}[]
|
|
742
|
+
>([]);
|
|
435
743
|
|
|
436
744
|
const formTitle = computed(() => {
|
|
437
745
|
const isContractorForm = prop.type === "contractor";
|
|
@@ -461,21 +769,21 @@ function handleFocusedContractorType() {
|
|
|
461
769
|
// }
|
|
462
770
|
}
|
|
463
771
|
|
|
464
|
-
const contractorTypeCombo = ref(null)
|
|
772
|
+
const contractorTypeCombo = ref(null);
|
|
465
773
|
|
|
466
774
|
async function handleAddNewContractorType() {
|
|
467
|
-
visitor.contractorType = contractorTypeInput.value
|
|
775
|
+
visitor.contractorType = contractorTypeInput.value;
|
|
468
776
|
|
|
469
|
-
const combo = contractorTypeCombo.value as any
|
|
777
|
+
const combo = contractorTypeCombo.value as any;
|
|
470
778
|
await nextTick();
|
|
471
779
|
|
|
472
780
|
combo.isMenuActive = false;
|
|
473
|
-
combo.$el.querySelector("input")?.blur();
|
|
474
|
-
|
|
781
|
+
combo.$el.querySelector("input")?.blur();
|
|
782
|
+
``;
|
|
475
783
|
}
|
|
476
784
|
|
|
477
785
|
async function handleSelectCompanyName(company: string) {
|
|
478
|
-
visitor.company = company
|
|
786
|
+
visitor.company = company;
|
|
479
787
|
// const selected = companyAutofillDataArray.value.find(x => x.companyName?.includes(company))
|
|
480
788
|
// if (!selected) return
|
|
481
789
|
|
|
@@ -485,48 +793,46 @@ async function handleSelectCompanyName(company: string) {
|
|
|
485
793
|
// visitor.unitName = selected.unitName || ""
|
|
486
794
|
}
|
|
487
795
|
|
|
488
|
-
const companyCombo = ref(null)
|
|
796
|
+
const companyCombo = ref(null);
|
|
489
797
|
|
|
490
798
|
async function handleAddNewCompany() {
|
|
491
|
-
visitor.company = companyNameInput.value
|
|
799
|
+
visitor.company = companyNameInput.value;
|
|
492
800
|
|
|
493
|
-
const combo = companyCombo.value as any
|
|
801
|
+
const combo = companyCombo.value as any;
|
|
494
802
|
await nextTick();
|
|
495
803
|
|
|
496
804
|
combo.isMenuActive = false;
|
|
497
805
|
combo.$el.querySelector("input")?.blur();
|
|
498
806
|
}
|
|
499
807
|
|
|
500
|
-
|
|
501
808
|
const {
|
|
502
809
|
data: fetchPersonByNRICReq,
|
|
503
810
|
refresh: fetchPersonByNRICRefresh,
|
|
504
811
|
pending: fetchPersonByNRICPending,
|
|
505
812
|
} = useLazyAsyncData(`fetch-person`, () => {
|
|
506
813
|
const NRIC = visitor.nric;
|
|
507
|
-
if (!NRIC) return Promise.resolve(null)
|
|
508
|
-
return findPersonByNRIC(NRIC)
|
|
509
|
-
}
|
|
510
|
-
);
|
|
814
|
+
if (!NRIC) return Promise.resolve(null);
|
|
815
|
+
return findPersonByNRIC(NRIC);
|
|
816
|
+
});
|
|
511
817
|
|
|
512
818
|
watch(fetchPersonByNRICReq, (obj) => {
|
|
513
819
|
if (obj) {
|
|
514
820
|
currentAutofillSource.value = "nric";
|
|
515
|
-
companyNames.value = obj.companyName ?? []
|
|
516
|
-
visitor.name = obj.name
|
|
517
|
-
visitor.contact = obj.contact
|
|
821
|
+
companyNames.value = obj.companyName ?? [];
|
|
822
|
+
visitor.name = obj.name;
|
|
823
|
+
visitor.contact = obj.contact;
|
|
518
824
|
if (!visitor.company) {
|
|
519
|
-
visitor.company = companyNames.value?.[0]
|
|
825
|
+
visitor.company = companyNames.value?.[0];
|
|
520
826
|
}
|
|
521
|
-
visitor.block = obj.block ?? ""
|
|
522
|
-
visitor.level = obj.level ?? ""
|
|
523
|
-
visitor.unit = obj.unit ?? ""
|
|
827
|
+
visitor.block = obj.block ?? "";
|
|
828
|
+
visitor.level = obj.level ?? "";
|
|
829
|
+
visitor.unit = obj.unit ?? "";
|
|
524
830
|
|
|
525
831
|
setTimeout(() => {
|
|
526
832
|
currentAutofillSource.value = null;
|
|
527
833
|
}, 1000);
|
|
528
834
|
}
|
|
529
|
-
})
|
|
835
|
+
});
|
|
530
836
|
|
|
531
837
|
const {
|
|
532
838
|
data: fetchPersonByContactReq,
|
|
@@ -534,82 +840,86 @@ const {
|
|
|
534
840
|
pending: fetchPersonByContactPending,
|
|
535
841
|
} = useLazyAsyncData(`fetch-contact`, () => {
|
|
536
842
|
const contact = visitor.contact;
|
|
537
|
-
if (!contact) return Promise.resolve(null)
|
|
538
|
-
return findPersonByContact(contact)
|
|
539
|
-
}
|
|
540
|
-
);
|
|
843
|
+
if (!contact) return Promise.resolve(null);
|
|
844
|
+
return findPersonByContact(contact);
|
|
845
|
+
});
|
|
541
846
|
|
|
542
847
|
watch(fetchPersonByContactReq, (obj: Partial<TPerson>) => {
|
|
543
848
|
if (obj) {
|
|
544
849
|
currentAutofillSource.value = "contact";
|
|
545
|
-
companyNames.value = obj.companyName ?? []
|
|
546
|
-
visitor.name = visitor.name ?? obj.name
|
|
850
|
+
companyNames.value = obj.companyName ?? [];
|
|
851
|
+
visitor.name = visitor.name ?? obj.name;
|
|
547
852
|
if (!visitor.company) {
|
|
548
|
-
visitor.company = companyNames.value?.[0]
|
|
853
|
+
visitor.company = companyNames.value?.[0];
|
|
549
854
|
}
|
|
550
855
|
// visitor.block = visitor.block ?? obj.block ?? ""
|
|
551
856
|
// visitor.level = visitor.level ?? obj.level ?? ""
|
|
552
857
|
// visitor.unit = visitor.unit ?? obj.unit ?? ""
|
|
553
|
-
visitor.nric = visitor.nric ?? obj.nric ?? ""
|
|
858
|
+
visitor.nric = visitor.nric ?? obj.nric ?? "";
|
|
554
859
|
|
|
555
860
|
setTimeout(() => {
|
|
556
861
|
currentAutofillSource.value = null;
|
|
557
862
|
}, 1000);
|
|
558
863
|
}
|
|
559
|
-
})
|
|
864
|
+
});
|
|
560
865
|
|
|
561
866
|
const {
|
|
562
867
|
data: fetchCompanyListReq,
|
|
563
868
|
refresh: fetchCompanyListRefresh,
|
|
564
869
|
pending: fetchCompanyListPending,
|
|
565
|
-
|
|
566
870
|
} = useLazyAsyncData(`fetch-company-list`, () => {
|
|
567
|
-
if (!companyNameInput.value) return Promise.resolve(null)
|
|
568
|
-
return searchCompanyList(companyNameInput.value)
|
|
569
|
-
})
|
|
871
|
+
if (!companyNameInput.value) return Promise.resolve(null);
|
|
872
|
+
return searchCompanyList(companyNameInput.value);
|
|
873
|
+
});
|
|
570
874
|
|
|
571
875
|
watch(fetchCompanyListReq, (arr) => {
|
|
572
876
|
if (Array.isArray(arr)) {
|
|
573
877
|
companyAutofillDataArray.value = arr;
|
|
574
|
-
companyNames.value = arr?.flatMap(x => x?.companyName)
|
|
878
|
+
companyNames.value = arr?.flatMap((x) => x?.companyName);
|
|
575
879
|
}
|
|
576
|
-
})
|
|
880
|
+
});
|
|
577
881
|
const {
|
|
578
882
|
data: fetchVisitorListByVehicleNumberReq,
|
|
579
883
|
refresh: fetchVisitorListByVehicleNumberRefresh,
|
|
580
884
|
pending: fetchVisitorListByVehicleNumberPending,
|
|
581
|
-
|
|
582
885
|
} = useLazyAsyncData(`fetch-visitor-list-by-vehicle-number`, () => {
|
|
583
|
-
if (!visitor.plateNumber) return Promise.resolve(null)
|
|
584
|
-
return getVisitors({
|
|
585
|
-
|
|
886
|
+
if (!visitor.plateNumber) return Promise.resolve(null);
|
|
887
|
+
return getVisitors({
|
|
888
|
+
page: 1,
|
|
889
|
+
limit: 20,
|
|
890
|
+
site: prop.site,
|
|
891
|
+
plateNumber: visitor.plateNumber,
|
|
892
|
+
checkedOut: false,
|
|
893
|
+
status: "registered",
|
|
894
|
+
});
|
|
895
|
+
});
|
|
586
896
|
|
|
587
897
|
watch(fetchVisitorListByVehicleNumberReq, (arr: any) => {
|
|
588
|
-
const itemsArray = arr?.items || []
|
|
589
|
-
const isValidArray = Array.isArray(itemsArray)
|
|
590
|
-
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : []
|
|
898
|
+
const itemsArray = arr?.items || [];
|
|
899
|
+
const isValidArray = Array.isArray(itemsArray);
|
|
900
|
+
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : [];
|
|
591
901
|
if (matchingPlateNumberNonCheckedOutArr.value.length > 0) {
|
|
592
902
|
dialog.showNonCheckedOutDialog = true;
|
|
593
903
|
} else {
|
|
594
904
|
dialog.showNonCheckedOutDialog = false;
|
|
595
905
|
}
|
|
596
|
-
})
|
|
597
|
-
|
|
906
|
+
});
|
|
598
907
|
|
|
599
908
|
const debounceSearchVisitorsByPlateNumbers = debounce(() => {
|
|
600
909
|
if (!visitor.plateNumber) {
|
|
601
|
-
matchingPlateNumberNonCheckedOutArr.value = []
|
|
910
|
+
matchingPlateNumberNonCheckedOutArr.value = [];
|
|
602
911
|
dialog.showNonCheckedOutDialog = false;
|
|
603
912
|
return;
|
|
604
913
|
}
|
|
605
914
|
fetchVisitorListByVehicleNumberRefresh();
|
|
606
915
|
}, 300);
|
|
607
916
|
|
|
608
|
-
watch(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
917
|
+
watch(
|
|
918
|
+
() => visitor.plateNumber,
|
|
919
|
+
(newVal) => {
|
|
920
|
+
debounceSearchVisitorsByPlateNumbers();
|
|
921
|
+
}
|
|
922
|
+
);
|
|
613
923
|
|
|
614
924
|
async function handleCheckout(visitorId: string) {
|
|
615
925
|
if (!visitorId) {
|
|
@@ -624,7 +934,6 @@ async function handleCheckout(visitorId: string) {
|
|
|
624
934
|
});
|
|
625
935
|
if (res) {
|
|
626
936
|
await fetchVisitorListByVehicleNumberRefresh();
|
|
627
|
-
|
|
628
937
|
}
|
|
629
938
|
} catch (error: any) {
|
|
630
939
|
const errorMessage = error?.response?._data?.message;
|
|
@@ -636,50 +945,42 @@ async function handleCheckout(visitorId: string) {
|
|
|
636
945
|
}
|
|
637
946
|
}
|
|
638
947
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
948
|
+
const debounceFetchCompany = debounce(
|
|
949
|
+
async () => fetchCompanyListRefresh(),
|
|
950
|
+
200
|
|
951
|
+
);
|
|
643
952
|
|
|
644
953
|
watch(companyNameInput, async (val) => {
|
|
645
954
|
if (!val) {
|
|
646
|
-
companyNames.value = []
|
|
647
|
-
return
|
|
955
|
+
companyNames.value = [];
|
|
956
|
+
return;
|
|
648
957
|
}
|
|
649
|
-
await debounceFetchCompany()
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
958
|
+
await debounceFetchCompany();
|
|
959
|
+
});
|
|
655
960
|
|
|
656
961
|
function handleAutofillDataViaVehicleNumber(item: TPeople) {
|
|
657
|
-
|
|
658
962
|
currentAutofillSource.value = "vehicleNumber";
|
|
659
963
|
|
|
660
|
-
visitor.name = item.name
|
|
661
|
-
visitor.nric = item.nric
|
|
662
|
-
visitor.contact = item.contact
|
|
663
|
-
companyNames.value = item.companyName ?? []
|
|
964
|
+
visitor.name = item.name;
|
|
965
|
+
visitor.nric = item.nric;
|
|
966
|
+
visitor.contact = item.contact;
|
|
967
|
+
companyNames.value = item.companyName ?? [];
|
|
664
968
|
if (!visitor.company) {
|
|
665
|
-
visitor.company = companyNames.value?.[0]
|
|
969
|
+
visitor.company = companyNames.value?.[0];
|
|
666
970
|
}
|
|
667
|
-
visitor.block = item.block ?? ""
|
|
668
|
-
visitor.level = item.level ?? ""
|
|
669
|
-
visitor.unit = item.unit ?? ""
|
|
971
|
+
visitor.block = item.block ?? "";
|
|
972
|
+
visitor.level = item.level ?? "";
|
|
973
|
+
visitor.unit = item.unit ?? "";
|
|
670
974
|
|
|
671
975
|
setTimeout(() => {
|
|
672
976
|
currentAutofillSource.value = null;
|
|
673
977
|
}, 1000);
|
|
674
978
|
}
|
|
675
979
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
} = useLazyAsyncData(`fetch-entrypass-settings-${prop.site}`, () =>
|
|
681
|
-
getEntryPassSettingsBySiteId(prop.site)
|
|
682
|
-
);
|
|
980
|
+
const { data: entryPassSettings, pending: entryPassSettingsPending } =
|
|
981
|
+
useLazyAsyncData(`fetch-entrypass-settings-${prop.site}`, () =>
|
|
982
|
+
getEntryPassSettingsBySiteId(prop.site)
|
|
983
|
+
);
|
|
683
984
|
|
|
684
985
|
const {
|
|
685
986
|
data: siteData,
|
|
@@ -690,18 +991,17 @@ const {
|
|
|
690
991
|
getSiteById(prop.site)
|
|
691
992
|
);
|
|
692
993
|
|
|
693
|
-
|
|
694
994
|
const {
|
|
695
995
|
data: blocksListData,
|
|
696
996
|
refresh: refreshBlockListData,
|
|
697
997
|
status: blocksListStatus,
|
|
698
998
|
pending: blockListDataPending,
|
|
699
|
-
} = useLazyAsyncData(
|
|
700
|
-
|
|
999
|
+
} = useLazyAsyncData(
|
|
1000
|
+
`visitor-fetch-block-list-data-${prop.site}`,
|
|
1001
|
+
() => getBlocksSelection({ siteId: prop.site }),
|
|
1002
|
+
{ watch: [() => prop.site], deep: true }
|
|
701
1003
|
);
|
|
702
1004
|
|
|
703
|
-
|
|
704
|
-
|
|
705
1005
|
const {
|
|
706
1006
|
data: levelsData,
|
|
707
1007
|
refresh: refreshLevelsData,
|
|
@@ -712,8 +1012,12 @@ const {
|
|
|
712
1012
|
async () => {
|
|
713
1013
|
if (!visitor.block) return Promise.resolve(null);
|
|
714
1014
|
// return await getSiteWithLevels(prop.site, { block: Number(visitor.block) });
|
|
715
|
-
return await getLevelsSelection({
|
|
716
|
-
|
|
1015
|
+
return await getLevelsSelection({
|
|
1016
|
+
siteId: prop.site,
|
|
1017
|
+
blockId: visitor.block,
|
|
1018
|
+
});
|
|
1019
|
+
},
|
|
1020
|
+
{ watch: [() => visitor.block], deep: true }
|
|
717
1021
|
);
|
|
718
1022
|
|
|
719
1023
|
const {
|
|
@@ -725,20 +1029,26 @@ const {
|
|
|
725
1029
|
`visitor-fetch-units-data-${prop.site}-${visitor.level}`,
|
|
726
1030
|
async () => {
|
|
727
1031
|
if (!visitor.level) return Promise.resolve(null);
|
|
728
|
-
return await getUnitSelection({
|
|
729
|
-
|
|
1032
|
+
return await getUnitSelection({
|
|
1033
|
+
siteId: prop.site,
|
|
1034
|
+
block: visitor.block,
|
|
1035
|
+
level: visitor.level,
|
|
1036
|
+
});
|
|
1037
|
+
},
|
|
1038
|
+
{ watch: [() => visitor.level], deep: true }
|
|
730
1039
|
);
|
|
731
1040
|
|
|
732
|
-
|
|
733
1041
|
watch(
|
|
734
1042
|
siteData,
|
|
735
1043
|
(newVal) => {
|
|
736
1044
|
const siteDataValue = newVal as TSite;
|
|
737
1045
|
if (siteDataValue) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1046
|
+
deliveryCompanyList.value = Array.isArray(
|
|
1047
|
+
siteDataValue?.deliveryCompanyList
|
|
1048
|
+
)
|
|
1049
|
+
? siteDataValue.deliveryCompanyList
|
|
1050
|
+
: [];
|
|
1051
|
+
}
|
|
742
1052
|
},
|
|
743
1053
|
{ immediate: true }
|
|
744
1054
|
);
|
|
@@ -746,83 +1056,96 @@ watch(
|
|
|
746
1056
|
watch(
|
|
747
1057
|
[blocksListData],
|
|
748
1058
|
() => {
|
|
749
|
-
if (blocksListStatus.value ===
|
|
1059
|
+
if (blocksListStatus.value === "pending") {
|
|
750
1060
|
blocksArray.value = [];
|
|
751
1061
|
return;
|
|
752
1062
|
}
|
|
753
1063
|
|
|
754
|
-
const arr = Array.isArray(blocksListData.value?.data)
|
|
1064
|
+
const arr = Array.isArray(blocksListData.value?.data)
|
|
1065
|
+
? blocksListData.value?.data
|
|
1066
|
+
: [];
|
|
755
1067
|
blocksArray.value = arr.map((block: any) => ({
|
|
756
1068
|
title: block.name,
|
|
757
1069
|
value: block._id,
|
|
758
1070
|
}));
|
|
759
1071
|
},
|
|
760
1072
|
{ immediate: true, deep: true }
|
|
761
|
-
)
|
|
762
|
-
|
|
1073
|
+
);
|
|
763
1074
|
|
|
764
|
-
watch(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1075
|
+
watch(
|
|
1076
|
+
[levelsData],
|
|
1077
|
+
() => {
|
|
1078
|
+
if (levelsStatus.value === "pending") {
|
|
1079
|
+
levelsArray.value = [];
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
769
1082
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
1083
|
+
const arr = Array.isArray(levelsData.value?.data)
|
|
1084
|
+
? levelsData.value?.data
|
|
1085
|
+
: [];
|
|
1086
|
+
const mappedLevels = arr.map((level: any) => ({
|
|
1087
|
+
title: level.name,
|
|
1088
|
+
value: level._id,
|
|
1089
|
+
}));
|
|
1090
|
+
levelsArray.value = mappedLevels;
|
|
1091
|
+
},
|
|
1092
|
+
{ immediate: true, deep: true }
|
|
1093
|
+
);
|
|
777
1094
|
|
|
778
1095
|
watch(
|
|
779
1096
|
unitsData,
|
|
780
1097
|
(newVal: any) => {
|
|
781
1098
|
if (!newVal || !Array.isArray(newVal)) {
|
|
782
|
-
unitsArray.value = []
|
|
783
|
-
return
|
|
1099
|
+
unitsArray.value = [];
|
|
1100
|
+
return;
|
|
784
1101
|
}
|
|
785
1102
|
|
|
786
1103
|
const mapped = newVal.map((unit: any) => ({
|
|
787
1104
|
title: unit.name,
|
|
788
1105
|
value: unit._id,
|
|
789
|
-
}))
|
|
1106
|
+
}));
|
|
790
1107
|
|
|
791
1108
|
// keep custom unit pushed from autofill
|
|
792
|
-
const selected = visitor.unit
|
|
793
|
-
const exists = mapped.some((u: TDefaultOptionObj) => u.value === selected)
|
|
1109
|
+
const selected = visitor.unit;
|
|
1110
|
+
const exists = mapped.some((u: TDefaultOptionObj) => u.value === selected);
|
|
794
1111
|
|
|
795
1112
|
if (!exists && visitor.unitName) {
|
|
796
1113
|
mapped.push({
|
|
797
1114
|
title: visitor.unitName,
|
|
798
1115
|
value: selected,
|
|
799
|
-
})
|
|
1116
|
+
});
|
|
800
1117
|
}
|
|
801
1118
|
|
|
802
|
-
unitsArray.value = mapped
|
|
1119
|
+
unitsArray.value = mapped;
|
|
803
1120
|
},
|
|
804
1121
|
{ immediate: true, deep: true }
|
|
805
|
-
)
|
|
806
|
-
|
|
1122
|
+
);
|
|
807
1123
|
|
|
808
|
-
const {
|
|
1124
|
+
const {
|
|
1125
|
+
data: buildingUnitData,
|
|
1126
|
+
refresh: refreshBuildingUnitData,
|
|
1127
|
+
pending: buildingUnitDataPending,
|
|
1128
|
+
} = useLazyAsyncData(`building-unit-data-${visitor.unit}`, () => {
|
|
809
1129
|
if (!visitor.unit) return Promise.resolve(null);
|
|
810
1130
|
return getUnitDataById(visitor.unit);
|
|
811
|
-
})
|
|
1131
|
+
});
|
|
812
1132
|
|
|
813
1133
|
// 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
|
-
|
|
1134
|
+
watch(
|
|
1135
|
+
() => visitor.unit,
|
|
1136
|
+
() => {
|
|
1137
|
+
if (visitor.unit) {
|
|
1138
|
+
refreshBuildingUnitData();
|
|
1139
|
+
} else {
|
|
1140
|
+
registeredUnitCompanyName.value = "N/A";
|
|
1141
|
+
}
|
|
819
1142
|
}
|
|
820
|
-
|
|
1143
|
+
);
|
|
821
1144
|
|
|
822
1145
|
watch(buildingUnitData, (newVal: any) => {
|
|
823
|
-
registeredUnitCompanyName.value =
|
|
824
|
-
|
|
825
|
-
|
|
1146
|
+
registeredUnitCompanyName.value =
|
|
1147
|
+
newVal?.data?.buildingUnit?.companyName || "N/A";
|
|
1148
|
+
});
|
|
826
1149
|
|
|
827
1150
|
function handleChangeBlock(value: any) {
|
|
828
1151
|
visitor.level = "";
|
|
@@ -839,17 +1162,19 @@ function handleUpdateUnit(value: any) {
|
|
|
839
1162
|
visitor.unitName = selectedUnit?.title || "";
|
|
840
1163
|
}
|
|
841
1164
|
|
|
842
|
-
const debounceFetchNRIC = debounce(fetchPersonByNRICRefresh, 500)
|
|
843
|
-
const debounceFetchContact = debounce(fetchPersonByContactRefresh, 500)
|
|
1165
|
+
const debounceFetchNRIC = debounce(fetchPersonByNRICRefresh, 500);
|
|
1166
|
+
const debounceFetchContact = debounce(fetchPersonByContactRefresh, 500);
|
|
844
1167
|
|
|
845
1168
|
function handleUpdateNRIC() {
|
|
846
|
-
if (currentAutofillSource.value && currentAutofillSource.value !== "nric")
|
|
847
|
-
|
|
1169
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "nric")
|
|
1170
|
+
return;
|
|
1171
|
+
debounceFetchNRIC();
|
|
848
1172
|
}
|
|
849
1173
|
|
|
850
1174
|
function handleUpdateContact() {
|
|
851
|
-
if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
|
|
852
|
-
|
|
1175
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
|
|
1176
|
+
return;
|
|
1177
|
+
debounceFetchContact();
|
|
853
1178
|
}
|
|
854
1179
|
|
|
855
1180
|
function backToSelection() {
|
|
@@ -902,12 +1227,20 @@ async function handleNextPage() {
|
|
|
902
1227
|
async function submit() {
|
|
903
1228
|
formRef.value!.validate();
|
|
904
1229
|
errorMessage.value = "";
|
|
1230
|
+
if (!validForm.value) {
|
|
1231
|
+
errorMessage.value = "Please complete all required fields *";
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (passType.value === "HID_QR" && !validateHidQrPrinterConfig()) return;
|
|
1236
|
+
|
|
905
1237
|
processing.value = true;
|
|
906
1238
|
|
|
907
1239
|
let payload: Partial<TVisitorPayload> = {
|
|
908
1240
|
type: prop.type,
|
|
909
1241
|
org: prop.org,
|
|
910
1242
|
site: prop.site,
|
|
1243
|
+
createdBy: prop.currentUser,
|
|
911
1244
|
};
|
|
912
1245
|
|
|
913
1246
|
if (prop.mode === "add" || prop.mode === "register") {
|
|
@@ -928,11 +1261,8 @@ async function submit() {
|
|
|
928
1261
|
payload.visitorPass = visitor.visitorPass;
|
|
929
1262
|
payload.passKeys = visitor.passKeys;
|
|
930
1263
|
}
|
|
931
|
-
|
|
932
1264
|
}
|
|
933
1265
|
|
|
934
|
-
|
|
935
|
-
|
|
936
1266
|
if (prop.type === "contractor") {
|
|
937
1267
|
// contractor type logic payload
|
|
938
1268
|
payload = {
|
|
@@ -944,30 +1274,40 @@ async function submit() {
|
|
|
944
1274
|
if (prop.type === "delivery") {
|
|
945
1275
|
payload = {
|
|
946
1276
|
...payload,
|
|
947
|
-
company: deliveryCompany.value
|
|
948
|
-
}
|
|
1277
|
+
company: deliveryCompany.value,
|
|
1278
|
+
};
|
|
949
1279
|
}
|
|
950
1280
|
|
|
951
1281
|
// add register status when registering unregistered visitor
|
|
952
|
-
if (prop.mode ===
|
|
953
|
-
payload.status = "registered"
|
|
1282
|
+
if (prop.mode === "register") {
|
|
1283
|
+
payload.status = "registered";
|
|
954
1284
|
}
|
|
955
1285
|
|
|
956
1286
|
try {
|
|
957
1287
|
let res;
|
|
958
1288
|
if (prop.mode === "add") {
|
|
959
1289
|
res = await createVisitor(payload as TVisitorPayload);
|
|
960
|
-
} else if (
|
|
961
|
-
|
|
1290
|
+
} else if (
|
|
1291
|
+
(prop.mode === "edit" || prop.mode === "register") &&
|
|
1292
|
+
prop.visitorData?._id
|
|
1293
|
+
) {
|
|
1294
|
+
const { unitName, ...rest } = payload;
|
|
962
1295
|
res = await updateVisitor(prop.visitorData._id, rest as TVisitorPayload);
|
|
963
1296
|
}
|
|
964
1297
|
|
|
965
1298
|
if (res) {
|
|
966
|
-
if (
|
|
1299
|
+
if (passType.value === "HID_QR") {
|
|
1300
|
+
const printed = await printHidQrCodePass(getVisitorIdFromResponse(res), false);
|
|
1301
|
+
if (!printed) return;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (prop.type === "contractor" && (passType.value === "QR" || passType.value === "NFC")) {
|
|
967
1305
|
const visitorId = res as unknown as string;
|
|
968
1306
|
const rawUrl = entryPassSettings.value?.data?.settings?.url;
|
|
969
1307
|
const { encrypted: acmUrl } = await $fetch<{ encrypted: string }>(
|
|
970
|
-
`/api/encrypt-acm-url${
|
|
1308
|
+
`/api/encrypt-acm-url${
|
|
1309
|
+
rawUrl ? `?url=${encodeURIComponent(rawUrl)}` : ""
|
|
1310
|
+
}`
|
|
971
1311
|
);
|
|
972
1312
|
if (visitorId && acmUrl) {
|
|
973
1313
|
const passRes = await createVisitorPass({
|
|
@@ -982,7 +1322,8 @@ async function submit() {
|
|
|
982
1322
|
|
|
983
1323
|
if (passType.value === "QR") {
|
|
984
1324
|
console.log("[QR Print] passRes:", passRes);
|
|
985
|
-
const accessCards: any[] =
|
|
1325
|
+
const accessCards: any[] =
|
|
1326
|
+
(passRes as any)?.data?.accessCards ?? [];
|
|
986
1327
|
console.log("[QR Print] accessCards:", accessCards);
|
|
987
1328
|
const printer = entryPassSettings.value?.data?.settings?.printer;
|
|
988
1329
|
console.log("[QR Print] printer config:", printer);
|
|
@@ -994,23 +1335,57 @@ async function submit() {
|
|
|
994
1335
|
const vendorId = parseInt(printer.vendorId);
|
|
995
1336
|
const productId = parseInt(printer.productId);
|
|
996
1337
|
const companyName = visitor.company || "";
|
|
997
|
-
const blockLabel =
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1338
|
+
const blockLabel =
|
|
1339
|
+
blocksArray.value.find((b: any) => b.value === visitor.block)
|
|
1340
|
+
?.title || "";
|
|
1341
|
+
const levelLabel =
|
|
1342
|
+
levelsArray.value.find((l: any) => l.value === visitor.level)
|
|
1343
|
+
?.title || "";
|
|
1344
|
+
const unitLabel =
|
|
1345
|
+
unitsArray.value.find((u: any) => u.value === visitor.unit)
|
|
1346
|
+
?.title || "";
|
|
1347
|
+
const address = [blockLabel, levelLabel, unitLabel]
|
|
1348
|
+
.filter(Boolean)
|
|
1349
|
+
.join("/");
|
|
1350
|
+
console.log(
|
|
1351
|
+
"[QR Print] Printing",
|
|
1352
|
+
accessCards.length,
|
|
1353
|
+
"card(s). vendorId:",
|
|
1354
|
+
vendorId,
|
|
1355
|
+
"productId:",
|
|
1356
|
+
productId,
|
|
1357
|
+
"address:",
|
|
1358
|
+
address
|
|
1359
|
+
);
|
|
1002
1360
|
|
|
1003
1361
|
await nextTick();
|
|
1004
1362
|
for (const qrCode of accessCards) {
|
|
1005
1363
|
console.log("[QR Print] Signing QR for card:", qrCode._id);
|
|
1006
|
-
const signed = await signQr({
|
|
1364
|
+
const signed = await signQr({
|
|
1365
|
+
cardId: qrCode._id,
|
|
1366
|
+
purpose: "vms",
|
|
1367
|
+
});
|
|
1007
1368
|
const qrData = signed?.data;
|
|
1008
|
-
console.log(
|
|
1369
|
+
console.log(
|
|
1370
|
+
"[QR Print] signQr result:",
|
|
1371
|
+
signed,
|
|
1372
|
+
"| qrData:",
|
|
1373
|
+
qrData
|
|
1374
|
+
);
|
|
1009
1375
|
if (!qrData) {
|
|
1010
|
-
console.warn(
|
|
1376
|
+
console.warn(
|
|
1377
|
+
"[QR Print] Skipping card",
|
|
1378
|
+
qrCode._id,
|
|
1379
|
+
"— signQr returned no data."
|
|
1380
|
+
);
|
|
1011
1381
|
continue;
|
|
1012
1382
|
}
|
|
1013
|
-
console.log(
|
|
1383
|
+
console.log(
|
|
1384
|
+
"[QR Print] Sending to printer — accessLevel:",
|
|
1385
|
+
qrCode.accessLevel,
|
|
1386
|
+
"liftLevel:",
|
|
1387
|
+
qrCode.liftAccessLevel
|
|
1388
|
+
);
|
|
1014
1389
|
const printResult = await testConnection(
|
|
1015
1390
|
vendorId,
|
|
1016
1391
|
productId,
|
|
@@ -1019,9 +1394,14 @@ async function submit() {
|
|
|
1019
1394
|
qrCode.accessLevel,
|
|
1020
1395
|
qrCode.liftAccessLevel,
|
|
1021
1396
|
companyName,
|
|
1022
|
-
address
|
|
1397
|
+
address
|
|
1398
|
+
);
|
|
1399
|
+
console.log(
|
|
1400
|
+
"[QR Print] Print result for card",
|
|
1401
|
+
qrCode._id,
|
|
1402
|
+
":",
|
|
1403
|
+
printResult
|
|
1023
1404
|
);
|
|
1024
|
-
console.log("[QR Print] Print result for card", qrCode._id, ":", printResult);
|
|
1025
1405
|
}
|
|
1026
1406
|
}
|
|
1027
1407
|
}
|
|
@@ -1035,13 +1415,139 @@ async function submit() {
|
|
|
1035
1415
|
const err = error?.data?.message;
|
|
1036
1416
|
errorMessage.value =
|
|
1037
1417
|
err ||
|
|
1038
|
-
`Failed to ${
|
|
1418
|
+
`Failed to ${
|
|
1419
|
+
prop.mode === "add" ? "add" : "update"
|
|
1039
1420
|
} visitor. Please try again.`;
|
|
1040
1421
|
} finally {
|
|
1041
1422
|
processing.value = false;
|
|
1042
1423
|
}
|
|
1043
1424
|
}
|
|
1044
1425
|
|
|
1426
|
+
function parseUsbId(value: unknown) {
|
|
1427
|
+
if (typeof value === "number") return value;
|
|
1428
|
+
const text = String(value || "").trim();
|
|
1429
|
+
if (!text) return NaN;
|
|
1430
|
+
return text.toLowerCase().startsWith("0x")
|
|
1431
|
+
? parseInt(text, 16)
|
|
1432
|
+
: parseInt(text, 10);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function getVisitorIdFromResponse(response: any) {
|
|
1436
|
+
if (typeof response === "string") return response;
|
|
1437
|
+
return (
|
|
1438
|
+
response?._id ||
|
|
1439
|
+
response?.id ||
|
|
1440
|
+
response?.data?._id ||
|
|
1441
|
+
response?.data?.id ||
|
|
1442
|
+
response?.insertedId ||
|
|
1443
|
+
response?.data?.insertedId ||
|
|
1444
|
+
""
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function getHidQrPrinterUsbIds() {
|
|
1449
|
+
const vendorId = parseUsbId(hidQrPrinterConfig.value.vendorId);
|
|
1450
|
+
const productId = parseUsbId(hidQrPrinterConfig.value.productId);
|
|
1451
|
+
return { vendorId, productId };
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function validateHidQrPrinterConfig() {
|
|
1455
|
+
if (!hasHidQrPrinterConfig.value) {
|
|
1456
|
+
errorMessage.value = "No printer is configured on HID settings. Please configure the HID QR Code printer before continuing.";
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const { vendorId, productId } = getHidQrPrinterUsbIds();
|
|
1461
|
+
|
|
1462
|
+
if (!Number.isFinite(vendorId) || !Number.isFinite(productId)) {
|
|
1463
|
+
errorMessage.value = "Invalid HID QR Code printer configuration. Please check Vendor and Product ID in Settings.";
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
return true;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function buildHidQrCodePayload(visitorId = "") {
|
|
1471
|
+
if (hidQrPrintPayload.value) return hidQrPrintPayload.value;
|
|
1472
|
+
|
|
1473
|
+
const issuedAt = new Date();
|
|
1474
|
+
const expiresAt = new Date(issuedAt.getTime() + hidQrValidityMinutes.value * 60 * 1000);
|
|
1475
|
+
const nonce =
|
|
1476
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
1477
|
+
? crypto.randomUUID()
|
|
1478
|
+
: `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1479
|
+
|
|
1480
|
+
hidQrPrintPayload.value = JSON.stringify({
|
|
1481
|
+
purpose: "hid-amico-visitor-access",
|
|
1482
|
+
visitorId,
|
|
1483
|
+
site: prop.site,
|
|
1484
|
+
org: prop.org,
|
|
1485
|
+
visitorType: prop.type,
|
|
1486
|
+
name: visitor.name || "",
|
|
1487
|
+
company: visitor.company || "",
|
|
1488
|
+
contact: visitor.contact || "",
|
|
1489
|
+
nric: visitor.nric || "",
|
|
1490
|
+
plateNumber: visitor.plateNumber || "",
|
|
1491
|
+
block: visitor.block || "",
|
|
1492
|
+
level: visitor.level || "",
|
|
1493
|
+
unit: visitor.unit || "",
|
|
1494
|
+
unitName: visitor.unitName || "",
|
|
1495
|
+
address: buildVisitorAddress(),
|
|
1496
|
+
issuedAt: issuedAt.toISOString(),
|
|
1497
|
+
expiresAt: expiresAt.toISOString(),
|
|
1498
|
+
nonce,
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
return hidQrPrintPayload.value;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function buildVisitorAddress() {
|
|
1505
|
+
const blockLabel = blocksArray.value.find((b: any) => b.value === visitor.block)?.title || "";
|
|
1506
|
+
const levelLabel = levelsArray.value.find((l: any) => l.value === visitor.level)?.title || "";
|
|
1507
|
+
const unitLabel = unitsArray.value.find((u: any) => u.value === visitor.unit)?.title || visitor.unitName || "";
|
|
1508
|
+
return [blockLabel, levelLabel, unitLabel].filter(Boolean).join("/");
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
|
|
1512
|
+
if (hidQrPrinted.value) return true;
|
|
1513
|
+
|
|
1514
|
+
if (!validateHidQrPrinterConfig()) return false;
|
|
1515
|
+
|
|
1516
|
+
const { vendorId, productId } = getHidQrPrinterUsbIds();
|
|
1517
|
+
|
|
1518
|
+
if (manageProcessing) processing.value = true;
|
|
1519
|
+
try {
|
|
1520
|
+
const printResult = await testConnection(
|
|
1521
|
+
vendorId,
|
|
1522
|
+
productId,
|
|
1523
|
+
true,
|
|
1524
|
+
buildHidQrCodePayload(visitorId),
|
|
1525
|
+
null,
|
|
1526
|
+
null,
|
|
1527
|
+
visitor.company || visitor.name || "",
|
|
1528
|
+
buildVisitorAddress(),
|
|
1529
|
+
hidQrTemplate.value?.header || "HID QR Code",
|
|
1530
|
+
hidQrTemplate.value?.subtext || "Scan QR code for access",
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
if (!printResult?.success || printResult?.printTest?.success === false) {
|
|
1534
|
+
errorMessage.value =
|
|
1535
|
+
printResult?.printTest?.error ||
|
|
1536
|
+
printResult?.error ||
|
|
1537
|
+
"Unable to print HID QR Code. Please check the printer connection.";
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
hidQrPrinted.value = true;
|
|
1542
|
+
return true;
|
|
1543
|
+
} catch (error: any) {
|
|
1544
|
+
errorMessage.value = error?.message || "Unable to print HID QR Code.";
|
|
1545
|
+
return false;
|
|
1546
|
+
} finally {
|
|
1547
|
+
if (manageProcessing) processing.value = false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1045
1551
|
watch(
|
|
1046
1552
|
() => visitor.plateNumber,
|
|
1047
1553
|
(newVal) => {
|
|
@@ -1049,6 +1555,22 @@ watch(
|
|
|
1049
1555
|
}
|
|
1050
1556
|
);
|
|
1051
1557
|
|
|
1558
|
+
watch(
|
|
1559
|
+
[
|
|
1560
|
+
() => passType.value,
|
|
1561
|
+
() => visitor.name,
|
|
1562
|
+
() => visitor.company,
|
|
1563
|
+
() => visitor.block,
|
|
1564
|
+
() => visitor.level,
|
|
1565
|
+
() => visitor.unit,
|
|
1566
|
+
() => visitor.unitName,
|
|
1567
|
+
],
|
|
1568
|
+
() => {
|
|
1569
|
+
hidQrPrintPayload.value = null;
|
|
1570
|
+
hidQrPrinted.value = false;
|
|
1571
|
+
}
|
|
1572
|
+
);
|
|
1573
|
+
|
|
1052
1574
|
const membersFieldIncomplete = computed(() => {
|
|
1053
1575
|
if (!visitor.members || visitor.members.length === 0) return false;
|
|
1054
1576
|
|
|
@@ -1065,9 +1587,12 @@ onMounted(() => {
|
|
|
1065
1587
|
contractorStep.value = 1;
|
|
1066
1588
|
currentAutofillSource.value = null;
|
|
1067
1589
|
|
|
1068
|
-
if (prop.mode ===
|
|
1069
|
-
console.log(
|
|
1070
|
-
|
|
1590
|
+
if (prop.mode === "register" && prop.visitorData) {
|
|
1591
|
+
console.log(
|
|
1592
|
+
"Register mode, prefill visitor data",
|
|
1593
|
+
prop.visitorData?.plateNumber
|
|
1594
|
+
);
|
|
1595
|
+
visitor.plateNumber = prop.visitorData?.plateNumber || "";
|
|
1071
1596
|
}
|
|
1072
1597
|
});
|
|
1073
1598
|
</script>
|