@7365admin1/layer-common 3.0.31-staging.14 → 3.0.31-staging.16
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.
- package/components/Input/InputPhoneNumberV2.vue +89 -72
- package/components/Input/VehicleNumber.vue +14 -11
- package/components/Nfc/NFCPatrolReportMain.vue +128 -38
- package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
- package/components/Nfc/PatrolReport/ReportFilters.vue +50 -18
- package/components/VisitorForm.vue +44 -70
- package/composables/useNFCPatrolDailyReport.ts +1 -4
- package/composables/useNFCPatrolMonthlyReport.ts +62 -30
- package/composables/usePeople.ts +15 -0
- package/package.json +1 -1
- package/types/nfc-patrol-report.ts +15 -1
- package/utils/mock-people-response.ts +38 -0
|
@@ -46,136 +46,153 @@
|
|
|
46
46
|
:density="density"
|
|
47
47
|
:disabled="props.disabled"
|
|
48
48
|
:placeholder="placeholder || currentMask"
|
|
49
|
+
@focus="emit('focus')"
|
|
50
|
+
@blur="emit('blur')"
|
|
49
51
|
/>
|
|
50
52
|
</v-col>
|
|
51
|
-
<span
|
|
53
|
+
<span
|
|
54
|
+
class="text-error text-caption w-100"
|
|
55
|
+
v-if="errorMessage && !hideDetails"
|
|
56
|
+
>
|
|
52
57
|
{{ errorMessage }}
|
|
53
58
|
</span>
|
|
54
59
|
</v-row>
|
|
55
60
|
</template>
|
|
56
61
|
|
|
57
62
|
<script setup lang="ts">
|
|
58
|
-
import { ref, computed, watch, type PropType } from
|
|
63
|
+
import { ref, computed, watch, type PropType } from "vue";
|
|
59
64
|
//@ts-ignore
|
|
60
|
-
import phoneMasks from
|
|
61
|
-
import type { ValidationRule } from
|
|
65
|
+
import phoneMasks from "~/utils/phoneMasks";
|
|
66
|
+
import type { ValidationRule } from "vuetify/lib/types.mjs";
|
|
62
67
|
|
|
63
68
|
const props = defineProps({
|
|
64
|
-
modelValue: { type: String as PropType<string>, default:
|
|
69
|
+
modelValue: { type: String as PropType<string>, default: "" },
|
|
65
70
|
rules: { type: Array as PropType<ValidationRule[]>, default: () => [] },
|
|
66
|
-
variant: { type: String as PropType<any>, default:
|
|
67
|
-
density: {
|
|
71
|
+
variant: { type: String as PropType<any>, default: "outlined" },
|
|
72
|
+
density: {
|
|
73
|
+
type: String as PropType<"default" | "comfortable" | "compact">,
|
|
74
|
+
default: "default",
|
|
75
|
+
},
|
|
68
76
|
placeholder: { type: String },
|
|
69
77
|
hideDetails: { type: Boolean, default: false },
|
|
70
78
|
loading: { type: Boolean, default: false },
|
|
71
79
|
readOnly: { type: Boolean, default: false },
|
|
72
80
|
disabled: { type: Boolean, default: false },
|
|
73
|
-
})
|
|
81
|
+
});
|
|
74
82
|
|
|
75
|
-
const emit = defineEmits([
|
|
83
|
+
const emit = defineEmits(["update:modelValue", "focus", "blur"]);
|
|
76
84
|
|
|
77
85
|
type TPhoneMask = {
|
|
78
|
-
name: string
|
|
79
|
-
flag: string
|
|
80
|
-
code: string
|
|
81
|
-
dial_code: string
|
|
82
|
-
regex: string
|
|
83
|
-
}
|
|
86
|
+
name: string;
|
|
87
|
+
flag: string;
|
|
88
|
+
code: string;
|
|
89
|
+
dial_code: string;
|
|
90
|
+
regex: string;
|
|
91
|
+
};
|
|
84
92
|
|
|
85
93
|
// Main reactive values
|
|
86
|
-
const phone = ref(props.modelValue)
|
|
87
|
-
const input = ref(
|
|
88
|
-
const selectedCode = ref(
|
|
89
|
-
const countries = phoneMasks
|
|
90
|
-
const errorMessage = ref(
|
|
91
|
-
const maskRef = ref()
|
|
92
|
-
const maskKey = ref(0)
|
|
93
|
-
|
|
94
|
+
const phone = ref(props.modelValue);
|
|
95
|
+
const input = ref("");
|
|
96
|
+
const selectedCode = ref("SG");
|
|
97
|
+
const countries = phoneMasks;
|
|
98
|
+
const errorMessage = ref("");
|
|
99
|
+
const maskRef = ref();
|
|
100
|
+
const maskKey = ref(0);
|
|
94
101
|
|
|
95
102
|
const currentMask = computed(() => {
|
|
96
|
-
const country = countries.find(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
const country = countries.find(
|
|
104
|
+
(c: TPhoneMask) => c.code === selectedCode.value
|
|
105
|
+
);
|
|
106
|
+
if (!country) return "############";
|
|
107
|
+
return generateMaskFromRegex(country.regex);
|
|
108
|
+
});
|
|
101
109
|
|
|
102
110
|
const phonePrefix = computed(() => {
|
|
103
|
-
const country = countries.find(
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
const country = countries.find(
|
|
112
|
+
(c: TPhoneMask) => c.code === selectedCode.value
|
|
113
|
+
);
|
|
114
|
+
return country?.dial_code || "";
|
|
115
|
+
});
|
|
106
116
|
|
|
107
117
|
const countriesByLongestDialCode = computed(() => {
|
|
108
|
-
return [...countries].sort(
|
|
109
|
-
|
|
118
|
+
return [...countries].sort(
|
|
119
|
+
(a: TPhoneMask, b: TPhoneMask) => b.dial_code.length - a.dial_code.length
|
|
120
|
+
);
|
|
121
|
+
});
|
|
110
122
|
|
|
111
123
|
function generateMaskFromRegex(regex: string): string {
|
|
112
|
-
let pattern = regex.replace(/^\^|\$$/g,
|
|
113
|
-
pattern = pattern.replace(/\(\?:\+?\d+\)\?/g,
|
|
114
|
-
pattern = pattern.replace(/\+?\d{1,4}/,
|
|
115
|
-
pattern = pattern.replace(/\\d\{(\d+)\}/g, (_, count) =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
pattern = pattern.replace(
|
|
119
|
-
|
|
124
|
+
let pattern = regex.replace(/^\^|\$$/g, "");
|
|
125
|
+
pattern = pattern.replace(/\(\?:\+?\d+\)\?/g, "");
|
|
126
|
+
pattern = pattern.replace(/\+?\d{1,4}/, "");
|
|
127
|
+
pattern = pattern.replace(/\\d\{(\d+)\}/g, (_, count) =>
|
|
128
|
+
"#".repeat(Number(count))
|
|
129
|
+
);
|
|
130
|
+
pattern = pattern.replace(/\\d/g, "#");
|
|
131
|
+
pattern = pattern.replace(/\\/g, "");
|
|
132
|
+
pattern = pattern.replace(/\(\?:/g, "");
|
|
133
|
+
return pattern.trim();
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
const validatePhone = (): boolean | string => {
|
|
123
|
-
if (props.readOnly) return true
|
|
124
|
-
if (!phone.value) return true
|
|
137
|
+
if (props.readOnly) return true;
|
|
138
|
+
if (!phone.value) return true;
|
|
125
139
|
|
|
126
|
-
const country = countries.find(
|
|
127
|
-
|
|
140
|
+
const country = countries.find(
|
|
141
|
+
(c: TPhoneMask) => c.code === selectedCode.value
|
|
142
|
+
);
|
|
143
|
+
if (!country) return true;
|
|
128
144
|
|
|
129
|
-
const regex = new RegExp(country.regex)
|
|
130
|
-
const isValid = regex.test(phone.value)
|
|
131
|
-
errorMessage.value = isValid ?
|
|
132
|
-
return isValid
|
|
133
|
-
}
|
|
145
|
+
const regex = new RegExp(country.regex);
|
|
146
|
+
const isValid = regex.test(phone.value);
|
|
147
|
+
errorMessage.value = isValid ? "" : `Invalid ${country.name} phone number`;
|
|
148
|
+
return isValid;
|
|
149
|
+
};
|
|
134
150
|
|
|
135
151
|
function findCountryFromPhone(value: string): TPhoneMask | undefined {
|
|
136
|
-
return countriesByLongestDialCode.value.find((c: TPhoneMask) =>
|
|
152
|
+
return countriesByLongestDialCode.value.find((c: TPhoneMask) =>
|
|
153
|
+
value.startsWith(c.dial_code)
|
|
154
|
+
);
|
|
137
155
|
}
|
|
138
156
|
|
|
139
157
|
function syncInputFromPhone(value: string) {
|
|
140
|
-
const prefix = phonePrefix.value
|
|
141
|
-
if (!value) input.value =
|
|
142
|
-
else
|
|
158
|
+
const prefix = phonePrefix.value;
|
|
159
|
+
if (!value) input.value = "";
|
|
160
|
+
else
|
|
161
|
+
input.value = value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
143
162
|
}
|
|
144
163
|
|
|
145
164
|
function handleUpdateCountry() {
|
|
146
|
-
const prefix = phonePrefix.value
|
|
147
|
-
phone.value = input.value ? prefix + input.value :
|
|
165
|
+
const prefix = phonePrefix.value;
|
|
166
|
+
phone.value = input.value ? prefix + input.value : "";
|
|
148
167
|
}
|
|
149
168
|
|
|
150
|
-
|
|
151
169
|
watch(input, (newInput) => {
|
|
152
|
-
const prefix = phonePrefix.value
|
|
153
|
-
if (!newInput) phone.value =
|
|
154
|
-
else phone.value = prefix + newInput
|
|
155
|
-
})
|
|
170
|
+
const prefix = phonePrefix.value;
|
|
171
|
+
if (!newInput) phone.value = "";
|
|
172
|
+
else phone.value = prefix + newInput;
|
|
173
|
+
});
|
|
156
174
|
|
|
157
175
|
watch(
|
|
158
176
|
() => props.modelValue,
|
|
159
177
|
(val) => {
|
|
160
|
-
phone.value = val ||
|
|
178
|
+
phone.value = val || "";
|
|
161
179
|
},
|
|
162
180
|
{ immediate: true }
|
|
163
|
-
)
|
|
164
|
-
|
|
181
|
+
);
|
|
165
182
|
|
|
166
183
|
watch(
|
|
167
184
|
phone,
|
|
168
185
|
(newVal) => {
|
|
169
|
-
const country = newVal ? findCountryFromPhone(newVal) : undefined
|
|
170
|
-
if (country && country.code !== selectedCode.value)
|
|
171
|
-
|
|
172
|
-
|
|
186
|
+
const country = newVal ? findCountryFromPhone(newVal) : undefined;
|
|
187
|
+
if (country && country.code !== selectedCode.value)
|
|
188
|
+
selectedCode.value = country.code;
|
|
189
|
+
syncInputFromPhone(newVal);
|
|
190
|
+
emit("update:modelValue", newVal);
|
|
173
191
|
},
|
|
174
192
|
{ immediate: true }
|
|
175
|
-
)
|
|
176
|
-
|
|
193
|
+
);
|
|
177
194
|
|
|
178
195
|
watch(selectedCode, () => {
|
|
179
|
-
maskKey.value
|
|
180
|
-
})
|
|
196
|
+
maskKey.value++;
|
|
197
|
+
});
|
|
181
198
|
</script>
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
:placeholder="placeholder"
|
|
7
7
|
:counter="maxlength"
|
|
8
8
|
@input="onInput"
|
|
9
|
+
@focus="emit('focus')"
|
|
10
|
+
@blur="emit('blur')"
|
|
9
11
|
outlined
|
|
10
12
|
:disabled="disabled"
|
|
11
13
|
:readonly="readonly"
|
|
@@ -14,36 +16,37 @@
|
|
|
14
16
|
</template>
|
|
15
17
|
|
|
16
18
|
<script setup>
|
|
19
|
+
const emit = defineEmits(["focus", "blur"]);
|
|
17
20
|
|
|
18
21
|
const props = defineProps({
|
|
19
22
|
placeholder: {
|
|
20
23
|
type: String,
|
|
21
|
-
default:
|
|
24
|
+
default: "Vehicle Number",
|
|
22
25
|
},
|
|
23
26
|
rules: {
|
|
24
27
|
type: Array,
|
|
25
|
-
default: () => []
|
|
28
|
+
default: () => [],
|
|
26
29
|
},
|
|
27
30
|
maxlength: {
|
|
28
31
|
type: [Number, String],
|
|
29
|
-
default: false
|
|
32
|
+
default: false,
|
|
30
33
|
},
|
|
31
34
|
disabled: {
|
|
32
35
|
type: Boolean,
|
|
33
36
|
},
|
|
34
37
|
readonly: {
|
|
35
38
|
type: Boolean,
|
|
36
|
-
}
|
|
37
|
-
})
|
|
39
|
+
},
|
|
40
|
+
});
|
|
38
41
|
|
|
39
|
-
const model = defineModel({required: true })
|
|
42
|
+
const model = defineModel({ required: true });
|
|
40
43
|
|
|
41
44
|
function onInput(event) {
|
|
42
|
-
const value = typeof event ===
|
|
45
|
+
const value = typeof event === "string" ? event : event?.target?.value || "";
|
|
43
46
|
|
|
44
|
-
let formatted = value.replace(/[^A-Za-z0-9]/g,
|
|
45
|
-
formatted = formatted.toUpperCase()
|
|
47
|
+
let formatted = value.replace(/[^A-Za-z0-9]/g, "");
|
|
48
|
+
formatted = formatted.toUpperCase();
|
|
46
49
|
|
|
47
|
-
model.value = formatted
|
|
50
|
+
model.value = formatted;
|
|
48
51
|
}
|
|
49
|
-
</script>
|
|
52
|
+
</script>
|
|
@@ -14,44 +14,63 @@
|
|
|
14
14
|
</v-col>
|
|
15
15
|
</v-row>
|
|
16
16
|
|
|
17
|
-
<v-tabs
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<v-tabs
|
|
18
|
+
v-model="activeTab"
|
|
19
|
+
class="mb-6"
|
|
20
|
+
bg-color="transparent"
|
|
21
|
+
grow
|
|
22
|
+
>
|
|
23
|
+
<v-tab value="patrol" class="text-none border-thin">
|
|
24
|
+
Patrol Report
|
|
25
|
+
</v-tab>
|
|
26
|
+
|
|
27
|
+
<v-tab value="daily" class="text-none border-thin">
|
|
28
|
+
Daily Report
|
|
29
|
+
</v-tab>
|
|
30
|
+
|
|
31
|
+
<v-tab value="monthly" class="text-none border-thin">
|
|
32
|
+
Monthly Report
|
|
33
|
+
</v-tab>
|
|
21
34
|
</v-tabs>
|
|
22
35
|
|
|
23
36
|
<v-window v-model="activeTab" class="pt-4">
|
|
24
37
|
<v-window-item value="patrol">
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
<div v-if="activeTab === 'patrol'">
|
|
39
|
+
<PatrolReportTab
|
|
40
|
+
:active="true"
|
|
41
|
+
:site="site"
|
|
42
|
+
:filters="filters"
|
|
43
|
+
:options="options"
|
|
44
|
+
@update:filters="updateFilters"
|
|
45
|
+
@export="exportReport"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
33
48
|
</v-window-item>
|
|
34
49
|
|
|
35
50
|
<v-window-item value="daily">
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
<div v-if="activeTab === 'daily'">
|
|
52
|
+
<DailyReportTab
|
|
53
|
+
:active="true"
|
|
54
|
+
:site="site"
|
|
55
|
+
:filters="filters"
|
|
56
|
+
:options="options"
|
|
57
|
+
@update:filters="updateFilters"
|
|
58
|
+
@export="exportReport"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
44
61
|
</v-window-item>
|
|
45
62
|
|
|
46
63
|
<v-window-item value="monthly">
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
<div v-if="activeTab === 'monthly'">
|
|
65
|
+
<MonthlyReportTab
|
|
66
|
+
:active="true"
|
|
67
|
+
:site="site"
|
|
68
|
+
:filters="filters"
|
|
69
|
+
:options="options"
|
|
70
|
+
@update:filters="updateFilters"
|
|
71
|
+
@export="exportReport"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
55
74
|
</v-window-item>
|
|
56
75
|
</v-window>
|
|
57
76
|
</v-col>
|
|
@@ -59,15 +78,20 @@
|
|
|
59
78
|
</template>
|
|
60
79
|
|
|
61
80
|
<script setup lang="ts">
|
|
81
|
+
import html2canvas from "html2canvas";
|
|
82
|
+
import jsPDF from "jspdf";
|
|
83
|
+
|
|
62
84
|
import type {
|
|
63
85
|
NFCPatrolReportFilters,
|
|
64
86
|
NFCPatrolReportTab,
|
|
65
87
|
} from "../../types/nfc-patrol-report";
|
|
66
|
-
|
|
88
|
+
|
|
67
89
|
import { useNFCPatrolReportFilters } from "../../composables/useNFCPatrolReportFilters";
|
|
90
|
+
|
|
91
|
+
import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
|
|
68
92
|
import DailyReportTab from "./PatrolReport/DailyReportTab.vue";
|
|
69
93
|
import MonthlyReportTab from "./PatrolReport/MonthlyReportTab.vue";
|
|
70
|
-
|
|
94
|
+
|
|
71
95
|
|
|
72
96
|
const props = defineProps<{
|
|
73
97
|
site: string;
|
|
@@ -75,11 +99,16 @@ const props = defineProps<{
|
|
|
75
99
|
}>();
|
|
76
100
|
|
|
77
101
|
const router = useRouter();
|
|
102
|
+
|
|
103
|
+
|
|
78
104
|
const activeTab = ref<NFCPatrolReportTab>("patrol");
|
|
79
|
-
const { filters, options, fetchOptions } = useNFCPatrolReportFilters(props.site);
|
|
80
|
-
const { exportReport: exportActiveReport } = useNFCPatrolReportExport();
|
|
81
105
|
|
|
82
|
-
|
|
106
|
+
const { filters, options, fetchOptions } =
|
|
107
|
+
useNFCPatrolReportFilters(props.site);
|
|
108
|
+
|
|
109
|
+
onMounted(() => {
|
|
110
|
+
fetchOptions();
|
|
111
|
+
});
|
|
83
112
|
|
|
84
113
|
function goBack() {
|
|
85
114
|
router.back();
|
|
@@ -91,11 +120,72 @@ function updateFilters(value: NFCPatrolReportFilters) {
|
|
|
91
120
|
filters.date = value.date;
|
|
92
121
|
}
|
|
93
122
|
|
|
94
|
-
async function exportReport() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
async function exportReport(type: "pdf" | "csv") {
|
|
124
|
+
const report = document.querySelector(
|
|
125
|
+
".report-export",
|
|
126
|
+
) as HTMLElement | null;
|
|
127
|
+
|
|
128
|
+
if (!report) return;
|
|
129
|
+
|
|
130
|
+
if (type === "pdf") {
|
|
131
|
+
const canvas = await html2canvas(report, {
|
|
132
|
+
scale: 2,
|
|
133
|
+
useCORS: true,
|
|
134
|
+
backgroundColor: "#fff",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const pdf = new jsPDF("p", "mm", "a4");
|
|
138
|
+
|
|
139
|
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
140
|
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
141
|
+
|
|
142
|
+
const imgWidth = pdfWidth;
|
|
143
|
+
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
144
|
+
|
|
145
|
+
const imgData = canvas.toDataURL("image/png");
|
|
146
|
+
|
|
147
|
+
let heightLeft = imgHeight;
|
|
148
|
+
let position = 0;
|
|
149
|
+
|
|
150
|
+
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
151
|
+
|
|
152
|
+
heightLeft -= pdfHeight;
|
|
153
|
+
|
|
154
|
+
while (heightLeft > 0) {
|
|
155
|
+
position -= pdfHeight;
|
|
156
|
+
|
|
157
|
+
pdf.addPage();
|
|
158
|
+
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
159
|
+
|
|
160
|
+
heightLeft -= pdfHeight;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pdf.save(`${activeTab.value}-report.pdf`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const table = report.querySelector("table");
|
|
168
|
+
|
|
169
|
+
if (!table) return;
|
|
170
|
+
|
|
171
|
+
const csv = Array.from(table.querySelectorAll("tr"))
|
|
172
|
+
.map((row) =>
|
|
173
|
+
Array.from(row.querySelectorAll("th,td"))
|
|
174
|
+
.map((cell) => `"${cell.textContent?.trim().replace(/"/g, '""') ?? ""}"`)
|
|
175
|
+
.join(","),
|
|
176
|
+
)
|
|
177
|
+
.join("\n");
|
|
178
|
+
|
|
179
|
+
const blob = new Blob([csv], {
|
|
180
|
+
type: "text/csv;charset=utf-8;",
|
|
99
181
|
});
|
|
182
|
+
|
|
183
|
+
const link = document.createElement("a");
|
|
184
|
+
|
|
185
|
+
link.href = URL.createObjectURL(blob);
|
|
186
|
+
link.download = `${activeTab.value}-report.csv`;
|
|
187
|
+
link.click();
|
|
188
|
+
|
|
189
|
+
URL.revokeObjectURL(link.href);
|
|
100
190
|
}
|
|
101
191
|
</script>
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
+
:show-time-filter="false"
|
|
6
7
|
@update:model-value="$emit('update:filters', $event)"
|
|
7
|
-
@export="$emit('export')"
|
|
8
|
+
@export="(type) => $emit('export', type)"
|
|
8
9
|
/>
|
|
9
|
-
|
|
10
|
+
<div class="report-export">
|
|
10
11
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
11
12
|
|
|
12
13
|
<template v-if="report">
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
<SummaryReportTable :title="report.title" :rows="report.rows" />
|
|
16
17
|
</template>
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
<ReportEmptyState
|
|
19
21
|
v-else-if="!loading && notFound"
|
|
20
22
|
title="No Daily Report"
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
/>
|
|
23
25
|
|
|
24
26
|
<ReportEmptyState v-else-if="!loading" />
|
|
27
|
+
</div>
|
|
25
28
|
</div>
|
|
26
29
|
</template>
|
|
27
30
|
|
|
@@ -38,23 +41,23 @@ import SummaryReportTable from "./SummaryReportTable.vue";
|
|
|
38
41
|
|
|
39
42
|
const props = defineProps<{
|
|
40
43
|
active: boolean;
|
|
41
|
-
site: string;
|
|
44
|
+
site: string;
|
|
42
45
|
filters: NFCPatrolReportFilters;
|
|
43
46
|
options: NFCPatrolReportFilterOptions;
|
|
44
47
|
}>();
|
|
45
48
|
|
|
46
49
|
defineEmits<{
|
|
47
50
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
48
|
-
export: [];
|
|
51
|
+
export: [type: "pdf" | "csv"];
|
|
49
52
|
}>();
|
|
50
53
|
|
|
51
54
|
const { report, loading, notFound, fetchReport } = useNFCPatrolDailyReport(
|
|
52
55
|
props.filters,
|
|
53
|
-
props.site,
|
|
56
|
+
props.site,
|
|
54
57
|
);
|
|
55
58
|
|
|
56
59
|
watch(
|
|
57
|
-
() => [props.filters.route, props.filters.
|
|
60
|
+
() => [props.filters.route, props.filters.date, props.active],
|
|
58
61
|
() => {
|
|
59
62
|
if (props.active) {
|
|
60
63
|
void fetchReport();
|
|
@@ -62,4 +65,5 @@ watch(
|
|
|
62
65
|
},
|
|
63
66
|
{ immediate: true },
|
|
64
67
|
);
|
|
68
|
+
|
|
65
69
|
</script>
|
|
@@ -3,23 +3,30 @@
|
|
|
3
3
|
<ReportFilters
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
|
+
:show-time-filter="false"
|
|
7
|
+
:show-date-filter="false"
|
|
6
8
|
@update:model-value="$emit('update:filters', $event)"
|
|
7
|
-
@export="$emit('export')"
|
|
9
|
+
@export="(type) => $emit('export', type)"
|
|
8
10
|
/>
|
|
9
|
-
|
|
11
|
+
<div class="report-export">
|
|
10
12
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
11
13
|
|
|
12
14
|
<template v-if="report">
|
|
13
15
|
<ReportLocationHeader :location="report.location" />
|
|
14
16
|
<v-divider :thickness="1" class="border-opacity-100 mb-6" />
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
<MonthlyReportTable
|
|
19
|
+
:title="report.title"
|
|
20
|
+
:rows="report.rows"
|
|
21
|
+
/>
|
|
16
22
|
</template>
|
|
17
23
|
|
|
18
24
|
<ReportEmptyState
|
|
19
25
|
v-else-if="!loading"
|
|
20
|
-
title="No Monthly Report
|
|
21
|
-
message="
|
|
26
|
+
title="No Monthly Report"
|
|
27
|
+
message="No patrol log found for this route"
|
|
22
28
|
/>
|
|
29
|
+
</div>
|
|
23
30
|
</div>
|
|
24
31
|
</template>
|
|
25
32
|
|
|
@@ -28,11 +35,13 @@ import type {
|
|
|
28
35
|
NFCPatrolReportFilterOptions,
|
|
29
36
|
NFCPatrolReportFilters,
|
|
30
37
|
} from "../../../types/nfc-patrol-report";
|
|
38
|
+
|
|
31
39
|
import { useNFCPatrolMonthlyReport } from "../../../composables/useNFCPatrolMonthlyReport";
|
|
40
|
+
|
|
32
41
|
import ReportEmptyState from "./ReportEmptyState.vue";
|
|
33
42
|
import ReportFilters from "./ReportFilters.vue";
|
|
34
43
|
import ReportLocationHeader from "./ReportLocationHeader.vue";
|
|
35
|
-
import
|
|
44
|
+
import MonthlyReportTable from "./MonthlyReportTable.vue";
|
|
36
45
|
|
|
37
46
|
const props = defineProps<{
|
|
38
47
|
active: boolean;
|
|
@@ -43,14 +52,14 @@ const props = defineProps<{
|
|
|
43
52
|
|
|
44
53
|
defineEmits<{
|
|
45
54
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
46
|
-
export: [];
|
|
55
|
+
export: [type: "pdf" | "csv"];
|
|
47
56
|
}>();
|
|
48
57
|
|
|
49
|
-
const { report, loading, fetchReport } =
|
|
58
|
+
const { report, loading, notFound, fetchReport } =
|
|
50
59
|
useNFCPatrolMonthlyReport(props.filters, props.site);
|
|
51
60
|
|
|
52
61
|
watch(
|
|
53
|
-
() => [props.filters.route, props.
|
|
62
|
+
() => [props.filters.route, props.active],
|
|
54
63
|
() => {
|
|
55
64
|
if (props.active) {
|
|
56
65
|
void fetchReport();
|
|
@@ -58,4 +67,5 @@ watch(
|
|
|
58
67
|
},
|
|
59
68
|
{ immediate: true },
|
|
60
69
|
);
|
|
70
|
+
|
|
61
71
|
</script>
|