@7365admin1/layer-common 3.0.24 → 3.0.26

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/AccessCardAddForm.vue +2 -2
  3. package/components/AccessCardAssignToUnitForm.vue +2 -2
  4. package/components/AccessCardDetailsDialog.vue +31 -1
  5. package/components/AccessManagement.vue +16 -6
  6. package/components/AddPassKeyToVisitor.vue +346 -225
  7. package/components/BuildingUnitFormEdit.vue +1 -1
  8. package/components/DashboardMain.vue +124 -60
  9. package/components/DocumentManagement.vue +294 -1
  10. package/components/HidIntercomManagement.vue +186 -0
  11. package/components/HidReaderManagement.vue +21 -1
  12. package/components/Nfc/NFCPatrolReportMain.vue +1 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +2 -1
  14. package/components/Nfc/PatrolReport/ReportFilters.vue +3 -1
  15. package/components/RolePermissionMain.vue +34 -5
  16. package/components/SiteSettings.vue +3 -1
  17. package/components/VisitorForm.vue +174 -398
  18. package/components/VisitorManagement.vue +302 -679
  19. package/components/VisitorSocketPopUp.vue +29 -1
  20. package/composables/useANPRSettings.ts +57 -8
  21. package/composables/useAccessManagement.ts +9 -1
  22. package/composables/useCleaningPermission.ts +2 -2
  23. package/composables/useCommonPermission.ts +2 -39
  24. package/composables/useDocument.ts +21 -0
  25. package/composables/useLocalAuth.ts +3 -1
  26. package/composables/useNFCPatrolLog.ts +35 -0
  27. package/composables/useNFCPatrolReport.ts +37 -4
  28. package/composables/useNFCPatrolReportFilters.ts +28 -5
  29. package/package.json +1 -1
  30. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
  31. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
  32. package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
  33. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
  34. package/services/nfcPatrolReport.ts +5 -1
  35. package/types/nfc-patrol-report.ts +7 -1
  36. package/types/visitor.d.ts +1 -0
@@ -1,11 +1,7 @@
1
1
  <template>
2
2
  <v-card width="100%" :loading="processing">
3
3
  <v-toolbar>
4
- <v-row
5
- no-gutters
6
- class="fill-height px-6 d-flex justify-space-between align-center"
7
- align="center"
8
- >
4
+ <v-row no-gutters class="fill-height px-6 d-flex justify-space-between align-center" align="center">
9
5
  <span class="font-weight-bold text-h5 text-capitalize">
10
6
  {{ prop.mode }} {{ formatVisitorType(type) }}
11
7
  </span>
@@ -16,54 +12,25 @@
16
12
  <div class="w-100 d-flex justify-space-between ga-2">
17
13
  <span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
18
14
  formTitle
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
- >
15
+ }}</span>
16
+ <span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
17
+ <span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
27
18
  </div>
28
- <v-form
29
- ref="formRef"
30
- v-model="validForm"
31
- :disabled="processing"
32
- @click="errorMessage = ''"
33
- >
19
+ <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
34
20
  <v-row no-gutters class="pt-4">
35
21
  <v-col v-if="shouldShowField('contractorType')" cols="12">
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
- >
22
+ <InputLabel class="text-capitalize" title="Contractor Type" required />
23
+ <v-combobox v-model="contractorTypeObj" v-model:search="contractorTypeInput" ref="contractorTypeCombo"
24
+ autocomplete="off" :hide-no-data="false" @update:focused="handleFocusedContractorType"
25
+ :items="contractorTypes" :rules="[requiredRule]" item-value="value"
26
+ @update:model-value="handleSelectContractorType" variant="outlined" density="comfortable" persistent-hint
27
+ small-chips>
57
28
  <template v-slot:no-data>
58
29
  <v-list-item>
59
- <v-list-item-title
60
- @click.stop="handleAddNewContractorType"
61
- class="d-flex align-center ga-1"
62
- >
30
+ <v-list-item-title @click.stop="handleAddNewContractorType" class="d-flex align-center ga-1">
63
31
  <span><v-icon icon="mdi-plus" /></span> Add "<strong>{{
64
32
  contractorTypeInput
65
- }}</strong
66
- >" as custom contractor type.
33
+ }}</strong>" as custom contractor type.
67
34
  </v-list-item-title>
68
35
  </v-list-item>
69
36
  </template>
@@ -72,26 +39,14 @@
72
39
 
73
40
  <v-col v-if="shouldShowField('attachments')" class="w-100">
74
41
  <InputLabel class="text-capitalize" title="Attach Image" />
75
- <InputFileV2
76
- v-model="visitor.attachments"
77
- multiple
78
- :max-length="3"
79
- />
42
+ <InputFileV2 v-model="visitor.attachments" multiple :max-length="3" />
80
43
  </v-col>
81
44
 
82
45
  <v-col v-if="shouldShowField('name')" cols="12">
83
46
  <v-row>
84
47
  <v-col cols="12">
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
- />
48
+ <InputLabel class="text-capitalize" title="Full Name" required />
49
+ <v-text-field v-model.trim="visitor.name" density="comfortable" :rules="[requiredRule]" />
95
50
  </v-col>
96
51
  </v-row>
97
52
  </v-col>
@@ -99,44 +54,36 @@
99
54
  <v-col v-if="shouldShowField('nric')" cols="12">
100
55
  <v-row>
101
56
  <v-col cols="12">
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
- />
57
+ <InputLabel class="text-capitalize" title="NRIC" :required="requireNRIC" />
58
+ <v-menu v-model="nricSearchMenu" location="bottom" offset-y :close-on-content-click="false">
59
+ <template #activator="{ props }">
60
+ <InputNRICNumber v-bind="props" v-model="visitor.nric" density="comfortable"
61
+ :rules="[requireNRIC ? requiredRule : () => true]" :key="currentAutofillSource + 'nric-key'"
62
+ @update:model-value="handleUpdateNRIC" :loading="fetchPersonByNRICPending" />
63
+ </template>
64
+
65
+ <v-list v-if="nricSearchResults.length">
66
+ <v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
67
+ @click="selectNricSearchResult(item)" class="cursor-pointer">
68
+ <v-list-item-title>{{ item.name || 'Unknown' }}</v-list-item-title>
69
+ <v-list-item-subtitle>{{ item.contact }}</v-list-item-subtitle>
70
+ </v-list-item>
71
+ </v-list>
72
+ </v-menu>
115
73
  </v-col>
116
74
  </v-row>
117
75
  </v-col>
118
76
 
119
77
  <v-col v-if="shouldShowField('contact')" cols="12">
120
78
  <InputLabel class="text-capitalize" title="Phone Number" required />
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
- />
79
+ <InputPhoneNumberV2 v-model="visitor.contact" :rules="[requiredRule]" density="comfortable"
80
+ :key="currentAutofillSource + 'phone-key'" />
129
81
  </v-col>
130
82
 
131
83
  <v-col v-if="shouldShowField('deliveryType')" cols="12">
132
84
  <v-row>
133
85
  <v-col cols="12">
134
- <v-radio-group
135
- v-model="visitor.deliveryType"
136
- inline
137
- color="primary"
138
- :rules="[requiredRule]"
139
- >
86
+ <v-radio-group v-model="visitor.deliveryType" inline color="primary" :rules="[requiredRule]">
140
87
  <v-radio label="Food" value="Food"></v-radio>
141
88
  <v-radio label="Parcel" value="Parcel"></v-radio>
142
89
  </v-radio-group>
@@ -147,44 +94,23 @@
147
94
  <template v-if="shouldShowField('company')">
148
95
  <v-col cols="12">
149
96
  <InputLabel class="text-capitalize" title="Company Name" />
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
- >
97
+ <v-combobox v-model="visitor.company" v-model:search="companyNameInput" ref="companyCombo"
98
+ autocomplete="off" :hide-no-data="false" :items="companyNames" item-value="value"
99
+ :loading="fetchCompanyListPending" @update:model-value="handleSelectCompanyName" variant="outlined"
100
+ density="comfortable" persistent-hint small-chips>
165
101
  <template v-slot:no-data>
166
102
  <v-list-item>
167
103
  <v-list-item-title v-if="fetchCompanyListPending">
168
- <v-progress-circular
169
- indeterminate
170
- size="20"
171
- class="mr-3"
172
- />
104
+ <v-progress-circular indeterminate size="20" class="mr-3" />
173
105
  Searching companies…
174
106
  </v-list-item-title>
175
- <v-list-item-title
176
- v-else-if="companyNameInput"
177
- @click.stop="handleAddNewCompany"
178
- class="d-flex align-center ga-1"
179
- >
107
+ <v-list-item-title v-else-if="companyNameInput" @click.stop="handleAddNewCompany"
108
+ class="d-flex align-center ga-1">
180
109
  <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
181
110
  companyNameInput
182
- }}</strong
183
- >" as new company.
111
+ }}</strong>" as new company.
184
112
  </v-list-item-title>
185
- <v-list-item-title
186
- v-else-if="!companyNameInput && companyNames.length === 0"
187
- >
113
+ <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
188
114
  Start typing to search for companies.
189
115
  </v-list-item-title>
190
116
  <v-list-item-title v-else>
@@ -199,43 +125,22 @@
199
125
  <template v-if="shouldShowField('delivery-company')">
200
126
  <v-col cols="12">
201
127
  <InputLabel class="text-capitalize" title="Delivery Company" />
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
- >
128
+ <v-combobox v-model="deliveryCompany" v-model:search="deliveryCompanyInput" ref="companyCombo"
129
+ autocomplete="off" :hide-no-data="false" :items="deliveryCompanyList" item-value="value"
130
+ :loading="siteDataPending" variant="outlined" density="comfortable" persistent-hint small-chips>
216
131
  <template v-slot:no-data>
217
132
  <v-list-item>
218
133
  <v-list-item-title v-if="fetchCompanyListPending">
219
- <v-progress-circular
220
- indeterminate
221
- size="20"
222
- class="mr-3"
223
- />
134
+ <v-progress-circular indeterminate size="20" class="mr-3" />
224
135
  Searching companies…
225
136
  </v-list-item-title>
226
- <v-list-item-title
227
- v-else-if="companyNameInput"
228
- @click.stop="handleAddNewCompany"
229
- class="d-flex align-center ga-1"
230
- >
137
+ <v-list-item-title v-else-if="companyNameInput" @click.stop="handleAddNewCompany"
138
+ class="d-flex align-center ga-1">
231
139
  <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
232
140
  companyNameInput
233
- }}</strong
234
- >" as new company.
141
+ }}</strong>" as new company.
235
142
  </v-list-item-title>
236
- <v-list-item-title
237
- v-else-if="!companyNameInput && companyNames.length === 0"
238
- >
143
+ <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
239
144
  Start typing to search for companies.
240
145
  </v-list-item-title>
241
146
  <v-list-item-title v-else>
@@ -250,135 +155,61 @@
250
155
  <v-col v-if="shouldShowField('plateNumber')" cols="12">
251
156
  <v-row>
252
157
  <v-col cols="12">
253
- <InputLabel
254
- class="text-capitalize"
255
- title="Vehicle Number"
256
- required
257
- />
158
+ <InputLabel class="text-capitalize" title="Vehicle Number" required />
258
159
  <!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
259
- <InputVehicleNumber
260
- v-model="visitor.plateNumber"
261
- density="comfortable"
262
- />
160
+ <InputVehicleNumber v-model="visitor.plateNumber" density="comfortable" />
263
161
  </v-col>
264
162
  </v-row>
265
163
  </v-col>
266
164
 
267
165
  <v-col v-if="shouldShowField('block')" cols="12">
268
166
  <InputLabel class="text-capitalize" title="Block" required />
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
- />
167
+ <v-autocomplete v-model="visitor.block" :items="blocksArray" item-value="value" autocomplete="off"
168
+ item-title="title" :key="blockListDataPending ? 'block-list-pending' : 'block-list-ready'
169
+ " :loading="blockListDataPending" @update:model-value="handleChangeBlock" density="comfortable"
170
+ :rules="[requiredRule]" />
283
171
  </v-col>
284
172
 
285
173
  <v-col v-if="shouldShowField('level')" cols="12">
286
174
  <InputLabel class="text-capitalize" title="Level" required />
287
- <v-autocomplete
288
- v-model="visitor.level"
289
- :items="levelsArray"
290
- density="comfortable"
291
- autocomplete="off"
292
- :disabled="!visitor.block"
293
- :key="
294
- levelsDataPending ? 'level-list-pending' : 'level-list-ready'
295
- "
296
- :loading="levelsDataPending"
297
- @update:model-value="handleChangeLevel"
298
- :rules="[requiredRule]"
299
- />
175
+ <v-autocomplete v-model="visitor.level" :items="levelsArray" density="comfortable" autocomplete="off"
176
+ :disabled="!visitor.block" :key="levelsDataPending ? 'level-list-pending' : 'level-list-ready'
177
+ " :loading="levelsDataPending" @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
300
178
  </v-col>
301
179
  <v-col v-if="shouldShowField('unit')" cols="12">
302
180
  <InputLabel class="text-capitalize" title="Unit" required />
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="unitsDataPending ? 'unit-list-pending' : 'unit-list-ready'"
312
- :loading="unitsDataPending"
313
- :rules="[requiredRule]"
314
- @update:model-value="handleUpdateUnit"
315
- />
181
+ <v-autocomplete v-model="visitor.unit" :items="unitsArray" density="comfortable" autocomplete="off"
182
+ item-title="title" item-value="value" :disabled="!visitor.level"
183
+ :key="unitsDataPending ? 'unit-list-pending' : 'unit-list-ready'" :loading="unitsDataPending"
184
+ :rules="[requiredRule]" @update:model-value="handleUpdateUnit" />
316
185
  </v-col>
317
186
 
318
187
  <v-col v-if="shouldShowField('unit')" cols="12">
319
- <InputLabel
320
- class="text-capitalize"
321
- title="Registered Unit Company Name"
322
- required
323
- :key="'unit-key' + visitor.level"
324
- />
325
- <v-text-field
326
- v-model.trim="registeredUnitCompanyName"
327
- density="comfortable"
328
- :loading="buildingUnitDataPending"
329
- readonly
330
- class="no-pointer"
331
- />
188
+ <InputLabel class="text-capitalize" title="Registered Unit Company Name" required
189
+ :key="'unit-key' + visitor.level" />
190
+ <v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable"
191
+ :loading="buildingUnitDataPending" readonly class="no-pointer" />
332
192
  </v-col>
333
193
 
334
194
  <v-col v-if="shouldShowField('remarks')" cols="12">
335
195
  <InputLabel class="text-capitalize" title="Remarks" />
336
- <v-textarea
337
- v-model="visitor.remarks"
338
- density="comfortable"
339
- :rows="3"
340
- no-resize
341
- />
196
+ <v-textarea v-model="visitor.remarks" density="comfortable" :rows="3" no-resize />
342
197
  </v-col>
343
198
 
344
- <v-col
345
- v-show="(withStep2 || withStep3) && contractorStep === 2"
346
- cols="12"
347
- >
348
- <PassInformation
349
- v-model:pass="visitor.visitorPass"
350
- v-model:keys="visitor.passKeys"
351
- :site="prop.site"
352
- :type="prop.type"
353
- :contractor-type="visitor.contractorType"
354
- :hide-keys="!showShowKeysField"
355
- :passKeys="visitor.passKeys"
356
- :clearable="true"
357
- />
358
- <EntryPassInformation
359
- v-if="showEntryPassInformation"
360
- v-model="passType"
361
- v-model:quantity="passQuantity"
362
- v-model:cards="passCards"
363
- :settings="entryPassSettings"
364
- :loading="entryPassSettingsPending || siteDataPending"
365
- :site-id="prop.site"
366
- :unit-id="visitor.unit || null"
367
- :visitor-type="prop.type"
368
- :hid-qr-code-enabled="hidQrCodePassAllowed"
369
- :hid-qr-printer-configured="hasHidQrPrinterConfig"
370
- />
199
+ <v-col v-show="(withStep2 || withStep3) && contractorStep === 2" cols="12">
200
+ <PassInformation v-model:pass="visitor.visitorPass" v-model:keys="visitor.passKeys" :site="prop.site"
201
+ :type="prop.type" :contractor-type="visitor.contractorType" :hide-keys="!showShowKeysField"
202
+ :passKeys="visitor.passKeys" :clearable="true" />
203
+ <EntryPassInformation v-if="showEntryPassInformation" v-model="passType" v-model:quantity="passQuantity"
204
+ v-model:cards="passCards" :settings="entryPassSettings"
205
+ :loading="entryPassSettingsPending || siteDataPending" :site-id="prop.site"
206
+ :unit-id="visitor.unit || null" :visitor-type="prop.type" :hid-qr-code-enabled="hidQrCodePassAllowed"
207
+ :hid-qr-printer-configured="hasHidQrPrinterConfig" />
371
208
  </v-col>
372
209
 
373
210
  <v-col v-show="withStep3 && contractorStep === 3" cols="12">
374
- <MemberInformation
375
- v-model="visitor.members"
376
- :type="prop.type"
377
- :contractor-type="visitor.contractorType"
378
- :site="prop.site"
379
- :selected-visitor-pass="visitor.visitorPass"
380
- :clearable="true"
381
- />
211
+ <MemberInformation v-model="visitor.members" :type="prop.type" :contractor-type="visitor.contractorType"
212
+ :site="prop.site" :selected-visitor-pass="visitor.visitorPass" :clearable="true" />
382
213
  </v-col>
383
214
 
384
215
  <v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
@@ -402,89 +233,33 @@
402
233
  <v-toolbar density="compact">
403
234
  <v-row no-gutters>
404
235
  <v-col cols="6">
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
- />
236
+ <v-btn v-if="
237
+ prop.mode === 'add' &&
238
+ (withStep2 || withStep3) &&
239
+ contractorStep > 1
240
+ " tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
241
+ <v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none"
242
+ size="48" @click="backToSelection" text="Back to Selection" />
243
+ <v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
439
244
  </v-col>
440
245
  <v-col cols="6">
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
- />
246
+ <v-btn v-if="showNextButton" tile block variant="flat" color="primary-button" class="text-none" size="48"
247
+ :disabled="processing" @click="handleNextPage" :text="'Next'" />
248
+ <v-btn v-else tile block variant="flat" color="black" class="text-none" size="48"
249
+ :disabled="!validForm || processing || membersFieldIncomplete" @click="submit"
250
+ :text="prop.mode == 'add' ? 'Submit' : 'Update'" />
465
251
  </v-col>
466
252
  </v-row>
467
253
  </v-toolbar>
468
254
 
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
- />
255
+ <v-dialog v-model="dialog.vehicleNumberUsersList" v-if="vehicleNumberUserItems.length > 0" persistent
256
+ max-width="600">
257
+ <SearchVehicleNumberUser :vehicle-number="visitor.plateNumber ?? ''"
258
+ :vehicle-number-users-list="vehicleNumberUserItems" @close="dialog.vehicleNumberUsersList = false"
259
+ @update:people="handleAutofillDataViaVehicleNumber" />
481
260
  </v-dialog>
482
261
 
483
- <v-dialog
484
- v-model="dialog.showNonCheckedOutDialog"
485
- max-width="700"
486
- persistent
487
- >
262
+ <v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="700" persistent>
488
263
  <v-card :loading="loading.checkingOut">
489
264
  <v-toolbar>
490
265
  <v-toolbar-title>
@@ -498,12 +273,8 @@
498
273
 
499
274
  <v-card-text>
500
275
  <v-list lines="three">
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
- >
276
+ <v-list-item v-if="matchingPlateNumberNonCheckedOutArr.length > 0"
277
+ v-for="v in matchingPlateNumberNonCheckedOutArr" :key="v._id" class="cursor-pointer">
507
278
  <v-list-item-title>
508
279
  {{ v.plateNumber }} - {{ v.name }}
509
280
  </v-list-item-title>
@@ -513,16 +284,8 @@
513
284
  </v-list-item-subtitle>
514
285
 
515
286
  <template #append>
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
- />
287
+ <v-btn size="x-small" class="text-capitalize" color="red" text="Checkout" :loading="loading.checkingOut && v?._id === prop.visitorData?._id
288
+ " @click.stop="handleCheckout(v._id)" />
526
289
  </template>
527
290
  </v-list-item>
528
291
  </v-list>
@@ -536,6 +299,14 @@
536
299
  import { propsFactory } from "vuetify/lib/util/propsFactory.mjs";
537
300
  import useBuildingUnit from "../composables/useBuildingUnit";
538
301
  import useWebUsb from "../composables/useWebUsb";
302
+ import useUtils from "../composables/useUtils";
303
+ import useSiteSettings from "../composables/useSiteSettings";
304
+ import useLocalAuth from "../composables/useLocalAuth";
305
+ import useBuilding from "../composables/useBuilding";
306
+ import useVisitor from "../composables/useVisitor";
307
+ import useSiteEntryPassSettings from "../composables/useSiteEntryPassSettings";
308
+ import useAccessManagement from "../composables/useAccessManagement";
309
+ import usePeople from "../composables/usePeople";
539
310
 
540
311
  const prop = defineProps({
541
312
  type: {
@@ -569,6 +340,7 @@ const currentAutofillSource = ref<AutofillSource>(null);
569
340
 
570
341
  const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
571
342
  const { getSiteById } = useSiteSettings();
343
+ const { currentUser } = useLocalAuth();
572
344
  const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
573
345
  useBuilding();
574
346
  const {
@@ -584,7 +356,7 @@ const { createVisitorPass, signQr } = useAccessManagement();
584
356
  const { testConnection } = useWebUsb();
585
357
  const {
586
358
  findPersonByNRIC,
587
- findPersonByContact,
359
+ findPersonByNRICMultipleResult,
588
360
  searchCompanyList,
589
361
  findUsersByPlateNumber,
590
362
  } = usePeople();
@@ -734,7 +506,7 @@ const hidQrCodePassAllowed = computed(() => {
734
506
  const showEntryPassInformation = computed(() => {
735
507
  return Boolean(
736
508
  entryPassSettings.value?.data?.settings?.nfcPass ||
737
- hidQrCodePassAllowed.value
509
+ hidQrCodePassAllowed.value
738
510
  );
739
511
  });
740
512
 
@@ -748,6 +520,9 @@ const companyAutofillDataArray = ref<
748
520
  unitName: string;
749
521
  }[]
750
522
  >([]);
523
+ const nricSearchMenu = ref(false);
524
+ const nricSearchResults = ref<Partial<TPeople>[]>([]);
525
+ const skipNricFetch = ref(false);
751
526
 
752
527
  const formTitle = computed(() => {
753
528
  const isContractorForm = prop.type === "contractor";
@@ -819,57 +594,50 @@ const {
819
594
  pending: fetchPersonByNRICPending,
820
595
  } = useLazyAsyncData(`fetch-person`, () => {
821
596
  const NRIC = visitor.nric;
822
- if (!NRIC) return Promise.resolve(null);
823
- return findPersonByNRIC(NRIC);
597
+ if (!NRIC || NRIC.length < 4) return Promise.resolve(null);
598
+ return findPersonByNRICMultipleResult(NRIC, prop.site);
824
599
  });
825
600
 
826
601
  watch(fetchPersonByNRICReq, (obj) => {
827
- if (obj) {
828
- currentAutofillSource.value = "nric";
829
- companyNames.value = obj.companyName ?? [];
830
- visitor.name = obj.name;
831
- visitor.contact = obj.contact;
832
- if (!visitor.company) {
833
- visitor.company = companyNames.value?.[0];
834
- }
835
- visitor.block = obj.block ?? "";
836
- visitor.level = obj.level ?? "";
837
- visitor.unit = obj.unit ?? "";
602
+ const items = obj?.items ?? [];
603
+ nricSearchResults.value = Array.isArray(items) ? items : [];
604
+ nricSearchMenu.value = nricSearchResults.value.length > 0;
605
+ });
838
606
 
839
- setTimeout(() => {
840
- currentAutofillSource.value = null;
841
- }, 1000);
607
+ watch(
608
+ () => visitor.nric,
609
+ (value) => {
610
+ if (!value || value.length < 4) {
611
+ nricSearchMenu.value = false;
612
+ nricSearchResults.value = [];
613
+ }
842
614
  }
843
- });
615
+ );
844
616
 
845
- const {
846
- data: fetchPersonByContactReq,
847
- refresh: fetchPersonByContactRefresh,
848
- pending: fetchPersonByContactPending,
849
- } = useLazyAsyncData(`fetch-contact`, () => {
850
- const contact = visitor.contact;
851
- if (!contact) return Promise.resolve(null);
852
- return findPersonByContact(contact);
853
- });
617
+ function selectNricSearchResult(item: Partial<TPeople>) {
618
+ nricSearchMenu.value = false;
619
+ nricSearchResults.value = [];
620
+ skipNricFetch.value = true;
854
621
 
855
- watch(fetchPersonByContactReq, (obj: Partial<TPerson>) => {
856
- if (obj) {
857
- currentAutofillSource.value = "contact";
858
- companyNames.value = obj.companyName ?? [];
859
- visitor.name = visitor.name ?? obj.name;
860
- if (!visitor.company) {
861
- visitor.company = companyNames.value?.[0];
862
- }
863
- // visitor.block = visitor.block ?? obj.block ?? ""
864
- // visitor.level = visitor.level ?? obj.level ?? ""
865
- // visitor.unit = visitor.unit ?? obj.unit ?? ""
866
- visitor.nric = visitor.nric ?? obj.nric ?? "";
867
-
868
- setTimeout(() => {
869
- currentAutofillSource.value = null;
870
- }, 1000);
622
+ currentAutofillSource.value = "nric";
623
+
624
+ visitor.nric = item.nric || visitor.nric;
625
+ visitor.name = item.name || visitor.name;
626
+ visitor.contact = item.contact || visitor.contact;
627
+ companyNames.value = item.companyName ?? [];
628
+ if (!visitor.company) {
629
+ visitor.company = companyNames.value?.[0] || visitor.company;
871
630
  }
872
- });
631
+ visitor.block = (item.block as any) ?? (visitor.block as any) ?? "";
632
+ visitor.level = (item.level as any) ?? (visitor.level as any) ?? "";
633
+ visitor.unit = item.unit ?? visitor.unit ?? "";
634
+ visitor.unitName = item.unitName ?? visitor.unitName;
635
+
636
+ setTimeout(() => {
637
+ currentAutofillSource.value = null;
638
+ skipNricFetch.value = false;
639
+ }, 1000);
640
+ }
873
641
 
874
642
  const {
875
643
  data: fetchCompanyListReq,
@@ -1171,20 +939,14 @@ function handleUpdateUnit(value: any) {
1171
939
  }
1172
940
 
1173
941
  const debounceFetchNRIC = debounce(fetchPersonByNRICRefresh, 500);
1174
- const debounceFetchContact = debounce(fetchPersonByContactRefresh, 500);
1175
942
 
1176
943
  function handleUpdateNRIC() {
944
+ if (skipNricFetch.value) return;
1177
945
  if (currentAutofillSource.value && currentAutofillSource.value !== "nric")
1178
946
  return;
1179
947
  debounceFetchNRIC();
1180
948
  }
1181
949
 
1182
- function handleUpdateContact() {
1183
- if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
1184
- return;
1185
- debounceFetchContact();
1186
- }
1187
-
1188
950
  function backToSelection() {
1189
951
  emit("back");
1190
952
  message.value = "";
@@ -1228,6 +990,10 @@ async function handleNextPage() {
1228
990
  return;
1229
991
  }
1230
992
  const step = contractorStep.value;
993
+ if (step === 2 && passType.value === "NFC" && passCards.value.length === 0) {
994
+ errorMessage.value = "Please select at least one NFC card before proceeding.";
995
+ return;
996
+ }
1231
997
  if (step < 3) contractorStep.value++;
1232
998
  else await submit();
1233
999
  }
@@ -1291,6 +1057,10 @@ async function submit() {
1291
1057
  // add register status when registering unregistered visitor
1292
1058
  if (prop.mode === "register") {
1293
1059
  payload.status = "registered";
1060
+ const currentUserId = currentUser.value?._id;
1061
+ if(currentUserId) {
1062
+ payload.updatedBy = currentUserId;
1063
+ }
1294
1064
  }
1295
1065
 
1296
1066
  try {
@@ -1321,8 +1091,7 @@ async function submit() {
1321
1091
  const visitorId = res as unknown as string;
1322
1092
  const rawUrl = entryPassSettings.value?.data?.settings?.url;
1323
1093
  const { encrypted: acmUrl } = await $fetch<{ encrypted: string }>(
1324
- `/api/encrypt-acm-url${
1325
- rawUrl ? `?url=${encodeURIComponent(rawUrl)}` : ""
1094
+ `/api/encrypt-acm-url${rawUrl ? `?url=${encodeURIComponent(rawUrl)}` : ""
1326
1095
  }`
1327
1096
  );
1328
1097
  if (visitorId && acmUrl) {
@@ -1333,7 +1102,15 @@ async function submit() {
1333
1102
  type: passType.value === "QR" ? "QRCODE" : passType.value,
1334
1103
  nfcCards: passCards.value,
1335
1104
  acm_url: acmUrl,
1336
- visitorId,
1105
+ user: {
1106
+ visitorId,
1107
+ name: visitor.name,
1108
+ nric: visitor.nric,
1109
+ contact: visitor.contact,
1110
+ company: visitor.company,
1111
+ type: visitor.type,
1112
+ plateNumber: visitor.plateNumber,
1113
+ },
1337
1114
  });
1338
1115
 
1339
1116
  if (passType.value === "QR") {
@@ -1431,8 +1208,7 @@ async function submit() {
1431
1208
  const err = error?.data?.message;
1432
1209
  errorMessage.value =
1433
1210
  err ||
1434
- `Failed to ${
1435
- prop.mode === "add" ? "add" : "update"
1211
+ `Failed to ${prop.mode === "add" ? "add" : "update"
1436
1212
  } visitor. Please try again.`;
1437
1213
  } finally {
1438
1214
  processing.value = false;