@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.
- package/CHANGELOG.md +13 -0
- package/components/AccessCardAddForm.vue +2 -2
- package/components/AccessCardAssignToUnitForm.vue +2 -2
- package/components/AccessCardDetailsDialog.vue +31 -1
- package/components/AccessManagement.vue +16 -6
- package/components/AddPassKeyToVisitor.vue +346 -225
- package/components/BuildingUnitFormEdit.vue +1 -1
- package/components/DashboardMain.vue +124 -60
- package/components/DocumentManagement.vue +294 -1
- package/components/HidIntercomManagement.vue +186 -0
- package/components/HidReaderManagement.vue +21 -1
- package/components/Nfc/NFCPatrolReportMain.vue +1 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +2 -1
- package/components/Nfc/PatrolReport/ReportFilters.vue +3 -1
- package/components/RolePermissionMain.vue +34 -5
- package/components/SiteSettings.vue +3 -1
- package/components/VisitorForm.vue +174 -398
- package/components/VisitorManagement.vue +302 -679
- package/components/VisitorSocketPopUp.vue +29 -1
- package/composables/useANPRSettings.ts +57 -8
- package/composables/useAccessManagement.ts +9 -1
- package/composables/useCleaningPermission.ts +2 -2
- package/composables/useCommonPermission.ts +2 -39
- package/composables/useDocument.ts +21 -0
- package/composables/useLocalAuth.ts +3 -1
- package/composables/useNFCPatrolLog.ts +35 -0
- package/composables/useNFCPatrolReport.ts +37 -4
- package/composables/useNFCPatrolReportFilters.ts +28 -5
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
- package/services/nfcPatrolReport.ts +5 -1
- package/types/nfc-patrol-report.ts +7 -1
- 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
|
-
|
|
20
|
-
<span
|
|
21
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
305
|
-
:
|
|
306
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<
|
|
349
|
-
v-model:
|
|
350
|
-
|
|
351
|
-
:
|
|
352
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
-
|
|
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;
|