@7365admin1/layer-common 3.1.1 → 3.1.2-staging.30
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/.github/workflows/publish-staging.yml +11 -0
- package/components/AddEqupmentForm.vue +52 -1
- package/components/AddPassKeyToVisitor.vue +115 -53
- package/components/DashboardMain.vue +231 -87
- package/components/EquipmentManagementMain.vue +56 -0
- package/components/InvitationMain.vue +4 -3
- package/components/ManageChecklistMain.vue +44 -38
- package/components/Nfc/NFCPatrolReportMain.vue +128 -38
- package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
- package/components/Nfc/PatrolReport/ReportFilters.vue +50 -18
- package/components/PhotoUpload.vue +19 -4
- package/components/ServiceProviderMain.vue +46 -29
- package/components/VisitorForm.vue +5 -5
- package/components/VisitorManagement.vue +109 -30
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/composables/useEquipment.ts +2 -0
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +1 -4
- package/composables/useNFCPatrolMonthlyReport.ts +62 -30
- package/package.json +1 -1
- package/types/customer.d.ts +2 -1
- package/types/equipment.d.ts +5 -1
- package/types/nfc-patrol-report.ts +15 -1
|
@@ -548,7 +548,7 @@
|
|
|
548
548
|
variant="flat"
|
|
549
549
|
class="text-none sp-invite-submit-btn"
|
|
550
550
|
height="76"
|
|
551
|
-
:disabled="!
|
|
551
|
+
:disabled="!canSubmitInvite"
|
|
552
552
|
:loading="disableServiceProvider"
|
|
553
553
|
@click="submitServiceProviderInvite()"
|
|
554
554
|
>
|
|
@@ -574,6 +574,7 @@ type ListTab = "active" | "pending" | "inactive";
|
|
|
574
574
|
|
|
575
575
|
type TableRow = {
|
|
576
576
|
_id: string;
|
|
577
|
+
userId: string;
|
|
577
578
|
serviceProviderOrgId: string;
|
|
578
579
|
companyName: string;
|
|
579
580
|
email: string;
|
|
@@ -733,11 +734,10 @@ const {
|
|
|
733
734
|
updateStatus: updateServiceProviderStatus,
|
|
734
735
|
invite,
|
|
735
736
|
} = useServiceProvider();
|
|
736
|
-
const {
|
|
737
|
+
const { getAllByUserId } = useMember();
|
|
737
738
|
|
|
738
739
|
const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
|
|
739
740
|
useVerification();
|
|
740
|
-
const { getOrgsByMembership } = useMember();
|
|
741
741
|
|
|
742
742
|
const existingAccount = ref<any>(null);
|
|
743
743
|
const checkingExistingAccount = ref(false);
|
|
@@ -757,11 +757,10 @@ const createInviteDialog = ref(false);
|
|
|
757
757
|
// const loadingInviteRoles = ref(false);
|
|
758
758
|
// const serviceProviderInviteRoles = ref<Array<Record<string, any>>>([]);
|
|
759
759
|
|
|
760
|
-
const { getByUserType } = useMember();
|
|
761
|
-
const { getOrgs } = useOrg();
|
|
762
760
|
|
|
763
761
|
async function checkExistingAccount(email: string) {
|
|
764
762
|
existingAccount.value = null;
|
|
763
|
+
messageServiceProvider.value = "";
|
|
765
764
|
|
|
766
765
|
if (!email.trim() || emailRule(email) !== true) return;
|
|
767
766
|
|
|
@@ -770,30 +769,50 @@ async function checkExistingAccount(email: string) {
|
|
|
770
769
|
try {
|
|
771
770
|
const user = await getUserByEmail(email);
|
|
772
771
|
|
|
773
|
-
|
|
772
|
+
if (!user?._id) return;
|
|
774
773
|
|
|
775
|
-
|
|
776
|
-
user: user._id,
|
|
777
|
-
});
|
|
774
|
+
const members = await getAllByUserId(user._id);
|
|
778
775
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
776
|
+
const appMember = (members.items ?? []).find(
|
|
777
|
+
(member: any) => member.type === serviceProviderInvite.value.app
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
if (!appMember) {
|
|
781
|
+
existingAccount.value = null;
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (
|
|
786
|
+
appMember.org === props.orgId &&
|
|
787
|
+
(appMember.siteId || "") === (props.siteId || "")
|
|
788
|
+
) {
|
|
789
|
+
existingAccount.value = null;
|
|
790
|
+
messageServiceProvider.value =
|
|
791
|
+
"This user is already a member of this service provider.";
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
existingAccount.value = {
|
|
796
|
+
_id: appMember.org,
|
|
797
|
+
text: appMember.orgName,
|
|
798
|
+
org: appMember.org,
|
|
799
|
+
orgName: appMember.orgName,
|
|
800
|
+
siteId: appMember.siteId,
|
|
801
|
+
siteName: appMember.siteName,
|
|
802
|
+
};
|
|
782
803
|
|
|
783
|
-
if (matchedOrg) {
|
|
784
|
-
existingAccount.value = {
|
|
785
|
-
_id: matchedOrg.value,
|
|
786
|
-
text: matchedOrg.text,
|
|
787
|
-
...matchedOrg,
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
804
|
} catch (error) {
|
|
791
805
|
existingAccount.value = null;
|
|
792
806
|
} finally {
|
|
793
807
|
checkingExistingAccount.value = false;
|
|
794
808
|
}
|
|
795
809
|
}
|
|
796
|
-
|
|
810
|
+
const canSubmitInvite = computed(() => {
|
|
811
|
+
return (
|
|
812
|
+
validServiceProvider.value &&
|
|
813
|
+
!messageServiceProvider.value
|
|
814
|
+
);
|
|
815
|
+
});
|
|
797
816
|
watch(
|
|
798
817
|
() => serviceProviderInvite.value.email,
|
|
799
818
|
async (value) => {
|
|
@@ -939,6 +958,7 @@ function formatEmailCell(value: unknown): string {
|
|
|
939
958
|
function mapProviderRows(raw: Record<string, any>[]): TableRow[] {
|
|
940
959
|
return raw.map((row) => ({
|
|
941
960
|
_id: String(row._id ?? ""),
|
|
961
|
+
userId: String(row.user ?? ""),
|
|
942
962
|
serviceProviderOrgId: String(row.serviceProviderOrgId ?? ""),
|
|
943
963
|
companyName: row.name ?? "-",
|
|
944
964
|
email: formatEmailCell(row.email),
|
|
@@ -961,6 +981,7 @@ function inviteTypeLabel(t: string) {
|
|
|
961
981
|
function mapInviteRows(raw: Record<string, any>[]): TableRow[] {
|
|
962
982
|
return raw.map((row) => ({
|
|
963
983
|
_id: String(row._id ?? ""),
|
|
984
|
+
userId: String(row.user ?? ""),
|
|
964
985
|
serviceProviderOrgId: String(row.metadata?.serviceProviderOrgId ?? ""),
|
|
965
986
|
companyName: row.metadata?.name || "-",
|
|
966
987
|
email: formatEmailCell(row.email),
|
|
@@ -1029,16 +1050,12 @@ async function openView(row: TableRow) {
|
|
|
1029
1050
|
|
|
1030
1051
|
loadingMembers.value = true;
|
|
1031
1052
|
try {
|
|
1032
|
-
const data = await
|
|
1033
|
-
|
|
1034
|
-
limit: 1000,
|
|
1035
|
-
org: row.serviceProviderOrgId,
|
|
1036
|
-
status: "active",
|
|
1037
|
-
});
|
|
1053
|
+
const data = await getAllByUserId(row.userId);
|
|
1054
|
+
|
|
1038
1055
|
serviceProviderMembers.value = (data.items ?? []).map((member: any) => ({
|
|
1039
|
-
_id:
|
|
1040
|
-
name: member.name ||
|
|
1041
|
-
role: member.
|
|
1056
|
+
_id: member._id,
|
|
1057
|
+
name: member.name || "-",
|
|
1058
|
+
role: member.role || "-",
|
|
1042
1059
|
}));
|
|
1043
1060
|
} catch (error: any) {
|
|
1044
1061
|
showMessage(errorConverter(error), "error");
|
|
@@ -308,10 +308,10 @@
|
|
|
308
308
|
|
|
309
309
|
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
310
310
|
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
311
|
-
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
311
|
+
<span v-if="!visitor.nric || currentAutofillSource === 'nric'" class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
312
312
|
</div>
|
|
313
313
|
|
|
314
|
-
<template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
|
|
314
|
+
<template v-if="(!visitor.contact || currentAutofillSource === 'contact') && selectedNricContactOptions.length >= 1">
|
|
315
315
|
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
316
316
|
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
317
317
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
</v-radio-group>
|
|
320
320
|
</template>
|
|
321
321
|
|
|
322
|
-
<template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
|
|
322
|
+
<template v-if="(!visitor.plateNumber || currentAutofillSource === 'vehicleNumber') && selectedNricPlateOptions.length >= 1">
|
|
323
323
|
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
324
324
|
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
325
325
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
@@ -1044,8 +1044,8 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1044
1044
|
const selectedPlate =
|
|
1045
1045
|
findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
|
|
1046
1046
|
|
|
1047
|
-
const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
|
|
1048
|
-
const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
|
|
1047
|
+
const needsContactChoice = (!visitor.contact?.trim() || source === 'contact') && contactOptions.length > 1;
|
|
1048
|
+
const needsPlateChoice = (!visitor.plateNumber?.trim() || source === 'vehicleNumber') && plateOptions.length > 1;
|
|
1049
1049
|
|
|
1050
1050
|
if (needsContactChoice || needsPlateChoice) {
|
|
1051
1051
|
nricSearchMenu.value = false;
|
|
@@ -315,7 +315,9 @@
|
|
|
315
315
|
style="cursor: pointer"
|
|
316
316
|
>
|
|
317
317
|
<v-chip
|
|
318
|
-
v-for="pass in item.visitorPass
|
|
318
|
+
v-for="pass in item.visitorPass.filter(
|
|
319
|
+
(i) => i.status != 'Removed'
|
|
320
|
+
)"
|
|
319
321
|
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
320
322
|
prepend-icon="mdi-card-bulleted-outline"
|
|
321
323
|
size="x-small"
|
|
@@ -325,7 +327,9 @@
|
|
|
325
327
|
{{ (pass as any)?.prefixAndName }}
|
|
326
328
|
</v-chip>
|
|
327
329
|
<v-chip
|
|
328
|
-
v-for="key in item.passKeys
|
|
330
|
+
v-for="key in item.passKeys.filter(
|
|
331
|
+
(i) => i.status != 'Removed'
|
|
332
|
+
)"
|
|
329
333
|
:key="(key as any)._id ?? (key as any).keyId"
|
|
330
334
|
prepend-icon="mdi-key"
|
|
331
335
|
size="x-small"
|
|
@@ -404,9 +408,16 @@
|
|
|
404
408
|
</v-menu>
|
|
405
409
|
|
|
406
410
|
<!-- QRCODE identifier -->
|
|
407
|
-
<div
|
|
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
|
+
>
|
|
408
417
|
<v-icon size="15" icon="mdi-qrcode" color="teal" />
|
|
409
|
-
<v-chip size="x-small" variant="tonal" color="teal"
|
|
418
|
+
<v-chip size="x-small" variant="tonal" color="teal"
|
|
419
|
+
>QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip
|
|
420
|
+
>
|
|
410
421
|
</div>
|
|
411
422
|
</v-row>
|
|
412
423
|
</template>
|
|
@@ -814,7 +825,9 @@
|
|
|
814
825
|
item-title="label"
|
|
815
826
|
item-value="value"
|
|
816
827
|
v-model="pass.status"
|
|
817
|
-
:disabled="
|
|
828
|
+
:disabled="
|
|
829
|
+
selectedVisitorObject.checkOut || pass.status == 'Removed'
|
|
830
|
+
"
|
|
818
831
|
></v-select>
|
|
819
832
|
<v-textarea
|
|
820
833
|
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
@@ -851,7 +864,9 @@
|
|
|
851
864
|
item-title="label"
|
|
852
865
|
item-value="value"
|
|
853
866
|
v-model="key.status"
|
|
854
|
-
:disabled="
|
|
867
|
+
:disabled="
|
|
868
|
+
selectedVisitorObject.checkOut || key.status == 'Removed'
|
|
869
|
+
"
|
|
855
870
|
></v-select>
|
|
856
871
|
<v-textarea
|
|
857
872
|
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
@@ -911,37 +926,87 @@
|
|
|
911
926
|
<v-dialog v-model="dialog.returnNfcCard" max-width="450" persistent>
|
|
912
927
|
<v-card>
|
|
913
928
|
<v-toolbar density="compact" color="">
|
|
914
|
-
<v-row
|
|
929
|
+
<v-row
|
|
930
|
+
no-gutters
|
|
931
|
+
class="d-flex fill-height justify-space-between align-center px-4"
|
|
932
|
+
>
|
|
915
933
|
<span class="font-weight-bold">Return NFC Card</span>
|
|
916
|
-
<v-btn
|
|
934
|
+
<v-btn
|
|
935
|
+
icon="mdi-close"
|
|
936
|
+
variant="text"
|
|
937
|
+
@click="dialog.returnNfcCard = false"
|
|
938
|
+
/>
|
|
917
939
|
</v-row>
|
|
918
940
|
</v-toolbar>
|
|
919
941
|
<v-card-text class="px-4 pb-2">
|
|
920
|
-
<p class="text-body-2 mb-3">
|
|
942
|
+
<p class="text-body-2 mb-3">
|
|
943
|
+
Please indicate the status of the NFC card before checking out.
|
|
944
|
+
</p>
|
|
921
945
|
<div>
|
|
922
946
|
<InputLabel class="text-capitalize" title="Full Name" />
|
|
923
|
-
<v-text-field
|
|
947
|
+
<v-text-field
|
|
948
|
+
v-model="selectedVisitorObject.name"
|
|
949
|
+
density="comfortable"
|
|
950
|
+
readonly
|
|
951
|
+
/>
|
|
924
952
|
</div>
|
|
925
953
|
<div>
|
|
926
954
|
<InputLabel class="text-capitalize" title="Location" />
|
|
927
|
-
<v-text-field
|
|
955
|
+
<v-text-field
|
|
956
|
+
v-model="selectedVisitorObject.location"
|
|
957
|
+
density="comfortable"
|
|
958
|
+
readonly
|
|
959
|
+
/>
|
|
928
960
|
</div>
|
|
929
|
-
<div
|
|
961
|
+
<div
|
|
962
|
+
v-for="(card, idx) in nfcCardReturnStatuses"
|
|
963
|
+
:key="card.cardId"
|
|
964
|
+
class="mb-4"
|
|
965
|
+
>
|
|
930
966
|
<div class="d-flex align-center ga-2 mb-2">
|
|
931
967
|
<v-icon size="18" icon="mdi-credit-card-outline" color="purple" />
|
|
932
|
-
<v-chip size="small" variant="tonal" color="purple">{{
|
|
968
|
+
<v-chip size="small" variant="tonal" color="purple">{{
|
|
969
|
+
card.cardNo
|
|
970
|
+
}}</v-chip>
|
|
933
971
|
</div>
|
|
934
|
-
<v-select
|
|
935
|
-
|
|
972
|
+
<v-select
|
|
973
|
+
v-model="nfcCardReturnStatuses[idx].status"
|
|
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
|
+
/>
|
|
936
989
|
</div>
|
|
937
990
|
</v-card-text>
|
|
938
991
|
<v-toolbar class="pa-0" density="compact">
|
|
939
992
|
<v-row no-gutters>
|
|
940
993
|
<v-col cols="6">
|
|
941
|
-
<v-btn variant="text" block @click="dialog.returnNfcCard = false"
|
|
994
|
+
<v-btn variant="text" block @click="dialog.returnNfcCard = false"
|
|
995
|
+
>Close</v-btn
|
|
996
|
+
>
|
|
942
997
|
</v-col>
|
|
943
998
|
<v-col cols="6">
|
|
944
|
-
<v-btn
|
|
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
|
+
>
|
|
945
1010
|
</v-col>
|
|
946
1011
|
</v-row>
|
|
947
1012
|
</v-toolbar>
|
|
@@ -971,7 +1036,7 @@
|
|
|
971
1036
|
@close="dialog.editPassKey = false"
|
|
972
1037
|
@done="
|
|
973
1038
|
() => {
|
|
974
|
-
dialog.editPassKey = false;
|
|
1039
|
+
// dialog.editPassKey = false;
|
|
975
1040
|
getVisitorRefresh();
|
|
976
1041
|
}
|
|
977
1042
|
"
|
|
@@ -1826,7 +1891,8 @@ const nfcCardReturnStatuses = ref<
|
|
|
1826
1891
|
const canConfirmCheckout = computed(() => {
|
|
1827
1892
|
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
1828
1893
|
return allEntries.every((entry) => {
|
|
1829
|
-
if (!entry.status || ["In Use","Not Returned"].includes(entry.status))
|
|
1894
|
+
if (!entry.status || ["In Use", "Not Returned"].includes(entry.status))
|
|
1895
|
+
return false;
|
|
1830
1896
|
if (
|
|
1831
1897
|
(entry.status === "Lost" || entry.status === "Damaged") &&
|
|
1832
1898
|
!entry.remarks.trim()
|
|
@@ -1840,7 +1906,8 @@ const canConfirmNfcCheckout = computed(() => {
|
|
|
1840
1906
|
if (nfcCardReturnStatuses.value.length === 0) return false;
|
|
1841
1907
|
return nfcCardReturnStatuses.value.every(({ status, remarks }) => {
|
|
1842
1908
|
if (!status || status === "In Use") return false;
|
|
1843
|
-
if ((status === "Lost" || status === "Damage") && !remarks.trim())
|
|
1909
|
+
if ((status === "Lost" || status === "Damage") && !remarks.trim())
|
|
1910
|
+
return false;
|
|
1844
1911
|
return true;
|
|
1845
1912
|
});
|
|
1846
1913
|
});
|
|
@@ -1883,17 +1950,28 @@ function handleCheckout(userId: string) {
|
|
|
1883
1950
|
return;
|
|
1884
1951
|
}
|
|
1885
1952
|
|
|
1886
|
-
const hasNfc =
|
|
1953
|
+
const hasNfc =
|
|
1954
|
+
visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
|
|
1887
1955
|
if (hasNfc) {
|
|
1888
1956
|
const allCards: any[] = visitor.cards?.flat() ?? [];
|
|
1889
1957
|
nfcCardReturnStatuses.value = allCards.map((card: any) => ({
|
|
1890
1958
|
cardId: card._id,
|
|
1891
|
-
cardNo:
|
|
1959
|
+
cardNo:
|
|
1960
|
+
card._id === visitor.accessCards._id
|
|
1961
|
+
? visitor.accessCards.cardNo
|
|
1962
|
+
: card._id,
|
|
1892
1963
|
status: "In Use",
|
|
1893
1964
|
remarks: "",
|
|
1894
1965
|
}));
|
|
1895
1966
|
if (nfcCardReturnStatuses.value.length === 0) {
|
|
1896
|
-
nfcCardReturnStatuses.value = [
|
|
1967
|
+
nfcCardReturnStatuses.value = [
|
|
1968
|
+
{
|
|
1969
|
+
cardId: visitor.accessCards._id,
|
|
1970
|
+
cardNo: visitor.accessCards.cardNo,
|
|
1971
|
+
status: "In Use",
|
|
1972
|
+
remarks: "",
|
|
1973
|
+
},
|
|
1974
|
+
];
|
|
1897
1975
|
}
|
|
1898
1976
|
dialog.returnNfcCard = true;
|
|
1899
1977
|
return;
|
|
@@ -1961,7 +2039,9 @@ async function handleNfcCheckout() {
|
|
|
1961
2039
|
})),
|
|
1962
2040
|
});
|
|
1963
2041
|
if (res) {
|
|
1964
|
-
await updateVisitor(userId as string, {
|
|
2042
|
+
await updateVisitor(userId as string, {
|
|
2043
|
+
checkOut: new Date().toISOString(),
|
|
2044
|
+
});
|
|
1965
2045
|
showMessage("Visitor successfully checked-out!", "info");
|
|
1966
2046
|
await getVisitorRefresh();
|
|
1967
2047
|
dialog.returnNfcCard = false;
|
|
@@ -1969,7 +2049,10 @@ async function handleNfcCheckout() {
|
|
|
1969
2049
|
}
|
|
1970
2050
|
} catch (error: any) {
|
|
1971
2051
|
const errorMessage = error?.response?._data?.message;
|
|
1972
|
-
showMessage(
|
|
2052
|
+
showMessage(
|
|
2053
|
+
errorMessage || "Something went wrong. Please try again later.",
|
|
2054
|
+
"error"
|
|
2055
|
+
);
|
|
1973
2056
|
} finally {
|
|
1974
2057
|
loading.checkingOut = false;
|
|
1975
2058
|
}
|
|
@@ -2241,11 +2324,7 @@ const syncVisitorFiltersFromRoute = () => {
|
|
|
2241
2324
|
|
|
2242
2325
|
onMounted(syncVisitorFiltersFromRoute);
|
|
2243
2326
|
|
|
2244
|
-
watch(
|
|
2245
|
-
() => route.query,
|
|
2246
|
-
syncVisitorFiltersFromRoute,
|
|
2247
|
-
{ deep: true }
|
|
2248
|
-
);
|
|
2327
|
+
watch(() => route.query, syncVisitorFiltersFromRoute, { deep: true });
|
|
2249
2328
|
|
|
2250
2329
|
const { socketUnregisteredVisitorTrigger, visitorSocketData } =
|
|
2251
2330
|
useVisitorSocket();
|
|
@@ -1,58 +1,23 @@
|
|
|
1
1
|
export function useCleaningSchedulePermission() {
|
|
2
|
-
const { hasPermission } = usePermission();
|
|
3
|
-
const { permissions } = useCleaningPermission();
|
|
4
|
-
|
|
5
2
|
const { userAppRole } = useLocalSetup();
|
|
6
3
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
});
|
|
4
|
+
const can = (action: string) =>
|
|
5
|
+
computed(() => {
|
|
6
|
+
const permissions = userAppRole.value?.permissions;
|
|
7
|
+
if (!permissions) return false;
|
|
8
|
+
if (permissions.includes("*")) return true;
|
|
9
|
+
return permissions.some((permission) =>
|
|
10
|
+
permission.endsWith(`-schedule-mgmt:${action}`),
|
|
11
|
+
);
|
|
12
|
+
});
|
|
48
13
|
|
|
49
14
|
return {
|
|
50
|
-
canViewSchedules,
|
|
51
|
-
canViewScheduleDetails,
|
|
52
|
-
canDownloadSchedule,
|
|
53
|
-
canManageScheduleTasks,
|
|
54
|
-
canGenerateChecklist,
|
|
55
|
-
canViewHistory,
|
|
56
|
-
canAddRemarks,
|
|
15
|
+
canViewSchedules: can("see-all-schedules"),
|
|
16
|
+
canViewScheduleDetails: can("see-schedule-details"),
|
|
17
|
+
canDownloadSchedule: can("download-schedule"),
|
|
18
|
+
canManageScheduleTasks: can("manage-schedule-tasks"),
|
|
19
|
+
canGenerateChecklist: can("generate-checklist"),
|
|
20
|
+
canViewHistory: can("view-history"),
|
|
21
|
+
canAddRemarks: can("add-remarks"),
|
|
57
22
|
};
|
|
58
23
|
}
|
|
@@ -27,6 +27,7 @@ export default function useEquipment() {
|
|
|
27
27
|
name: payload.name,
|
|
28
28
|
unitOfMeasurement: payload.unitOfMeasurement,
|
|
29
29
|
serviceType,
|
|
30
|
+
...(payload.attachment ? { attachment: payload.attachment } : {}),
|
|
30
31
|
},
|
|
31
32
|
}
|
|
32
33
|
);
|
|
@@ -40,6 +41,7 @@ export default function useEquipment() {
|
|
|
40
41
|
body: {
|
|
41
42
|
name: payload.name,
|
|
42
43
|
unitOfMeasurement: payload.unitOfMeasurement,
|
|
44
|
+
...(payload.attachment ? { attachment: payload.attachment } : {}),
|
|
43
45
|
},
|
|
44
46
|
}
|
|
45
47
|
);
|
|
@@ -63,15 +63,12 @@ export function useNFCPatrolDailyReport(
|
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
65
|
const [logsRes, siteInfo] = await Promise.all([
|
|
66
|
-
getPatrolLogs
|
|
66
|
+
getPatrolLogs({
|
|
67
67
|
page: 1,
|
|
68
68
|
limit: 100,
|
|
69
69
|
site,
|
|
70
70
|
date: filters.date,
|
|
71
71
|
routeId: filters.route,
|
|
72
|
-
routeStartTime: filters.timeRange
|
|
73
|
-
? filters.timeRange.split(" - ")[0]
|
|
74
|
-
: undefined,
|
|
75
72
|
}),
|
|
76
73
|
getSiteById(site),
|
|
77
74
|
]);
|