@7365admin1/layer-common 3.1.2-staging.38 → 3.1.2
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 +0 -11
- package/CHANGELOG.md +6 -0
- package/components/AddEqupmentForm.vue +1 -52
- package/components/CameraMain.vue +6 -5
- package/components/DashboardMain.vue +87 -231
- package/components/EquipmentManagementMain.vue +0 -56
- package/components/InvitationMain.vue +3 -4
- package/components/ManageChecklistMain.vue +38 -44
- package/components/Nfc/NFCPatrolReportMain.vue +38 -146
- package/components/Nfc/PatrolReport/DailyReportTab.vue +6 -10
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +9 -19
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +0 -20
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +3 -5
- package/components/Nfc/PatrolReport/ReportFilters.vue +20 -52
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +0 -22
- package/components/PhotoUpload.vue +4 -19
- package/components/ServiceProviderMain.vue +25 -59
- package/components/VisitorForm.vue +18 -78
- package/components/VisitorManagement.vue +16 -12
- package/composables/useCleaningSchedulePermission.ts +51 -16
- package/composables/useEquipment.ts +0 -2
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +4 -3
- package/composables/useNFCPatrolMonthlyReport.ts +30 -62
- package/composables/useNFCPatrolReport.ts +0 -2
- package/composables/useServiceProvider.ts +0 -21
- 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 -19
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +0 -69
- package/components/Nfc/PatrolReport/ReportDatePicker.vue +0 -61
|
@@ -46,19 +46,6 @@
|
|
|
46
46
|
</v-row>
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
|
-
<template #item.attachment="{ value }">
|
|
50
|
-
<v-btn
|
|
51
|
-
v-if="value"
|
|
52
|
-
size="small"
|
|
53
|
-
variant="tonal"
|
|
54
|
-
class="text-none"
|
|
55
|
-
@click.stop="viewAttachment(value)"
|
|
56
|
-
>
|
|
57
|
-
View Image
|
|
58
|
-
</v-btn>
|
|
59
|
-
<span v-else>—</span>
|
|
60
|
-
</template>
|
|
61
|
-
|
|
62
49
|
<template #item.status="{ value }">
|
|
63
50
|
<v-chip class="text-capitalize">{{ value || "N/A" }}</v-chip>
|
|
64
51
|
</template>
|
|
@@ -74,7 +61,6 @@
|
|
|
74
61
|
v-model="dialogShowForm"
|
|
75
62
|
v-model:name="equipmentName"
|
|
76
63
|
v-model:unitOfMeasurement="equipmentUnit"
|
|
77
|
-
v-model:attachment="equipmentAttachment"
|
|
78
64
|
:mode="dialogMode"
|
|
79
65
|
:equipmentData="editingEquipment"
|
|
80
66
|
:site="props.site"
|
|
@@ -256,29 +242,6 @@
|
|
|
256
242
|
</v-card>
|
|
257
243
|
</v-dialog>
|
|
258
244
|
|
|
259
|
-
<v-dialog v-model="showAttachmentDialog" max-width="720" persistent>
|
|
260
|
-
<v-card rounded="lg">
|
|
261
|
-
<v-card-title class="d-flex align-center pa-4">
|
|
262
|
-
<span class="text-h6 font-weight-bold">Attachment</span>
|
|
263
|
-
|
|
264
|
-
<v-spacer />
|
|
265
|
-
|
|
266
|
-
<v-btn
|
|
267
|
-
icon="mdi-close"
|
|
268
|
-
variant="text"
|
|
269
|
-
size="small"
|
|
270
|
-
@click="(showAttachmentDialog = false), (attachmentUrl = '')"
|
|
271
|
-
/>
|
|
272
|
-
</v-card-title>
|
|
273
|
-
|
|
274
|
-
<v-divider />
|
|
275
|
-
|
|
276
|
-
<v-card-text class="pa-6">
|
|
277
|
-
<v-img :src="attachmentUrl" contain />
|
|
278
|
-
</v-card-text>
|
|
279
|
-
</v-card>
|
|
280
|
-
</v-dialog>
|
|
281
|
-
|
|
282
245
|
<Snackbar
|
|
283
246
|
v-model="messageSnackbar"
|
|
284
247
|
:text="snackbarMessage"
|
|
@@ -290,7 +253,6 @@ import useEquipment from "../composables/useEquipment";
|
|
|
290
253
|
import useStock from "../composables/useStock";
|
|
291
254
|
import useUtils from "../composables/useUtils";
|
|
292
255
|
import useEquipmentManagement from "../composables/useEquipmentManagement";
|
|
293
|
-
import useFile from "../composables/useFile";
|
|
294
256
|
|
|
295
257
|
const props = defineProps({
|
|
296
258
|
orgId: { type: String, default: "" },
|
|
@@ -318,7 +280,6 @@ const siteName = ref<string>("");
|
|
|
318
280
|
const submitting = ref(false);
|
|
319
281
|
|
|
320
282
|
const headers = [
|
|
321
|
-
{ title: "Image", value: "attachment" },
|
|
322
283
|
{ title: "Name", value: "name" },
|
|
323
284
|
{ title: "Available Stock Qty", value: "qty" },
|
|
324
285
|
{ title: "Status", value: "status" },
|
|
@@ -332,7 +293,6 @@ const {
|
|
|
332
293
|
deleteEquipment,
|
|
333
294
|
} = useEquipment();
|
|
334
295
|
const { debounce, requiredRule } = useUtils();
|
|
335
|
-
const { getFileUrl } = useFile();
|
|
336
296
|
|
|
337
297
|
const searchInput = ref("");
|
|
338
298
|
const startDate = ref("");
|
|
@@ -392,21 +352,11 @@ const dialogMode = ref<"add" | "edit">("add");
|
|
|
392
352
|
const editingEquipment = ref<Record<string, any>>({});
|
|
393
353
|
const equipmentName = ref("");
|
|
394
354
|
const equipmentUnit = ref("");
|
|
395
|
-
const equipmentAttachment = ref("");
|
|
396
355
|
|
|
397
356
|
const stockActionFormRef = ref<any>(null);
|
|
398
357
|
const stockActionValid = ref(false);
|
|
399
358
|
|
|
400
359
|
const snackbarMessage = ref("");
|
|
401
|
-
const showAttachmentDialog = ref(false);
|
|
402
|
-
const attachmentUrl = ref("");
|
|
403
|
-
|
|
404
|
-
function viewAttachment(attachmentId: string) {
|
|
405
|
-
if (!attachmentId) return;
|
|
406
|
-
|
|
407
|
-
attachmentUrl.value = getFileUrl(attachmentId);
|
|
408
|
-
showAttachmentDialog.value = true;
|
|
409
|
-
}
|
|
410
360
|
|
|
411
361
|
function showMessage(msg: string, color: string = "error") {
|
|
412
362
|
message.value = msg;
|
|
@@ -420,7 +370,6 @@ function onCreateUnit() {
|
|
|
420
370
|
editingEquipment.value = {};
|
|
421
371
|
equipmentName.value = "";
|
|
422
372
|
equipmentUnit.value = "";
|
|
423
|
-
equipmentAttachment.value = "";
|
|
424
373
|
dialogShowForm.value = true;
|
|
425
374
|
}
|
|
426
375
|
|
|
@@ -430,9 +379,6 @@ async function _createEquipment() {
|
|
|
430
379
|
const payload: TEquipmentCreate = {
|
|
431
380
|
name: equipmentName.value,
|
|
432
381
|
unitOfMeasurement: equipmentUnit.value,
|
|
433
|
-
...(equipmentAttachment.value
|
|
434
|
-
? { attachment: equipmentAttachment.value }
|
|
435
|
-
: {}),
|
|
436
382
|
};
|
|
437
383
|
|
|
438
384
|
let response;
|
|
@@ -452,7 +398,6 @@ async function _createEquipment() {
|
|
|
452
398
|
editingEquipment.value = {};
|
|
453
399
|
equipmentName.value = "";
|
|
454
400
|
equipmentUnit.value = "";
|
|
455
|
-
equipmentAttachment.value = "";
|
|
456
401
|
|
|
457
402
|
await getEquipmentsRefresh();
|
|
458
403
|
} catch (error: any) {
|
|
@@ -510,7 +455,6 @@ const onEditFromMoreAction = async () => {
|
|
|
510
455
|
editingEquipment.value = response;
|
|
511
456
|
equipmentName.value = response.name || "";
|
|
512
457
|
equipmentUnit.value = response.unitOfMeasurement || "";
|
|
513
|
-
equipmentAttachment.value = response.attachment || "";
|
|
514
458
|
dialogShowForm.value = true;
|
|
515
459
|
};
|
|
516
460
|
|
|
@@ -221,7 +221,7 @@ const headers = computed(() => {
|
|
|
221
221
|
title: "Status",
|
|
222
222
|
value: "status",
|
|
223
223
|
},
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
];
|
|
226
226
|
|
|
227
227
|
if (props.status === "pending" && props.cancelInvitation) {
|
|
@@ -270,11 +270,10 @@ const {
|
|
|
270
270
|
page: page.value,
|
|
271
271
|
status: props.status,
|
|
272
272
|
search: headerSearch.value,
|
|
273
|
-
|
|
274
|
-
type: "member-invite",
|
|
273
|
+
type: "user-invite,member-invite",
|
|
275
274
|
app: props.app,
|
|
276
275
|
orgId: props.org,
|
|
277
|
-
|
|
276
|
+
siteId: props.siteId,
|
|
278
277
|
}),
|
|
279
278
|
{
|
|
280
279
|
watch: [() => props.status]
|
|
@@ -97,10 +97,7 @@
|
|
|
97
97
|
Rejected
|
|
98
98
|
</v-chip>
|
|
99
99
|
</div>
|
|
100
|
-
<div
|
|
101
|
-
v-if="item.remarks"
|
|
102
|
-
class="text-body-2 text-medium-emphasis"
|
|
103
|
-
>
|
|
100
|
+
<div v-if="item.remarks" class="text-body-2 text-medium-emphasis">
|
|
104
101
|
{{ item.remarks }}
|
|
105
102
|
</div>
|
|
106
103
|
<div
|
|
@@ -228,7 +225,11 @@
|
|
|
228
225
|
<v-card-text class="pa-4">
|
|
229
226
|
<v-row>
|
|
230
227
|
<v-col cols="12">
|
|
231
|
-
<InputLabel
|
|
228
|
+
<InputLabel
|
|
229
|
+
title="Remarks (Optional)"
|
|
230
|
+
:required="false"
|
|
231
|
+
class="mb-2"
|
|
232
|
+
/>
|
|
232
233
|
<v-textarea
|
|
233
234
|
v-model="modalData.remark"
|
|
234
235
|
placeholder="Enter your remarks here"
|
|
@@ -242,7 +243,7 @@
|
|
|
242
243
|
<v-col cols="12">
|
|
243
244
|
<v-divider class="mb-4" />
|
|
244
245
|
<PhotoUpload
|
|
245
|
-
title="Take/Upload
|
|
246
|
+
title="Take/Upload Photo"
|
|
246
247
|
:required="false"
|
|
247
248
|
v-model="modalData.photos"
|
|
248
249
|
v-model:photo-ids="modalData.photoIds"
|
|
@@ -304,38 +305,31 @@
|
|
|
304
305
|
|
|
305
306
|
<v-divider />
|
|
306
307
|
|
|
307
|
-
<v-
|
|
308
|
-
<v-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
@click="submitRejectDialog"
|
|
333
|
-
>
|
|
334
|
-
Confirm
|
|
335
|
-
</v-btn>
|
|
336
|
-
</v-col>
|
|
337
|
-
</v-row>
|
|
338
|
-
</v-toolbar>
|
|
308
|
+
<v-card-actions class="pa-0">
|
|
309
|
+
<v-btn
|
|
310
|
+
color="grey"
|
|
311
|
+
variant="flat"
|
|
312
|
+
rounded="0"
|
|
313
|
+
class="flex-grow-1 text-none"
|
|
314
|
+
height="48"
|
|
315
|
+
:disabled="rejectModal.submitting"
|
|
316
|
+
@click="closeRejectDialog"
|
|
317
|
+
>
|
|
318
|
+
Cancel
|
|
319
|
+
</v-btn>
|
|
320
|
+
<v-btn
|
|
321
|
+
color="error"
|
|
322
|
+
variant="flat"
|
|
323
|
+
rounded="0"
|
|
324
|
+
class="flex-grow-1 text-none"
|
|
325
|
+
height="48"
|
|
326
|
+
:loading="rejectModal.submitting"
|
|
327
|
+
:disabled="!isRejectModalValid || rejectModal.submitting"
|
|
328
|
+
@click="submitRejectDialog"
|
|
329
|
+
>
|
|
330
|
+
Confirm
|
|
331
|
+
</v-btn>
|
|
332
|
+
</v-card-actions>
|
|
339
333
|
</v-card>
|
|
340
334
|
</v-dialog>
|
|
341
335
|
|
|
@@ -441,12 +435,12 @@ const { back } = useUtils();
|
|
|
441
435
|
const route = useRoute();
|
|
442
436
|
const scheduleTitle = computed(() => {
|
|
443
437
|
const path = route.path;
|
|
444
|
-
if (path.includes(
|
|
445
|
-
if (path.includes(
|
|
446
|
-
if (path.includes(
|
|
447
|
-
if (path.includes(
|
|
448
|
-
if (path.includes(
|
|
449
|
-
return
|
|
438
|
+
if (path.includes('cleaning-schedule')) return 'Cleaning Checklist';
|
|
439
|
+
if (path.includes('technician-schedule')) return 'Technician Checklist';
|
|
440
|
+
if (path.includes('landscape-schedule')) return 'Landscape Checklist';
|
|
441
|
+
if (path.includes('pool-schedule')) return 'Pool Checklist';
|
|
442
|
+
if (path.includes('pest-schedule')) return 'Pest Checklist';
|
|
443
|
+
return 'Cleaner Checklist';
|
|
450
444
|
});
|
|
451
445
|
|
|
452
446
|
const canAddRemarks = computed(() => props.canAddRemarks);
|
|
@@ -14,63 +14,44 @@
|
|
|
14
14
|
</v-col>
|
|
15
15
|
</v-row>
|
|
16
16
|
|
|
17
|
-
<v-tabs
|
|
18
|
-
v-
|
|
19
|
-
class="
|
|
20
|
-
|
|
21
|
-
grow
|
|
22
|
-
>
|
|
23
|
-
<v-tab value="patrol" class="text-none border-thin">
|
|
24
|
-
Patrol Report
|
|
25
|
-
</v-tab>
|
|
26
|
-
|
|
27
|
-
<v-tab value="daily" class="text-none border-thin">
|
|
28
|
-
Daily Report
|
|
29
|
-
</v-tab>
|
|
30
|
-
|
|
31
|
-
<v-tab value="monthly" class="text-none border-thin">
|
|
32
|
-
Monthly Report
|
|
33
|
-
</v-tab>
|
|
17
|
+
<v-tabs v-model="activeTab" class="mb-6" bg-color="transparent" grow>
|
|
18
|
+
<v-tab value="patrol" class="text-none border-thin">Patrol Report</v-tab>
|
|
19
|
+
<v-tab value="daily" class="text-none border-thin">Daily Report</v-tab>
|
|
20
|
+
<v-tab value="monthly" class="text-none border-thin">Monthly Report</v-tab>
|
|
34
21
|
</v-tabs>
|
|
35
22
|
|
|
36
23
|
<v-window v-model="activeTab" class="pt-4">
|
|
37
24
|
<v-window-item value="patrol">
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
25
|
+
<PatrolReportTab
|
|
26
|
+
:active="activeTab === 'patrol'"
|
|
27
|
+
:site="site"
|
|
28
|
+
:filters="filters"
|
|
29
|
+
:options="options"
|
|
30
|
+
@update:filters="updateFilters"
|
|
31
|
+
@export="exportReport"
|
|
32
|
+
/>
|
|
48
33
|
</v-window-item>
|
|
49
34
|
|
|
50
35
|
<v-window-item value="daily">
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/>
|
|
60
|
-
</div>
|
|
36
|
+
<DailyReportTab
|
|
37
|
+
:active="activeTab === 'daily'"
|
|
38
|
+
:site="site"
|
|
39
|
+
:filters="filters"
|
|
40
|
+
:options="options"
|
|
41
|
+
@update:filters="updateFilters"
|
|
42
|
+
@export="exportReport"
|
|
43
|
+
/>
|
|
61
44
|
</v-window-item>
|
|
62
45
|
|
|
63
46
|
<v-window-item value="monthly">
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
47
|
+
<MonthlyReportTab
|
|
48
|
+
:active="activeTab === 'monthly'"
|
|
49
|
+
:site="site"
|
|
50
|
+
:filters="filters"
|
|
51
|
+
:options="options"
|
|
52
|
+
@update:filters="updateFilters"
|
|
53
|
+
@export="exportReport"
|
|
54
|
+
/>
|
|
74
55
|
</v-window-item>
|
|
75
56
|
</v-window>
|
|
76
57
|
</v-col>
|
|
@@ -78,20 +59,15 @@
|
|
|
78
59
|
</template>
|
|
79
60
|
|
|
80
61
|
<script setup lang="ts">
|
|
81
|
-
import html2canvas from "html2canvas";
|
|
82
|
-
import jsPDF from "jspdf";
|
|
83
|
-
|
|
84
62
|
import type {
|
|
85
63
|
NFCPatrolReportFilters,
|
|
86
64
|
NFCPatrolReportTab,
|
|
87
65
|
} from "../../types/nfc-patrol-report";
|
|
88
|
-
|
|
66
|
+
import { useNFCPatrolReportExport } from "../../composables/useNFCPatrolReportExport";
|
|
89
67
|
import { useNFCPatrolReportFilters } from "../../composables/useNFCPatrolReportFilters";
|
|
90
|
-
|
|
91
|
-
import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
|
|
92
68
|
import DailyReportTab from "./PatrolReport/DailyReportTab.vue";
|
|
93
69
|
import MonthlyReportTab from "./PatrolReport/MonthlyReportTab.vue";
|
|
94
|
-
|
|
70
|
+
import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
|
|
95
71
|
|
|
96
72
|
const props = defineProps<{
|
|
97
73
|
site: string;
|
|
@@ -99,16 +75,11 @@ const props = defineProps<{
|
|
|
99
75
|
}>();
|
|
100
76
|
|
|
101
77
|
const router = useRouter();
|
|
102
|
-
|
|
103
|
-
|
|
104
78
|
const activeTab = ref<NFCPatrolReportTab>("patrol");
|
|
79
|
+
const { filters, options, fetchOptions } = useNFCPatrolReportFilters(props.site);
|
|
80
|
+
const { exportReport: exportActiveReport } = useNFCPatrolReportExport();
|
|
105
81
|
|
|
106
|
-
|
|
107
|
-
useNFCPatrolReportFilters(props.site);
|
|
108
|
-
|
|
109
|
-
onMounted(() => {
|
|
110
|
-
fetchOptions();
|
|
111
|
-
});
|
|
82
|
+
onMounted(fetchOptions);
|
|
112
83
|
|
|
113
84
|
function goBack() {
|
|
114
85
|
router.back();
|
|
@@ -120,90 +91,11 @@ function updateFilters(value: NFCPatrolReportFilters) {
|
|
|
120
91
|
filters.date = value.date;
|
|
121
92
|
}
|
|
122
93
|
|
|
123
|
-
async function exportReport(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!report) return;
|
|
129
|
-
|
|
130
|
-
if (type === "pdf") {
|
|
131
|
-
const canvas = await html2canvas(report, {
|
|
132
|
-
scale: 2,
|
|
133
|
-
useCORS: true,
|
|
134
|
-
backgroundColor: "#fff",
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const pdf = new jsPDF("p", "mm", "a4");
|
|
138
|
-
|
|
139
|
-
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
140
|
-
const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
141
|
-
|
|
142
|
-
const imgWidth = pdfWidth;
|
|
143
|
-
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
144
|
-
|
|
145
|
-
const imgData = canvas.toDataURL("image/png");
|
|
146
|
-
|
|
147
|
-
let heightLeft = imgHeight;
|
|
148
|
-
let position = 0;
|
|
149
|
-
|
|
150
|
-
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
151
|
-
|
|
152
|
-
heightLeft -= pdfHeight;
|
|
153
|
-
|
|
154
|
-
while (heightLeft > 0) {
|
|
155
|
-
position -= pdfHeight;
|
|
156
|
-
|
|
157
|
-
pdf.addPage();
|
|
158
|
-
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
159
|
-
|
|
160
|
-
heightLeft -= pdfHeight;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
pdf.save(`${activeTab.value}-report.pdf`);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const table = report.querySelector("table");
|
|
168
|
-
|
|
169
|
-
if (!table) return;
|
|
170
|
-
|
|
171
|
-
const csv = Array.from(table.querySelectorAll("tr"))
|
|
172
|
-
.map((row) =>
|
|
173
|
-
Array.from(row.querySelectorAll("th,td"))
|
|
174
|
-
.map((cell) => escapeCsvCell(getCsvCellValue(cell)))
|
|
175
|
-
.join(","),
|
|
176
|
-
)
|
|
177
|
-
.join("\n");
|
|
178
|
-
|
|
179
|
-
const blob = new Blob([csv], {
|
|
180
|
-
type: "text/csv;charset=utf-8;",
|
|
94
|
+
async function exportReport() {
|
|
95
|
+
await exportActiveReport(activeTab.value, {
|
|
96
|
+
route: filters.route,
|
|
97
|
+
timeRange: filters.timeRange,
|
|
98
|
+
date: filters.date,
|
|
181
99
|
});
|
|
182
|
-
|
|
183
|
-
const link = document.createElement("a");
|
|
184
|
-
|
|
185
|
-
link.href = URL.createObjectURL(blob);
|
|
186
|
-
link.download = `${activeTab.value}-report.csv`;
|
|
187
|
-
link.click();
|
|
188
|
-
|
|
189
|
-
URL.revokeObjectURL(link.href);
|
|
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
100
|
}
|
|
209
101
|
</script>
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
-
:show-time-filter="false"
|
|
7
6
|
@update:model-value="$emit('update:filters', $event)"
|
|
8
|
-
@export="
|
|
7
|
+
@export="$emit('export')"
|
|
9
8
|
/>
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
12
11
|
|
|
13
12
|
<template v-if="report">
|
|
@@ -16,7 +15,6 @@
|
|
|
16
15
|
<SummaryReportTable :title="report.title" :rows="report.rows" />
|
|
17
16
|
</template>
|
|
18
17
|
|
|
19
|
-
|
|
20
18
|
<ReportEmptyState
|
|
21
19
|
v-else-if="!loading && notFound"
|
|
22
20
|
title="No Daily Report"
|
|
@@ -24,7 +22,6 @@
|
|
|
24
22
|
/>
|
|
25
23
|
|
|
26
24
|
<ReportEmptyState v-else-if="!loading" />
|
|
27
|
-
</div>
|
|
28
25
|
</div>
|
|
29
26
|
</template>
|
|
30
27
|
|
|
@@ -41,23 +38,23 @@ import SummaryReportTable from "./SummaryReportTable.vue";
|
|
|
41
38
|
|
|
42
39
|
const props = defineProps<{
|
|
43
40
|
active: boolean;
|
|
44
|
-
site: string;
|
|
41
|
+
site: string; // ✅ thêm
|
|
45
42
|
filters: NFCPatrolReportFilters;
|
|
46
43
|
options: NFCPatrolReportFilterOptions;
|
|
47
44
|
}>();
|
|
48
45
|
|
|
49
46
|
defineEmits<{
|
|
50
47
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
51
|
-
export: [
|
|
48
|
+
export: [];
|
|
52
49
|
}>();
|
|
53
50
|
|
|
54
51
|
const { report, loading, notFound, fetchReport } = useNFCPatrolDailyReport(
|
|
55
52
|
props.filters,
|
|
56
|
-
props.site,
|
|
53
|
+
props.site, // ✅
|
|
57
54
|
);
|
|
58
55
|
|
|
59
56
|
watch(
|
|
60
|
-
() => [props.filters.route, props.filters.date, props.active],
|
|
57
|
+
() => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
|
|
61
58
|
() => {
|
|
62
59
|
if (props.active) {
|
|
63
60
|
void fetchReport();
|
|
@@ -65,5 +62,4 @@ watch(
|
|
|
65
62
|
},
|
|
66
63
|
{ immediate: true },
|
|
67
64
|
);
|
|
68
|
-
|
|
69
65
|
</script>
|
|
@@ -3,30 +3,23 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
-
:show-time-filter="false"
|
|
7
|
-
:show-date-filter="false"
|
|
8
6
|
@update:model-value="$emit('update:filters', $event)"
|
|
9
|
-
@export="
|
|
7
|
+
@export="$emit('export')"
|
|
10
8
|
/>
|
|
11
|
-
|
|
9
|
+
|
|
12
10
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
13
11
|
|
|
14
12
|
<template v-if="report">
|
|
15
13
|
<ReportLocationHeader :location="report.location" />
|
|
16
14
|
<v-divider :thickness="1" class="border-opacity-100 mb-6" />
|
|
17
|
-
|
|
18
|
-
<MonthlyReportTable
|
|
19
|
-
:title="report.title"
|
|
20
|
-
:rows="report.rows"
|
|
21
|
-
/>
|
|
15
|
+
<SummaryReportTable :title="report.title" :rows="report.rows" />
|
|
22
16
|
</template>
|
|
23
17
|
|
|
24
18
|
<ReportEmptyState
|
|
25
19
|
v-else-if="!loading"
|
|
26
|
-
title="No Monthly Report"
|
|
27
|
-
message="
|
|
20
|
+
title="No Monthly Report Selected"
|
|
21
|
+
message="Please select a route, time range, and date to view the monthly report"
|
|
28
22
|
/>
|
|
29
|
-
</div>
|
|
30
23
|
</div>
|
|
31
24
|
</template>
|
|
32
25
|
|
|
@@ -35,13 +28,11 @@ import type {
|
|
|
35
28
|
NFCPatrolReportFilterOptions,
|
|
36
29
|
NFCPatrolReportFilters,
|
|
37
30
|
} from "../../../types/nfc-patrol-report";
|
|
38
|
-
|
|
39
31
|
import { useNFCPatrolMonthlyReport } from "../../../composables/useNFCPatrolMonthlyReport";
|
|
40
|
-
|
|
41
32
|
import ReportEmptyState from "./ReportEmptyState.vue";
|
|
42
33
|
import ReportFilters from "./ReportFilters.vue";
|
|
43
34
|
import ReportLocationHeader from "./ReportLocationHeader.vue";
|
|
44
|
-
import
|
|
35
|
+
import SummaryReportTable from "./SummaryReportTable.vue";
|
|
45
36
|
|
|
46
37
|
const props = defineProps<{
|
|
47
38
|
active: boolean;
|
|
@@ -52,14 +43,14 @@ const props = defineProps<{
|
|
|
52
43
|
|
|
53
44
|
defineEmits<{
|
|
54
45
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
55
|
-
export: [
|
|
46
|
+
export: [];
|
|
56
47
|
}>();
|
|
57
48
|
|
|
58
|
-
const { report, loading,
|
|
49
|
+
const { report, loading, fetchReport } =
|
|
59
50
|
useNFCPatrolMonthlyReport(props.filters, props.site);
|
|
60
51
|
|
|
61
52
|
watch(
|
|
62
|
-
() => [props.filters.route, props.active],
|
|
53
|
+
() => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
|
|
63
54
|
() => {
|
|
64
55
|
if (props.active) {
|
|
65
56
|
void fetchReport();
|
|
@@ -67,5 +58,4 @@ watch(
|
|
|
67
58
|
},
|
|
68
59
|
{ immediate: true },
|
|
69
60
|
);
|
|
70
|
-
|
|
71
61
|
</script>
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
<thead>
|
|
8
8
|
<tr>
|
|
9
9
|
<th>Checkpoints</th>
|
|
10
|
-
<th>Security Personnel</th>
|
|
11
|
-
<th>Signature</th>
|
|
12
10
|
<th>Start - End Time</th>
|
|
13
11
|
<th>
|
|
14
12
|
Travel time<br />
|
|
@@ -25,17 +23,6 @@
|
|
|
25
23
|
@click="handleActivityClick(activity)"
|
|
26
24
|
>
|
|
27
25
|
<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>
|
|
39
26
|
<td>
|
|
40
27
|
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
|
41
28
|
{{ activity.startTime }}
|
|
@@ -72,8 +59,6 @@ defineProps<{
|
|
|
72
59
|
activities: NFCPatrolActivity[];
|
|
73
60
|
}>();
|
|
74
61
|
|
|
75
|
-
const { getFileUrl } = useFile();
|
|
76
|
-
|
|
77
62
|
const emit = defineEmits<{
|
|
78
63
|
remarks: [activity: NFCPatrolActivity];
|
|
79
64
|
}>();
|
|
@@ -93,9 +78,4 @@ function handleActivityClick(activity: NFCPatrolActivity) {
|
|
|
93
78
|
.cursor-pointer {
|
|
94
79
|
cursor: pointer;
|
|
95
80
|
}
|
|
96
|
-
|
|
97
|
-
.guard-signature {
|
|
98
|
-
width: 80px;
|
|
99
|
-
height: 40px;
|
|
100
|
-
}
|
|
101
81
|
</style>
|