@7365admin1/layer-common 1.11.28 → 1.11.29
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,400 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card height="100%" width="100%" flat class="pa-2 pa-sm-5">
|
|
3
|
+
<v-row no-gutters>
|
|
4
|
+
<v-col cols="12">
|
|
5
|
+
<v-row no-gutters class="d-flex ga-2 justify-space-between align-start mb-3 mb-sm-5 flex-wrap">
|
|
6
|
+
|
|
7
|
+
<div class="d-flex flex-column" style="min-width: 0;">
|
|
8
|
+
<span class="text-subtitle-1 font-weight-bold" style="word-break: break-word;">Visitor Management - Visitor ({{ items.length }} visitors)</span>
|
|
9
|
+
<span style="font-size: clamp(11px, 1.1vw, 13px); word-break: break-word;">Date Range: {{ formatDateDDMMYYYY(dateFrom) }} - {{ formatDateDDMMYYYY(dateTo) ?? "" }}</span>
|
|
10
|
+
<span class="text-subtitle-1" style="word-break: break-word;">Site Name: {{ activeSiteName }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
</v-row>
|
|
14
|
+
<div style="overflow-x: auto; max-width: 100%;">
|
|
15
|
+
<v-data-table :headers="headers" :items="items" show-header :items-per-page="100" hide-default-footer class="text-black"
|
|
16
|
+
class="text-black" no-data-text="No entries to display">
|
|
17
|
+
|
|
18
|
+
<template v-slot:item.typeCompany="{ item }">
|
|
19
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
20
|
+
<v-icon icon="mdi-user" size="15" />
|
|
21
|
+
<span v-if="item.type === 'contractor'" class="text-capitalize" style="word-break: break-word;">{{
|
|
22
|
+
formatCamelCaseToWords(item.contractorType as string)
|
|
23
|
+
}}</span>
|
|
24
|
+
<span v-else class="text-capitalize" style="word-break: break-word;">{{ formatType(item) }}</span>
|
|
25
|
+
</span>
|
|
26
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
27
|
+
<v-icon icon="mdi-domain" size="15" />
|
|
28
|
+
<span class="text-capitalize" style="word-break: break-word;">{{ item?.company || "N/A" }}</span>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<template v-slot:item.location="{ item }">
|
|
32
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
33
|
+
<v-icon icon="mdi-storefront-outline" size="15" />
|
|
34
|
+
<span class="text-capitalize" style="word-break: break-word;">{{
|
|
35
|
+
formatLocation({
|
|
36
|
+
block: item.block as number,
|
|
37
|
+
level: item.level as string,
|
|
38
|
+
unit: item.unitName as string,
|
|
39
|
+
})
|
|
40
|
+
}}</span>
|
|
41
|
+
</span>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template v-slot:item.contact-vehicleNumber="{ item }">
|
|
45
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
46
|
+
<v-icon icon="mdi-phone" size="15" />
|
|
47
|
+
<span class="text-capitalize" style="word-break: break-word;">{{ item?.contact || "N/A" }}</span>
|
|
48
|
+
</span>
|
|
49
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
50
|
+
<v-icon icon="mdi-car-back" size="15" />
|
|
51
|
+
<span class="text-capitalize" style="word-break: break-word;">{{ item?.plateNumber || "N/A" }}</span>
|
|
52
|
+
</span>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template v-slot:item.checkInOut="{ item }">
|
|
56
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
57
|
+
<v-icon icon="mdi-login" size="15" />
|
|
58
|
+
<span class="text-capitalize" style="word-break: break-word;">{{ formatDate(item.checkIn as string) }}</span>
|
|
59
|
+
</span>
|
|
60
|
+
<span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
|
|
61
|
+
<v-icon icon="mdi-logout" size="15" />
|
|
62
|
+
<span class="text-capitalize" style="word-break: break-word;">{{ formatDate(item.checkOut as string) }}</span>
|
|
63
|
+
</span>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<template v-slot:item.checkInOutImage="{ item }">
|
|
67
|
+
<div class="d-flex flex-column ga-2">
|
|
68
|
+
<div v-if="item?.snapshotEntryImage" class="d-flex flex-column">
|
|
69
|
+
<span class="text-caption font-weight-bold text-blue-darken-1">Check-In</span>
|
|
70
|
+
<v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotEntryImage}`)"
|
|
71
|
+
style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
|
|
72
|
+
class="border" />
|
|
73
|
+
</div>
|
|
74
|
+
<div v-if="item?.snapshotExitImage" class="d-flex flex-column">
|
|
75
|
+
<span class="text-caption font-weight-bold text-red-darken-1">Check-Out</span>
|
|
76
|
+
<v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotExitImage}`)"
|
|
77
|
+
style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
|
|
78
|
+
class="border" />
|
|
79
|
+
</div>
|
|
80
|
+
<span v-if="!item?.snapshotEntryImage && !item?.snapshotExitImage"
|
|
81
|
+
class="text-caption text-disabled">
|
|
82
|
+
No images
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
</v-data-table>
|
|
89
|
+
</div>
|
|
90
|
+
</v-col>
|
|
91
|
+
|
|
92
|
+
</v-row>
|
|
93
|
+
</v-card>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<script setup lang="ts">
|
|
97
|
+
import moment from 'moment-timezone';
|
|
98
|
+
import useLocalAuth from '../composables/useLocalAuth';
|
|
99
|
+
import useVisitor from '../composables/useVisitor';
|
|
100
|
+
import useUtils from '../composables/useUtils';
|
|
101
|
+
import useSite from '../composables/useSite';
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
definePageMeta({
|
|
105
|
+
middleware: ["01-auth", "02-org"],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
const props = defineProps({
|
|
110
|
+
siteId: {
|
|
111
|
+
type: String,
|
|
112
|
+
required: true,
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const { getVisitors } = useVisitor();
|
|
117
|
+
const { formatCamelCaseToWords } = useUtils();
|
|
118
|
+
const { currentUser } = useLocalAuth();
|
|
119
|
+
const { getSiteById } = useSite(); const { getFileUrlAnpr } = useFile(); const route = useRoute();
|
|
120
|
+
const { org: orgId, site: siteId } = route.params as {
|
|
121
|
+
org: string;
|
|
122
|
+
site: string;
|
|
123
|
+
reportId: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const activeSiteName = computed(() => {
|
|
127
|
+
const site = sites.value.find(s => s._id === props.siteId)
|
|
128
|
+
return site ? site.name : ''
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
const headers: Record<string, any>[] = [
|
|
133
|
+
{ title: "Name", value: "name", align: "start" },
|
|
134
|
+
{ title: "Type/Company", value: "typeCompany", align: "start" },
|
|
135
|
+
{ title: "Location", value: "location", align: "start" },
|
|
136
|
+
{ title: "Contact/Vehicle No", value: "contact-vehicleNumber", align: "start" },
|
|
137
|
+
{ title: "Check In/Out", value: "checkInOut", align: "center" },
|
|
138
|
+
{ title: "Check In/Out Image", value: "checkInOutImage", align: "start" },
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
const searchInput = ref("");
|
|
145
|
+
const dateFrom = ref<string | null>(null);
|
|
146
|
+
const dateTo = ref<string | null>(null);
|
|
147
|
+
const filterTypes = ref<string[]>([]);
|
|
148
|
+
const activeTab = ref("registered");
|
|
149
|
+
const displayNotCheckedOut = ref(false);
|
|
150
|
+
|
|
151
|
+
const sites = ref<Array<Record<string, any>>>([]);
|
|
152
|
+
|
|
153
|
+
const items = ref<TVisitor[]>([])
|
|
154
|
+
|
|
155
|
+
const formatType = (item: any) =>
|
|
156
|
+
(item.deliveryType ? item.deliveryType + "-" : "") + item.type;
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
const { data: getSitesReq } = await useLazyAsyncData(
|
|
160
|
+
"get-customer-site-" + props.siteId,
|
|
161
|
+
() =>
|
|
162
|
+
getSiteById(props.siteId),
|
|
163
|
+
{
|
|
164
|
+
watch: [() => props.siteId],
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
watch(getSitesReq, (newData: any) => {
|
|
169
|
+
if (newData) {
|
|
170
|
+
sites.value = newData ? [newData] : []
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const formatLocation = ({ block, level, unit }: { block: number, level: string, unit: string | TBuildingUnit }) => {
|
|
175
|
+
const parts = [];
|
|
176
|
+
|
|
177
|
+
if (block) parts.push(`Block ${block}`);
|
|
178
|
+
if (level) parts.push(level);
|
|
179
|
+
if (unit) parts.push(unit);
|
|
180
|
+
|
|
181
|
+
return parts.join(" / ");
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
function formatDate(dateString: string) {
|
|
185
|
+
if (!dateString) return "";
|
|
186
|
+
|
|
187
|
+
const date = new Date(dateString);
|
|
188
|
+
return date.toLocaleDateString("en-US", {
|
|
189
|
+
year: "numeric",
|
|
190
|
+
month: "short",
|
|
191
|
+
day: "numeric",
|
|
192
|
+
hour: "2-digit",
|
|
193
|
+
minute: "2-digit",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
const {
|
|
199
|
+
data: getVisitorReq,
|
|
200
|
+
refresh: getVisitorRefresh,
|
|
201
|
+
pending: getVisitorPending,
|
|
202
|
+
} = await useLazyAsyncData(
|
|
203
|
+
"get-all-visitors",
|
|
204
|
+
async () => {
|
|
205
|
+
const params: any = {
|
|
206
|
+
page: 1,
|
|
207
|
+
site: siteId,
|
|
208
|
+
limit: 100,
|
|
209
|
+
search: searchInput.value,
|
|
210
|
+
dateTo: dateTo.value ? moment(dateTo.value).endOf("day").toISOString() : undefined,
|
|
211
|
+
dateFrom: dateFrom.value ? moment(dateFrom.value).startOf("day").toISOString() : undefined,
|
|
212
|
+
type:
|
|
213
|
+
filterTypes.value.length === 0 && activeTab.value === "registered"
|
|
214
|
+
? "contractor,delivery,walk-in,pick-up,drop-off,guest"
|
|
215
|
+
: filterTypes.value.filter(Boolean).join(","),
|
|
216
|
+
checkedOut: displayNotCheckedOut.value ? false : undefined,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (activeTab.value === "guests") {
|
|
220
|
+
params.type = "guest";
|
|
221
|
+
params.status = "approved";
|
|
222
|
+
} else if (activeTab.value === "resident-transactions") {
|
|
223
|
+
params.status = "registered";
|
|
224
|
+
params.type = "resident,tenant";
|
|
225
|
+
} else if (activeTab.value === "overnight-parking") {
|
|
226
|
+
params.tab = "Overnight Parking";
|
|
227
|
+
} else {
|
|
228
|
+
params.status = activeTab.value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return await getVisitors(params);
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
watch: [
|
|
235
|
+
searchInput,
|
|
236
|
+
dateFrom,
|
|
237
|
+
dateTo,
|
|
238
|
+
filterTypes,
|
|
239
|
+
activeTab,
|
|
240
|
+
displayNotCheckedOut,
|
|
241
|
+
],
|
|
242
|
+
immediate: true,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
watch(getVisitorReq, (newData: any) => {
|
|
247
|
+
if (newData) {
|
|
248
|
+
items.value = newData.items || []
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
function normalizeDateOnly(value: string) {
|
|
254
|
+
if (!value) return "";
|
|
255
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
256
|
+
|
|
257
|
+
const parsedDate = new Date(value);
|
|
258
|
+
if (Number.isNaN(parsedDate.getTime())) return "";
|
|
259
|
+
|
|
260
|
+
const year = parsedDate.getFullYear();
|
|
261
|
+
const month = String(parsedDate.getMonth() + 1).padStart(2, "0");
|
|
262
|
+
const day = String(parsedDate.getDate()).padStart(2, "0");
|
|
263
|
+
return `${year}-${month}-${day}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
function formatDateDDMMYYYY(date: Date | string | null) {
|
|
268
|
+
if (!date) return "";
|
|
269
|
+
const parsedDate = typeof date === "string" ? new Date(date) : date;
|
|
270
|
+
const day = String(parsedDate?.getDate()).padStart(2, '0')
|
|
271
|
+
const month = String(parsedDate?.getMonth() + 1).padStart(2, '0')
|
|
272
|
+
const year = parsedDate?.getFullYear()
|
|
273
|
+
|
|
274
|
+
return `${day}/${month}/${year}`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
onMounted(() => {
|
|
279
|
+
activeTab.value = (route.query.tab as string) || "unregistered";
|
|
280
|
+
searchInput.value = (route.query.search as string) || "";
|
|
281
|
+
dateFrom.value = normalizeDateOnly((route.query.dateFrom as string) || "");
|
|
282
|
+
dateTo.value = normalizeDateOnly((route.query.dateTo as string) || "");
|
|
283
|
+
filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(
|
|
284
|
+
Boolean
|
|
285
|
+
) as TVisitorType[];
|
|
286
|
+
displayNotCheckedOut.value =
|
|
287
|
+
(route.query.checkedOut as string) == "true" || false;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
</script>
|
|
292
|
+
|
|
293
|
+
<style scoped>
|
|
294
|
+
* {
|
|
295
|
+
box-sizing: border-box;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
:deep(.v-card) {
|
|
299
|
+
overflow-x: auto;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
:deep(.v-data-table-header__content) {
|
|
303
|
+
background-color: #1976d2 !important;
|
|
304
|
+
color: white !important;
|
|
305
|
+
font-weight: bold;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
:deep(.v-data-table thead tr th) {
|
|
309
|
+
background-color: #1976d2 !important;
|
|
310
|
+
color: white !important;
|
|
311
|
+
border-right: 1px solid rgba(255, 255, 255, 0.2) !important;
|
|
312
|
+
font-size: clamp(11px, 1.2vw, 14px) !important;
|
|
313
|
+
padding: clamp(8px, 1vw, 12px) !important;
|
|
314
|
+
word-break: break-word;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
:deep(.v-data-table thead tr th:last-child) {
|
|
318
|
+
border-right: none !important;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
:deep(.v-data-table tbody tr td) {
|
|
322
|
+
border-right: 1px solid #e0e0e0 !important;
|
|
323
|
+
font-size: clamp(10px, 1.1vw, 13px) !important;
|
|
324
|
+
padding: clamp(6px, 0.8vw, 10px) !important;
|
|
325
|
+
word-break: break-word;
|
|
326
|
+
word-wrap: break-word;
|
|
327
|
+
overflow-wrap: break-word;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
:deep(.v-data-table tbody tr td:last-child) {
|
|
331
|
+
border-right: none !important;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
:deep(.v-data-table tbody tr:last-child td) {
|
|
335
|
+
border-bottom: 1px solid #e0e0e0 !important;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
:deep(.v-img) {
|
|
339
|
+
max-width: clamp(60px, 10vw, 100px) !important;
|
|
340
|
+
max-height: clamp(60px, 10vw, 100px) !important;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
:deep(.text-caption) {
|
|
344
|
+
font-size: clamp(9px, 1vw, 12px) !important;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
:deep(.text-subtitle-1) {
|
|
348
|
+
font-size: clamp(14px, 2vw, 18px) !important;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
:deep(.text-h5) {
|
|
352
|
+
font-size: clamp(16px, 2.5vw, 24px) !important;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
:deep(.v-icon) {
|
|
356
|
+
width: clamp(12px, 1.5vw, 20px) !important;
|
|
357
|
+
height: clamp(12px, 1.5vw, 20px) !important;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
:deep(.gap-2) {
|
|
361
|
+
gap: clamp(4px, 0.5vw, 8px) !important;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Mobile optimization */
|
|
365
|
+
@media (max-width: 768px) {
|
|
366
|
+
:deep(.v-data-table) {
|
|
367
|
+
min-width: 100%;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
:deep(.v-data-table thead tr th) {
|
|
371
|
+
padding: 6px !important;
|
|
372
|
+
font-size: 11px !important;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
:deep(.v-data-table tbody tr td) {
|
|
376
|
+
padding: 4px !important;
|
|
377
|
+
font-size: 10px !important;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
:deep(.v-img) {
|
|
381
|
+
max-width: 60px !important;
|
|
382
|
+
max-height: 60px !important;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* Tablet optimization */
|
|
387
|
+
@media (max-width: 1024px) {
|
|
388
|
+
:deep(.v-data-table) {
|
|
389
|
+
font-size: 12px;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
:deep(.v-data-table thead tr th) {
|
|
393
|
+
padding: 8px !important;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
:deep(.v-data-table tbody tr td) {
|
|
397
|
+
padding: 6px !important;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
</style>
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<VisitorsReportPreview :site-id="siteId" />
|
|
4
|
+
</v-row>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
definePageMeta({
|
|
9
|
+
layout: "plain",
|
|
10
|
+
})
|
|
11
|
+
const route = useRoute();
|
|
12
|
+
const { org: orgId, site: siteId } = route.params as {
|
|
13
|
+
org: string;
|
|
14
|
+
site: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
|
|
21
|
+
</style>
|