@7365admin1/layer-common 3.0.21 → 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.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/DashboardMain.vue +211 -58
  3. package/components/InvitationMain.vue +32 -3
  4. package/components/MemberMain.vue +1 -1
  5. package/components/Nfc/NFCPatrolReportMain.vue +48 -541
  6. package/components/Nfc/PatrolReport/DailyReportTab.vue +59 -0
  7. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +59 -0
  8. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +81 -0
  9. package/components/Nfc/PatrolReport/PatrolReportTab.vue +73 -0
  10. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +63 -0
  11. package/components/Nfc/PatrolReport/RemarksDialog.vue +37 -0
  12. package/components/Nfc/PatrolReport/ReportEmptyState.vue +24 -0
  13. package/components/Nfc/PatrolReport/ReportFilters.vue +88 -0
  14. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +17 -0
  15. package/components/Nfc/PatrolReport/SummaryReportTable.vue +51 -0
  16. package/components/ScheduleTaskMain.vue +47 -12
  17. package/components/ServiceProviderMain.vue +182 -184
  18. package/components/UnitMain.vue +8 -1
  19. package/components/VisitorForm.vue +2 -0
  20. package/components/VisitorManagement.vue +31 -0
  21. package/components/VisitorSocketPopUp.vue +292 -0
  22. package/composables/useNFCPatrolDailyReport.ts +27 -0
  23. package/composables/useNFCPatrolMonthlyReport.ts +27 -0
  24. package/composables/useNFCPatrolReport.ts +38 -0
  25. package/composables/useNFCPatrolReportExport.ts +31 -0
  26. package/composables/useNFCPatrolReportFilters.ts +39 -0
  27. package/composables/useVisitor.ts +3 -0
  28. package/composables/useVisitorSocket.ts +25 -0
  29. package/package.json +2 -1
  30. package/services/nfcPatrolReport.ts +172 -0
  31. package/types/nfc-patrol-report.ts +73 -0
  32. package/types/verification.d.ts +6 -1
@@ -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>
@@ -162,7 +162,9 @@
162
162
  class="mb-2"
163
163
  >
164
164
  <v-col cols="12">
165
- <div class="text-subtitle-2 font-weight-bold mb-2">Units Under Selected Areas:</div>
165
+ <div class="text-subtitle-2 font-weight-bold mb-2">
166
+ Units Under Selected Areas:
167
+ </div>
166
168
  <v-card variant="outlined" rounded>
167
169
  <v-list density="compact" nav>
168
170
  <template
@@ -179,33 +181,62 @@
179
181
  <v-icon size="16">
180
182
  {{
181
183
  openViewAreas.includes(area.value || area._id)
182
- ? 'mdi-chevron-down'
183
- : 'mdi-chevron-right'
184
+ ? "mdi-chevron-down"
185
+ : "mdi-chevron-right"
184
186
  }}
185
187
  </v-icon>
186
188
  </template>
187
189
  <template #append>
188
190
  <v-progress-circular
189
- v-if="loadingViewUnits && !(area.value || area._id in fetchedViewAreaUnits)"
191
+ v-if="
192
+ loadingViewUnits &&
193
+ !(
194
+ area.value ||
195
+ area._id in fetchedViewAreaUnits
196
+ )
197
+ "
190
198
  size="14"
191
199
  width="2"
192
200
  indeterminate
193
201
  color="primary"
194
202
  />
195
- <v-chip v-else size="x-small" variant="tonal" color="primary">
196
- {{ (fetchedViewAreaUnits[area.value || area._id] || []).length }}
203
+ <v-chip
204
+ v-else
205
+ size="x-small"
206
+ variant="tonal"
207
+ color="primary"
208
+ >
209
+ {{
210
+ (
211
+ fetchedViewAreaUnits[
212
+ area.value || area._id
213
+ ] || []
214
+ ).length
215
+ }}
197
216
  </v-chip>
198
217
  </template>
199
218
  </v-list-item>
200
219
  <v-expand-transition>
201
- <div v-show="openViewAreas.includes(area.value || area._id)">
220
+ <div
221
+ v-show="
222
+ openViewAreas.includes(area.value || area._id)
223
+ "
224
+ >
202
225
  <v-card-text class="px-3 py-2">
203
226
  <div
204
- v-if="(fetchedViewAreaUnits[area.value || area._id] || []).length > 0"
227
+ v-if="
228
+ (
229
+ fetchedViewAreaUnits[
230
+ area.value || area._id
231
+ ] || []
232
+ ).length > 0
233
+ "
205
234
  class="d-flex flex-wrap ga-3"
206
235
  >
207
236
  <v-chip
208
- v-for="unit in fetchedViewAreaUnits[area.value || area._id]"
237
+ v-for="unit in fetchedViewAreaUnits[
238
+ area.value || area._id
239
+ ]"
209
240
  :key="unit.unit"
210
241
  size="small"
211
242
  variant="tonal"
@@ -213,7 +244,9 @@
213
244
  {{ unit.name }}
214
245
  </v-chip>
215
246
  </div>
216
- <span v-else class="text-caption text-disabled">No units configured</span>
247
+ <span v-else class="text-caption text-disabled"
248
+ >No units configured</span
249
+ >
217
250
  </v-card-text>
218
251
  </div>
219
252
  </v-expand-transition>
@@ -371,7 +404,9 @@ const messageSnackbar = ref(false);
371
404
  const messageColor = ref("success");
372
405
 
373
406
  const loadingViewUnits = ref(false);
374
- const fetchedViewAreaUnits = ref<Record<string, { name: string; unit: string }[]>>({});
407
+ const fetchedViewAreaUnits = ref<
408
+ Record<string, { name: string; unit: string }[]>
409
+ >({});
375
410
  const openViewAreas = ref<string[]>([]);
376
411
 
377
412
  const toggleViewArea = (id: string) => {
@@ -528,7 +563,7 @@ async function _deleteScheduleTask() {
528
563
 
529
564
  const response = await deleteScheduleTask(id);
530
565
  dialogDeleteTask.value = false;
531
- showMessage(response?.message, "success");
566
+ showMessage("Scheduled task successfully deleted.", "success");
532
567
 
533
568
  await getTaskRefresh();
534
569
  } catch (error: any) {