@7365admin1/layer-common 3.0.31-staging.23 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/components/AddEqupmentForm.vue +1 -52
- package/components/AddPassKeyToVisitor.vue +53 -115
- package/components/DashboardMain.vue +87 -231
- package/components/EquipmentManagementMain.vue +0 -56
- package/components/InvitationMain.vue +3 -4
- package/components/ManageChecklistMain.vue +25 -32
- package/components/Nfc/NFCPatrolReportMain.vue +38 -128
- package/components/Nfc/PatrolReport/DailyReportTab.vue +6 -10
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +9 -19
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +3 -5
- package/components/Nfc/PatrolReport/ReportFilters.vue +18 -50
- package/components/PhotoUpload.vue +4 -19
- package/components/ServiceProviderMain.vue +29 -46
- package/components/VisitorForm.vue +182 -19
- package/components/VisitorManagement.vue +30 -109
- package/composables/useCleaningSchedulePermission.ts +51 -16
- package/composables/useEquipment.ts +0 -2
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +4 -1
- package/composables/useNFCPatrolMonthlyReport.ts +30 -62
- package/package.json +1 -1
- package/types/customer.d.ts +1 -2
- package/types/equipment.d.ts +1 -5
- package/types/nfc-patrol-report.ts +1 -15
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +0 -69
|
@@ -121,6 +121,77 @@
|
|
|
121
121
|
</v-row>
|
|
122
122
|
</v-col>
|
|
123
123
|
|
|
124
|
+
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
125
|
+
<v-row>
|
|
126
|
+
<v-col cols="12">
|
|
127
|
+
<InputLabel class="text-capitalize" title="NRIC" :required="requireNRIC" />
|
|
128
|
+
<v-menu v-model="nricSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
129
|
+
<template #activator="{ props }">
|
|
130
|
+
<InputNRICNumber v-bind="props" v-model="visitor.nric" density="comfortable" @focus="isNricFieldFocused = true" @blur="isNricFieldFocused = false"
|
|
131
|
+
:rules="[requireNRIC ? requiredRule : () => true]" :key="currentAutofillSource + 'nric-key'"
|
|
132
|
+
@update:model-value="handleUpdateNRIC" :loading="fetchPersonByNRICPending" />
|
|
133
|
+
</template>
|
|
134
|
+
|
|
135
|
+
<v-list v-if="nricSearchResults.length" class="pa-1">
|
|
136
|
+
<v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
|
|
137
|
+
@click="selectNricSearchResult(item)" class="cursor-pointer">
|
|
138
|
+
<v-list-item-title>{{ item.name || 'Unknown' }}</v-list-item-title>
|
|
139
|
+
<v-list-item-subtitle>{{ item.contact }}</v-list-item-subtitle>
|
|
140
|
+
</v-list-item>
|
|
141
|
+
</v-list>
|
|
142
|
+
</v-menu>
|
|
143
|
+
</v-col>
|
|
144
|
+
</v-row>
|
|
145
|
+
</v-col>
|
|
146
|
+
|
|
147
|
+
<v-col v-if="shouldShowField('contact')" cols="12">
|
|
148
|
+
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
149
|
+
<v-menu v-model="phoneSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
150
|
+
<template #activator="{ props }">
|
|
151
|
+
<InputPhoneNumberV2 v-bind="props" v-model="visitor.contact" :rules="[requiredRule]" @focus="isPhoneFieldFocused = true" @blur="isPhoneFieldFocused = false"
|
|
152
|
+
density="comfortable" :key="currentAutofillSource + 'phone-key'"
|
|
153
|
+
@update:model-value="handleUpdatePhoneNumber" />
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<v-list v-if="phoneSearchResults.length" class="pa-1">
|
|
157
|
+
<v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
|
|
158
|
+
@click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
159
|
+
<div class="d-flex align-center justify-space-between ga-4 w-100">
|
|
160
|
+
<span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
|
|
161
|
+
<span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
|
|
162
|
+
</div>
|
|
163
|
+
</v-list-item>
|
|
164
|
+
</v-list>
|
|
165
|
+
</v-menu>
|
|
166
|
+
</v-col>
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
170
|
+
<v-row>
|
|
171
|
+
<v-col cols="12">
|
|
172
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
|
|
173
|
+
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
174
|
+
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
175
|
+
<template #activator="{ props }">
|
|
176
|
+
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
177
|
+
@update:model-value="handleUpdateVehicleNumber" @focus="isPlateNumberFieldFocused = true" @blur="isPlateNumberFieldFocused = false" />
|
|
178
|
+
</template>
|
|
179
|
+
|
|
180
|
+
<v-list v-if="vehicleSearchResults.length" class="pa-1">
|
|
181
|
+
<v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
|
|
182
|
+
@click="selectPlateNumberSearchResult(item)"
|
|
183
|
+
class="cursor-pointer nric-search-item">
|
|
184
|
+
<div class="d-flex align-center justify-space-between ga-4 w-100">
|
|
185
|
+
<span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
|
|
186
|
+
<span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
|
|
187
|
+
</div>
|
|
188
|
+
</v-list-item>
|
|
189
|
+
</v-list>
|
|
190
|
+
</v-menu>
|
|
191
|
+
</v-col>
|
|
192
|
+
</v-row>
|
|
193
|
+
</v-col>
|
|
194
|
+
|
|
124
195
|
<v-col v-if="shouldShowField('deliveryType')" cols="12">
|
|
125
196
|
<v-row>
|
|
126
197
|
<v-col cols="12">
|
|
@@ -167,7 +238,7 @@
|
|
|
167
238
|
:loading="siteDataPending" variant="outlined" density="comfortable" persistent-hint small-chips>
|
|
168
239
|
<template v-slot:no-data>
|
|
169
240
|
<v-list-item>
|
|
170
|
-
<v-list-item-title v-if="
|
|
241
|
+
<v-list-item-title v-if="fetchCompanyListPending">
|
|
171
242
|
<v-progress-circular indeterminate size="20" class="mr-3" />
|
|
172
243
|
Searching companies…
|
|
173
244
|
</v-list-item-title>
|
|
@@ -189,7 +260,45 @@
|
|
|
189
260
|
</v-col>
|
|
190
261
|
</template>
|
|
191
262
|
|
|
263
|
+
<template v-if="shouldShowField('delivery-company')">
|
|
264
|
+
<v-col cols="12">
|
|
265
|
+
<InputLabel class="text-capitalize" title="Delivery Company" />
|
|
266
|
+
<v-combobox v-model="deliveryCompany" v-model:search="deliveryCompanyInput" ref="companyCombo"
|
|
267
|
+
autocomplete="off" :hide-no-data="false" :items="deliveryCompanyList" item-value="value"
|
|
268
|
+
:loading="siteDataPending" variant="outlined" density="comfortable" persistent-hint small-chips>
|
|
269
|
+
<template v-slot:no-data>
|
|
270
|
+
<v-list-item>
|
|
271
|
+
<v-list-item-title v-if="fetchCompanyListPending">
|
|
272
|
+
<v-progress-circular indeterminate size="20" class="mr-3" />
|
|
273
|
+
Searching companies…
|
|
274
|
+
</v-list-item-title>
|
|
275
|
+
<v-list-item-title v-else-if="companyNameInput" @click.stop="handleAddNewCompany"
|
|
276
|
+
class="d-flex align-center ga-1">
|
|
277
|
+
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
|
|
278
|
+
companyNameInput
|
|
279
|
+
}}</strong>" as new company.
|
|
280
|
+
</v-list-item-title>
|
|
281
|
+
<v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
|
|
282
|
+
Start typing to search for companies.
|
|
283
|
+
</v-list-item-title>
|
|
284
|
+
<v-list-item-title v-else>
|
|
285
|
+
No companies available. Start typing to add a new one.
|
|
286
|
+
</v-list-item-title>
|
|
287
|
+
</v-list-item>
|
|
288
|
+
</template>
|
|
289
|
+
</v-combobox>
|
|
290
|
+
</v-col>
|
|
291
|
+
</template>
|
|
192
292
|
|
|
293
|
+
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
294
|
+
<v-row>
|
|
295
|
+
<v-col cols="12">
|
|
296
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" required />
|
|
297
|
+
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
298
|
+
<InputVehicleNumber v-model="visitor.plateNumber" density="comfortable" />
|
|
299
|
+
</v-col>
|
|
300
|
+
</v-row>
|
|
301
|
+
</v-col>
|
|
193
302
|
|
|
194
303
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
195
304
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
@@ -307,7 +416,7 @@
|
|
|
307
416
|
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
308
417
|
</div>
|
|
309
418
|
|
|
310
|
-
<template v-if="selectedNricContactOptions.length >= 1">
|
|
419
|
+
<template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
|
|
311
420
|
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
312
421
|
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
313
422
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
@@ -315,7 +424,7 @@
|
|
|
315
424
|
</v-radio-group>
|
|
316
425
|
</template>
|
|
317
426
|
|
|
318
|
-
<template v-if="selectedNricPlateOptions.length >= 1">
|
|
427
|
+
<template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
|
|
319
428
|
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
320
429
|
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
321
430
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
@@ -683,10 +792,6 @@ const requireNRIC = computed(() => {
|
|
|
683
792
|
return prop.type !== "walk-in" && prop.type !== "guest";
|
|
684
793
|
});
|
|
685
794
|
|
|
686
|
-
const isVehicleRequired = computed(() => {
|
|
687
|
-
return ["guest", "drop-off", "pick-up"].includes(prop.type);
|
|
688
|
-
});
|
|
689
|
-
|
|
690
795
|
const hidQrCodePassConfig = computed(() => {
|
|
691
796
|
return (siteData.value as any)?.metadata?.hidQrCodePass || {};
|
|
692
797
|
});
|
|
@@ -754,6 +859,7 @@ type SearchPerson = Omit<Partial<TPeople>, "contact"> & {
|
|
|
754
859
|
contacts?: string | string[];
|
|
755
860
|
plateNumbers?: string | string[];
|
|
756
861
|
plateNumber?: string | string[];
|
|
862
|
+
plates?: { plateNumber?: string }[];
|
|
757
863
|
};
|
|
758
864
|
|
|
759
865
|
function getPeopleSearchItems(res: any): Partial<TPeople>[] {
|
|
@@ -803,7 +909,6 @@ async function handleAddNewContractorType() {
|
|
|
803
909
|
|
|
804
910
|
combo.isMenuActive = false;
|
|
805
911
|
combo.$el.querySelector("input")?.blur();
|
|
806
|
-
``;
|
|
807
912
|
}
|
|
808
913
|
|
|
809
914
|
async function handleSelectCompanyName(company: string) {
|
|
@@ -974,19 +1079,44 @@ function applyNricSearchResult(
|
|
|
974
1079
|
|
|
975
1080
|
currentAutofillSource.value = options.source || "nric";
|
|
976
1081
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1082
|
+
const shouldAutofillField = (fieldSource?: Exclude<AutofillSource, null>) => {
|
|
1083
|
+
if (!fieldSource) return false;
|
|
1084
|
+
return currentAutofillSource.value === fieldSource;
|
|
1085
|
+
};
|
|
1086
|
+
const getAutofillValue = (
|
|
1087
|
+
currentValue: string | undefined,
|
|
1088
|
+
nextValue: string | undefined,
|
|
1089
|
+
fieldSource?: Exclude<AutofillSource, null>
|
|
1090
|
+
) => {
|
|
1091
|
+
if (!nextValue) return currentValue;
|
|
1092
|
+
if (currentValue && !shouldAutofillField(fieldSource)) return currentValue;
|
|
1093
|
+
return nextValue;
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
visitor.nric = getAutofillValue(visitor.nric, item.nric, "nric");
|
|
1097
|
+
visitor.name = getAutofillValue(visitor.name, item.name);
|
|
1098
|
+
visitor.contact = getAutofillValue(
|
|
1099
|
+
visitor.contact,
|
|
1100
|
+
options.contacts || getPersonContactOptions(item)[0],
|
|
1101
|
+
"contact"
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
if(prop.type !== 'walk-in'){
|
|
1105
|
+
visitor.plateNumber = getAutofillValue(
|
|
1106
|
+
visitor.plateNumber,
|
|
1107
|
+
options.plateNumber || getPersonPlateOptions(item)[0],
|
|
1108
|
+
"vehicleNumber"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
981
1111
|
|
|
982
1112
|
companyNames.value = Array.isArray(item.companyName) ? item.companyName : [];
|
|
983
1113
|
|
|
984
|
-
if (item.companyName && item.companyName.length === 1) {
|
|
1114
|
+
if (!visitor.company && item.companyName && item.companyName.length === 1) {
|
|
985
1115
|
visitor.company = item.companyName?.[0] || visitor.company;
|
|
986
1116
|
}
|
|
987
1117
|
|
|
988
|
-
if (item.companyName && item.companyName.length > 1) {
|
|
989
|
-
visitor.company = ""
|
|
1118
|
+
if (!visitor.company && item.companyName && item.companyName.length > 1) {
|
|
1119
|
+
visitor.company = "";
|
|
990
1120
|
}
|
|
991
1121
|
|
|
992
1122
|
|
|
@@ -1019,7 +1149,10 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1019
1149
|
const selectedPlate =
|
|
1020
1150
|
findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
|
|
1021
1151
|
|
|
1022
|
-
|
|
1152
|
+
const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
|
|
1153
|
+
const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
|
|
1154
|
+
|
|
1155
|
+
if (needsContactChoice || needsPlateChoice) {
|
|
1023
1156
|
nricSearchMenu.value = false;
|
|
1024
1157
|
phoneSearchMenu.value = false;
|
|
1025
1158
|
vehicleSearchMenu.value = false;
|
|
@@ -1056,9 +1189,21 @@ function confirmNricAutofillOptions() {
|
|
|
1056
1189
|
closeNricAutofillOptions();
|
|
1057
1190
|
}
|
|
1058
1191
|
|
|
1192
|
+
const {
|
|
1193
|
+
data: fetchCompanyListReq,
|
|
1194
|
+
refresh: fetchCompanyListRefresh,
|
|
1195
|
+
pending: fetchCompanyListPending,
|
|
1196
|
+
} = useLazyAsyncData(`fetch-company-list`, () => {
|
|
1197
|
+
if (!companyNameInput.value) return Promise.resolve(null);
|
|
1198
|
+
return searchCompanyList(companyNameInput.value);
|
|
1199
|
+
});
|
|
1059
1200
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1201
|
+
watch(fetchCompanyListReq, (arr) => {
|
|
1202
|
+
if (Array.isArray(arr)) {
|
|
1203
|
+
companyAutofillDataArray.value = arr;
|
|
1204
|
+
companyNames.value = arr?.flatMap((x) => x?.companyName);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1062
1207
|
function getVisitorSearchItems(res: any): TVisitor[] {
|
|
1063
1208
|
const items =
|
|
1064
1209
|
res?.items ||
|
|
@@ -1170,6 +1315,18 @@ async function handleCheckout(visitorId: string) {
|
|
|
1170
1315
|
}
|
|
1171
1316
|
}
|
|
1172
1317
|
|
|
1318
|
+
const debounceFetchCompany = debounce(
|
|
1319
|
+
async () => fetchCompanyListRefresh(),
|
|
1320
|
+
200
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
watch(companyNameInput, async (val) => {
|
|
1324
|
+
if (!val) {
|
|
1325
|
+
companyNames.value = [];
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
await debounceFetchCompany();
|
|
1329
|
+
});
|
|
1173
1330
|
|
|
1174
1331
|
function handleAutofillDataViaVehicleNumber(item: TPeople) {
|
|
1175
1332
|
currentAutofillSource.value = "vehicleNumber";
|
|
@@ -1178,7 +1335,9 @@ function handleAutofillDataViaVehicleNumber(item: TPeople) {
|
|
|
1178
1335
|
visitor.nric = item.nric;
|
|
1179
1336
|
visitor.contact = item.contact;
|
|
1180
1337
|
companyNames.value = item.companyName ?? [];
|
|
1181
|
-
|
|
1338
|
+
if (!visitor.company) {
|
|
1339
|
+
visitor.company = companyNames.value?.[0];
|
|
1340
|
+
}
|
|
1182
1341
|
visitor.block = item.block ?? "";
|
|
1183
1342
|
visitor.level = item.level ?? "";
|
|
1184
1343
|
visitor.unit = item.unit ?? "";
|
|
@@ -1379,6 +1538,7 @@ function handleUpdateNRIC() {
|
|
|
1379
1538
|
if (skipNricFetch.value) return;
|
|
1380
1539
|
if (!isNricFieldFocused.value) return;
|
|
1381
1540
|
if (visitor.nric && visitor.nric?.length < 4) return;
|
|
1541
|
+
if(prop.type === 'walk-in' || prop.type === 'drop-off') return;
|
|
1382
1542
|
debounceFetchNRIC();
|
|
1383
1543
|
}
|
|
1384
1544
|
|
|
@@ -1410,6 +1570,7 @@ function handleUpdatePhoneNumber() {
|
|
|
1410
1570
|
phoneSearchMenu.value = false;
|
|
1411
1571
|
return;
|
|
1412
1572
|
}
|
|
1573
|
+
if(prop.type === 'walk-in' || prop.type === 'drop-off') return;
|
|
1413
1574
|
|
|
1414
1575
|
debounceFetchPhoneNumber();
|
|
1415
1576
|
}
|
|
@@ -1442,6 +1603,8 @@ function handleUpdateVehicleNumber() {
|
|
|
1442
1603
|
return;
|
|
1443
1604
|
}
|
|
1444
1605
|
|
|
1606
|
+
if(prop.type === 'walk-in' || prop.type === 'drop-off') return;
|
|
1607
|
+
|
|
1445
1608
|
debounceFetchPlateNumber();
|
|
1446
1609
|
}
|
|
1447
1610
|
|
|
@@ -315,9 +315,7 @@
|
|
|
315
315
|
style="cursor: pointer"
|
|
316
316
|
>
|
|
317
317
|
<v-chip
|
|
318
|
-
v-for="pass in item.visitorPass
|
|
319
|
-
(i) => i.status != 'Removed'
|
|
320
|
-
)"
|
|
318
|
+
v-for="pass in item.visitorPass"
|
|
321
319
|
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
322
320
|
prepend-icon="mdi-card-bulleted-outline"
|
|
323
321
|
size="x-small"
|
|
@@ -327,9 +325,7 @@
|
|
|
327
325
|
{{ (pass as any)?.prefixAndName }}
|
|
328
326
|
</v-chip>
|
|
329
327
|
<v-chip
|
|
330
|
-
v-for="key in item.passKeys
|
|
331
|
-
(i) => i.status != 'Removed'
|
|
332
|
-
)"
|
|
328
|
+
v-for="key in item.passKeys"
|
|
333
329
|
:key="(key as any)._id ?? (key as any).keyId"
|
|
334
330
|
prepend-icon="mdi-key"
|
|
335
331
|
size="x-small"
|
|
@@ -408,16 +404,9 @@
|
|
|
408
404
|
</v-menu>
|
|
409
405
|
|
|
410
406
|
<!-- QRCODE identifier -->
|
|
411
|
-
<div
|
|
412
|
-
v-if="
|
|
413
|
-
item.accessCards?.type === 'QRCODE' && item.accessCards?.cardNo
|
|
414
|
-
"
|
|
415
|
-
class="d-flex align-center ga-1 mt-1"
|
|
416
|
-
>
|
|
407
|
+
<div v-if="item.accessCards?.type === 'QRCODE' && item.accessCards?.cardNo" class="d-flex align-center ga-1 mt-1">
|
|
417
408
|
<v-icon size="15" icon="mdi-qrcode" color="teal" />
|
|
418
|
-
<v-chip size="x-small" variant="tonal" color="teal"
|
|
419
|
-
>QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip
|
|
420
|
-
>
|
|
409
|
+
<v-chip size="x-small" variant="tonal" color="teal">QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip>
|
|
421
410
|
</div>
|
|
422
411
|
</v-row>
|
|
423
412
|
</template>
|
|
@@ -825,9 +814,7 @@
|
|
|
825
814
|
item-title="label"
|
|
826
815
|
item-value="value"
|
|
827
816
|
v-model="pass.status"
|
|
828
|
-
:disabled="
|
|
829
|
-
selectedVisitorObject.checkOut || pass.status == 'Removed'
|
|
830
|
-
"
|
|
817
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
831
818
|
></v-select>
|
|
832
819
|
<v-textarea
|
|
833
820
|
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
@@ -864,9 +851,7 @@
|
|
|
864
851
|
item-title="label"
|
|
865
852
|
item-value="value"
|
|
866
853
|
v-model="key.status"
|
|
867
|
-
:disabled="
|
|
868
|
-
selectedVisitorObject.checkOut || key.status == 'Removed'
|
|
869
|
-
"
|
|
854
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
870
855
|
></v-select>
|
|
871
856
|
<v-textarea
|
|
872
857
|
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
@@ -926,87 +911,37 @@
|
|
|
926
911
|
<v-dialog v-model="dialog.returnNfcCard" max-width="450" persistent>
|
|
927
912
|
<v-card>
|
|
928
913
|
<v-toolbar density="compact" color="">
|
|
929
|
-
<v-row
|
|
930
|
-
no-gutters
|
|
931
|
-
class="d-flex fill-height justify-space-between align-center px-4"
|
|
932
|
-
>
|
|
914
|
+
<v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
|
|
933
915
|
<span class="font-weight-bold">Return NFC Card</span>
|
|
934
|
-
<v-btn
|
|
935
|
-
icon="mdi-close"
|
|
936
|
-
variant="text"
|
|
937
|
-
@click="dialog.returnNfcCard = false"
|
|
938
|
-
/>
|
|
916
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.returnNfcCard = false" />
|
|
939
917
|
</v-row>
|
|
940
918
|
</v-toolbar>
|
|
941
919
|
<v-card-text class="px-4 pb-2">
|
|
942
|
-
<p class="text-body-2 mb-3">
|
|
943
|
-
Please indicate the status of the NFC card before checking out.
|
|
944
|
-
</p>
|
|
920
|
+
<p class="text-body-2 mb-3">Please indicate the status of the NFC card before checking out.</p>
|
|
945
921
|
<div>
|
|
946
922
|
<InputLabel class="text-capitalize" title="Full Name" />
|
|
947
|
-
<v-text-field
|
|
948
|
-
v-model="selectedVisitorObject.name"
|
|
949
|
-
density="comfortable"
|
|
950
|
-
readonly
|
|
951
|
-
/>
|
|
923
|
+
<v-text-field v-model="selectedVisitorObject.name" density="comfortable" readonly />
|
|
952
924
|
</div>
|
|
953
925
|
<div>
|
|
954
926
|
<InputLabel class="text-capitalize" title="Location" />
|
|
955
|
-
<v-text-field
|
|
956
|
-
v-model="selectedVisitorObject.location"
|
|
957
|
-
density="comfortable"
|
|
958
|
-
readonly
|
|
959
|
-
/>
|
|
927
|
+
<v-text-field v-model="selectedVisitorObject.location" density="comfortable" readonly />
|
|
960
928
|
</div>
|
|
961
|
-
<div
|
|
962
|
-
v-for="(card, idx) in nfcCardReturnStatuses"
|
|
963
|
-
:key="card.cardId"
|
|
964
|
-
class="mb-4"
|
|
965
|
-
>
|
|
929
|
+
<div v-for="(card, idx) in nfcCardReturnStatuses" :key="card.cardId" class="mb-4">
|
|
966
930
|
<div class="d-flex align-center ga-2 mb-2">
|
|
967
931
|
<v-icon size="18" icon="mdi-credit-card-outline" color="purple" />
|
|
968
|
-
<v-chip size="small" variant="tonal" color="purple">{{
|
|
969
|
-
card.cardNo
|
|
970
|
-
}}</v-chip>
|
|
932
|
+
<v-chip size="small" variant="tonal" color="purple">{{ card.cardNo }}</v-chip>
|
|
971
933
|
</div>
|
|
972
|
-
<v-select
|
|
973
|
-
|
|
974
|
-
:items="nfcPassStatusOptions"
|
|
975
|
-
item-title="label"
|
|
976
|
-
item-value="value"
|
|
977
|
-
density="comfortable"
|
|
978
|
-
hide-details
|
|
979
|
-
/>
|
|
980
|
-
<v-textarea
|
|
981
|
-
v-if="card.status === 'Lost' || card.status === 'Damage'"
|
|
982
|
-
v-model="nfcCardReturnStatuses[idx].remarks"
|
|
983
|
-
label="Remarks (required)"
|
|
984
|
-
no-resize
|
|
985
|
-
rows="3"
|
|
986
|
-
class="mt-2"
|
|
987
|
-
density="compact"
|
|
988
|
-
/>
|
|
934
|
+
<v-select v-model="nfcCardReturnStatuses[idx].status" :items="nfcPassStatusOptions" item-title="label" item-value="value" density="comfortable" hide-details />
|
|
935
|
+
<v-textarea v-if="card.status === 'Lost' || card.status === 'Damage'" v-model="nfcCardReturnStatuses[idx].remarks" label="Remarks (required)" no-resize rows="3" class="mt-2" density="compact" />
|
|
989
936
|
</div>
|
|
990
937
|
</v-card-text>
|
|
991
938
|
<v-toolbar class="pa-0" density="compact">
|
|
992
939
|
<v-row no-gutters>
|
|
993
940
|
<v-col cols="6">
|
|
994
|
-
<v-btn variant="text" block @click="dialog.returnNfcCard = false"
|
|
995
|
-
>Close</v-btn
|
|
996
|
-
>
|
|
941
|
+
<v-btn variant="text" block @click="dialog.returnNfcCard = false">Close</v-btn>
|
|
997
942
|
</v-col>
|
|
998
943
|
<v-col cols="6">
|
|
999
|
-
<v-btn
|
|
1000
|
-
color="red"
|
|
1001
|
-
variant="flat"
|
|
1002
|
-
height="48"
|
|
1003
|
-
rounded="0"
|
|
1004
|
-
block
|
|
1005
|
-
:loading="loading.checkingOut"
|
|
1006
|
-
:disabled="!canConfirmNfcCheckout"
|
|
1007
|
-
@click="handleNfcCheckout"
|
|
1008
|
-
>Confirm Checkout</v-btn
|
|
1009
|
-
>
|
|
944
|
+
<v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut" :disabled="!canConfirmNfcCheckout" @click="handleNfcCheckout">Confirm Checkout</v-btn>
|
|
1010
945
|
</v-col>
|
|
1011
946
|
</v-row>
|
|
1012
947
|
</v-toolbar>
|
|
@@ -1036,7 +971,7 @@
|
|
|
1036
971
|
@close="dialog.editPassKey = false"
|
|
1037
972
|
@done="
|
|
1038
973
|
() => {
|
|
1039
|
-
|
|
974
|
+
dialog.editPassKey = false;
|
|
1040
975
|
getVisitorRefresh();
|
|
1041
976
|
}
|
|
1042
977
|
"
|
|
@@ -1891,8 +1826,7 @@ const nfcCardReturnStatuses = ref<
|
|
|
1891
1826
|
const canConfirmCheckout = computed(() => {
|
|
1892
1827
|
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
1893
1828
|
return allEntries.every((entry) => {
|
|
1894
|
-
if (!entry.status || ["In Use",
|
|
1895
|
-
return false;
|
|
1829
|
+
if (!entry.status || ["In Use","Not Returned"].includes(entry.status)) return false;
|
|
1896
1830
|
if (
|
|
1897
1831
|
(entry.status === "Lost" || entry.status === "Damaged") &&
|
|
1898
1832
|
!entry.remarks.trim()
|
|
@@ -1906,8 +1840,7 @@ const canConfirmNfcCheckout = computed(() => {
|
|
|
1906
1840
|
if (nfcCardReturnStatuses.value.length === 0) return false;
|
|
1907
1841
|
return nfcCardReturnStatuses.value.every(({ status, remarks }) => {
|
|
1908
1842
|
if (!status || status === "In Use") return false;
|
|
1909
|
-
if ((status === "Lost" || status === "Damage") && !remarks.trim())
|
|
1910
|
-
return false;
|
|
1843
|
+
if ((status === "Lost" || status === "Damage") && !remarks.trim()) return false;
|
|
1911
1844
|
return true;
|
|
1912
1845
|
});
|
|
1913
1846
|
});
|
|
@@ -1950,28 +1883,17 @@ function handleCheckout(userId: string) {
|
|
|
1950
1883
|
return;
|
|
1951
1884
|
}
|
|
1952
1885
|
|
|
1953
|
-
const hasNfc =
|
|
1954
|
-
visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
|
|
1886
|
+
const hasNfc = visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
|
|
1955
1887
|
if (hasNfc) {
|
|
1956
1888
|
const allCards: any[] = visitor.cards?.flat() ?? [];
|
|
1957
1889
|
nfcCardReturnStatuses.value = allCards.map((card: any) => ({
|
|
1958
1890
|
cardId: card._id,
|
|
1959
|
-
cardNo:
|
|
1960
|
-
card._id === visitor.accessCards._id
|
|
1961
|
-
? visitor.accessCards.cardNo
|
|
1962
|
-
: card._id,
|
|
1891
|
+
cardNo: card._id === visitor.accessCards._id ? visitor.accessCards.cardNo : card._id,
|
|
1963
1892
|
status: "In Use",
|
|
1964
1893
|
remarks: "",
|
|
1965
1894
|
}));
|
|
1966
1895
|
if (nfcCardReturnStatuses.value.length === 0) {
|
|
1967
|
-
nfcCardReturnStatuses.value = [
|
|
1968
|
-
{
|
|
1969
|
-
cardId: visitor.accessCards._id,
|
|
1970
|
-
cardNo: visitor.accessCards.cardNo,
|
|
1971
|
-
status: "In Use",
|
|
1972
|
-
remarks: "",
|
|
1973
|
-
},
|
|
1974
|
-
];
|
|
1896
|
+
nfcCardReturnStatuses.value = [{ cardId: visitor.accessCards._id, cardNo: visitor.accessCards.cardNo, status: "In Use", remarks: "" }];
|
|
1975
1897
|
}
|
|
1976
1898
|
dialog.returnNfcCard = true;
|
|
1977
1899
|
return;
|
|
@@ -2039,9 +1961,7 @@ async function handleNfcCheckout() {
|
|
|
2039
1961
|
})),
|
|
2040
1962
|
});
|
|
2041
1963
|
if (res) {
|
|
2042
|
-
await updateVisitor(userId as string, {
|
|
2043
|
-
checkOut: new Date().toISOString(),
|
|
2044
|
-
});
|
|
1964
|
+
await updateVisitor(userId as string, { checkOut: new Date().toISOString() });
|
|
2045
1965
|
showMessage("Visitor successfully checked-out!", "info");
|
|
2046
1966
|
await getVisitorRefresh();
|
|
2047
1967
|
dialog.returnNfcCard = false;
|
|
@@ -2049,10 +1969,7 @@ async function handleNfcCheckout() {
|
|
|
2049
1969
|
}
|
|
2050
1970
|
} catch (error: any) {
|
|
2051
1971
|
const errorMessage = error?.response?._data?.message;
|
|
2052
|
-
showMessage(
|
|
2053
|
-
errorMessage || "Something went wrong. Please try again later.",
|
|
2054
|
-
"error"
|
|
2055
|
-
);
|
|
1972
|
+
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
2056
1973
|
} finally {
|
|
2057
1974
|
loading.checkingOut = false;
|
|
2058
1975
|
}
|
|
@@ -2324,7 +2241,11 @@ const syncVisitorFiltersFromRoute = () => {
|
|
|
2324
2241
|
|
|
2325
2242
|
onMounted(syncVisitorFiltersFromRoute);
|
|
2326
2243
|
|
|
2327
|
-
watch(
|
|
2244
|
+
watch(
|
|
2245
|
+
() => route.query,
|
|
2246
|
+
syncVisitorFiltersFromRoute,
|
|
2247
|
+
{ deep: true }
|
|
2248
|
+
);
|
|
2328
2249
|
|
|
2329
2250
|
const { socketUnregisteredVisitorTrigger, visitorSocketData } =
|
|
2330
2251
|
useVisitorSocket();
|
|
@@ -1,23 +1,58 @@
|
|
|
1
1
|
export function useCleaningSchedulePermission() {
|
|
2
|
+
const { hasPermission } = usePermission();
|
|
3
|
+
const { permissions } = useCleaningPermission();
|
|
4
|
+
|
|
2
5
|
const { userAppRole } = useLocalSetup();
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const canViewSchedules = computed(() => {
|
|
8
|
+
if (!userAppRole.value) return false;
|
|
9
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
10
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-all-schedules");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const canViewScheduleDetails = computed(() => {
|
|
14
|
+
if (!userAppRole.value) return false;
|
|
15
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
16
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-schedule-details");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const canDownloadSchedule = computed(() => {
|
|
20
|
+
if (!userAppRole.value) return false;
|
|
21
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
22
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "download-schedule");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const canManageScheduleTasks = computed(() => {
|
|
26
|
+
if (!userAppRole.value) return false;
|
|
27
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
28
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "manage-schedule-tasks");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const canGenerateChecklist = computed(() => {
|
|
32
|
+
if (!userAppRole.value) return false;
|
|
33
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
34
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "generate-checklist");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const canViewHistory = computed(() => {
|
|
38
|
+
if (!userAppRole.value) return false;
|
|
39
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
40
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "view-history");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const canAddRemarks = computed(() => {
|
|
44
|
+
if (!userAppRole.value) return false;
|
|
45
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
46
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "add-remarks");
|
|
47
|
+
});
|
|
13
48
|
|
|
14
49
|
return {
|
|
15
|
-
canViewSchedules
|
|
16
|
-
canViewScheduleDetails
|
|
17
|
-
canDownloadSchedule
|
|
18
|
-
canManageScheduleTasks
|
|
19
|
-
canGenerateChecklist
|
|
20
|
-
canViewHistory
|
|
21
|
-
canAddRemarks
|
|
50
|
+
canViewSchedules,
|
|
51
|
+
canViewScheduleDetails,
|
|
52
|
+
canDownloadSchedule,
|
|
53
|
+
canManageScheduleTasks,
|
|
54
|
+
canGenerateChecklist,
|
|
55
|
+
canViewHistory,
|
|
56
|
+
canAddRemarks,
|
|
22
57
|
};
|
|
23
58
|
}
|
|
@@ -27,7 +27,6 @@ export default function useEquipment() {
|
|
|
27
27
|
name: payload.name,
|
|
28
28
|
unitOfMeasurement: payload.unitOfMeasurement,
|
|
29
29
|
serviceType,
|
|
30
|
-
...(payload.attachment ? { attachment: payload.attachment } : {}),
|
|
31
30
|
},
|
|
32
31
|
}
|
|
33
32
|
);
|
|
@@ -41,7 +40,6 @@ export default function useEquipment() {
|
|
|
41
40
|
body: {
|
|
42
41
|
name: payload.name,
|
|
43
42
|
unitOfMeasurement: payload.unitOfMeasurement,
|
|
44
|
-
...(payload.attachment ? { attachment: payload.attachment } : {}),
|
|
45
43
|
},
|
|
46
44
|
}
|
|
47
45
|
);
|