@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
|
@@ -46,6 +46,19 @@
|
|
|
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
|
+
|
|
49
62
|
<template #item.status="{ value }">
|
|
50
63
|
<v-chip class="text-capitalize">{{ value || "N/A" }}</v-chip>
|
|
51
64
|
</template>
|
|
@@ -61,6 +74,7 @@
|
|
|
61
74
|
v-model="dialogShowForm"
|
|
62
75
|
v-model:name="equipmentName"
|
|
63
76
|
v-model:unitOfMeasurement="equipmentUnit"
|
|
77
|
+
v-model:attachment="equipmentAttachment"
|
|
64
78
|
:mode="dialogMode"
|
|
65
79
|
:equipmentData="editingEquipment"
|
|
66
80
|
:site="props.site"
|
|
@@ -242,6 +256,29 @@
|
|
|
242
256
|
</v-card>
|
|
243
257
|
</v-dialog>
|
|
244
258
|
|
|
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
|
+
|
|
245
282
|
<Snackbar
|
|
246
283
|
v-model="messageSnackbar"
|
|
247
284
|
:text="snackbarMessage"
|
|
@@ -253,6 +290,7 @@ import useEquipment from "../composables/useEquipment";
|
|
|
253
290
|
import useStock from "../composables/useStock";
|
|
254
291
|
import useUtils from "../composables/useUtils";
|
|
255
292
|
import useEquipmentManagement from "../composables/useEquipmentManagement";
|
|
293
|
+
import useFile from "../composables/useFile";
|
|
256
294
|
|
|
257
295
|
const props = defineProps({
|
|
258
296
|
orgId: { type: String, default: "" },
|
|
@@ -280,6 +318,7 @@ const siteName = ref<string>("");
|
|
|
280
318
|
const submitting = ref(false);
|
|
281
319
|
|
|
282
320
|
const headers = [
|
|
321
|
+
{ title: "Image", value: "attachment" },
|
|
283
322
|
{ title: "Name", value: "name" },
|
|
284
323
|
{ title: "Available Stock Qty", value: "qty" },
|
|
285
324
|
{ title: "Status", value: "status" },
|
|
@@ -293,6 +332,7 @@ const {
|
|
|
293
332
|
deleteEquipment,
|
|
294
333
|
} = useEquipment();
|
|
295
334
|
const { debounce, requiredRule } = useUtils();
|
|
335
|
+
const { getFileUrl } = useFile();
|
|
296
336
|
|
|
297
337
|
const searchInput = ref("");
|
|
298
338
|
const startDate = ref("");
|
|
@@ -352,11 +392,21 @@ const dialogMode = ref<"add" | "edit">("add");
|
|
|
352
392
|
const editingEquipment = ref<Record<string, any>>({});
|
|
353
393
|
const equipmentName = ref("");
|
|
354
394
|
const equipmentUnit = ref("");
|
|
395
|
+
const equipmentAttachment = ref("");
|
|
355
396
|
|
|
356
397
|
const stockActionFormRef = ref<any>(null);
|
|
357
398
|
const stockActionValid = ref(false);
|
|
358
399
|
|
|
359
400
|
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
|
+
}
|
|
360
410
|
|
|
361
411
|
function showMessage(msg: string, color: string = "error") {
|
|
362
412
|
message.value = msg;
|
|
@@ -370,6 +420,7 @@ function onCreateUnit() {
|
|
|
370
420
|
editingEquipment.value = {};
|
|
371
421
|
equipmentName.value = "";
|
|
372
422
|
equipmentUnit.value = "";
|
|
423
|
+
equipmentAttachment.value = "";
|
|
373
424
|
dialogShowForm.value = true;
|
|
374
425
|
}
|
|
375
426
|
|
|
@@ -379,6 +430,9 @@ async function _createEquipment() {
|
|
|
379
430
|
const payload: TEquipmentCreate = {
|
|
380
431
|
name: equipmentName.value,
|
|
381
432
|
unitOfMeasurement: equipmentUnit.value,
|
|
433
|
+
...(equipmentAttachment.value
|
|
434
|
+
? { attachment: equipmentAttachment.value }
|
|
435
|
+
: {}),
|
|
382
436
|
};
|
|
383
437
|
|
|
384
438
|
let response;
|
|
@@ -398,6 +452,7 @@ async function _createEquipment() {
|
|
|
398
452
|
editingEquipment.value = {};
|
|
399
453
|
equipmentName.value = "";
|
|
400
454
|
equipmentUnit.value = "";
|
|
455
|
+
equipmentAttachment.value = "";
|
|
401
456
|
|
|
402
457
|
await getEquipmentsRefresh();
|
|
403
458
|
} catch (error: any) {
|
|
@@ -455,6 +510,7 @@ const onEditFromMoreAction = async () => {
|
|
|
455
510
|
editingEquipment.value = response;
|
|
456
511
|
equipmentName.value = response.name || "";
|
|
457
512
|
equipmentUnit.value = response.unitOfMeasurement || "";
|
|
513
|
+
equipmentAttachment.value = response.attachment || "";
|
|
458
514
|
dialogShowForm.value = true;
|
|
459
515
|
};
|
|
460
516
|
|
|
@@ -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,10 +270,11 @@ const {
|
|
|
270
270
|
page: page.value,
|
|
271
271
|
status: props.status,
|
|
272
272
|
search: headerSearch.value,
|
|
273
|
-
type: "user-invite,member-invite",
|
|
273
|
+
// type: "user-invite,member-invite",
|
|
274
|
+
type: "member-invite",
|
|
274
275
|
app: props.app,
|
|
275
276
|
orgId: props.org,
|
|
276
|
-
siteId: props.siteId,
|
|
277
|
+
// siteId: props.siteId,
|
|
277
278
|
}),
|
|
278
279
|
{
|
|
279
280
|
watch: [() => props.status]
|
|
@@ -97,7 +97,10 @@
|
|
|
97
97
|
Rejected
|
|
98
98
|
</v-chip>
|
|
99
99
|
</div>
|
|
100
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
v-if="item.remarks"
|
|
102
|
+
class="text-body-2 text-medium-emphasis"
|
|
103
|
+
>
|
|
101
104
|
{{ item.remarks }}
|
|
102
105
|
</div>
|
|
103
106
|
<div
|
|
@@ -225,11 +228,7 @@
|
|
|
225
228
|
<v-card-text class="pa-4">
|
|
226
229
|
<v-row>
|
|
227
230
|
<v-col cols="12">
|
|
228
|
-
<InputLabel
|
|
229
|
-
title="Remarks (Optional)"
|
|
230
|
-
:required="false"
|
|
231
|
-
class="mb-2"
|
|
232
|
-
/>
|
|
231
|
+
<InputLabel title="Remarks" :required="false" class="mb-2" />
|
|
233
232
|
<v-textarea
|
|
234
233
|
v-model="modalData.remark"
|
|
235
234
|
placeholder="Enter your remarks here"
|
|
@@ -243,7 +242,7 @@
|
|
|
243
242
|
<v-col cols="12">
|
|
244
243
|
<v-divider class="mb-4" />
|
|
245
244
|
<PhotoUpload
|
|
246
|
-
title="Take/Upload
|
|
245
|
+
title="Take/Upload Photos"
|
|
247
246
|
:required="false"
|
|
248
247
|
v-model="modalData.photos"
|
|
249
248
|
v-model:photo-ids="modalData.photoIds"
|
|
@@ -305,31 +304,38 @@
|
|
|
305
304
|
|
|
306
305
|
<v-divider />
|
|
307
306
|
|
|
308
|
-
<v-
|
|
309
|
-
<v-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
class="
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
307
|
+
<v-toolbar class="pa-0" density="compact">
|
|
308
|
+
<v-row no-gutters>
|
|
309
|
+
<v-col cols="6" class="pa-0">
|
|
310
|
+
<v-btn
|
|
311
|
+
block
|
|
312
|
+
variant="text"
|
|
313
|
+
class="text-none"
|
|
314
|
+
size="large"
|
|
315
|
+
height="48"
|
|
316
|
+
:disabled="rejectModal.submitting"
|
|
317
|
+
@click="closeRejectDialog"
|
|
318
|
+
>
|
|
319
|
+
Cancel
|
|
320
|
+
</v-btn>
|
|
321
|
+
</v-col>
|
|
322
|
+
|
|
323
|
+
<v-col cols="6" class="pa-0">
|
|
324
|
+
<v-btn
|
|
325
|
+
block
|
|
326
|
+
variant="flat"
|
|
327
|
+
color="black"
|
|
328
|
+
class="text-none font-weight-bold rounded-0"
|
|
329
|
+
height="48"
|
|
330
|
+
:loading="rejectModal.submitting"
|
|
331
|
+
:disabled="!isRejectModalValid || rejectModal.submitting"
|
|
332
|
+
@click="submitRejectDialog"
|
|
333
|
+
>
|
|
334
|
+
Confirm
|
|
335
|
+
</v-btn>
|
|
336
|
+
</v-col>
|
|
337
|
+
</v-row>
|
|
338
|
+
</v-toolbar>
|
|
333
339
|
</v-card>
|
|
334
340
|
</v-dialog>
|
|
335
341
|
|
|
@@ -435,12 +441,12 @@ const { back } = useUtils();
|
|
|
435
441
|
const route = useRoute();
|
|
436
442
|
const scheduleTitle = computed(() => {
|
|
437
443
|
const path = route.path;
|
|
438
|
-
if (path.includes(
|
|
439
|
-
if (path.includes(
|
|
440
|
-
if (path.includes(
|
|
441
|
-
if (path.includes(
|
|
442
|
-
if (path.includes(
|
|
443
|
-
return
|
|
444
|
+
if (path.includes("cleaning-schedule")) return "Cleaning Checklist";
|
|
445
|
+
if (path.includes("technician-schedule")) return "Technician Checklist";
|
|
446
|
+
if (path.includes("landscape-schedule")) return "Landscape Checklist";
|
|
447
|
+
if (path.includes("pool-schedule")) return "Pool Checklist";
|
|
448
|
+
if (path.includes("pest-schedule")) return "Pest Checklist";
|
|
449
|
+
return "Cleaner Checklist";
|
|
444
450
|
});
|
|
445
451
|
|
|
446
452
|
const canAddRemarks = computed(() => props.canAddRemarks);
|
|
@@ -14,44 +14,63 @@
|
|
|
14
14
|
</v-col>
|
|
15
15
|
</v-row>
|
|
16
16
|
|
|
17
|
-
<v-tabs
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<v-tabs
|
|
18
|
+
v-model="activeTab"
|
|
19
|
+
class="mb-6"
|
|
20
|
+
bg-color="transparent"
|
|
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>
|
|
21
34
|
</v-tabs>
|
|
22
35
|
|
|
23
36
|
<v-window v-model="activeTab" class="pt-4">
|
|
24
37
|
<v-window-item value="patrol">
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
<div v-if="activeTab === 'patrol'">
|
|
39
|
+
<PatrolReportTab
|
|
40
|
+
:active="true"
|
|
41
|
+
:site="site"
|
|
42
|
+
:filters="filters"
|
|
43
|
+
:options="options"
|
|
44
|
+
@update:filters="updateFilters"
|
|
45
|
+
@export="exportReport"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
33
48
|
</v-window-item>
|
|
34
49
|
|
|
35
50
|
<v-window-item value="daily">
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
<div v-if="activeTab === 'daily'">
|
|
52
|
+
<DailyReportTab
|
|
53
|
+
:active="true"
|
|
54
|
+
:site="site"
|
|
55
|
+
:filters="filters"
|
|
56
|
+
:options="options"
|
|
57
|
+
@update:filters="updateFilters"
|
|
58
|
+
@export="exportReport"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
44
61
|
</v-window-item>
|
|
45
62
|
|
|
46
63
|
<v-window-item value="monthly">
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
<div v-if="activeTab === 'monthly'">
|
|
65
|
+
<MonthlyReportTab
|
|
66
|
+
:active="true"
|
|
67
|
+
:site="site"
|
|
68
|
+
:filters="filters"
|
|
69
|
+
:options="options"
|
|
70
|
+
@update:filters="updateFilters"
|
|
71
|
+
@export="exportReport"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
55
74
|
</v-window-item>
|
|
56
75
|
</v-window>
|
|
57
76
|
</v-col>
|
|
@@ -59,15 +78,20 @@
|
|
|
59
78
|
</template>
|
|
60
79
|
|
|
61
80
|
<script setup lang="ts">
|
|
81
|
+
import html2canvas from "html2canvas";
|
|
82
|
+
import jsPDF from "jspdf";
|
|
83
|
+
|
|
62
84
|
import type {
|
|
63
85
|
NFCPatrolReportFilters,
|
|
64
86
|
NFCPatrolReportTab,
|
|
65
87
|
} from "../../types/nfc-patrol-report";
|
|
66
|
-
|
|
88
|
+
|
|
67
89
|
import { useNFCPatrolReportFilters } from "../../composables/useNFCPatrolReportFilters";
|
|
90
|
+
|
|
91
|
+
import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
|
|
68
92
|
import DailyReportTab from "./PatrolReport/DailyReportTab.vue";
|
|
69
93
|
import MonthlyReportTab from "./PatrolReport/MonthlyReportTab.vue";
|
|
70
|
-
|
|
94
|
+
|
|
71
95
|
|
|
72
96
|
const props = defineProps<{
|
|
73
97
|
site: string;
|
|
@@ -75,11 +99,16 @@ const props = defineProps<{
|
|
|
75
99
|
}>();
|
|
76
100
|
|
|
77
101
|
const router = useRouter();
|
|
102
|
+
|
|
103
|
+
|
|
78
104
|
const activeTab = ref<NFCPatrolReportTab>("patrol");
|
|
79
|
-
const { filters, options, fetchOptions } = useNFCPatrolReportFilters(props.site);
|
|
80
|
-
const { exportReport: exportActiveReport } = useNFCPatrolReportExport();
|
|
81
105
|
|
|
82
|
-
|
|
106
|
+
const { filters, options, fetchOptions } =
|
|
107
|
+
useNFCPatrolReportFilters(props.site);
|
|
108
|
+
|
|
109
|
+
onMounted(() => {
|
|
110
|
+
fetchOptions();
|
|
111
|
+
});
|
|
83
112
|
|
|
84
113
|
function goBack() {
|
|
85
114
|
router.back();
|
|
@@ -91,11 +120,90 @@ function updateFilters(value: NFCPatrolReportFilters) {
|
|
|
91
120
|
filters.date = value.date;
|
|
92
121
|
}
|
|
93
122
|
|
|
94
|
-
async function exportReport() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
async function exportReport(type: "pdf" | "csv") {
|
|
124
|
+
const report = document.querySelector(
|
|
125
|
+
".report-export",
|
|
126
|
+
) as HTMLElement | null;
|
|
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;",
|
|
99
181
|
});
|
|
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, '""')}"`;
|
|
100
208
|
}
|
|
101
209
|
</script>
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
+
:show-time-filter="false"
|
|
6
7
|
@update:model-value="$emit('update:filters', $event)"
|
|
7
|
-
@export="$emit('export')"
|
|
8
|
+
@export="(type) => $emit('export', type)"
|
|
8
9
|
/>
|
|
9
|
-
|
|
10
|
+
<div class="report-export">
|
|
10
11
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
11
12
|
|
|
12
13
|
<template v-if="report">
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
<SummaryReportTable :title="report.title" :rows="report.rows" />
|
|
16
17
|
</template>
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
<ReportEmptyState
|
|
19
21
|
v-else-if="!loading && notFound"
|
|
20
22
|
title="No Daily Report"
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
/>
|
|
23
25
|
|
|
24
26
|
<ReportEmptyState v-else-if="!loading" />
|
|
27
|
+
</div>
|
|
25
28
|
</div>
|
|
26
29
|
</template>
|
|
27
30
|
|
|
@@ -38,23 +41,23 @@ import SummaryReportTable from "./SummaryReportTable.vue";
|
|
|
38
41
|
|
|
39
42
|
const props = defineProps<{
|
|
40
43
|
active: boolean;
|
|
41
|
-
site: string;
|
|
44
|
+
site: string;
|
|
42
45
|
filters: NFCPatrolReportFilters;
|
|
43
46
|
options: NFCPatrolReportFilterOptions;
|
|
44
47
|
}>();
|
|
45
48
|
|
|
46
49
|
defineEmits<{
|
|
47
50
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
48
|
-
export: [];
|
|
51
|
+
export: [type: "pdf" | "csv"];
|
|
49
52
|
}>();
|
|
50
53
|
|
|
51
54
|
const { report, loading, notFound, fetchReport } = useNFCPatrolDailyReport(
|
|
52
55
|
props.filters,
|
|
53
|
-
props.site,
|
|
56
|
+
props.site,
|
|
54
57
|
);
|
|
55
58
|
|
|
56
59
|
watch(
|
|
57
|
-
() => [props.filters.route, props.filters.
|
|
60
|
+
() => [props.filters.route, props.filters.date, props.active],
|
|
58
61
|
() => {
|
|
59
62
|
if (props.active) {
|
|
60
63
|
void fetchReport();
|
|
@@ -62,4 +65,5 @@ watch(
|
|
|
62
65
|
},
|
|
63
66
|
{ immediate: true },
|
|
64
67
|
);
|
|
68
|
+
|
|
65
69
|
</script>
|
|
@@ -3,23 +3,30 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
+
:show-time-filter="false"
|
|
7
|
+
:show-date-filter="false"
|
|
6
8
|
@update:model-value="$emit('update:filters', $event)"
|
|
7
|
-
@export="$emit('export')"
|
|
9
|
+
@export="(type) => $emit('export', type)"
|
|
8
10
|
/>
|
|
9
|
-
|
|
11
|
+
<div class="report-export">
|
|
10
12
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
11
13
|
|
|
12
14
|
<template v-if="report">
|
|
13
15
|
<ReportLocationHeader :location="report.location" />
|
|
14
16
|
<v-divider :thickness="1" class="border-opacity-100 mb-6" />
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
<MonthlyReportTable
|
|
19
|
+
:title="report.title"
|
|
20
|
+
:rows="report.rows"
|
|
21
|
+
/>
|
|
16
22
|
</template>
|
|
17
23
|
|
|
18
24
|
<ReportEmptyState
|
|
19
25
|
v-else-if="!loading"
|
|
20
|
-
title="No Monthly Report
|
|
21
|
-
message="
|
|
26
|
+
title="No Monthly Report"
|
|
27
|
+
message="No patrol log found for this route"
|
|
22
28
|
/>
|
|
29
|
+
</div>
|
|
23
30
|
</div>
|
|
24
31
|
</template>
|
|
25
32
|
|
|
@@ -28,11 +35,13 @@ import type {
|
|
|
28
35
|
NFCPatrolReportFilterOptions,
|
|
29
36
|
NFCPatrolReportFilters,
|
|
30
37
|
} from "../../../types/nfc-patrol-report";
|
|
38
|
+
|
|
31
39
|
import { useNFCPatrolMonthlyReport } from "../../../composables/useNFCPatrolMonthlyReport";
|
|
40
|
+
|
|
32
41
|
import ReportEmptyState from "./ReportEmptyState.vue";
|
|
33
42
|
import ReportFilters from "./ReportFilters.vue";
|
|
34
43
|
import ReportLocationHeader from "./ReportLocationHeader.vue";
|
|
35
|
-
import
|
|
44
|
+
import MonthlyReportTable from "./MonthlyReportTable.vue";
|
|
36
45
|
|
|
37
46
|
const props = defineProps<{
|
|
38
47
|
active: boolean;
|
|
@@ -43,14 +52,14 @@ const props = defineProps<{
|
|
|
43
52
|
|
|
44
53
|
defineEmits<{
|
|
45
54
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
46
|
-
export: [];
|
|
55
|
+
export: [type: "pdf" | "csv"];
|
|
47
56
|
}>();
|
|
48
57
|
|
|
49
|
-
const { report, loading, fetchReport } =
|
|
58
|
+
const { report, loading, notFound, fetchReport } =
|
|
50
59
|
useNFCPatrolMonthlyReport(props.filters, props.site);
|
|
51
60
|
|
|
52
61
|
watch(
|
|
53
|
-
() => [props.filters.route, props.
|
|
62
|
+
() => [props.filters.route, props.active],
|
|
54
63
|
() => {
|
|
55
64
|
if (props.active) {
|
|
56
65
|
void fetchReport();
|
|
@@ -58,4 +67,5 @@ watch(
|
|
|
58
67
|
},
|
|
59
68
|
{ immediate: true },
|
|
60
69
|
);
|
|
70
|
+
|
|
61
71
|
</script>
|