@7365admin1/layer-common 3.0.29 → 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.
@@ -0,0 +1,36 @@
1
+ name: Publish Staging
2
+ on:
3
+ push:
4
+ branches:
5
+ - staging
6
+
7
+ concurrency: ${{ github.workflow }}-${{ github.ref }}
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20.x
18
+ cache: "yarn"
19
+ cache-dependency-path: yarn.lock
20
+
21
+ - run: yarn install --immutable
22
+
23
+ - run: yarn build
24
+
25
+ - name: Create .npmrc for publishing
26
+ run: |
27
+ echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ~/.npmrc
28
+
29
+ - name: Set staging prerelease version
30
+ run: |
31
+ BASE=$(node -p "const [x,y,z]=require('./package.json').version.split('-')[0].split('.'); [x,y,+z+1].join('.')")
32
+ npm version "$BASE-staging.${{ github.run_number }}" --no-git-tag-version
33
+ node -p "'Publishing ' + require('./package.json').version"
34
+
35
+ - name: Publish to npm with staging tag
36
+ run: npm publish --tag staging --registry https://registry.npmjs.org
@@ -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,
@@ -1615,6 +1615,7 @@ async function handleVisitorDataFromScannedQRCodeCheckInOut(
1615
1615
  type,
1616
1616
  filter: {
1617
1617
  _id: visitorId,
1618
+ site: visitorData.site ?? siteId,
1618
1619
  },
1619
1620
  update: {
1620
1621
  updatedBy: currentUser.value._id,
@@ -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.29",
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 {