@7365admin1/layer-common 3.1.2-staging.32 → 3.1.2-staging.34

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.
@@ -171,7 +171,7 @@ async function exportReport(type: "pdf" | "csv") {
171
171
  const csv = Array.from(table.querySelectorAll("tr"))
172
172
  .map((row) =>
173
173
  Array.from(row.querySelectorAll("th,td"))
174
- .map((cell) => `"${cell.textContent?.trim().replace(/"/g, '""') ?? ""}"`)
174
+ .map((cell) => escapeCsvCell(getCsvCellValue(cell)))
175
175
  .join(","),
176
176
  )
177
177
  .join("\n");
@@ -188,4 +188,22 @@ async function exportReport(type: "pdf" | "csv") {
188
188
 
189
189
  URL.revokeObjectURL(link.href);
190
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, '""')}"`;
208
+ }
191
209
  </script>
@@ -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>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div class="d-flex flex-column">
3
+ <v-text-field
4
+ v-bind="$attrs"
5
+ :model-value="formattedDate"
6
+ autocomplete="off"
7
+ placeholder="DD/MM/YY"
8
+ readonly
9
+ @click="openDatePicker"
10
+ >
11
+ <template #append-inner>
12
+ <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
13
+ </template>
14
+ </v-text-field>
15
+
16
+ <input
17
+ ref="dateInput"
18
+ v-model="date"
19
+ class="native-date-input"
20
+ type="date"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ defineOptions({ inheritAttrs: false });
27
+
28
+ const date = defineModel<string>({ default: "" });
29
+ const dateInput = ref<HTMLInputElement | null>(null);
30
+
31
+ const formattedDate = computed(() => {
32
+ const match = date.value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
33
+
34
+ if (!match) return "";
35
+
36
+ const [, year, month, day] = match;
37
+ return `${day}/${month}/${year.slice(-2)}`;
38
+ });
39
+
40
+ function openDatePicker() {
41
+ const input = dateInput.value;
42
+
43
+ if (!input) return;
44
+
45
+ if (input.showPicker) {
46
+ input.showPicker();
47
+ } else {
48
+ input.click();
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <style scoped>
54
+ .native-date-input {
55
+ position: absolute;
56
+ width: 1px;
57
+ height: 1px;
58
+ opacity: 0;
59
+ pointer-events: none;
60
+ }
61
+ </style>
@@ -41,9 +41,8 @@
41
41
  md="2"
42
42
  class="px-md-2 mb-2 mb-md-0"
43
43
  >
44
- <v-text-field
44
+ <ReportDatePicker
45
45
  :model-value="modelValue.date"
46
- type="date"
47
46
  variant="outlined"
48
47
  density="comfortable"
49
48
  rounded="lg"
@@ -90,6 +89,7 @@ import type {
90
89
  NFCPatrolReportFilterOptions,
91
90
  NFCPatrolReportFilters,
92
91
  } from "../../../types/nfc-patrol-report";
92
+ import ReportDatePicker from "./ReportDatePicker.vue";
93
93
 
94
94
  const props = withDefaults(
95
95
  defineProps<{
@@ -8,6 +8,8 @@
8
8
  <tr>
9
9
  <th>Patrol Route Name</th>
10
10
  <th>Security Check Schedule</th>
11
+ <th>Security Personnel</th>
12
+ <th>Signature</th>
11
13
  <th>Checked</th>
12
14
  <th>Missed</th>
13
15
  <th>Status</th>
@@ -20,6 +22,17 @@
20
22
  <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
21
23
  {{ row.securityCheckSchedule }}
22
24
  </td>
25
+ <td>{{ row.guardName || "-" }}</td>
26
+ <td>
27
+ <v-img
28
+ v-if="row.guardSignatureFileId"
29
+ :src="getFileUrl(row.guardSignatureFileId)"
30
+ :alt="`${row.guardName || 'Guard'} signature`"
31
+ class="guard-signature"
32
+ contain
33
+ />
34
+ <span v-else>-</span>
35
+ </td>
23
36
  <td>{{ row.checked }}</td>
24
37
  <td>{{ row.missed }}</td>
25
38
  <td>
@@ -45,7 +58,16 @@ defineProps<{
45
58
  rows: NFCPatrolReportSummaryRow[];
46
59
  }>();
47
60
 
61
+ const { getFileUrl } = useFile();
62
+
48
63
  function statusClass(status: NFCPatrolReportSummaryStatus) {
49
64
  return status === "Completed" ? "text-success" : "text-error";
50
65
  }
51
66
  </script>
67
+
68
+ <style scoped>
69
+ .guard-signature {
70
+ width: 80px;
71
+ height: 40px;
72
+ }
73
+ </style>
@@ -34,6 +34,8 @@ function mapLogToSummaryRow(log: Record<string, any>, index: number): NFCPatrolS
34
34
  id: log._id,
35
35
  routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
36
36
  securityCheckSchedule: log.route?.startTime ?? "-",
37
+ guardName: log.sign?.guardName,
38
+ guardSignatureFileId: log.sign?.guardSignatureFileId,
37
39
  checked,
38
40
  missed,
39
41
  status: missed === 0 ? "Completed" : "Incomplete",
@@ -51,6 +51,8 @@ function mapLogToPatrolReport(
51
51
  const activities: NFCPatrolActivity[] = checkpoints.map((cp) => ({
52
52
  id: cp.nfcTag_id,
53
53
  checkpoint: cp.nfcTag_name,
54
+ guardName: log.sign?.guardName,
55
+ guardSignatureFileId: log.sign?.guardSignatureFileId,
54
56
  startTime: formatTime(cp.startDateTime),
55
57
  endTime: formatTime(cp.endDateTime),
56
58
  actualTime: diffMinutes(cp.startDateTime, cp.endDateTime),
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.1.2-staging.32",
5
+ "version": "3.1.2-staging.34",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -47,6 +47,8 @@ export interface NFCPatrolRouteSummary {
47
47
  export interface NFCPatrolActivity {
48
48
  id: string;
49
49
  checkpoint: string;
50
+ guardName?: string;
51
+ guardSignatureFileId?: string;
50
52
  startTime: string;
51
53
  endTime: string;
52
54
  actualTime: string;
@@ -67,6 +69,8 @@ export interface NFCPatrolReportSummaryRow {
67
69
  id: string;
68
70
  routeName: string;
69
71
  securityCheckSchedule: string;
72
+ guardName?: string;
73
+ guardSignatureFileId?: string;
70
74
  checked: number;
71
75
  missed: number;
72
76
  status: NFCPatrolReportSummaryStatus;