@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.
Files changed (31) hide show
  1. package/.github/workflows/publish-staging.yml +11 -0
  2. package/components/AddEqupmentForm.vue +52 -1
  3. package/components/CameraMain.vue +17 -9
  4. package/components/DashboardMain.vue +231 -87
  5. package/components/EquipmentManagementMain.vue +56 -0
  6. package/components/InvitationMain.vue +4 -3
  7. package/components/ManageChecklistMain.vue +44 -38
  8. package/components/Nfc/NFCPatrolReportMain.vue +146 -38
  9. package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
  10. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
  11. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
  12. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +20 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
  14. package/components/Nfc/PatrolReport/ReportDatePicker.vue +61 -0
  15. package/components/Nfc/PatrolReport/ReportFilters.vue +52 -20
  16. package/components/Nfc/PatrolReport/SummaryReportTable.vue +22 -0
  17. package/components/PhotoUpload.vue +19 -4
  18. package/components/ServiceProviderMain.vue +59 -25
  19. package/components/VisitorForm.vue +78 -18
  20. package/components/VisitorManagement.vue +12 -16
  21. package/composables/useCleaningSchedulePermission.ts +16 -51
  22. package/composables/useEquipment.ts +2 -0
  23. package/composables/useLocalAuth.ts +1 -1
  24. package/composables/useNFCPatrolDailyReport.ts +3 -4
  25. package/composables/useNFCPatrolMonthlyReport.ts +62 -30
  26. package/composables/useNFCPatrolReport.ts +2 -0
  27. package/composables/useServiceProvider.ts +21 -0
  28. package/package.json +1 -1
  29. package/types/customer.d.ts +2 -1
  30. package/types/equipment.d.ts +5 -1
  31. 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 v-if="item.remarks" class="text-body-2 text-medium-emphasis">
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 Photo"
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-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>
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('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';
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 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>
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
- <PatrolReportTab
26
- :active="activeTab === 'patrol'"
27
- :site="site"
28
- :filters="filters"
29
- :options="options"
30
- @update:filters="updateFilters"
31
- @export="exportReport"
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
- <DailyReportTab
37
- :active="activeTab === 'daily'"
38
- :site="site"
39
- :filters="filters"
40
- :options="options"
41
- @update:filters="updateFilters"
42
- @export="exportReport"
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
- <MonthlyReportTab
48
- :active="activeTab === 'monthly'"
49
- :site="site"
50
- :filters="filters"
51
- :options="options"
52
- @update:filters="updateFilters"
53
- @export="exportReport"
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
- import { useNFCPatrolReportExport } from "../../composables/useNFCPatrolReportExport";
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
- import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
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
- onMounted(fetchOptions);
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
- await exportActiveReport(activeTab.value, {
96
- route: filters.route,
97
- timeRange: filters.timeRange,
98
- date: filters.date,
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; // ✅ thêm
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.timeRange, props.filters.date, props.active],
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
- <SummaryReportTable :title="report.title" :rows="report.rows" />
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 Selected"
21
- message="Please select a route, time range, and date to view the monthly report"
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 SummaryReportTable from "./SummaryReportTable.vue";
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.filters.timeRange, props.filters.date, props.active],
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>