@7365admin1/layer-common 1.11.27 → 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.
- package/CHANGELOG.md +12 -0
- package/components/AccessCardAddForm.vue +30 -51
- package/components/AccessCardAssignToUnitForm.vue +19 -35
- package/components/AccessManagement.vue +1 -1
- package/components/InvitationForm.vue +18 -5
- package/components/RolePermissionFormCreate.vue +3 -0
- package/components/RolePermissionFormPreviewUpdate.vue +3 -0
- package/components/ServiceProviderMain.vue +12 -7
- package/components/SiteSettings.vue +144 -128
- package/components/VisitorManagement.vue +361 -602
- package/components/VisitorsReportPreview.vue +400 -0
- package/composables/useAccessManagement.ts +11 -0
- package/composables/useOrg.ts +2 -2
- package/composables/useRole.ts +2 -0
- package/composables/useServiceProvider.ts +12 -4
- package/package.json +1 -1
- package/pages/[org]/[site]/visitor-management/preview/index.vue +21 -0
- package/types/customer.d.ts +1 -1
|
@@ -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>
|
|
@@ -279,6 +279,16 @@ export default function useAccessManagement() {
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
function getBlockLevelUnitList(siteId: string) {
|
|
283
|
+
return useNuxtApp().$api<{ message: string; data: any[] }>(
|
|
284
|
+
`/api/access-management/block-level-unit-list`,
|
|
285
|
+
{
|
|
286
|
+
method: "GET",
|
|
287
|
+
query: { site: siteId },
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
282
292
|
function signQr(payload: { cardId: string; purpose: string }) {
|
|
283
293
|
return useNuxtApp().$api<{ message: string; data: string }>(
|
|
284
294
|
`/api/access-management/sign-qr`,
|
|
@@ -310,5 +320,6 @@ export default function useAccessManagement() {
|
|
|
310
320
|
generateQrVms,
|
|
311
321
|
createVisitorPass,
|
|
312
322
|
signQr,
|
|
323
|
+
getBlockLevelUnitList,
|
|
313
324
|
};
|
|
314
325
|
}
|
package/composables/useOrg.ts
CHANGED
|
@@ -116,7 +116,7 @@ export default function useOrg() {
|
|
|
116
116
|
value: Partial<Pick<TOrg, "name" | "email" | "nature" | "contact">>
|
|
117
117
|
) {
|
|
118
118
|
return useNuxtApp()
|
|
119
|
-
.$api<Record<string, any>>("/api/organizations
|
|
119
|
+
.$api<Record<string, any>>("/api/organizations", {
|
|
120
120
|
method: "POST",
|
|
121
121
|
body: value,
|
|
122
122
|
})
|
|
@@ -126,7 +126,7 @@ export default function useOrg() {
|
|
|
126
126
|
) as TOrg;
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
return {
|
|
131
131
|
org,
|
|
132
132
|
getOrgs,
|
package/composables/useRole.ts
CHANGED
|
@@ -128,21 +128,29 @@ export default function useServiceProvider() {
|
|
|
128
128
|
|
|
129
129
|
async function createServiceProviderInvite({
|
|
130
130
|
email,
|
|
131
|
+
orgId,
|
|
131
132
|
siteId,
|
|
132
|
-
|
|
133
|
+
siteName,
|
|
133
134
|
}: {
|
|
134
135
|
email: string;
|
|
136
|
+
orgId: string;
|
|
135
137
|
siteId: string;
|
|
136
|
-
|
|
138
|
+
siteName: string;
|
|
137
139
|
}) {
|
|
138
140
|
try {
|
|
139
|
-
|
|
141
|
+
const res = await useNuxtApp().$api<Record<string, any>>(
|
|
140
142
|
"/api/auth/invite/service-provider",
|
|
141
143
|
{
|
|
142
144
|
method: "POST",
|
|
143
|
-
|
|
145
|
+
body: {
|
|
146
|
+
email,
|
|
147
|
+
orgId,
|
|
148
|
+
siteId,
|
|
149
|
+
siteName,
|
|
150
|
+
},
|
|
144
151
|
}
|
|
145
152
|
);
|
|
153
|
+
console.log(">>> response:", res);
|
|
146
154
|
} catch (err) {
|
|
147
155
|
console.error("Error creating service provider invite:", err);
|
|
148
156
|
}
|
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>
|
package/types/customer.d.ts
CHANGED
|
@@ -35,4 +35,4 @@ declare type TCustomerSite = {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
declare type TSiteCategory = "residential" | "commercial" | "industrial" | "institutional" | "mixed_development" | "infrastructure" | "hospitality"
|
|
38
|
+
declare type TSiteCategory = "residential" | "commercial" | "industrial" | "institutional" | "mixed_development" | "infrastructure" | "hospitality"
|