@7365admin1/layer-common 3.0.30-staging.1 → 3.0.30-staging.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.
@@ -35,6 +35,7 @@
35
35
  <v-window-item value="daily">
36
36
  <DailyReportTab
37
37
  :active="activeTab === 'daily'"
38
+ :site="site"
38
39
  :filters="filters"
39
40
  :options="options"
40
41
  @update:filters="updateFilters"
@@ -45,6 +46,7 @@
45
46
  <v-window-item value="monthly">
46
47
  <MonthlyReportTab
47
48
  :active="activeTab === 'monthly'"
49
+ :site="site"
48
50
  :filters="filters"
49
51
  :options="options"
50
52
  @update:filters="updateFilters"
@@ -16,10 +16,12 @@
16
16
  </template>
17
17
 
18
18
  <ReportEmptyState
19
- v-else-if="!loading"
20
- title="No Daily Report Selected"
21
- message="Please select a route, time range, and date to view the daily report"
19
+ v-else-if="!loading && notFound"
20
+ title="No Daily Report"
21
+ message="No patrol log for this date"
22
22
  />
23
+
24
+ <ReportEmptyState v-else-if="!loading" />
23
25
  </div>
24
26
  </template>
25
27
 
@@ -36,6 +38,7 @@ import SummaryReportTable from "./SummaryReportTable.vue";
36
38
 
37
39
  const props = defineProps<{
38
40
  active: boolean;
41
+ site: string; // ✅ thêm
39
42
  filters: NFCPatrolReportFilters;
40
43
  options: NFCPatrolReportFilterOptions;
41
44
  }>();
@@ -45,7 +48,10 @@ defineEmits<{
45
48
  export: [];
46
49
  }>();
47
50
 
48
- const { report, loading, fetchReport } = useNFCPatrolDailyReport(props.filters);
51
+ const { report, loading, notFound, fetchReport } = useNFCPatrolDailyReport(
52
+ props.filters,
53
+ props.site, // ✅
54
+ );
49
55
 
50
56
  watch(
51
57
  () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
@@ -36,6 +36,7 @@ import SummaryReportTable from "./SummaryReportTable.vue";
36
36
 
37
37
  const props = defineProps<{
38
38
  active: boolean;
39
+ site: string;
39
40
  filters: NFCPatrolReportFilters;
40
41
  options: NFCPatrolReportFilterOptions;
41
42
  }>();
@@ -45,7 +46,8 @@ defineEmits<{
45
46
  export: [];
46
47
  }>();
47
48
 
48
- const { report, loading, fetchReport } = useNFCPatrolMonthlyReport(props.filters);
49
+ const { report, loading, fetchReport } =
50
+ useNFCPatrolMonthlyReport(props.filters, props.site);
49
51
 
50
52
  watch(
51
53
  () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
@@ -22,6 +22,11 @@
22
22
  />
23
23
  </template>
24
24
 
25
+ <ReportEmptyState
26
+ v-else-if="!loading && notFound"
27
+ title="No Patrol Log"
28
+ message="No patrol log for this date"
29
+ />
25
30
  <ReportEmptyState v-else-if="!loading" />
26
31
 
27
32
  <RemarksDialog v-model="dialogRemarks" :remarks="selectedActivityRemarks" />
@@ -56,6 +61,7 @@ defineEmits<{
56
61
  const {
57
62
  report,
58
63
  loading,
64
+ notFound,
59
65
  selectedActivityRemarks,
60
66
  dialogRemarks,
61
67
  fetchReport,
@@ -1,19 +1,98 @@
1
1
  import type {
2
2
  NFCPatrolReportFilters,
3
3
  NFCPatrolSummaryReport,
4
+ NFCPatrolReportSummaryRow,
4
5
  } from "../types/nfc-patrol-report";
5
- import { useNFCPatrolReportService } from "../services/nfcPatrolReport";
6
+ import useNFCPatrolLog from "./useNFCPatrolLog";
7
+ import useSite from "./useSite";
8
+
9
+ interface NFCPatrolLogListResponse {
10
+ items?: Record<string, any>[];
11
+ pages?: number;
12
+ pageRange?: string;
13
+ }
14
+
15
+ function buildAddress(address?: Record<string, string>) {
16
+ if (!address) return undefined;
17
+ const parts = [
18
+ address.line1,
19
+ address.line2,
20
+ address.city,
21
+ address.state,
22
+ address.postalCode,
23
+ address.country,
24
+ ].filter((p) => p && p.trim() !== "");
25
+ return parts.length ? parts.join(", ") : undefined;
26
+ }
27
+
28
+ function mapLogToSummaryRow(log: Record<string, any>, index: number): NFCPatrolSummaryRow {
29
+ const checkpoints: Record<string, any>[] = log.checkPoints ?? [];
30
+ const checked = checkpoints.filter((cp) => cp.status === "Completed").length;
31
+ const missed = checkpoints.filter((cp) => cp.status === "Skipped").length;
32
+
33
+ return {
34
+ id: log._id,
35
+ routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
36
+ securityCheckSchedule: log.route?.startTime ?? "-",
37
+ checked,
38
+ missed,
39
+ status: missed === 0 ? "Completed" : "Incomplete",
40
+ };
41
+ }
42
+
43
+ export function useNFCPatrolDailyReport(
44
+ filters: NFCPatrolReportFilters,
45
+ site: string,
46
+ ) {
47
+ const { getAll: getPatrolLogs } = useNFCPatrolLog();
48
+ const { getSiteById } = useSite();
6
49
 
7
- export function useNFCPatrolDailyReport(filters: NFCPatrolReportFilters) {
8
- const reportService = useNFCPatrolReportService();
9
50
  const report = ref<NFCPatrolSummaryReport | null>(null);
10
51
  const loading = ref(false);
52
+ const notFound = ref(false);
11
53
 
12
54
  async function fetchReport() {
55
+ if (!site || !filters.date || !filters.route) {
56
+ report.value = null;
57
+ notFound.value = false;
58
+ return;
59
+ }
60
+
13
61
  loading.value = true;
62
+ notFound.value = false;
14
63
 
15
64
  try {
16
- report.value = await reportService.getDailyReport(filters);
65
+ const [logsRes, siteInfo] = await Promise.all([
66
+ getPatrolLogs<NFCPatrolLogListResponse>({
67
+ page: 1,
68
+ limit: 100,
69
+ site,
70
+ date: filters.date,
71
+ routeId: filters.route,
72
+ routeStartTime: filters.timeRange
73
+ ? filters.timeRange.split(" - ")[0]
74
+ : undefined,
75
+ }),
76
+ getSiteById(site),
77
+ ]);
78
+
79
+ const logs = logsRes.items ?? [];
80
+
81
+ if (logs.length) {
82
+ const routeName = logs[0]?.route?.name ?? "-";
83
+
84
+ report.value = {
85
+ location: {
86
+ name: siteInfo.name,
87
+ address: buildAddress(siteInfo.address),
88
+ },
89
+ title: `Patrol Route Summary: ${routeName}`,
90
+ rows: logs.map(mapLogToSummaryRow),
91
+ };
92
+ } else {
93
+ report.value = null;
94
+ notFound.value = true;
95
+ }
17
96
  } finally {
18
97
  loading.value = false;
19
98
  }
@@ -22,6 +101,7 @@ export function useNFCPatrolDailyReport(filters: NFCPatrolReportFilters) {
22
101
  return {
23
102
  report,
24
103
  loading,
104
+ notFound,
25
105
  fetchReport,
26
106
  };
27
107
  }
@@ -3,8 +3,9 @@ interface NFCPatrolLogQuery {
3
3
  limit?: number;
4
4
  site: string;
5
5
  date: string;
6
+ type?: string;
6
7
  routeId: string;
7
- routeStartTime: string;
8
+ routeStartTime?: string;
8
9
  }
9
10
 
10
11
  export default function useNFCPatrolLog() {
@@ -13,6 +14,7 @@ export default function useNFCPatrolLog() {
13
14
  limit = 10,
14
15
  site,
15
16
  date,
17
+ type,
16
18
  routeId,
17
19
  routeStartTime,
18
20
  }: NFCPatrolLogQuery) {
@@ -23,6 +25,7 @@ export default function useNFCPatrolLog() {
23
25
  limit,
24
26
  site,
25
27
  date,
28
+ type,
26
29
  "route._id": routeId,
27
30
  "route.startTime": routeStartTime,
28
31
  },
@@ -1,19 +1,104 @@
1
1
  import type {
2
2
  NFCPatrolReportFilters,
3
3
  NFCPatrolSummaryReport,
4
+ NFCPatrolReportSummaryRow,
4
5
  } from "../types/nfc-patrol-report";
5
- import { useNFCPatrolReportService } from "../services/nfcPatrolReport";
6
+ import useNFCPatrolLog from "./useNFCPatrolLog";
7
+ import useSite from "./useSite";
8
+
9
+ interface NFCPatrolLogListResponse {
10
+ items?: Record<string, any>[];
11
+ pages?: number;
12
+ pageRange?: string;
13
+ }
14
+
15
+ function buildAddress(address?: Record<string, string>) {
16
+ if (!address) return undefined;
17
+
18
+ const parts = [
19
+ address.line1,
20
+ address.line2,
21
+ address.city,
22
+ address.state,
23
+ address.postalCode,
24
+ address.country,
25
+ ].filter((p) => p && p.trim() !== "");
26
+
27
+ return parts.length ? parts.join(", ") : undefined;
28
+ }
29
+
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
+
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
+
48
+ export function useNFCPatrolMonthlyReport(
49
+ filters: NFCPatrolReportFilters,
50
+ site: string,
51
+ ) {
52
+ const { getAll: getPatrolLogs } = useNFCPatrolLog();
53
+ const { getSiteById } = useSite();
6
54
 
7
- export function useNFCPatrolMonthlyReport(filters: NFCPatrolReportFilters) {
8
- const reportService = useNFCPatrolReportService();
9
55
  const report = ref<NFCPatrolSummaryReport | null>(null);
10
56
  const loading = ref(false);
57
+ const notFound = ref(false);
11
58
 
12
59
  async function fetchReport() {
60
+ if (!site || !filters.date || !filters.route) {
61
+ report.value = null;
62
+ notFound.value = false;
63
+ return;
64
+ }
65
+
13
66
  loading.value = true;
67
+ notFound.value = false;
14
68
 
15
69
  try {
16
- report.value = await reportService.getMonthlyReport(filters);
70
+ 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",
81
+ }),
82
+ getSiteById(site),
83
+ ]);
84
+
85
+ const logs = logsRes.items ?? [];
86
+
87
+ if (logs.length) {
88
+ const routeName = logs[0]?.route?.name ?? "-";
89
+
90
+ report.value = {
91
+ location: {
92
+ name: siteInfo.name,
93
+ address: buildAddress(siteInfo.address),
94
+ },
95
+ title: `Patrol Route Summary: ${routeName}`,
96
+ rows: logs.map(mapLogToSummaryRow),
97
+ };
98
+ } else {
99
+ report.value = null;
100
+ notFound.value = true;
101
+ }
17
102
  } finally {
18
103
  loading.value = false;
19
104
  }
@@ -22,6 +107,7 @@ export function useNFCPatrolMonthlyReport(filters: NFCPatrolReportFilters) {
22
107
  return {
23
108
  report,
24
109
  loading,
110
+ notFound,
25
111
  fetchReport,
26
112
  };
27
113
  }
@@ -3,12 +3,80 @@ import type {
3
3
  NFCPatrolReport,
4
4
  NFCPatrolReportFilters,
5
5
  } from "../types/nfc-patrol-report";
6
+ import useNFCPatrolLog from "./useNFCPatrolLog";
7
+ import useSite from "./useSite";
6
8
 
7
- interface NFCPatrolLogResponse {
8
- data?: NFCPatrolLogResponse | NFCPatrolReport;
9
- item?: NFCPatrolReport;
10
- items?: NFCPatrolReport[];
11
- nfcPatrolLog?: NFCPatrolReport;
9
+ interface NFCPatrolLogListResponse {
10
+ items?: Record<string, any>[];
11
+ pages?: number;
12
+ pageRange?: string;
13
+ }
14
+
15
+ function formatTime(iso?: string) {
16
+ return iso ? iso.slice(11, 16) : "-";
17
+ }
18
+ function formatDuration(totalMinutes: number) {
19
+ if (totalMinutes < 60) return `${totalMinutes} mins`;
20
+
21
+ const hrs = Math.floor(totalMinutes / 60);
22
+ const mins = totalMinutes % 60;
23
+
24
+ if (mins === 0) return `${hrs} hrs`;
25
+ return `${hrs} hrs ${mins} mins`;
26
+ }
27
+
28
+ function diffMinutes(startIso?: string, endIso?: string) {
29
+ if (!startIso || !endIso) return "-";
30
+ const totalMinutes = Math.max(
31
+ 0,
32
+ Math.round((new Date(endIso).getTime() - new Date(startIso).getTime()) / 60000),
33
+ );
34
+ return formatDuration(totalMinutes);
35
+ }
36
+
37
+
38
+ function mapLogToPatrolReport(
39
+ log: Record<string, any>,
40
+ location: NFCPatrolReport["location"],
41
+ ): NFCPatrolReport {
42
+ const checkpoints: Record<string, any>[] = log.checkPoints ?? [];
43
+ const actualStart = checkpoints[0]?.startDateTime ?? log.startDateTime;
44
+ const actualEnd = checkpoints[checkpoints.length - 1]?.endDateTime ?? log.endDateTime;
45
+
46
+ const totalTravelMinutes = checkpoints.reduce(
47
+ (sum, cp) => sum + (Number(cp.travelTime) || 0),
48
+ 0,
49
+ );
50
+
51
+ const activities: NFCPatrolActivity[] = checkpoints.map((cp) => ({
52
+ id: cp.nfcTag_id,
53
+ checkpoint: cp.nfcTag_name,
54
+ startTime: formatTime(cp.startDateTime),
55
+ endTime: formatTime(cp.endDateTime),
56
+ actualTime: diffMinutes(cp.startDateTime, cp.endDateTime),
57
+ expectedTime: formatDuration(cp.travelTime),
58
+ status: cp.status,
59
+ remarks: cp.skippedRemarks && cp.skippedRemarks !== "N/A" ? cp.skippedRemarks : undefined,
60
+ }));
61
+
62
+ return {
63
+ location,
64
+ routeSummary: {
65
+ routeName: log.route?.name,
66
+ tolerance: totalTravelMinutes,
67
+ schedule: {
68
+ start: log.route?.startTime,
69
+ end: log.route?.endTime,
70
+ totalTime: diffMinutes(log.startDateTime, log.endDateTime),
71
+ },
72
+ actual: {
73
+ start: formatTime(actualStart),
74
+ end: formatTime(actualEnd),
75
+ totalTime: diffMinutes(actualStart, actualEnd),
76
+ },
77
+ },
78
+ activities,
79
+ };
12
80
  }
13
81
 
14
82
  export function useNFCPatrolReport(
@@ -16,43 +84,51 @@ export function useNFCPatrolReport(
16
84
  site: string,
17
85
  ) {
18
86
  const { getAll: getPatrolLogs } = useNFCPatrolLog();
87
+ const { getSiteById } = useSite();
88
+
19
89
  const report = ref<NFCPatrolReport | null>(null);
20
90
  const loading = ref(false);
91
+ const notFound = ref(false);
21
92
  const selectedActivityRemarks = ref("");
22
93
  const dialogRemarks = ref(false);
23
94
 
24
95
  async function fetchReport() {
25
96
  if (!site || !filters.date || !filters.route || !filters.timeRange) {
26
97
  report.value = null;
98
+ notFound.value = false;
27
99
  return;
28
100
  }
29
101
 
30
102
  loading.value = true;
103
+ notFound.value = false;
31
104
 
32
105
  try {
33
- const response = await getPatrolLogs<NFCPatrolLogResponse>({
34
- page: 1,
35
- limit: 10,
36
- site,
37
- date: filters.date,
38
- routeId: filters.route,
39
- routeStartTime: filters.timeRange.split(" - ")[0],
40
- });
41
-
42
- report.value = extractReport(response);
43
- } finally {
44
- loading.value = false;
45
- }
46
- }
106
+ const [logsRes, siteInfo] = await Promise.all([
107
+ getPatrolLogs<NFCPatrolLogListResponse>({
108
+ page: 1,
109
+ limit: 10,
110
+ site,
111
+ date: filters.date,
112
+ routeId: filters.route,
113
+ routeStartTime: filters.timeRange.split(" - ")[0],
114
+ }),
115
+ getSiteById(site),
116
+ ]);
47
117
 
48
- function extractReport(response: NFCPatrolLogResponse): NFCPatrolReport | null {
49
- const payload = response.data ?? response;
118
+ const log = logsRes.items?.[0];
50
119
 
51
- if ("location" in payload && "activities" in payload) {
52
- return payload;
120
+ if (log) {
121
+ report.value = mapLogToPatrolReport(log, {
122
+ name: siteInfo.name,
123
+ address: buildAddress(siteInfo.address),
124
+ });
125
+ } else {
126
+ report.value = null;
127
+ notFound.value = true;
128
+ }
129
+ } finally {
130
+ loading.value = false;
53
131
  }
54
-
55
- return payload.nfcPatrolLog ?? payload.item ?? payload.items?.[0] ?? null;
56
132
  }
57
133
 
58
134
  function openRemarksDialog(activity: NFCPatrolActivity) {
@@ -60,9 +136,33 @@ export function useNFCPatrolReport(
60
136
  dialogRemarks.value = true;
61
137
  }
62
138
 
139
+ function buildAddress(address?: {
140
+ line1?: string;
141
+ line2?: string;
142
+ city?: string;
143
+ state?: string;
144
+ country?: string;
145
+ postalCode?: string;
146
+ }) {
147
+ if (!address) return undefined;
148
+
149
+ const parts = [
150
+ address.line1,
151
+ address.line2,
152
+ address.city,
153
+ address.state,
154
+ address.postalCode,
155
+ address.country,
156
+ ].filter((part) => part && part.trim() !== "");
157
+
158
+ return parts.length ? parts.join(", ") : undefined;
159
+ }
160
+
161
+
63
162
  return {
64
163
  report,
65
164
  loading,
165
+ notFound,
66
166
  selectedActivityRemarks,
67
167
  dialogRemarks,
68
168
  fetchReport,
@@ -2,14 +2,22 @@ import type {
2
2
  NFCPatrolReportFilterOptions,
3
3
  NFCPatrolReportFilters,
4
4
  } from "../types/nfc-patrol-report";
5
+ import useNFCPatrolRoute from "./useNFCPatrolRoute";
5
6
 
7
+ function getTodayDate() {
8
+ const now = new Date();
9
+ const y = now.getFullYear();
10
+ const m = String(now.getMonth() + 1).padStart(2, "0");
11
+ const d = String(now.getDate()).padStart(2, "0");
12
+ return `${y}-${m}-${d}`;
13
+ }
6
14
  export function useNFCPatrolReportFilters(site: string) {
7
15
  const { getAll: getPatrolRoutes } = useNFCPatrolRoute();
8
16
 
9
17
  const filters = reactive<NFCPatrolReportFilters>({
10
18
  route: "",
11
19
  timeRange: "",
12
- date: "2025-11-27",
20
+ date: getTodayDate(),
13
21
  });
14
22
 
15
23
  const options = ref<NFCPatrolReportFilterOptions>({
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.30-staging.1",
5
+ "version": "3.0.30-staging.2",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -25,8 +25,8 @@ export interface NFCPatrolReportFilterOptions {
25
25
  }
26
26
 
27
27
  export interface NFCPatrolReportLocation {
28
- name: string;
29
- address: string;
28
+ name?: string;
29
+ address?: string;
30
30
  }
31
31
 
32
32
  export interface NFCPatrolRouteSummary {