@7365admin1/layer-common 3.1.2 → 3.1.3-staging.40
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/CameraMain.vue +17 -9
- 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 +146 -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/PatrolActivityTable.vue +20 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
- package/components/Nfc/PatrolReport/ReportDatePicker.vue +61 -0
- package/components/Nfc/PatrolReport/ReportFilters.vue +52 -20
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +22 -0
- package/components/PhotoUpload.vue +19 -4
- package/components/ServiceProviderMain.vue +59 -25
- package/components/VisitorForm.vue +78 -18
- package/components/VisitorManagement.vue +12 -16
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/composables/useEquipment.ts +2 -0
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +3 -4
- package/composables/useNFCPatrolMonthlyReport.ts +62 -30
- package/composables/useNFCPatrolReport.ts +2 -0
- package/composables/useServiceProvider.ts +21 -0
- 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 +19 -1
|
@@ -36,10 +36,17 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<v-list v-if="nricSearchResults.length" class="pa-1 phone-search-results">
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
<div class="search-results-scroll">
|
|
40
|
+
<v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
|
|
41
|
+
@click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
42
|
+
<VisitorSearchResultSummary :item="item" />
|
|
43
|
+
</v-list-item>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="search-results-close pa-1">
|
|
46
|
+
<v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('nric')">
|
|
47
|
+
Close
|
|
48
|
+
</v-btn>
|
|
49
|
+
</div>
|
|
43
50
|
</v-list>
|
|
44
51
|
</v-menu>
|
|
45
52
|
</v-col>
|
|
@@ -58,10 +65,17 @@
|
|
|
58
65
|
</template>
|
|
59
66
|
|
|
60
67
|
<v-list v-if="phoneSearchResults.length" class="pa-1 phone-search-results">
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
<div class="search-results-scroll">
|
|
69
|
+
<v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
|
|
70
|
+
@click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
71
|
+
<VisitorSearchResultSummary :item="item" />
|
|
72
|
+
</v-list-item>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="search-results-close pa-1">
|
|
75
|
+
<v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('contact')">
|
|
76
|
+
Close
|
|
77
|
+
</v-btn>
|
|
78
|
+
</div>
|
|
65
79
|
</v-list>
|
|
66
80
|
</v-menu>
|
|
67
81
|
</v-col>
|
|
@@ -77,15 +91,23 @@
|
|
|
77
91
|
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
78
92
|
<template #activator="{ props }">
|
|
79
93
|
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
80
|
-
@update:model-value="handleUpdateVehicleNumber" @focus="
|
|
94
|
+
@update:model-value="handleUpdateVehicleNumber" @focus="handleFocusPlateNumber"
|
|
81
95
|
@blur="isPlateNumberFieldFocused = false" />
|
|
82
96
|
</template>
|
|
83
97
|
|
|
84
98
|
<v-list v-if="vehicleSearchResults.length" class="pa-1 phone-search-results">
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
<div class="search-results-scroll">
|
|
100
|
+
<v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
|
|
101
|
+
@click="selectPlateNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
102
|
+
<VisitorSearchResultSummary :item="item" />
|
|
103
|
+
</v-list-item>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="search-results-close pa-1">
|
|
106
|
+
<v-btn block variant="flat" color="red"
|
|
107
|
+
@click.stop="closeAutofillSearchResults('vehicleNumber')">
|
|
108
|
+
Close
|
|
109
|
+
</v-btn>
|
|
110
|
+
</div>
|
|
89
111
|
</v-list>
|
|
90
112
|
</v-menu>
|
|
91
113
|
</v-col>
|
|
@@ -308,10 +330,10 @@
|
|
|
308
330
|
|
|
309
331
|
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
310
332
|
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
311
|
-
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
333
|
+
<span v-if="!visitor.nric || currentAutofillSource === 'nric'" class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
312
334
|
</div>
|
|
313
335
|
|
|
314
|
-
<template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
|
|
336
|
+
<template v-if="(!visitor.contact || currentAutofillSource === 'contact') && selectedNricContactOptions.length >= 1">
|
|
315
337
|
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
316
338
|
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
317
339
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
@@ -319,7 +341,7 @@
|
|
|
319
341
|
</v-radio-group>
|
|
320
342
|
</template>
|
|
321
343
|
|
|
322
|
-
<template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
|
|
344
|
+
<template v-if="(!visitor.plateNumber || currentAutofillSource === 'vehicleNumber') && selectedNricPlateOptions.length >= 1">
|
|
323
345
|
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
324
346
|
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
325
347
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
@@ -1044,8 +1066,8 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1044
1066
|
const selectedPlate =
|
|
1045
1067
|
findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
|
|
1046
1068
|
|
|
1047
|
-
const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
|
|
1048
|
-
const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
|
|
1069
|
+
const needsContactChoice = (!visitor.contact?.trim() || source === 'contact') && contactOptions.length > 1;
|
|
1070
|
+
const needsPlateChoice = (!visitor.plateNumber?.trim() || source === 'vehicleNumber') && plateOptions.length > 1;
|
|
1049
1071
|
|
|
1050
1072
|
if (needsContactChoice || needsPlateChoice) {
|
|
1051
1073
|
nricSearchMenu.value = false;
|
|
@@ -1066,6 +1088,23 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1066
1088
|
});
|
|
1067
1089
|
}
|
|
1068
1090
|
|
|
1091
|
+
function closeAutofillSearchResults(source: Exclude<AutofillSource, null>) {
|
|
1092
|
+
if (source === "nric") {
|
|
1093
|
+
nricSearchMenu.value = false;
|
|
1094
|
+
nricSearchResults.value = [];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (source === "contact") {
|
|
1098
|
+
phoneSearchMenu.value = false;
|
|
1099
|
+
phoneSearchResults.value = [];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (source === "vehicleNumber") {
|
|
1103
|
+
vehicleSearchMenu.value = false;
|
|
1104
|
+
vehicleSearchResults.value = [];
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1069
1108
|
function closeNricAutofillOptions() {
|
|
1070
1109
|
dialog.nricAutofillOptions = false;
|
|
1071
1110
|
selectedNricSearchResult.value = null;
|
|
@@ -1503,6 +1542,15 @@ function handleUpdateVehicleNumber() {
|
|
|
1503
1542
|
debounceFetchPlateNumber();
|
|
1504
1543
|
}
|
|
1505
1544
|
|
|
1545
|
+
const handleFocusPlateNumber = () => {
|
|
1546
|
+
isPlateNumberFieldFocused.value = true;
|
|
1547
|
+
if (prop.mode === 'register') {
|
|
1548
|
+
|
|
1549
|
+
handleUpdateVehicleNumber();
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
|
|
1506
1554
|
function backToSelection() {
|
|
1507
1555
|
emit("back");
|
|
1508
1556
|
message.value = "";
|
|
@@ -2108,10 +2156,22 @@ onMounted(() => {
|
|
|
2108
2156
|
}
|
|
2109
2157
|
|
|
2110
2158
|
.phone-search-results {
|
|
2159
|
+
display: flex;
|
|
2160
|
+
flex-direction: column;
|
|
2111
2161
|
max-height: 400px;
|
|
2162
|
+
overflow: hidden;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.search-results-scroll {
|
|
2166
|
+
flex: 1 1 auto;
|
|
2167
|
+
min-height: 0;
|
|
2112
2168
|
overflow-y: auto;
|
|
2113
2169
|
}
|
|
2114
2170
|
|
|
2171
|
+
.search-results-close {
|
|
2172
|
+
flex: 0 0 auto;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2115
2175
|
.nric-options-card {
|
|
2116
2176
|
border-radius: 16px !important;
|
|
2117
2177
|
}
|
|
@@ -825,10 +825,8 @@
|
|
|
825
825
|
item-title="label"
|
|
826
826
|
item-value="value"
|
|
827
827
|
v-model="pass.status"
|
|
828
|
-
:disabled="
|
|
829
|
-
|
|
830
|
-
"
|
|
831
|
-
></v-select>
|
|
828
|
+
:disabled="pass.status == 'Removed'"
|
|
829
|
+
/>
|
|
832
830
|
<v-textarea
|
|
833
831
|
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
834
832
|
no-resize
|
|
@@ -836,8 +834,7 @@
|
|
|
836
834
|
class="w-100"
|
|
837
835
|
density="compact"
|
|
838
836
|
v-model="pass.remarks"
|
|
839
|
-
|
|
840
|
-
></v-textarea>
|
|
837
|
+
/>
|
|
841
838
|
</v-row>
|
|
842
839
|
</div>
|
|
843
840
|
<div v-if="keyReturnStatuses.length > 0" class="mb-2">
|
|
@@ -864,10 +861,8 @@
|
|
|
864
861
|
item-title="label"
|
|
865
862
|
item-value="value"
|
|
866
863
|
v-model="key.status"
|
|
867
|
-
:disabled="
|
|
868
|
-
|
|
869
|
-
"
|
|
870
|
-
></v-select>
|
|
864
|
+
:disabled="key.status == 'Removed'"
|
|
865
|
+
/>
|
|
871
866
|
<v-textarea
|
|
872
867
|
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
873
868
|
no-resize
|
|
@@ -875,8 +870,7 @@
|
|
|
875
870
|
class="w-100"
|
|
876
871
|
density="compact"
|
|
877
872
|
v-model="key.remarks"
|
|
878
|
-
|
|
879
|
-
></v-textarea>
|
|
873
|
+
/>
|
|
880
874
|
</v-row>
|
|
881
875
|
</div>
|
|
882
876
|
|
|
@@ -886,11 +880,11 @@
|
|
|
886
880
|
color="blue"
|
|
887
881
|
density="comfortable"
|
|
888
882
|
class="text-capitalize"
|
|
889
|
-
:disabled="selectedVisitorObject.checkOut"
|
|
890
883
|
:loading="loading.updatingPassKeys"
|
|
891
884
|
@click.stop="handleUpdatePassKeys"
|
|
892
|
-
>Update Pass/Keys</v-btn
|
|
893
885
|
>
|
|
886
|
+
Update Pass/Keys
|
|
887
|
+
</v-btn>
|
|
894
888
|
</v-row>
|
|
895
889
|
</v-card-text>
|
|
896
890
|
<v-toolbar class="pa-0" density="compact">
|
|
@@ -900,8 +894,9 @@
|
|
|
900
894
|
variant="text"
|
|
901
895
|
block
|
|
902
896
|
@click="dialog.returnPassesKeys = false"
|
|
903
|
-
>Close</v-btn
|
|
904
897
|
>
|
|
898
|
+
Close
|
|
899
|
+
</v-btn>
|
|
905
900
|
</v-col>
|
|
906
901
|
<v-col cols="6">
|
|
907
902
|
<v-btn
|
|
@@ -915,8 +910,9 @@
|
|
|
915
910
|
!canConfirmCheckout || selectedVisitorObject.checkOut
|
|
916
911
|
"
|
|
917
912
|
@click="proceedCheckout"
|
|
918
|
-
>Confirm Checkout</v-btn
|
|
919
913
|
>
|
|
914
|
+
Confirm Checkout
|
|
915
|
+
</v-btn>
|
|
920
916
|
</v-col>
|
|
921
917
|
</v-row>
|
|
922
918
|
</v-toolbar>
|
|
@@ -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
|
);
|
|
@@ -34,6 +34,8 @@ function mapLogToSummaryRow(log: Record<string, any>, index: number): NFCPatrolS
|
|
|
34
34
|
id: log._id,
|
|
35
35
|
routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
|
|
36
36
|
securityCheckSchedule: log.route?.startTime ?? "-",
|
|
37
|
+
guardName: log.sign?.guardName,
|
|
38
|
+
guardSignatureFileId: log.sign?.guardSignatureFileId,
|
|
37
39
|
checked,
|
|
38
40
|
missed,
|
|
39
41
|
status: missed === 0 ? "Completed" : "Incomplete",
|
|
@@ -63,15 +65,12 @@ export function useNFCPatrolDailyReport(
|
|
|
63
65
|
|
|
64
66
|
try {
|
|
65
67
|
const [logsRes, siteInfo] = await Promise.all([
|
|
66
|
-
getPatrolLogs
|
|
68
|
+
getPatrolLogs({
|
|
67
69
|
page: 1,
|
|
68
70
|
limit: 100,
|
|
69
71
|
site,
|
|
70
72
|
date: filters.date,
|
|
71
73
|
routeId: filters.route,
|
|
72
|
-
routeStartTime: filters.timeRange
|
|
73
|
-
? filters.timeRange.split(" - ")[0]
|
|
74
|
-
: undefined,
|
|
75
74
|
}),
|
|
76
75
|
getSiteById(site),
|
|
77
76
|
]);
|
|
@@ -2,6 +2,8 @@ import type {
|
|
|
2
2
|
NFCPatrolReportFilters,
|
|
3
3
|
NFCPatrolSummaryReport,
|
|
4
4
|
NFCPatrolReportSummaryRow,
|
|
5
|
+
NFCPatrolMonthlyReportRow,
|
|
6
|
+
NFCPatrolMonthlyReport,
|
|
5
7
|
} from "../types/nfc-patrol-report";
|
|
6
8
|
import useNFCPatrolLog from "./useNFCPatrolLog";
|
|
7
9
|
import useSite from "./useSite";
|
|
@@ -12,6 +14,55 @@ interface NFCPatrolLogListResponse {
|
|
|
12
14
|
pageRange?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
const MONTHS = [
|
|
18
|
+
"January",
|
|
19
|
+
"February",
|
|
20
|
+
"March",
|
|
21
|
+
"April",
|
|
22
|
+
"May",
|
|
23
|
+
"June",
|
|
24
|
+
"July",
|
|
25
|
+
"August",
|
|
26
|
+
"September",
|
|
27
|
+
"October",
|
|
28
|
+
"November",
|
|
29
|
+
"December",
|
|
30
|
+
];
|
|
31
|
+
function mapLogsToMonthlyRows(
|
|
32
|
+
logs: Record<string, any>[],
|
|
33
|
+
): NFCPatrolMonthlyReportRow[] {
|
|
34
|
+
const rows = MONTHS.map((month) => ({
|
|
35
|
+
month,
|
|
36
|
+
checked: 0,
|
|
37
|
+
checkedPercentage: 0,
|
|
38
|
+
missed: 0,
|
|
39
|
+
missedPercentage: 0,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
logs.forEach((log) => {
|
|
43
|
+
const monthIndex = new Date(log.date).getMonth();
|
|
44
|
+
|
|
45
|
+
for (const checkpoint of log.checkPoints ?? []) {
|
|
46
|
+
if (checkpoint.status === "Completed") {
|
|
47
|
+
rows[monthIndex].checked++;
|
|
48
|
+
} else if (checkpoint.status === "Skipped") {
|
|
49
|
+
rows[monthIndex].missed++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
rows.forEach((row) => {
|
|
55
|
+
const total = row.checked + row.missed;
|
|
56
|
+
|
|
57
|
+
if (total > 0) {
|
|
58
|
+
row.checkedPercentage = Math.round((row.checked / total) * 100);
|
|
59
|
+
row.missedPercentage = Math.round((row.missed / total) * 100);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return rows;
|
|
64
|
+
}
|
|
65
|
+
|
|
15
66
|
function buildAddress(address?: Record<string, string>) {
|
|
16
67
|
if (!address) return undefined;
|
|
17
68
|
|
|
@@ -27,23 +78,7 @@ function buildAddress(address?: Record<string, string>) {
|
|
|
27
78
|
return parts.length ? parts.join(", ") : undefined;
|
|
28
79
|
}
|
|
29
80
|
|
|
30
|
-
function mapLogToSummaryRow(
|
|
31
|
-
log: Record<string, any>,
|
|
32
|
-
index: number,
|
|
33
|
-
): NFCPatrolReportSummaryRow {
|
|
34
|
-
const checkpoints: Record<string, any>[] = log.checkPoints ?? [];
|
|
35
|
-
const checked = checkpoints.filter((cp) => cp.status === "Completed").length;
|
|
36
|
-
const missed = checkpoints.filter((cp) => cp.status === "Skipped").length;
|
|
37
81
|
|
|
38
|
-
return {
|
|
39
|
-
id: log._id,
|
|
40
|
-
routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
|
|
41
|
-
securityCheckSchedule: log.route?.startTime ?? "-",
|
|
42
|
-
checked,
|
|
43
|
-
missed,
|
|
44
|
-
status: missed === 0 ? "Completed" : "Incomplete",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
82
|
|
|
48
83
|
export function useNFCPatrolMonthlyReport(
|
|
49
84
|
filters: NFCPatrolReportFilters,
|
|
@@ -52,12 +87,12 @@ export function useNFCPatrolMonthlyReport(
|
|
|
52
87
|
const { getAll: getPatrolLogs } = useNFCPatrolLog();
|
|
53
88
|
const { getSiteById } = useSite();
|
|
54
89
|
|
|
55
|
-
const report = ref<
|
|
90
|
+
const report = ref<NFCPatrolMonthlyReport | null>(null);
|
|
56
91
|
const loading = ref(false);
|
|
57
92
|
const notFound = ref(false);
|
|
58
93
|
|
|
59
94
|
async function fetchReport() {
|
|
60
|
-
if (!site || !filters.
|
|
95
|
+
if (!site || !filters.route) {
|
|
61
96
|
report.value = null;
|
|
62
97
|
notFound.value = false;
|
|
63
98
|
return;
|
|
@@ -65,19 +100,16 @@ export function useNFCPatrolMonthlyReport(
|
|
|
65
100
|
|
|
66
101
|
loading.value = true;
|
|
67
102
|
notFound.value = false;
|
|
68
|
-
|
|
103
|
+
const currentYear = new Date().getFullYear().toString();
|
|
69
104
|
try {
|
|
70
105
|
const [logsRes, siteInfo] = await Promise.all([
|
|
71
|
-
getPatrolLogs
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
? filters.timeRange.split(" - ")[0]
|
|
79
|
-
: undefined,
|
|
80
|
-
type: "month",
|
|
106
|
+
getPatrolLogs({
|
|
107
|
+
page: 1,
|
|
108
|
+
limit: 100,
|
|
109
|
+
site,
|
|
110
|
+
routeId: filters.route,
|
|
111
|
+
date: currentYear,
|
|
112
|
+
type: "month",
|
|
81
113
|
}),
|
|
82
114
|
getSiteById(site),
|
|
83
115
|
]);
|
|
@@ -93,7 +125,7 @@ export function useNFCPatrolMonthlyReport(
|
|
|
93
125
|
address: buildAddress(siteInfo.address),
|
|
94
126
|
},
|
|
95
127
|
title: `Patrol Route Summary: ${routeName}`,
|
|
96
|
-
rows: logs
|
|
128
|
+
rows: mapLogsToMonthlyRows(logs),
|
|
97
129
|
};
|
|
98
130
|
} else {
|
|
99
131
|
report.value = null;
|
|
@@ -51,6 +51,8 @@ function mapLogToPatrolReport(
|
|
|
51
51
|
const activities: NFCPatrolActivity[] = checkpoints.map((cp) => ({
|
|
52
52
|
id: cp.nfcTag_id,
|
|
53
53
|
checkpoint: cp.nfcTag_name,
|
|
54
|
+
guardName: log.sign?.guardName,
|
|
55
|
+
guardSignatureFileId: log.sign?.guardSignatureFileId,
|
|
54
56
|
startTime: formatTime(cp.startDateTime),
|
|
55
57
|
endTime: formatTime(cp.endDateTime),
|
|
56
58
|
actualTime: diffMinutes(cp.startDateTime, cp.endDateTime),
|
|
@@ -236,6 +236,26 @@ export default function useServiceProvider() {
|
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
async function getByEmail({
|
|
240
|
+
email = "",
|
|
241
|
+
orgId = "",
|
|
242
|
+
siteId = "",
|
|
243
|
+
serviceProviderOrgId = "",
|
|
244
|
+
} = {}) {
|
|
245
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
246
|
+
"/api/service-providers/email",
|
|
247
|
+
{
|
|
248
|
+
method: "GET",
|
|
249
|
+
query: {
|
|
250
|
+
email,
|
|
251
|
+
...(orgId ? { orgId } : {}),
|
|
252
|
+
...(siteId ? { siteId } : {}),
|
|
253
|
+
...(serviceProviderOrgId ? { serviceProviderOrgId } : {}),
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
239
259
|
return {
|
|
240
260
|
serviceProviders,
|
|
241
261
|
serviceProvider,
|
|
@@ -254,5 +274,6 @@ export default function useServiceProvider() {
|
|
|
254
274
|
add,
|
|
255
275
|
addViaInvite,
|
|
256
276
|
updateStatus,
|
|
277
|
+
getByEmail,
|
|
257
278
|
};
|
|
258
279
|
}
|
package/package.json
CHANGED
package/types/customer.d.ts
CHANGED
package/types/equipment.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ declare type TEquipment = {
|
|
|
6
6
|
unitOfMeasurement: string;
|
|
7
7
|
createdAt: string;
|
|
8
8
|
updatedAt: string;
|
|
9
|
+
attachment?: string;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
declare type TEquipmentCreate = Pick<
|
|
12
|
+
declare type TEquipmentCreate = Pick<
|
|
13
|
+
TEquipment,
|
|
14
|
+
"name" | "unitOfMeasurement" | "attachment"
|
|
15
|
+
>;
|
|
@@ -26,7 +26,7 @@ export interface NFCPatrolReportFilterOptions {
|
|
|
26
26
|
|
|
27
27
|
export interface NFCPatrolReportLocation {
|
|
28
28
|
name?: string;
|
|
29
|
-
address?: string;
|
|
29
|
+
address?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export interface NFCPatrolRouteSummary {
|
|
@@ -47,6 +47,8 @@ export interface NFCPatrolRouteSummary {
|
|
|
47
47
|
export interface NFCPatrolActivity {
|
|
48
48
|
id: string;
|
|
49
49
|
checkpoint: string;
|
|
50
|
+
guardName?: string;
|
|
51
|
+
guardSignatureFileId?: string;
|
|
50
52
|
startTime: string;
|
|
51
53
|
endTime: string;
|
|
52
54
|
actualTime: string;
|
|
@@ -67,6 +69,8 @@ export interface NFCPatrolReportSummaryRow {
|
|
|
67
69
|
id: string;
|
|
68
70
|
routeName: string;
|
|
69
71
|
securityCheckSchedule: string;
|
|
72
|
+
guardName?: string;
|
|
73
|
+
guardSignatureFileId?: string;
|
|
70
74
|
checked: number;
|
|
71
75
|
missed: number;
|
|
72
76
|
status: NFCPatrolReportSummaryStatus;
|
|
@@ -77,3 +81,17 @@ export interface NFCPatrolSummaryReport {
|
|
|
77
81
|
title: string;
|
|
78
82
|
rows: NFCPatrolReportSummaryRow[];
|
|
79
83
|
}
|
|
84
|
+
|
|
85
|
+
export interface NFCPatrolMonthlyReportRow {
|
|
86
|
+
month: string;
|
|
87
|
+
checked: number;
|
|
88
|
+
checkedPercentage: number;
|
|
89
|
+
missed: number;
|
|
90
|
+
missedPercentage: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface NFCPatrolMonthlyReport {
|
|
94
|
+
location: NFCPatrolReportLocation;
|
|
95
|
+
title: string;
|
|
96
|
+
rows: NFCPatrolMonthlyReportRow[];
|
|
97
|
+
}
|