@7365admin1/layer-common 3.0.31-staging.15 → 3.0.31-staging.16

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.
@@ -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,72 @@ 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) => `"${cell.textContent?.trim().replace(/"/g, '""') ?? ""}"`)
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);
100
190
  }
101
191
  </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>
@@ -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>
@@ -16,7 +16,12 @@
16
16
  />
17
17
  </v-col>
18
18
 
19
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
19
+ <v-col
20
+ v-if="showTimeFilter"
21
+ cols="12"
22
+ md="2"
23
+ class="px-md-2 mb-2 mb-md-0"
24
+ >
20
25
  <v-select
21
26
  :model-value="modelValue.timeRange"
22
27
  :items="options.timeRanges"
@@ -30,7 +35,12 @@
30
35
  />
31
36
  </v-col>
32
37
 
33
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
38
+ <v-col
39
+ v-if="showDateFilter"
40
+ cols="12"
41
+ md="2"
42
+ class="px-md-2 mb-2 mb-md-0"
43
+ >
34
44
  <v-text-field
35
45
  :model-value="modelValue.date"
36
46
  type="date"
@@ -46,17 +56,31 @@
46
56
  <v-spacer />
47
57
 
48
58
  <v-col cols="12" md="2" class="pl-md-2">
49
- <v-btn
50
- variant="outlined"
51
- block
52
- size="large"
53
- rounded="lg"
54
- class="text-none"
55
- prepend-icon="mdi-export"
56
- @click="$emit('export')"
57
- >
58
- Export
59
- </v-btn>
59
+ <v-menu>
60
+ <template #activator="{ props: menuProps }">
61
+ <v-btn
62
+ v-bind="menuProps"
63
+ variant="outlined"
64
+ block
65
+ size="large"
66
+ rounded="lg"
67
+ class="text-none"
68
+ prepend-icon="mdi-export"
69
+ >
70
+ Export
71
+ </v-btn>
72
+ </template>
73
+
74
+ <v-list density="compact">
75
+ <v-list-item @click="$emit('export', 'pdf')">
76
+ <v-list-item-title>PDF</v-list-item-title>
77
+ </v-list-item>
78
+
79
+ <v-list-item @click="$emit('export', 'csv')">
80
+ <v-list-item-title>CSV</v-list-item-title>
81
+ </v-list-item>
82
+ </v-list>
83
+ </v-menu>
60
84
  </v-col>
61
85
  </v-row>
62
86
  </template>
@@ -67,14 +91,22 @@ import type {
67
91
  NFCPatrolReportFilters,
68
92
  } from "../../../types/nfc-patrol-report";
69
93
 
70
- const props = defineProps<{
71
- modelValue: NFCPatrolReportFilters;
72
- options: NFCPatrolReportFilterOptions;
73
- }>();
94
+ const props = withDefaults(
95
+ defineProps<{
96
+ modelValue: NFCPatrolReportFilters;
97
+ options: NFCPatrolReportFilterOptions;
98
+ showTimeFilter?: boolean;
99
+ showDateFilter?: boolean;
100
+ }>(),
101
+ {
102
+ showTimeFilter: true,
103
+ showDateFilter: true,
104
+ },
105
+ );;
74
106
 
75
107
  const emit = defineEmits<{
76
108
  "update:modelValue": [value: NFCPatrolReportFilters];
77
- export: [];
109
+ export: [type: "pdf" | "csv"];
78
110
  }>();
79
111
 
80
112
  function updateFilter(key: keyof NFCPatrolReportFilters, value: unknown) {
@@ -63,15 +63,12 @@ export function useNFCPatrolDailyReport(
63
63
 
64
64
  try {
65
65
  const [logsRes, siteInfo] = await Promise.all([
66
- getPatrolLogs<NFCPatrolLogListResponse>({
66
+ getPatrolLogs({
67
67
  page: 1,
68
68
  limit: 100,
69
69
  site,
70
70
  date: filters.date,
71
71
  routeId: filters.route,
72
- routeStartTime: filters.timeRange
73
- ? filters.timeRange.split(" - ")[0]
74
- : undefined,
75
72
  }),
76
73
  getSiteById(site),
77
74
  ]);
@@ -2,6 +2,8 @@ import type {
2
2
  NFCPatrolReportFilters,
3
3
  NFCPatrolSummaryReport,
4
4
  NFCPatrolReportSummaryRow,
5
+ NFCPatrolMonthlyReportRow,
6
+ NFCPatrolMonthlyReport,
5
7
  } from "../types/nfc-patrol-report";
6
8
  import useNFCPatrolLog from "./useNFCPatrolLog";
7
9
  import useSite from "./useSite";
@@ -12,6 +14,55 @@ interface NFCPatrolLogListResponse {
12
14
  pageRange?: string;
13
15
  }
14
16
 
17
+ const MONTHS = [
18
+ "January",
19
+ "February",
20
+ "March",
21
+ "April",
22
+ "May",
23
+ "June",
24
+ "July",
25
+ "August",
26
+ "September",
27
+ "October",
28
+ "November",
29
+ "December",
30
+ ];
31
+ function mapLogsToMonthlyRows(
32
+ logs: Record<string, any>[],
33
+ ): NFCPatrolMonthlyReportRow[] {
34
+ const rows = MONTHS.map((month) => ({
35
+ month,
36
+ checked: 0,
37
+ checkedPercentage: 0,
38
+ missed: 0,
39
+ missedPercentage: 0,
40
+ }));
41
+
42
+ logs.forEach((log) => {
43
+ const monthIndex = new Date(log.date).getMonth();
44
+
45
+ for (const checkpoint of log.checkPoints ?? []) {
46
+ if (checkpoint.status === "Completed") {
47
+ rows[monthIndex].checked++;
48
+ } else if (checkpoint.status === "Skipped") {
49
+ rows[monthIndex].missed++;
50
+ }
51
+ }
52
+ });
53
+
54
+ rows.forEach((row) => {
55
+ const total = row.checked + row.missed;
56
+
57
+ if (total > 0) {
58
+ row.checkedPercentage = Math.round((row.checked / total) * 100);
59
+ row.missedPercentage = Math.round((row.missed / total) * 100);
60
+ }
61
+ });
62
+
63
+ return rows;
64
+ }
65
+
15
66
  function buildAddress(address?: Record<string, string>) {
16
67
  if (!address) return undefined;
17
68
 
@@ -27,23 +78,7 @@ function buildAddress(address?: Record<string, string>) {
27
78
  return parts.length ? parts.join(", ") : undefined;
28
79
  }
29
80
 
30
- function mapLogToSummaryRow(
31
- log: Record<string, any>,
32
- index: number,
33
- ): NFCPatrolReportSummaryRow {
34
- const checkpoints: Record<string, any>[] = log.checkPoints ?? [];
35
- const checked = checkpoints.filter((cp) => cp.status === "Completed").length;
36
- const missed = checkpoints.filter((cp) => cp.status === "Skipped").length;
37
81
 
38
- return {
39
- id: log._id,
40
- routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
41
- securityCheckSchedule: log.route?.startTime ?? "-",
42
- checked,
43
- missed,
44
- status: missed === 0 ? "Completed" : "Incomplete",
45
- };
46
- }
47
82
 
48
83
  export function useNFCPatrolMonthlyReport(
49
84
  filters: NFCPatrolReportFilters,
@@ -52,12 +87,12 @@ export function useNFCPatrolMonthlyReport(
52
87
  const { getAll: getPatrolLogs } = useNFCPatrolLog();
53
88
  const { getSiteById } = useSite();
54
89
 
55
- const report = ref<NFCPatrolSummaryReport | null>(null);
90
+ const report = ref<NFCPatrolMonthlyReport | null>(null);
56
91
  const loading = ref(false);
57
92
  const notFound = ref(false);
58
93
 
59
94
  async function fetchReport() {
60
- if (!site || !filters.date || !filters.route) {
95
+ if (!site || !filters.route) {
61
96
  report.value = null;
62
97
  notFound.value = false;
63
98
  return;
@@ -65,19 +100,16 @@ export function useNFCPatrolMonthlyReport(
65
100
 
66
101
  loading.value = true;
67
102
  notFound.value = false;
68
-
103
+ const currentYear = new Date().getFullYear().toString();
69
104
  try {
70
105
  const [logsRes, siteInfo] = await Promise.all([
71
- getPatrolLogs<NFCPatrolLogListResponse>({
72
- page: 1,
73
- limit: 100,
74
- site,
75
- date: filters.date,
76
- routeId: filters.route,
77
- routeStartTime: filters.timeRange
78
- ? filters.timeRange.split(" - ")[0]
79
- : undefined,
80
- type: "month",
106
+ getPatrolLogs({
107
+ page: 1,
108
+ limit: 100,
109
+ site,
110
+ routeId: filters.route,
111
+ date: currentYear,
112
+ type: "month",
81
113
  }),
82
114
  getSiteById(site),
83
115
  ]);
@@ -93,7 +125,7 @@ export function useNFCPatrolMonthlyReport(
93
125
  address: buildAddress(siteInfo.address),
94
126
  },
95
127
  title: `Patrol Route Summary: ${routeName}`,
96
- rows: logs.map(mapLogToSummaryRow),
128
+ rows: mapLogsToMonthlyRows(logs),
97
129
  };
98
130
  } else {
99
131
  report.value = null;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.0.31-staging.15",
5
+ "version": "3.0.31-staging.16",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -26,7 +26,7 @@ export interface NFCPatrolReportFilterOptions {
26
26
 
27
27
  export interface NFCPatrolReportLocation {
28
28
  name?: string;
29
- address?: string;
29
+ address?: string;
30
30
  }
31
31
 
32
32
  export interface NFCPatrolRouteSummary {
@@ -77,3 +77,17 @@ export interface NFCPatrolSummaryReport {
77
77
  title: string;
78
78
  rows: NFCPatrolReportSummaryRow[];
79
79
  }
80
+
81
+ export interface NFCPatrolMonthlyReportRow {
82
+ month: string;
83
+ checked: number;
84
+ checkedPercentage: number;
85
+ missed: number;
86
+ missedPercentage: number;
87
+ }
88
+
89
+ export interface NFCPatrolMonthlyReport {
90
+ location: NFCPatrolReportLocation;
91
+ title: string;
92
+ rows: NFCPatrolMonthlyReportRow[];
93
+ }