@7365admin1/layer-common 3.1.2 → 3.1.3-staging.39

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 +5 -6
  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
@@ -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>
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <div class="mt-6">
3
+ <div class="text-h6 font-weight-bold mb-4">
4
+ {{ title }}
5
+ </div>
6
+
7
+ <v-table class="border rounded-lg">
8
+ <thead>
9
+ <tr>
10
+ <th class="text-left font-weight-bold">Month</th>
11
+ <th class="text-center font-weight-bold">Checked</th>
12
+ <th class="text-center font-weight-bold">% Checked</th>
13
+ <th class="text-center font-weight-bold">Missed</th>
14
+ <th class="text-center font-weight-bold">% Missed</th>
15
+ </tr>
16
+ </thead>
17
+
18
+ <tbody>
19
+ <tr
20
+ v-for="row in rows"
21
+ :key="row.month"
22
+ >
23
+ <td>{{ row.month }}</td>
24
+
25
+ <td class="text-center">
26
+ {{ row.checked }}
27
+ </td>
28
+
29
+ <td class="text-center">
30
+ {{ row.checkedPercentage }}%
31
+ </td>
32
+
33
+ <td class="text-center">
34
+ {{ row.missed }}
35
+ </td>
36
+
37
+ <td class="text-center">
38
+ {{ row.missedPercentage }}%
39
+ </td>
40
+ </tr>
41
+ </tbody>
42
+ </v-table>
43
+ </div>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ import type { NFCPatrolMonthlyReportRow } from "../../../types/nfc-patrol-report";
48
+
49
+ defineProps<{
50
+ title: string;
51
+ rows: NFCPatrolMonthlyReportRow[];
52
+ }>();
53
+ </script>
54
+
55
+ <style scoped>
56
+ .v-table {
57
+ border: 1px solid rgb(var(--v-theme-outline));
58
+ }
59
+
60
+ thead th {
61
+ background: transparent;
62
+ white-space: nowrap;
63
+ font-weight: 600;
64
+ }
65
+
66
+ tbody td {
67
+ height: 56px;
68
+ }
69
+ </style>
@@ -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>
@@ -4,9 +4,9 @@
4
4
  :model-value="filters"
5
5
  :options="options"
6
6
  @update:model-value="$emit('update:filters', $event)"
7
- @export="$emit('export')"
7
+ @export="(type) => $emit('export', type)"
8
8
  />
9
-
9
+ <div class="report-export">
10
10
  <v-divider :thickness="1" class="border-opacity-100" />
11
11
 
12
12
  <template v-if="report">
@@ -30,6 +30,7 @@
30
30
  <ReportEmptyState v-else-if="!loading" />
31
31
 
32
32
  <RemarksDialog v-model="dialogRemarks" :remarks="selectedActivityRemarks" />
33
+ </div>
33
34
  </div>
34
35
  </template>
35
36
 
@@ -55,7 +56,7 @@ const props = defineProps<{
55
56
 
56
57
  defineEmits<{
57
58
  "update:filters": [value: NFCPatrolReportFilters];
58
- export: [];
59
+ export: [type: "pdf" | "csv"];
59
60
  }>();
60
61
 
61
62
  const {
@@ -77,4 +78,5 @@ watch(
77
78
  },
78
79
  { immediate: true },
79
80
  );
81
+
80
82
  </script>