@7365admin1/layer-common 3.0.22 → 3.0.24

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,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 Monthly Report Selected"
21
+ message="Please select a route, time range, and date to view the monthly 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 { useNFCPatrolMonthlyReport } from "../../../composables/useNFCPatrolMonthlyReport";
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 } = useNFCPatrolMonthlyReport(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>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <v-card variant="flat" rounded="lg">
3
+ <v-card-title>Patrol Activity</v-card-title>
4
+
5
+ <v-card-text class="pa-0">
6
+ <v-table class="border-thin">
7
+ <thead>
8
+ <tr>
9
+ <th>Checkpoints</th>
10
+ <th>Start - End Time</th>
11
+ <th>
12
+ Travel time<br />
13
+ <span class="text-caption text-medium-emphasis">Actual/Expected</span>
14
+ </th>
15
+ <th>Status</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <tr
20
+ v-for="activity in activities"
21
+ :key="activity.id"
22
+ :class="{ 'cursor-pointer': activity.status === 'Skipped' }"
23
+ @click="handleActivityClick(activity)"
24
+ >
25
+ <td>{{ activity.checkpoint }}</td>
26
+ <td>
27
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
28
+ {{ activity.startTime }}
29
+ <v-icon size="small" class="mx-2">mdi-arrow-right</v-icon>
30
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
31
+ {{ activity.endTime }}
32
+ </td>
33
+ <td>
34
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
35
+ {{ activity.actualTime }} / {{ activity.expectedTime }}
36
+ </td>
37
+ <td>
38
+ <v-chip :color="statusColor(activity.status)" size="small" variant="flat">
39
+ {{ activity.status }}
40
+ <v-icon v-if="activity.status === 'Skipped'" size="small" class="ml-1">
41
+ mdi-flag
42
+ </v-icon>
43
+ </v-chip>
44
+ </td>
45
+ </tr>
46
+ </tbody>
47
+ </v-table>
48
+ </v-card-text>
49
+ </v-card>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import type {
54
+ NFCPatrolActivity,
55
+ NFCPatrolActivityStatus,
56
+ } from "../../../types/nfc-patrol-report";
57
+
58
+ defineProps<{
59
+ activities: NFCPatrolActivity[];
60
+ }>();
61
+
62
+ const emit = defineEmits<{
63
+ remarks: [activity: NFCPatrolActivity];
64
+ }>();
65
+
66
+ function statusColor(status: NFCPatrolActivityStatus) {
67
+ return status === "Completed" ? "success" : "error";
68
+ }
69
+
70
+ function handleActivityClick(activity: NFCPatrolActivity) {
71
+ if (activity.status === "Skipped") {
72
+ emit("remarks", activity);
73
+ }
74
+ }
75
+ </script>
76
+
77
+ <style scoped>
78
+ .cursor-pointer {
79
+ cursor: pointer;
80
+ }
81
+ </style>
@@ -0,0 +1,73 @@
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
+
16
+ <PatrolRouteSummary v-if="report.routeSummary" :summary="report.routeSummary" />
17
+
18
+ <PatrolActivityTable
19
+ v-if="report.activities.length"
20
+ :activities="report.activities"
21
+ @remarks="openRemarksDialog"
22
+ />
23
+ </template>
24
+
25
+ <ReportEmptyState v-else-if="!loading" />
26
+
27
+ <RemarksDialog v-model="dialogRemarks" :remarks="selectedActivityRemarks" />
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import type {
33
+ NFCPatrolReportFilterOptions,
34
+ NFCPatrolReportFilters,
35
+ } from "../../../types/nfc-patrol-report";
36
+ import { useNFCPatrolReport } from "../../../composables/useNFCPatrolReport";
37
+ import PatrolActivityTable from "./PatrolActivityTable.vue";
38
+ import PatrolRouteSummary from "./PatrolRouteSummary.vue";
39
+ import RemarksDialog from "./RemarksDialog.vue";
40
+ import ReportEmptyState from "./ReportEmptyState.vue";
41
+ import ReportFilters from "./ReportFilters.vue";
42
+ import ReportLocationHeader from "./ReportLocationHeader.vue";
43
+
44
+ const props = defineProps<{
45
+ active: boolean;
46
+ filters: NFCPatrolReportFilters;
47
+ options: NFCPatrolReportFilterOptions;
48
+ }>();
49
+
50
+ defineEmits<{
51
+ "update:filters": [value: NFCPatrolReportFilters];
52
+ export: [];
53
+ }>();
54
+
55
+ const {
56
+ report,
57
+ loading,
58
+ selectedActivityRemarks,
59
+ dialogRemarks,
60
+ fetchReport,
61
+ openRemarksDialog,
62
+ } = useNFCPatrolReport(props.filters);
63
+
64
+ watch(
65
+ () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
66
+ () => {
67
+ if (props.active) {
68
+ void fetchReport();
69
+ }
70
+ },
71
+ { immediate: true },
72
+ );
73
+ </script>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <v-card class="mb-6" variant="flat" rounded="lg">
3
+ <v-card-title class="d-flex justify-space-between align-center">
4
+ <span>Patrol Route Summary: {{ summary.routeName }}</span>
5
+ <span class="text-body-2 text-medium-emphasis">
6
+ Tolerance: {{ summary.tolerance }} mins
7
+ </span>
8
+ </v-card-title>
9
+
10
+ <v-card-text class="pa-0">
11
+ <v-table class="border-thin">
12
+ <thead>
13
+ <tr>
14
+ <th>Time</th>
15
+ <th>Start</th>
16
+ <th>End</th>
17
+ <th>Total travel time</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <tr>
22
+ <td class="font-weight-medium">Schedule</td>
23
+ <td>
24
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
25
+ {{ summary.schedule.start }}
26
+ </td>
27
+ <td>
28
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
29
+ {{ summary.schedule.end }}
30
+ </td>
31
+ <td>
32
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
33
+ {{ summary.schedule.totalTime }}
34
+ </td>
35
+ </tr>
36
+ <tr>
37
+ <td class="font-weight-medium">Actual</td>
38
+ <td>
39
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
40
+ {{ summary.actual.start }}
41
+ </td>
42
+ <td>
43
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
44
+ {{ summary.actual.end }}
45
+ </td>
46
+ <td>
47
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
48
+ {{ summary.actual.totalTime }}
49
+ </td>
50
+ </tr>
51
+ </tbody>
52
+ </v-table>
53
+ </v-card-text>
54
+ </v-card>
55
+ </template>
56
+
57
+ <script setup lang="ts">
58
+ import type { NFCPatrolRouteSummary } from "../../../types/nfc-patrol-report";
59
+
60
+ defineProps<{
61
+ summary: NFCPatrolRouteSummary;
62
+ }>();
63
+ </script>
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <v-dialog :model-value="modelValue" max-width="600" persistent>
3
+ <v-card rounded="xl">
4
+ <v-card-title class="d-flex align-center pa-6">
5
+ <span class="text-h6">Skip Checkpoint Remarks</span>
6
+ <v-spacer />
7
+ <v-btn icon="mdi-close" variant="text" size="small" @click="close" />
8
+ </v-card-title>
9
+
10
+ <v-card-text class="px-6 pb-6">
11
+ <v-textarea
12
+ :model-value="remarks"
13
+ variant="outlined"
14
+ rounded="lg"
15
+ readonly
16
+ rows="4"
17
+ hide-details
18
+ />
19
+ </v-card-text>
20
+ </v-card>
21
+ </v-dialog>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ defineProps<{
26
+ modelValue: boolean;
27
+ remarks: string;
28
+ }>();
29
+
30
+ const emit = defineEmits<{
31
+ "update:modelValue": [value: boolean];
32
+ }>();
33
+
34
+ function close() {
35
+ emit("update:modelValue", false);
36
+ }
37
+ </script>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <v-card class="text-center pa-12" variant="outlined" rounded="lg">
3
+ <v-icon size="64" color="grey-lighten-1" class="mb-4">
4
+ mdi-clipboard-text-outline
5
+ </v-icon>
6
+ <h3 class="text-h6 mb-2">{{ title }}</h3>
7
+ <p class="text-body-2 text-medium-emphasis">
8
+ {{ message }}
9
+ </p>
10
+ </v-card>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ withDefaults(
15
+ defineProps<{
16
+ title?: string;
17
+ message?: string;
18
+ }>(),
19
+ {
20
+ title: "No Patrol Report Selected",
21
+ message: "Please select a route, time range, and date to view the patrol report",
22
+ },
23
+ );
24
+ </script>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <v-row no-gutters class="mb-6">
3
+ <v-col cols="12" md="3" class="pr-md-2 mb-2 mb-md-0">
4
+ <v-select
5
+ :model-value="modelValue.route"
6
+ :items="options.routes"
7
+ label="Select Route"
8
+ variant="outlined"
9
+ density="comfortable"
10
+ rounded="lg"
11
+ hide-details
12
+ bg-color="white"
13
+ @update:model-value="updateFilter('route', $event)"
14
+ />
15
+ </v-col>
16
+
17
+ <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
18
+ <v-select
19
+ :model-value="modelValue.timeRange"
20
+ :items="options.timeRanges"
21
+ label="Time Range"
22
+ variant="outlined"
23
+ density="comfortable"
24
+ rounded="lg"
25
+ hide-details
26
+ bg-color="white"
27
+ @update:model-value="updateFilter('timeRange', $event)"
28
+ />
29
+ </v-col>
30
+
31
+ <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
32
+ <v-text-field
33
+ :model-value="modelValue.date"
34
+ type="date"
35
+ variant="outlined"
36
+ density="comfortable"
37
+ rounded="lg"
38
+ hide-details
39
+ bg-color="white"
40
+ @update:model-value="updateFilter('date', $event)"
41
+ />
42
+ </v-col>
43
+
44
+ <v-spacer />
45
+
46
+ <v-col cols="12" md="2" class="pl-md-2">
47
+ <v-btn
48
+ variant="outlined"
49
+ block
50
+ size="large"
51
+ rounded="lg"
52
+ class="text-none"
53
+ prepend-icon="mdi-export"
54
+ @click="$emit('export')"
55
+ >
56
+ Export
57
+ </v-btn>
58
+ </v-col>
59
+ </v-row>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import type {
64
+ NFCPatrolReportFilterOptions,
65
+ NFCPatrolReportFilters,
66
+ } from "../../../types/nfc-patrol-report";
67
+
68
+ const props = defineProps<{
69
+ modelValue: NFCPatrolReportFilters;
70
+ options: NFCPatrolReportFilterOptions;
71
+ }>();
72
+
73
+ const emit = defineEmits<{
74
+ "update:modelValue": [value: NFCPatrolReportFilters];
75
+ export: [];
76
+ }>();
77
+
78
+ function updateFilter(key: keyof NFCPatrolReportFilters, value: unknown) {
79
+ if (typeof value !== "string") {
80
+ return;
81
+ }
82
+
83
+ emit("update:modelValue", {
84
+ ...props.modelValue,
85
+ [key]: value,
86
+ });
87
+ }
88
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <v-card class="text-center pa-8 mt-2" variant="flat" rounded="lg">
3
+ <v-icon size="64" class="mb-4">mdi-office-building</v-icon>
4
+ <h2 class="text-h5 mb-2">{{ location.name }}</h2>
5
+ <p class="text-body-2 text-medium-emphasis">
6
+ {{ location.address }}
7
+ </p>
8
+ </v-card>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import type { NFCPatrolReportLocation } from "../../../types/nfc-patrol-report";
13
+
14
+ defineProps<{
15
+ location: NFCPatrolReportLocation;
16
+ }>();
17
+ </script>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <v-card class="mb-6" variant="flat" rounded="lg">
3
+ <v-card-title>{{ title }}</v-card-title>
4
+
5
+ <v-card-text>
6
+ <v-table class="border-thin">
7
+ <thead>
8
+ <tr>
9
+ <th>Patrol Route Name</th>
10
+ <th>Security Check Schedule</th>
11
+ <th>Checked</th>
12
+ <th>Missed</th>
13
+ <th>Status</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <tr v-for="row in rows" :key="row.id">
18
+ <td>{{ row.routeName }}</td>
19
+ <td>
20
+ <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
21
+ {{ row.securityCheckSchedule }}
22
+ </td>
23
+ <td>{{ row.checked }}</td>
24
+ <td>{{ row.missed }}</td>
25
+ <td>
26
+ <span :class="statusClass(row.status)">
27
+ {{ row.status }}
28
+ </span>
29
+ </td>
30
+ </tr>
31
+ </tbody>
32
+ </v-table>
33
+ </v-card-text>
34
+ </v-card>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import type {
39
+ NFCPatrolReportSummaryRow,
40
+ NFCPatrolReportSummaryStatus,
41
+ } from "../../../types/nfc-patrol-report";
42
+
43
+ defineProps<{
44
+ title: string;
45
+ rows: NFCPatrolReportSummaryRow[];
46
+ }>();
47
+
48
+ function statusClass(status: NFCPatrolReportSummaryStatus) {
49
+ return status === "Completed" ? "text-success" : "text-error";
50
+ }
51
+ </script>