@7365admin1/layer-common 3.1.2-staging.31 → 3.1.2-staging.33
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/components/Nfc/NFCPatrolReportMain.vue +19 -1
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +20 -0
- package/components/Nfc/PatrolReport/ReportDatePicker.vue +61 -0
- package/components/Nfc/PatrolReport/ReportFilters.vue +2 -2
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +22 -0
- package/components/VisitorManagement.vue +12 -16
- package/composables/useNFCPatrolDailyReport.ts +2 -0
- package/composables/useNFCPatrolReport.ts +2 -0
- package/package.json +1 -1
- package/types/nfc-patrol-report.ts +4 -0
|
@@ -171,7 +171,7 @@ async function exportReport(type: "pdf" | "csv") {
|
|
|
171
171
|
const csv = Array.from(table.querySelectorAll("tr"))
|
|
172
172
|
.map((row) =>
|
|
173
173
|
Array.from(row.querySelectorAll("th,td"))
|
|
174
|
-
.map((cell) =>
|
|
174
|
+
.map((cell) => escapeCsvCell(getCsvCellValue(cell)))
|
|
175
175
|
.join(","),
|
|
176
176
|
)
|
|
177
177
|
.join("\n");
|
|
@@ -188,4 +188,22 @@ async function exportReport(type: "pdf" | "csv") {
|
|
|
188
188
|
|
|
189
189
|
URL.revokeObjectURL(link.href);
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
function getCsvCellValue(cell: Element) {
|
|
193
|
+
const image = cell.querySelector("img");
|
|
194
|
+
|
|
195
|
+
if (image) {
|
|
196
|
+
const imageUrl = image.currentSrc || image.src;
|
|
197
|
+
|
|
198
|
+
if (imageUrl) {
|
|
199
|
+
return imageUrl;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return cell.textContent?.trim() ?? "";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function escapeCsvCell(value: string) {
|
|
207
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
208
|
+
}
|
|
191
209
|
</script>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<thead>
|
|
8
8
|
<tr>
|
|
9
9
|
<th>Checkpoints</th>
|
|
10
|
+
<th>Security Personnel</th>
|
|
11
|
+
<th>Signature</th>
|
|
10
12
|
<th>Start - End Time</th>
|
|
11
13
|
<th>
|
|
12
14
|
Travel time<br />
|
|
@@ -23,6 +25,17 @@
|
|
|
23
25
|
@click="handleActivityClick(activity)"
|
|
24
26
|
>
|
|
25
27
|
<td>{{ activity.checkpoint }}</td>
|
|
28
|
+
<td>{{ activity.guardName || "-" }}</td>
|
|
29
|
+
<td>
|
|
30
|
+
<v-img
|
|
31
|
+
v-if="activity.guardSignatureFileId"
|
|
32
|
+
:src="getFileUrl(activity.guardSignatureFileId)"
|
|
33
|
+
:alt="`${activity.guardName || 'Guard'} signature`"
|
|
34
|
+
class="guard-signature"
|
|
35
|
+
contain
|
|
36
|
+
/>
|
|
37
|
+
<span v-else>-</span>
|
|
38
|
+
</td>
|
|
26
39
|
<td>
|
|
27
40
|
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
|
28
41
|
{{ activity.startTime }}
|
|
@@ -59,6 +72,8 @@ defineProps<{
|
|
|
59
72
|
activities: NFCPatrolActivity[];
|
|
60
73
|
}>();
|
|
61
74
|
|
|
75
|
+
const { getFileUrl } = useFile();
|
|
76
|
+
|
|
62
77
|
const emit = defineEmits<{
|
|
63
78
|
remarks: [activity: NFCPatrolActivity];
|
|
64
79
|
}>();
|
|
@@ -78,4 +93,9 @@ function handleActivityClick(activity: NFCPatrolActivity) {
|
|
|
78
93
|
.cursor-pointer {
|
|
79
94
|
cursor: pointer;
|
|
80
95
|
}
|
|
96
|
+
|
|
97
|
+
.guard-signature {
|
|
98
|
+
width: 80px;
|
|
99
|
+
height: 40px;
|
|
100
|
+
}
|
|
81
101
|
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-flex flex-column">
|
|
3
|
+
<v-text-field
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
:model-value="formattedDate"
|
|
6
|
+
autocomplete="off"
|
|
7
|
+
placeholder="DD/MM/YY"
|
|
8
|
+
readonly
|
|
9
|
+
@click="openDatePicker"
|
|
10
|
+
>
|
|
11
|
+
<template #append-inner>
|
|
12
|
+
<v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
|
|
13
|
+
</template>
|
|
14
|
+
</v-text-field>
|
|
15
|
+
|
|
16
|
+
<input
|
|
17
|
+
ref="dateInput"
|
|
18
|
+
v-model="date"
|
|
19
|
+
class="native-date-input"
|
|
20
|
+
type="date"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
defineOptions({ inheritAttrs: false });
|
|
27
|
+
|
|
28
|
+
const date = defineModel<string>({ default: "" });
|
|
29
|
+
const dateInput = ref<HTMLInputElement | null>(null);
|
|
30
|
+
|
|
31
|
+
const formattedDate = computed(() => {
|
|
32
|
+
const match = date.value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
33
|
+
|
|
34
|
+
if (!match) return "";
|
|
35
|
+
|
|
36
|
+
const [, year, month, day] = match;
|
|
37
|
+
return `${day}/${month}/${year.slice(-2)}`;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function openDatePicker() {
|
|
41
|
+
const input = dateInput.value;
|
|
42
|
+
|
|
43
|
+
if (!input) return;
|
|
44
|
+
|
|
45
|
+
if (input.showPicker) {
|
|
46
|
+
input.showPicker();
|
|
47
|
+
} else {
|
|
48
|
+
input.click();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style scoped>
|
|
54
|
+
.native-date-input {
|
|
55
|
+
position: absolute;
|
|
56
|
+
width: 1px;
|
|
57
|
+
height: 1px;
|
|
58
|
+
opacity: 0;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -41,9 +41,8 @@
|
|
|
41
41
|
md="2"
|
|
42
42
|
class="px-md-2 mb-2 mb-md-0"
|
|
43
43
|
>
|
|
44
|
-
<
|
|
44
|
+
<ReportDatePicker
|
|
45
45
|
:model-value="modelValue.date"
|
|
46
|
-
type="date"
|
|
47
46
|
variant="outlined"
|
|
48
47
|
density="comfortable"
|
|
49
48
|
rounded="lg"
|
|
@@ -90,6 +89,7 @@ import type {
|
|
|
90
89
|
NFCPatrolReportFilterOptions,
|
|
91
90
|
NFCPatrolReportFilters,
|
|
92
91
|
} from "../../../types/nfc-patrol-report";
|
|
92
|
+
import ReportDatePicker from "./ReportDatePicker.vue";
|
|
93
93
|
|
|
94
94
|
const props = withDefaults(
|
|
95
95
|
defineProps<{
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<tr>
|
|
9
9
|
<th>Patrol Route Name</th>
|
|
10
10
|
<th>Security Check Schedule</th>
|
|
11
|
+
<th>Security Personnel</th>
|
|
12
|
+
<th>Signature</th>
|
|
11
13
|
<th>Checked</th>
|
|
12
14
|
<th>Missed</th>
|
|
13
15
|
<th>Status</th>
|
|
@@ -20,6 +22,17 @@
|
|
|
20
22
|
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
|
21
23
|
{{ row.securityCheckSchedule }}
|
|
22
24
|
</td>
|
|
25
|
+
<td>{{ row.guardName || "-" }}</td>
|
|
26
|
+
<td>
|
|
27
|
+
<v-img
|
|
28
|
+
v-if="row.guardSignatureFileId"
|
|
29
|
+
:src="getFileUrl(row.guardSignatureFileId)"
|
|
30
|
+
:alt="`${row.guardName || 'Guard'} signature`"
|
|
31
|
+
class="guard-signature"
|
|
32
|
+
contain
|
|
33
|
+
/>
|
|
34
|
+
<span v-else>-</span>
|
|
35
|
+
</td>
|
|
23
36
|
<td>{{ row.checked }}</td>
|
|
24
37
|
<td>{{ row.missed }}</td>
|
|
25
38
|
<td>
|
|
@@ -45,7 +58,16 @@ defineProps<{
|
|
|
45
58
|
rows: NFCPatrolReportSummaryRow[];
|
|
46
59
|
}>();
|
|
47
60
|
|
|
61
|
+
const { getFileUrl } = useFile();
|
|
62
|
+
|
|
48
63
|
function statusClass(status: NFCPatrolReportSummaryStatus) {
|
|
49
64
|
return status === "Completed" ? "text-success" : "text-error";
|
|
50
65
|
}
|
|
51
66
|
</script>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.guard-signature {
|
|
70
|
+
width: 80px;
|
|
71
|
+
height: 40px;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -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>
|
|
@@ -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",
|
|
@@ -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),
|
package/package.json
CHANGED
|
@@ -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;
|