@7365admin1/layer-common 3.0.22 → 3.0.23

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.
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <v-row no-gutters class="border-thin pa-6 rounded-lg">
3
3
  <v-col cols="12">
4
- <!-- Header -->
5
4
  <v-row no-gutters class="mb-4" align="center">
6
5
  <v-col cols="12" md="6">
7
6
  <v-btn
@@ -13,390 +12,59 @@
13
12
  Patrol Report Log
14
13
  </v-btn>
15
14
  </v-col>
16
-
17
15
  </v-row>
18
16
 
19
- <!-- Tabs -->
20
- <v-tabs v-model="activeTab" class="mb-6 " bg-color="transparent" grow>
21
- <v-tab value="patrol" class="text-none border-thin" >Patrol Report</v-tab>
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>
22
19
  <v-tab value="daily" class="text-none border-thin">Daily Report</v-tab>
23
20
  <v-tab value="monthly" class="text-none border-thin">Monthly Report</v-tab>
24
21
  </v-tabs>
25
22
 
26
23
  <v-window v-model="activeTab" class="pt-4">
27
- <!-- Patrol Report Tab -->
28
24
  <v-window-item value="patrol">
29
- <!-- Filters -->
30
- <v-row no-gutters class="mb-6">
31
- <v-col cols="12" md="3" class="pr-md-2 mb-2 mb-md-0">
32
- <v-select
33
- v-model="selectedRoute"
34
- :items="routes"
35
- label="Select Route"
36
- variant="outlined"
37
- density="comfortable"
38
- rounded="lg"
39
- hide-details
40
- bg-color="white"
41
- />
42
- </v-col>
43
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
44
- <v-select
45
- v-model="selectedTimeRange"
46
- :items="timeRanges"
47
- label="Time Range"
48
- variant="outlined"
49
- density="comfortable"
50
- rounded="lg"
51
- hide-details
52
- bg-color="white"
53
- />
54
- </v-col>
55
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
56
- <v-text-field
57
- v-model="selectedDate"
58
- type="date"
59
- variant="outlined"
60
- density="comfortable"
61
- rounded="lg"
62
- hide-details
63
- bg-color="white"
64
- />
65
- </v-col>
66
-
67
- <v-col cols="12" md="2" class="pl-md-2">
68
- <v-btn
69
- variant="outlined"
70
- block
71
- size="large"
72
- rounded="lg"
73
- class="text-none"
74
- prepend-icon="mdi-export"
75
- @click="exportReport"
76
- >
77
- Export
78
- </v-btn>
79
- </v-col>
80
- </v-row>
81
-
82
- <v-divider :thickness="1" class="border-opacity-100"></v-divider>
83
-
84
-
85
- <!-- Location Card -->
86
- <v-card
87
- v-if="locationData"
88
- class=" text-center pa-8 mt-2"
89
- variant="flat"
90
- rounded="lg"
91
- >
92
- <v-icon size="64" class="mb-4">mdi-office-building</v-icon>
93
- <h2 class="text-h5 mb-2">{{ locationData.name }}</h2>
94
- <p class="text-body-2 text-medium-emphasis">
95
- {{ locationData.address }}
96
- </p>
97
- </v-card>
98
- <v-divider :thickness="1" class="border-opacity-100 mb-6"></v-divider>
99
- <!-- Route Summary -->
100
- <v-card v-if="routeSummary" class="mb-6" variant="flat" rounded="lg">
101
- <v-card-title class="d-flex justify-space-between align-center">
102
- <span>Patrol Route Summary: {{ routeSummary.routeName }}</span>
103
- <span class="text-body-2 text-medium-emphasis">
104
- Tolerance: {{ routeSummary.tolerance }} mins
105
- </span>
106
- </v-card-title>
107
- <v-card-text class="pa-0">
108
- <v-table class=" border-thin">
109
- <thead>
110
- <tr>
111
- <th>Time</th>
112
- <th>Start</th>
113
- <th>End</th>
114
- <th>Total travel time</th>
115
- </tr>
116
- </thead>
117
- <tbody>
118
- <tr>
119
- <td class="font-weight-medium">Schedule</td>
120
- <td>
121
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
122
- {{ routeSummary.schedule.start }}
123
- </td>
124
- <td>
125
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
126
- {{ routeSummary.schedule.end }}
127
- </td>
128
- <td>
129
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
130
- {{ routeSummary.schedule.totalTime }}
131
- </td>
132
- </tr>
133
- <tr>
134
- <td class="font-weight-medium">Actual</td>
135
- <td>
136
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
137
- {{ routeSummary.actual.start }}
138
- </td>
139
- <td>
140
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
141
- {{ routeSummary.actual.end }}
142
- </td>
143
- <td>
144
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
145
- {{ routeSummary.actual.totalTime }}
146
- </td>
147
- </tr>
148
- </tbody>
149
- </v-table>
150
- </v-card-text>
151
- </v-card>
152
-
153
- <!-- Patrol Activity -->
154
- <v-card
155
- v-if="patrolActivity.length"
156
- variant="flat"
157
- rounded="lg"
158
- >
159
- <v-card-title>Patrol Activity</v-card-title>
160
- <v-card-text class="pa-0">
161
- <v-table class="border-thin">
162
- <thead>
163
- <tr>
164
- <th>Checkpoints</th>
165
- <th>Start - End Time</th>
166
- <th>
167
- Travel time<br />
168
- <span class="text-caption text-medium-emphasis">Actual/Expected</span>
169
- </th>
170
- <th>Status</th>
171
- </tr>
172
- </thead>
173
- <tbody>
174
- <tr
175
- v-for="activity in patrolActivity"
176
- :key="activity.id"
177
- :class="{ 'cursor-pointer': activity.status === 'Skipped' }"
178
- @click="activity.status === 'Skipped' && openRemarksDialog(activity)"
179
- >
180
- <td>{{ activity.checkpoint }}</td>
181
- <td>
182
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
183
- {{ activity.startTime }}
184
- <v-icon size="small" class="mx-2">mdi-arrow-right</v-icon>
185
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
186
- {{ activity.endTime }}
187
- </td>
188
- <td>
189
- <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
190
- {{ activity.actualTime }} / {{ activity.expectedTime }}
191
- </td>
192
- <td>
193
- <v-chip
194
- :color="getStatusColor(activity.status)"
195
- size="small"
196
- variant="flat"
197
- >
198
- {{ activity.status }}
199
- <v-icon
200
- v-if="activity.status === 'Skipped'"
201
- size="small"
202
- class="ml-1"
203
- >
204
- mdi-flag
205
- </v-icon>
206
- </v-chip>
207
- </td>
208
- </tr>
209
- </tbody>
210
- </v-table>
211
- </v-card-text>
212
- </v-card>
213
-
214
- <!-- Empty State -->
215
- <v-card
216
- v-if="!routeSummary && !loading"
217
- class="text-center pa-12"
218
- variant="outlined"
219
- rounded="lg"
220
- >
221
- <v-icon size="64" color="grey-lighten-1" class="mb-4">
222
- mdi-clipboard-text-outline
223
- </v-icon>
224
- <h3 class="text-h6 mb-2">No Patrol Report Selected</h3>
225
- <p class="text-body-2 text-medium-emphasis">
226
- Please select a route, time range, and date to view the patrol
227
- report
228
- </p>
229
- </v-card>
25
+ <PatrolReportTab
26
+ :active="activeTab === 'patrol'"
27
+ :filters="filters"
28
+ :options="options"
29
+ @update:filters="updateFilters"
30
+ @export="exportReport"
31
+ />
230
32
  </v-window-item>
231
33
 
232
- <!-- Daily Report Tab -->
233
34
  <v-window-item value="daily">
234
- <v-card class="text-center pa-12" variant="outlined" rounded="lg">
235
- <v-icon size="64" color="grey-lighten-1" class="mb-4">
236
- mdi-calendar-today
237
- </v-icon>
238
- <h3 class="text-h6 mb-2">Daily Report</h3>
239
- <p class="text-body-2 text-medium-emphasis">Coming soon...</p>
240
- </v-card>
35
+ <DailyReportTab
36
+ :active="activeTab === 'daily'"
37
+ :filters="filters"
38
+ :options="options"
39
+ @update:filters="updateFilters"
40
+ @export="exportReport"
41
+ />
241
42
  </v-window-item>
242
43
 
243
- <!-- Monthly Report Tab -->
244
44
  <v-window-item value="monthly">
245
- <v-card class="text-center pa-12" variant="outlined" rounded="lg">
246
- <v-icon size="64" color="grey-lighten-1" class="mb-4">
247
- mdi-calendar-month
248
- </v-icon>
249
- <h3 class="text-h6 mb-2">Monthly Report</h3>
250
- <p class="text-body-2 text-medium-emphasis">Coming soon...</p>
251
- </v-card>
45
+ <MonthlyReportTab
46
+ :active="activeTab === 'monthly'"
47
+ :filters="filters"
48
+ :options="options"
49
+ @update:filters="updateFilters"
50
+ @export="exportReport"
51
+ />
252
52
  </v-window-item>
253
53
  </v-window>
254
54
  </v-col>
255
-
256
- <!-- Skip Checkpoint Remarks Dialog -->
257
- <v-dialog v-model="dialogRemarks" max-width="600" persistent>
258
- <v-card rounded="xl">
259
- <v-card-title class="d-flex align-center pa-6">
260
- <span class="text-h6">Skip Checkpoint Remarks</span>
261
- <v-spacer />
262
- <v-btn
263
- icon="mdi-close"
264
- variant="text"
265
- size="small"
266
- @click="dialogRemarks = false"
267
- />
268
- </v-card-title>
269
-
270
- <v-card-text class="px-6 pb-6">
271
- <v-textarea
272
- :model-value="selectedActivityRemarks"
273
- variant="outlined"
274
- rounded="lg"
275
- readonly
276
- rows="4"
277
- hide-details
278
- />
279
- </v-card-text>
280
- </v-card>
281
- </v-dialog>
282
-
283
- <!-- Select Routes & Schedule Dialog -->
284
- <v-dialog v-model="dialogSelectRoute" max-width="680" persistent>
285
- <v-card rounded="xl">
286
- <v-card-title class="pa-6">
287
- <v-btn
288
- icon="mdi-chevron-left"
289
- variant="text"
290
- size="small"
291
- @click="dialogSelectRoute = false"
292
- />
293
- <span class="text-h6 ml-2">Select Routes & Schedule </span>
294
- </v-card-title>
295
-
296
- <v-card-text class="px-6 pb-6">
297
- <!-- Route Selector -->
298
- <v-select
299
- v-model="dialogRoute"
300
- :items="routes"
301
- variant="outlined"
302
- rounded="lg"
303
- density="comfortable"
304
- hide-details
305
- class="mb-4"
306
- />
307
-
308
- <!-- Date and Time Row -->
309
- <v-row no-gutters>
310
- <v-col cols="12" md="6" class="pr-md-2">
311
- <v-text-field
312
- v-model="dialogDate"
313
- type="date"
314
- variant="outlined"
315
- rounded="lg"
316
- density="comfortable"
317
- hide-details
318
- prepend-inner-icon="mdi-calendar"
319
- />
320
- </v-col>
321
- <v-col cols="12" md="6" class="pl-md-2 mt-2 mt-md-0">
322
- <v-select
323
- v-model="dialogTime"
324
- :items="timeRanges"
325
- variant="outlined"
326
- rounded="lg"
327
- density="comfortable"
328
- hide-details
329
- prepend-inner-icon="mdi-clock-outline"
330
- />
331
- </v-col>
332
- </v-row>
333
- </v-card-text>
334
-
335
- <!-- Action Buttons -->
336
- <v-card-actions class="px-6 pb-6">
337
- <v-row no-gutters>
338
- <v-col cols="6" class="pr-2">
339
- <v-btn
340
- block
341
- size="x-large"
342
- variant="outlined"
343
- rounded="lg"
344
- class="text-none"
345
- @click="dialogSelectRoute = false"
346
- >
347
- Cancel
348
- </v-btn>
349
- </v-col>
350
- <v-col cols="6" class="pl-2">
351
- <v-btn
352
- block
353
- size="x-large"
354
- color="primary"
355
- rounded="lg"
356
- class="text-none"
357
- @click="submitSelection"
358
- >
359
- Submit
360
- </v-btn>
361
- </v-col>
362
- </v-row>
363
- </v-card-actions>
364
- </v-card>
365
- </v-dialog>
366
55
  </v-row>
367
56
  </template>
368
57
 
369
58
  <script setup lang="ts">
370
- interface PatrolActivity {
371
- id: string;
372
- checkpoint: string;
373
- startTime: string;
374
- endTime: string;
375
- actualTime: string;
376
- expectedTime: string;
377
- status: "Completed" | "Skipped";
378
- remarks?: string;
379
- }
380
-
381
- interface RouteSummary {
382
- routeName: string;
383
- tolerance: number;
384
- schedule: {
385
- start: string;
386
- end: string;
387
- totalTime: string;
388
- };
389
- actual: {
390
- start: string;
391
- end: string;
392
- totalTime: string;
393
- };
394
- }
395
-
396
- interface LocationData {
397
- name: string;
398
- address: string;
399
- }
59
+ import type {
60
+ NFCPatrolReportFilters,
61
+ NFCPatrolReportTab,
62
+ } from "../../types/nfc-patrol-report";
63
+ import { useNFCPatrolReportExport } from "../../composables/useNFCPatrolReportExport";
64
+ import { useNFCPatrolReportFilters } from "../../composables/useNFCPatrolReportFilters";
65
+ import DailyReportTab from "./PatrolReport/DailyReportTab.vue";
66
+ import MonthlyReportTab from "./PatrolReport/MonthlyReportTab.vue";
67
+ import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
400
68
 
401
69
  const props = defineProps<{
402
70
  site: string;
@@ -404,166 +72,27 @@ const props = defineProps<{
404
72
  }>();
405
73
 
406
74
  const router = useRouter();
75
+ const activeTab = ref<NFCPatrolReportTab>("patrol");
76
+ const { filters, options, fetchOptions } = useNFCPatrolReportFilters(props.site);
77
+ const { exportReport: exportActiveReport } = useNFCPatrolReportExport();
407
78
 
408
- // State
409
- const activeTab = ref("patrol");
410
- const selectedRoute = ref("WH01 Patrol Route");
411
- const selectedTimeRange = ref("15:00 - 17:00");
412
- const selectedDate = ref("2025-11-27");
413
- const loading = ref(false);
414
-
415
- // Dialog state
416
- const dialogSelectRoute = ref(false);
417
- const dialogRoute = ref("WH01 Patrol Route");
418
- const dialogDate = ref("2025-11-27");
419
- const dialogTime = ref("15:00 - 17:00");
420
-
421
- // Remarks dialog state
422
- const dialogRemarks = ref(false);
423
- const selectedActivityRemarks = ref("");
424
-
425
- // Mock data - replace with actual API calls
426
- const routes = ref([
427
- "WH01 Patrol Route",
428
- "WH02 Patrol Route",
429
- "WH03 Patrol Route",
430
- ]);
431
-
432
- const timeRanges = ref([
433
- "15:00 - 17:00",
434
- "08:00 - 10:00",
435
- "13:00 - 15:00",
436
- "18:00 - 20:00",
437
- ]);
438
-
439
- const locationData = ref<LocationData>({
440
- name: "Winsland Homes",
441
- address: "221A Upper Thomson Road THOMSON RIDGE ESTATE",
442
- });
443
-
444
- const routeSummary = ref<RouteSummary>({
445
- routeName: "WH01 Patrol Route",
446
- tolerance: 30,
447
- schedule: {
448
- start: "14:00",
449
- end: "15:00",
450
- totalTime: "25 mins",
451
- },
452
- actual: {
453
- start: "14:01",
454
- end: "14:16",
455
- totalTime: "15 mins",
456
- },
457
- });
79
+ onMounted(fetchOptions);
458
80
 
459
- const patrolActivity = ref<PatrolActivity[]>([
460
- {
461
- id: "1",
462
- checkpoint: "Point 1 WH01",
463
- startTime: "14:01",
464
- endTime: "14:04",
465
- actualTime: "3:00 mins",
466
- expectedTime: "5:00 mins",
467
- status: "Skipped",
468
- remarks: "Raining",
469
- },
470
- {
471
- id: "2",
472
- checkpoint: "Point 2 WH02",
473
- startTime: "14:04",
474
- endTime: "14:07",
475
- actualTime: "3:00 mins",
476
- expectedTime: "5:00 mins",
477
- status: "Completed",
478
- },
479
- {
480
- id: "3",
481
- checkpoint: "Point 3 WH03",
482
- startTime: "14:07",
483
- endTime: "15:10",
484
- actualTime: "3:00 mins",
485
- expectedTime: "5:00 mins",
486
- status: "Completed",
487
- },
488
- {
489
- id: "4",
490
- checkpoint: "Point 4 WH04",
491
- startTime: "14:10",
492
- endTime: "14:13",
493
- actualTime: "3:00 mins",
494
- expectedTime: "5:00 mins",
495
- status: "Completed",
496
- },
497
- {
498
- id: "5",
499
- checkpoint: "Point 5 WH05",
500
- startTime: "14:13",
501
- endTime: "14:16",
502
- actualTime: "3:00 mins",
503
- expectedTime: "5:00 mins",
504
- status: "Completed",
505
- },
506
- ]);
507
-
508
- // Methods
509
- const goBack = () => {
81
+ function goBack() {
510
82
  router.back();
511
- };
512
-
513
- const loadReport = () => {
514
- // Open the dialog
515
- dialogRoute.value = selectedRoute.value;
516
- dialogDate.value = selectedDate.value;
517
- dialogTime.value = selectedTimeRange.value;
518
- dialogSelectRoute.value = true;
519
- };
520
-
521
- const submitSelection = () => {
522
- // Update the main filters with dialog values
523
- selectedRoute.value = dialogRoute.value;
524
- selectedDate.value = dialogDate.value;
525
- selectedTimeRange.value = dialogTime.value;
526
-
527
- // Close the dialog
528
- dialogSelectRoute.value = false;
529
-
530
- // TODO: Implement report loading based on filters
531
- console.log("Loading report...", {
532
- route: selectedRoute.value,
533
- timeRange: selectedTimeRange.value,
534
- date: selectedDate.value,
535
- });
536
- };
537
-
538
- const exportReport = () => {
539
- // TODO: Implement export functionality
540
- console.log("Exporting report...");
541
- };
542
-
543
- const getStatusColor = (status: string) => {
544
- return status === "Completed" ? "success" : "error";
545
- };
546
-
547
- const openRemarksDialog = (activity: PatrolActivity) => {
548
- selectedActivityRemarks.value = activity.remarks || "No remarks provided";
549
- dialogRemarks.value = true;
550
- };
551
-
552
- // TODO: Add API integration
553
- // TODO: Add data fetching based on filters
554
- // TODO: Implement Daily and Monthly reports
555
- </script>
556
-
557
- <style scoped>
558
- .cursor-pointer {
559
- cursor: pointer;
560
83
  }
561
84
 
562
- .cursor-pointer:hover {
563
- background-color: rgba(0, 0, 0, 0.04);
85
+ function updateFilters(value: NFCPatrolReportFilters) {
86
+ filters.route = value.route;
87
+ filters.timeRange = value.timeRange;
88
+ filters.date = value.date;
564
89
  }
565
90
 
566
- .v-table {
567
- border-radius: 0;
91
+ async function exportReport() {
92
+ await exportActiveReport(activeTab.value, {
93
+ route: filters.route,
94
+ timeRange: filters.timeRange,
95
+ date: filters.date,
96
+ });
568
97
  }
569
- </style>
98
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div>
3
+ <ReportFilters
4
+ :model-value="filters"
5
+ :options="options"
6
+ @update:model-value="$emit('update:filters', $event)"
7
+ @export="$emit('export')"
8
+ />
9
+
10
+ <v-divider :thickness="1" class="border-opacity-100" />
11
+
12
+ <template v-if="report">
13
+ <ReportLocationHeader :location="report.location" />
14
+ <v-divider :thickness="1" class="border-opacity-100 mb-6" />
15
+ <SummaryReportTable :title="report.title" :rows="report.rows" />
16
+ </template>
17
+
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"
22
+ />
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import type {
28
+ NFCPatrolReportFilterOptions,
29
+ NFCPatrolReportFilters,
30
+ } from "../../../types/nfc-patrol-report";
31
+ import { useNFCPatrolDailyReport } from "../../../composables/useNFCPatrolDailyReport";
32
+ import ReportEmptyState from "./ReportEmptyState.vue";
33
+ import ReportFilters from "./ReportFilters.vue";
34
+ import ReportLocationHeader from "./ReportLocationHeader.vue";
35
+ import SummaryReportTable from "./SummaryReportTable.vue";
36
+
37
+ const props = defineProps<{
38
+ active: boolean;
39
+ filters: NFCPatrolReportFilters;
40
+ options: NFCPatrolReportFilterOptions;
41
+ }>();
42
+
43
+ defineEmits<{
44
+ "update:filters": [value: NFCPatrolReportFilters];
45
+ export: [];
46
+ }>();
47
+
48
+ const { report, loading, fetchReport } = useNFCPatrolDailyReport(props.filters);
49
+
50
+ watch(
51
+ () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
52
+ () => {
53
+ if (props.active) {
54
+ void fetchReport();
55
+ }
56
+ },
57
+ { immediate: true },
58
+ );
59
+ </script>