@7365admin1/layer-common 1.11.36 → 1.11.37
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 +6 -0
- package/components/ClientMain.vue +243 -109
- package/components/InvitationClientForm.vue +3 -3
- package/components/VehicleForm.vue +82 -50
- package/components/VehicleManagement.vue +67 -27
- package/composables/useCustomerSite.ts +11 -0
- package/composables/useUser.ts +26 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
|
|
4
|
-
<!-- MAIN TABLE -->
|
|
5
3
|
<v-col cols="12">
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
<TableMain
|
|
5
|
+
:headers="currentHeaders"
|
|
6
|
+
:items="items"
|
|
7
|
+
:loading="loading"
|
|
8
|
+
:page="page"
|
|
9
|
+
:pages="pages"
|
|
10
|
+
:pageRange="pageRange"
|
|
11
|
+
:items-per-page="-1"
|
|
12
|
+
:extension-height="120"
|
|
13
|
+
:offset="300"
|
|
14
|
+
show-header
|
|
15
|
+
@refresh="handleRefresh"
|
|
16
|
+
@update:page="handleUpdatePage"
|
|
17
|
+
>
|
|
10
18
|
<!-- EXTENSION -->
|
|
11
19
|
<template #extension>
|
|
12
20
|
<v-row no-gutters class="w-100 d-flex flex-column ga-2 pt-2">
|
|
13
|
-
|
|
14
21
|
<v-tabs v-model="tab" class="w-100" height="32" @update:model-value="onTabChange">
|
|
15
22
|
<v-tab value="active">Active</v-tab>
|
|
16
23
|
<v-tab value="suspended">Suspended</v-tab>
|
|
@@ -18,34 +25,61 @@
|
|
|
18
25
|
</v-tabs>
|
|
19
26
|
|
|
20
27
|
<div class="px-3 py-2 w-100">
|
|
21
|
-
<v-text-field
|
|
22
|
-
|
|
28
|
+
<v-text-field
|
|
29
|
+
v-model="search"
|
|
30
|
+
density="compact"
|
|
31
|
+
placeholder="Search"
|
|
32
|
+
clearable
|
|
33
|
+
max-width="300"
|
|
34
|
+
append-inner-icon="mdi-magnify"
|
|
35
|
+
hide-details
|
|
36
|
+
@update:model-value="handleSearch"
|
|
37
|
+
/>
|
|
23
38
|
</div>
|
|
24
|
-
|
|
25
39
|
</v-row>
|
|
26
40
|
</template>
|
|
27
41
|
|
|
28
|
-
<!-- ACTIONS
|
|
42
|
+
<!-- ACTIONS -->
|
|
29
43
|
<template #actions>
|
|
30
|
-
<v-btn
|
|
44
|
+
<v-btn
|
|
45
|
+
v-if="tab !== 'suspended'"
|
|
46
|
+
class="text-none"
|
|
47
|
+
rounded="pill"
|
|
48
|
+
variant="tonal"
|
|
49
|
+
size="large"
|
|
50
|
+
@click="openInviteDialog"
|
|
51
|
+
>
|
|
31
52
|
Invite Client
|
|
32
53
|
</v-btn>
|
|
33
54
|
</template>
|
|
34
55
|
|
|
35
|
-
<!--
|
|
36
|
-
<template #item.index="{ index }">
|
|
56
|
+
<!-- INDEX CELL -->
|
|
57
|
+
<!-- <template #item.index="{ index }">
|
|
37
58
|
{{ (currentPage - 1) * 10 + index + 1 }}
|
|
38
|
-
</template>
|
|
59
|
+
</template> -->
|
|
39
60
|
|
|
61
|
+
<!-- STATUS CELL -->
|
|
40
62
|
<template #item.status="{ value }">
|
|
41
|
-
<v-chip
|
|
63
|
+
<v-chip
|
|
64
|
+
size="small"
|
|
65
|
+
:color="value === 'suspended' ? 'error' : 'success'"
|
|
66
|
+
variant="flat"
|
|
67
|
+
>
|
|
42
68
|
{{ value }}
|
|
43
69
|
</v-chip>
|
|
44
70
|
</template>
|
|
71
|
+
|
|
72
|
+
<!-- ACTION CELL (pending only) -->
|
|
45
73
|
<template #item.action="{ item }">
|
|
46
|
-
<v-btn
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
<v-btn
|
|
75
|
+
v-if="tab === 'pending'"
|
|
76
|
+
size="small"
|
|
77
|
+
color="error"
|
|
78
|
+
variant="tonal"
|
|
79
|
+
:loading="cancelLoadingId === item._id"
|
|
80
|
+
:disabled="cancelLoadingId === item._id"
|
|
81
|
+
@click="cancelInvite(item)"
|
|
82
|
+
>
|
|
49
83
|
Cancel Invite
|
|
50
84
|
</v-btn>
|
|
51
85
|
</template>
|
|
@@ -54,66 +88,61 @@
|
|
|
54
88
|
|
|
55
89
|
<!-- DIALOG -->
|
|
56
90
|
<v-dialog v-model="dialog" max-width="500">
|
|
57
|
-
<InvitationClientForm
|
|
91
|
+
<InvitationClientForm
|
|
92
|
+
title="Invite Client"
|
|
93
|
+
@success="handleSuccess"
|
|
94
|
+
@cancel="dialog = false"
|
|
95
|
+
/>
|
|
58
96
|
</v-dialog>
|
|
59
|
-
s
|
|
60
97
|
</v-row>
|
|
61
98
|
</template>
|
|
62
|
-
<script setup lang="ts">
|
|
63
99
|
|
|
64
|
-
|
|
100
|
+
<script setup lang="ts">
|
|
101
|
+
import { computed, onMounted, ref } from 'vue'
|
|
65
102
|
import useOrg from '../composables/useOrg'
|
|
66
103
|
import useVerification from '../composables/useVerification'
|
|
67
104
|
import useRole from '../composables/useRole'
|
|
68
105
|
import InvitationClientForm from './InvitationClientForm.vue'
|
|
69
|
-
|
|
106
|
+
import useCustomerSite from '../composables/useCustomerSite'
|
|
107
|
+
import useSubscription from '../composables/useSubscription'
|
|
70
108
|
|
|
71
109
|
const tab = ref<'active' | 'suspended' | 'pending'>('active')
|
|
72
110
|
const dialog = ref(false)
|
|
73
111
|
|
|
74
112
|
const { getAll } = useOrg()
|
|
75
113
|
const { getVerifications, cancelUserInvitation } = useVerification()
|
|
114
|
+
const { getCustomerSites: getCustomerSitesByOrgId } = useCustomerSite()
|
|
115
|
+
const { getByOrgId: getSubscriptionByOrgId } = useSubscription()
|
|
76
116
|
const { getRoleById } = useRole()
|
|
77
117
|
const roleCache = ref<Record<string, string>>({})
|
|
78
118
|
|
|
79
119
|
/* ================= STATE ================= */
|
|
80
120
|
const items = ref<any[]>([])
|
|
81
121
|
const loading = ref(false)
|
|
82
|
-
|
|
83
122
|
const page = ref(1)
|
|
84
123
|
const pages = ref(1)
|
|
85
|
-
const search = ref(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (roleCache.value[id]) return roleCache.value[id]
|
|
90
|
-
|
|
91
|
-
const res = await getRoleById(id)
|
|
92
|
-
|
|
93
|
-
const roleName = res?.name ?? "-"
|
|
94
|
-
|
|
95
|
-
roleCache.value[id] = roleName
|
|
124
|
+
const search = ref('')
|
|
125
|
+
const totalItems = ref(0)
|
|
126
|
+
const cancelLoadingId = ref<string | null>(null)
|
|
96
127
|
|
|
97
|
-
return roleName
|
|
98
|
-
}
|
|
99
128
|
/* ================= HEADERS ================= */
|
|
100
129
|
const orgHeaders = [
|
|
101
|
-
{ title:
|
|
102
|
-
{ title:
|
|
103
|
-
{ title:
|
|
104
|
-
{ title:
|
|
105
|
-
{ title:
|
|
106
|
-
{ title:
|
|
107
|
-
{ title:
|
|
108
|
-
{ title:
|
|
130
|
+
{ title: 'Organization Name', key: 'name' },
|
|
131
|
+
{ title: 'Sites', key: 'sites' },
|
|
132
|
+
{ title: 'Plan Type', key: 'planType' },
|
|
133
|
+
{ title: 'Billing Cycle', key: 'billingCycle' },
|
|
134
|
+
{ title: 'Subscription Start', key: 'subscriptionStart' },
|
|
135
|
+
{ title: 'Subscription End', key: 'subscriptionEnd' },
|
|
136
|
+
{ title: 'Status', key: 'status' },
|
|
137
|
+
{ title: 'Actions', key: 'actions' },
|
|
109
138
|
]
|
|
110
139
|
|
|
111
140
|
const inviteHeaders = [
|
|
112
|
-
{ title:
|
|
113
|
-
{ title:
|
|
114
|
-
{ title:
|
|
115
|
-
{ title:
|
|
116
|
-
{ title:
|
|
141
|
+
{ title: 'Email', key: 'email' },
|
|
142
|
+
{ title: 'App', key: 'app' },
|
|
143
|
+
{ title: 'Role', key: 'role' },
|
|
144
|
+
{ title: 'Status', key: 'status' },
|
|
145
|
+
{ title: 'Action', key: 'action' },
|
|
117
146
|
]
|
|
118
147
|
|
|
119
148
|
/* ================= COMPUTED ================= */
|
|
@@ -121,65 +150,173 @@ const currentHeaders = computed(() =>
|
|
|
121
150
|
tab.value === 'pending' ? inviteHeaders : orgHeaders
|
|
122
151
|
)
|
|
123
152
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
const
|
|
153
|
+
const pageRange = computed(() => {
|
|
154
|
+
if (!totalItems.value) return '0-0 of 0'
|
|
155
|
+
const pageSize = 10
|
|
156
|
+
const start = (page.value - 1) * pageSize + 1
|
|
157
|
+
const end = Math.min(page.value * pageSize, totalItems.value)
|
|
158
|
+
return `${start}-${end} of ${totalItems.value}`
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
/* ================= HELPERS ================= */
|
|
162
|
+
function formatDate(date?: string) {
|
|
163
|
+
if (!date) return '-'
|
|
164
|
+
return new Date(date).toLocaleDateString('en-US')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function resolveRoleName(id: string) {
|
|
168
|
+
if (!id) return '-'
|
|
169
|
+
if (roleCache.value[id]) return roleCache.value[id]
|
|
170
|
+
const res = await getRoleById(id)
|
|
171
|
+
const roleName = res?.name ?? '-'
|
|
172
|
+
roleCache.value[id] = roleName
|
|
173
|
+
return roleName
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ================= FETCH ================= */
|
|
177
|
+
async function fetchActive() {
|
|
178
|
+
const res = await getAll({ page: page.value, search: search.value })
|
|
179
|
+
const orgs = res?.data?.items || res?.items || []
|
|
180
|
+
|
|
181
|
+
const mapped = await Promise.all(
|
|
182
|
+
orgs.map(async (org: any) => {
|
|
183
|
+
let sitesCount = 0
|
|
184
|
+
let planType = '-'
|
|
185
|
+
let billingCycle = '-'
|
|
186
|
+
let subscriptionStart = '-'
|
|
187
|
+
let subscriptionEnd = '-'
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const siteRes = await getCustomerSitesByOrgId(org._id)
|
|
191
|
+
sitesCount =
|
|
192
|
+
siteRes?.data?.totalItems ||
|
|
193
|
+
siteRes?.totalItems ||
|
|
194
|
+
siteRes?.data?.items?.length ||
|
|
195
|
+
siteRes?.items?.length ||
|
|
196
|
+
0
|
|
197
|
+
|
|
198
|
+
const subRes = await getSubscriptionByOrgId(org._id)
|
|
199
|
+
const subscription = subRes?.data || subRes
|
|
200
|
+
if (subscription) {
|
|
201
|
+
planType = subscription?.paidType || '-'
|
|
202
|
+
billingCycle = subscription?.billingCycle || '-'
|
|
203
|
+
subscriptionStart = formatDate(subscription?.createdAt)
|
|
204
|
+
subscriptionEnd = formatDate(subscription?.nextBillingDate)
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.error('Error mapping org:', org._id, e)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
_id: org._id,
|
|
212
|
+
...org,
|
|
213
|
+
sites: `${sitesCount} Sites`,
|
|
214
|
+
planType,
|
|
215
|
+
billingCycle,
|
|
216
|
+
subscriptionStart,
|
|
217
|
+
subscriptionEnd,
|
|
218
|
+
status: org?.status || 'Active',
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
items.value = mapped
|
|
224
|
+
pages.value = res?.data?.totalPages || res?.totalPages || 1
|
|
225
|
+
totalItems.value = res?.data?.totalItems || res?.totalItems || mapped.length
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function fetchSuspended() {
|
|
229
|
+
const res = await getAll({
|
|
230
|
+
page: page.value,
|
|
231
|
+
search: search.value,
|
|
232
|
+
// status: 'suspended',
|
|
233
|
+
})
|
|
234
|
+
const orgs = res?.data?.items || res?.items || []
|
|
235
|
+
|
|
236
|
+
const mapped = await Promise.all(
|
|
237
|
+
orgs.map(async (org: any) => {
|
|
238
|
+
let sitesCount = 0
|
|
239
|
+
let planType = '-'
|
|
240
|
+
let billingCycle = '-'
|
|
241
|
+
let subscriptionStart = '-'
|
|
242
|
+
let subscriptionEnd = '-'
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const siteRes = await getCustomerSitesByOrgId(org._id)
|
|
246
|
+
sitesCount =
|
|
247
|
+
siteRes?.data?.totalItems ||
|
|
248
|
+
siteRes?.totalItems ||
|
|
249
|
+
siteRes?.data?.items?.length ||
|
|
250
|
+
siteRes?.items?.length ||
|
|
251
|
+
0
|
|
252
|
+
|
|
253
|
+
const subRes = await getSubscriptionByOrgId(org._id)
|
|
254
|
+
const subscription = subRes?.data || subRes
|
|
255
|
+
if (subscription) {
|
|
256
|
+
planType = subscription?.paidType || '-'
|
|
257
|
+
billingCycle = subscription?.billingCycle || '-'
|
|
258
|
+
subscriptionStart = formatDate(subscription?.createdAt)
|
|
259
|
+
subscriptionEnd = formatDate(subscription?.nextBillingDate)
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {
|
|
262
|
+
console.error('Error mapping suspended org:', org._id, e)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
_id: org._id,
|
|
267
|
+
...org,
|
|
268
|
+
sites: `${sitesCount} Sites`,
|
|
269
|
+
planType,
|
|
270
|
+
billingCycle,
|
|
271
|
+
subscriptionStart,
|
|
272
|
+
subscriptionEnd,
|
|
273
|
+
status: org?.status || 'suspended',
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
items.value = mapped
|
|
279
|
+
pages.value = res?.data?.totalPages || res?.totalPages || 1
|
|
280
|
+
totalItems.value = res?.data?.totalItems || res?.totalItems || mapped.length
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function fetchPending() {
|
|
284
|
+
const res = await getVerifications({
|
|
285
|
+
status: 'pending',
|
|
286
|
+
type: 'user-invite',
|
|
287
|
+
page: page.value,
|
|
288
|
+
search: search.value,
|
|
289
|
+
app: 'organization',
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
items.value = await Promise.all(
|
|
293
|
+
(res?.items || []).map(async (i: any) => ({
|
|
294
|
+
...i,
|
|
295
|
+
app: i.metadata?.app ?? '-',
|
|
296
|
+
role: await resolveRoleName(i.metadata?.role),
|
|
297
|
+
}))
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
pages.value = res?.pages || 1
|
|
301
|
+
totalItems.value = res?.total || items.value.length
|
|
302
|
+
}
|
|
127
303
|
|
|
128
|
-
const loadingState = computed(() => loading.value)
|
|
129
|
-
const totalItems = ref(0)
|
|
130
|
-
/* ================= API ================= */
|
|
131
304
|
async function fetchData() {
|
|
132
305
|
loading.value = true
|
|
133
|
-
|
|
134
306
|
try {
|
|
135
|
-
if (tab.value === '
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
search: search.value,
|
|
141
|
-
app: "organization",
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
items.value = await Promise.all(
|
|
145
|
-
(res?.items || []).map(async (i: any) => ({
|
|
146
|
-
...i,
|
|
147
|
-
app: i.metadata?.app ?? "-",
|
|
148
|
-
role: await resolveRoleName(i.metadata?.role),
|
|
149
|
-
}))
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
pages.value = res?.pages || 1
|
|
153
|
-
totalItems.value = res?.total || items.value.length
|
|
154
|
-
} else {
|
|
155
|
-
const res = await getAll({
|
|
156
|
-
page: page.value,
|
|
157
|
-
search: search.value,
|
|
158
|
-
nature: tab.value === "active" ? "" : "suspended",
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
items.value = res?.data?.items || res?.items || []
|
|
162
|
-
|
|
163
|
-
pages.value = res?.data?.totalPages || res?.totalPages || 1
|
|
164
|
-
totalItems.value =
|
|
165
|
-
res?.data?.totalItems ||
|
|
166
|
-
res?.totalItems ||
|
|
167
|
-
items.value.length
|
|
307
|
+
if (tab.value === 'suspended') {
|
|
308
|
+
items.value = []
|
|
309
|
+
pages.value = 1
|
|
310
|
+
totalItems.value = 0
|
|
311
|
+
return
|
|
168
312
|
}
|
|
313
|
+
if (tab.value === 'active') await fetchActive()
|
|
314
|
+
else await fetchPending()
|
|
169
315
|
} finally {
|
|
170
316
|
loading.value = false
|
|
171
317
|
}
|
|
172
318
|
}
|
|
173
|
-
const pageRange = computed(() => {
|
|
174
|
-
if (!totalItems.value) return "0-0 of 0"
|
|
175
|
-
|
|
176
|
-
const pageSize = 10
|
|
177
|
-
|
|
178
|
-
const start = (page.value - 1) * pageSize + 1
|
|
179
|
-
const end = Math.min(page.value * pageSize, totalItems.value)
|
|
180
319
|
|
|
181
|
-
return `${start}-${end} of ${totalItems.value}`
|
|
182
|
-
})
|
|
183
320
|
/* ================= EVENTS ================= */
|
|
184
321
|
function onTabChange() {
|
|
185
322
|
page.value = 1
|
|
@@ -210,22 +347,19 @@ function handleSuccess() {
|
|
|
210
347
|
fetchData()
|
|
211
348
|
}
|
|
212
349
|
|
|
213
|
-
/* =================
|
|
214
|
-
onMounted(() => {
|
|
215
|
-
fetchData()
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
const cancelLoadingId = ref<string | null>(null)
|
|
219
|
-
|
|
350
|
+
/* ================= CANCEL INVITE ================= */
|
|
220
351
|
async function cancelInvite(item: any) {
|
|
221
352
|
try {
|
|
222
353
|
cancelLoadingId.value = item._id
|
|
223
|
-
|
|
224
354
|
await cancelUserInvitation(item._id)
|
|
225
|
-
|
|
226
355
|
await fetchData()
|
|
227
356
|
} finally {
|
|
228
357
|
cancelLoadingId.value = null
|
|
229
358
|
}
|
|
230
359
|
}
|
|
360
|
+
|
|
361
|
+
/* ================= INIT ================= */
|
|
362
|
+
onMounted(() => {
|
|
363
|
+
fetchData()
|
|
364
|
+
})
|
|
231
365
|
</script>
|
|
@@ -236,7 +236,7 @@ function handleUpdateSite(id: string) {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
/* ================= SUBMIT ================= */
|
|
239
|
-
const {
|
|
239
|
+
const { inviteUserClient } = useUser()
|
|
240
240
|
const { requiredRule, emailRule } = useUtils()
|
|
241
241
|
|
|
242
242
|
const message = ref("")
|
|
@@ -251,14 +251,14 @@ async function submit() {
|
|
|
251
251
|
|
|
252
252
|
await Promise.all(
|
|
253
253
|
apps.map(app =>
|
|
254
|
-
|
|
254
|
+
inviteUserClient({
|
|
255
255
|
email: invite.value.email,
|
|
256
256
|
app,
|
|
257
257
|
role: invite.value.role,
|
|
258
258
|
org: invite.value.org,
|
|
259
259
|
site: invite.value.site,
|
|
260
260
|
siteName: invite.value.siteName,
|
|
261
|
-
isMemberInvite: invite.value.isMemberInvite,
|
|
261
|
+
// isMemberInvite: invite.value.isMemberInvite,
|
|
262
262
|
})
|
|
263
263
|
)
|
|
264
264
|
)
|
|
@@ -35,34 +35,32 @@
|
|
|
35
35
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
36
36
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
37
37
|
<v-select v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
|
|
38
|
-
@update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]"
|
|
39
|
-
/>
|
|
38
|
+
@update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" />
|
|
40
39
|
</v-col>
|
|
41
40
|
|
|
42
41
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
43
42
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
44
|
-
<v-select v-model="vehicle.level" :items="levelsArray" density="comfortable"
|
|
45
|
-
|
|
46
|
-
:rules="[requiredRule]" />
|
|
43
|
+
<v-select v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block"
|
|
44
|
+
@update:model-value="handleChangeLevel" :rules="[requiredRule]" />
|
|
47
45
|
</v-col>
|
|
48
46
|
|
|
49
47
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
50
48
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
51
|
-
<v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable"
|
|
52
|
-
:
|
|
49
|
+
<v-select v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level"
|
|
50
|
+
:rules="[requiredRule]" />
|
|
53
51
|
</v-col>
|
|
54
52
|
|
|
55
53
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
56
54
|
<InputLabel class="text-capitalize" title="NRIC" required />
|
|
57
|
-
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
55
|
+
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
56
|
+
:disabled="disablePrefilledInputs && type !== 'blocklist'" />
|
|
58
57
|
</v-col>
|
|
59
58
|
|
|
60
59
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
61
60
|
<v-row>
|
|
62
61
|
<v-col cols="12">
|
|
63
62
|
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
64
|
-
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]"
|
|
65
|
-
/>
|
|
63
|
+
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" />
|
|
66
64
|
</v-col>
|
|
67
65
|
</v-row>
|
|
68
66
|
</v-col>
|
|
@@ -75,42 +73,49 @@
|
|
|
75
73
|
</v-col>
|
|
76
74
|
|
|
77
75
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
78
|
-
|
|
79
|
-
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
80
|
-
<template v-for="plate in vehicle.plates" :key="plate.plateNumber">
|
|
81
|
-
<InputLabel class="text-capitalize" title="Existing Vehicle Numbers" required />
|
|
82
|
-
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]" class="mb-2"
|
|
83
|
-
readonly disabled>
|
|
84
|
-
<template #append-inner>
|
|
85
|
-
<v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{
|
|
86
|
-
formatVehicleStatus(plate?.status)?.label }}</v-chip>
|
|
87
|
-
</template>
|
|
88
|
-
</v-text-field>
|
|
89
|
-
</template>
|
|
90
76
|
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
<v-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
<template v-if="prop.mode === 'add'">
|
|
78
|
+
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
79
|
+
<template v-for="plate in vehicle.plates" :key="plate.plateNumber">
|
|
80
|
+
<InputLabel class="text-capitalize" title="Existing Vehicle Numbers" required />
|
|
81
|
+
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]"
|
|
82
|
+
class="mb-2 class-readonly" readonly>
|
|
83
|
+
<template #append-inner>
|
|
84
|
+
<v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{
|
|
85
|
+
formatVehicleStatus(plate?.status)?.label }}</v-chip>
|
|
86
|
+
</template>
|
|
87
|
+
</v-text-field>
|
|
88
|
+
</template>
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
<InputLabel class="text-capitalize" title="New Vehicle Numbers" required />
|
|
91
|
+
<template v-for="(plate, index) in newPlateNumbers" :key="index">
|
|
92
|
+
<v-row class="" :class="`${index === newPlateNumbers.length - 1 ? 'mb-1' : 'mb-5'}`" no-gutters>
|
|
93
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '11' : '12'">
|
|
94
|
+
<InputVehicleNumber v-model="newPlateNumbers[index]" density="comfortable"
|
|
95
|
+
placeholder="New Vehicle Number" :rules="[requiredRule]" />
|
|
96
|
+
</v-col>
|
|
97
|
+
|
|
98
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '1' : '0'" class="d-flex align-start">
|
|
99
|
+
<v-btn icon="mdi-delete-outline" variant="text" color="error" v-if="newPlateNumbers.length > 1"
|
|
100
|
+
@click="removePlate(index)" />
|
|
101
|
+
</v-col>
|
|
102
|
+
</v-row>
|
|
103
|
+
|
|
104
|
+
<v-btn prepend-icon="mdi-plus" variant="outlined" class="text-none" @click="addPlate">
|
|
105
|
+
Add Plate
|
|
106
|
+
</v-btn>
|
|
107
|
+
</template>
|
|
104
108
|
</template>
|
|
105
109
|
|
|
106
|
-
<v-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
<template v-else-if="prop.mode === 'edit'">
|
|
111
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" required />
|
|
112
|
+
<InputVehicleNumber v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" />
|
|
113
|
+
</template>
|
|
109
114
|
</v-col>
|
|
110
115
|
|
|
111
116
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
112
|
-
<InputLabel class="text-capitalize" title="Remarks"
|
|
113
|
-
<v-textarea v-model="vehicle.remarks" density="comfortable" :rows="3" no-resize
|
|
117
|
+
<InputLabel class="text-capitalize" title="Remarks" />
|
|
118
|
+
<v-textarea v-model="vehicle.remarks" density="comfortable" :rows="3" no-resize />
|
|
114
119
|
</v-col>
|
|
115
120
|
|
|
116
121
|
<v-col v-if="shouldShowField('start')" cols="12">
|
|
@@ -192,8 +197,7 @@
|
|
|
192
197
|
Matching Records for "{{ searchUnitName ?? "-" }}"
|
|
193
198
|
</span>
|
|
194
199
|
<span>
|
|
195
|
-
<v-btn icon="mdi-close" variant="text"
|
|
196
|
-
@click="handleCloseMatchDialog" />
|
|
200
|
+
<v-btn icon="mdi-close" variant="text" @click="handleCloseMatchDialog" />
|
|
197
201
|
</span>
|
|
198
202
|
</v-row>
|
|
199
203
|
</v-toolbar-title>
|
|
@@ -209,7 +213,7 @@
|
|
|
209
213
|
</v-list-item-title>
|
|
210
214
|
|
|
211
215
|
<v-list-item-subtitle>
|
|
212
|
-
|
|
216
|
+
NRIC: {{ v.nric || "-" }}
|
|
213
217
|
</v-list-item-subtitle>
|
|
214
218
|
|
|
215
219
|
<div class="mt-1">
|
|
@@ -262,6 +266,10 @@ const prop = defineProps({
|
|
|
262
266
|
vehicleData: {
|
|
263
267
|
type: Object as PropType<Partial<TVehicle> | null>,
|
|
264
268
|
default: null
|
|
269
|
+
},
|
|
270
|
+
plateNumberId: {
|
|
271
|
+
type: String,
|
|
272
|
+
default: ''
|
|
265
273
|
}
|
|
266
274
|
});
|
|
267
275
|
|
|
@@ -291,6 +299,7 @@ const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicl
|
|
|
291
299
|
org: prop.org,
|
|
292
300
|
_id: '',
|
|
293
301
|
peopleId: '',
|
|
302
|
+
plateNumber: "",
|
|
294
303
|
});
|
|
295
304
|
|
|
296
305
|
const newPlateNumbers = ref<string[]>(['']);
|
|
@@ -520,7 +529,6 @@ async function submit() {
|
|
|
520
529
|
const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org, peopleId } = vehicle
|
|
521
530
|
|
|
522
531
|
let payload: Partial<TVehiclePayload> = {
|
|
523
|
-
plateNumber: newPlateNumbers.value,
|
|
524
532
|
name,
|
|
525
533
|
phoneNumber,
|
|
526
534
|
type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
|
|
@@ -533,7 +541,16 @@ async function submit() {
|
|
|
533
541
|
...payload,
|
|
534
542
|
category,
|
|
535
543
|
site,
|
|
536
|
-
org
|
|
544
|
+
org,
|
|
545
|
+
plateNumber: newPlateNumbers.value
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (prop.mode === 'edit' && prop.plateNumberId) {
|
|
550
|
+
payload = {
|
|
551
|
+
...payload,
|
|
552
|
+
site: prop.site,
|
|
553
|
+
plateNumber
|
|
537
554
|
}
|
|
538
555
|
}
|
|
539
556
|
|
|
@@ -564,8 +581,8 @@ async function submit() {
|
|
|
564
581
|
if (prop.mode === 'add') {
|
|
565
582
|
await addVehicle(payload);
|
|
566
583
|
} else if (prop.mode === 'edit') {
|
|
567
|
-
const
|
|
568
|
-
await updateVehicle(
|
|
584
|
+
const plateNumberId = prop.plateNumberId as string;
|
|
585
|
+
await updateVehicle(plateNumberId, payload);
|
|
569
586
|
}
|
|
570
587
|
emit("done");
|
|
571
588
|
|
|
@@ -692,14 +709,14 @@ function selectPeopleRecord(record: TPeople) {
|
|
|
692
709
|
|
|
693
710
|
|
|
694
711
|
async function checkPeopleById() {
|
|
695
|
-
if (!vehicle.unit) return;
|
|
712
|
+
if (!vehicle.unit || prop.mode === 'edit') return;
|
|
696
713
|
|
|
697
714
|
checkingUnit.value = true;
|
|
698
715
|
showMatchingPeopleDialog.value = true;
|
|
699
716
|
// Lock editable fields while we verify if this NRIC has existing records.
|
|
700
717
|
disablePrefilledInputs.value = true;
|
|
701
718
|
try {
|
|
702
|
-
const res = await getPeopleByUnit(vehicle.unit, { status: 'active', type: 'resident,tenant' }) as TPeople[]| null;
|
|
719
|
+
const res = await getPeopleByUnit(vehicle.unit, { status: 'active', type: 'resident,tenant' }) as TPeople[] | null;
|
|
703
720
|
|
|
704
721
|
if (Array.isArray(res) && res.length > 0) {
|
|
705
722
|
matchingPeople.value = res || []
|
|
@@ -745,12 +762,23 @@ const resetVehicleDetails = () => {
|
|
|
745
762
|
|
|
746
763
|
function handleCloseMatchDialog() {
|
|
747
764
|
showMatchingPeopleDialog.value = false;
|
|
748
|
-
if(matchingPeople.value.length > 0) {
|
|
765
|
+
if (matchingPeople.value.length > 0) {
|
|
749
766
|
vehicle.unit = null
|
|
750
767
|
}
|
|
751
768
|
}
|
|
752
769
|
|
|
753
|
-
|
|
770
|
+
onMounted(() => {
|
|
771
|
+
setTimeout(() => {
|
|
772
|
+
console.log("VehicleForm mounted with props:", prop);
|
|
773
|
+
if (prop.mode === 'edit' && prop.vehicleData) {
|
|
774
|
+
// In edit mode, we only want to check for matching people records if there is a pre-filled unit value. If there is no unit value, we can assume that this vehicle record is not linked to any existing people record and skip the check.
|
|
775
|
+
vehicle.seasonPassType = prop.vehicleData.seasonPassType || '';
|
|
776
|
+
vehicle.end = prop.vehicleData.end || '';
|
|
777
|
+
vehicle.start = prop.vehicleData.start || '';
|
|
778
|
+
vehicle.plateNumber = prop.vehicleData.plates?.find((p) => p._id === prop.plateNumberId)?.plateNumber || '';
|
|
779
|
+
}
|
|
780
|
+
}, 300);
|
|
781
|
+
})
|
|
754
782
|
|
|
755
783
|
|
|
756
784
|
</script>
|
|
@@ -758,4 +786,8 @@ function handleCloseMatchDialog() {
|
|
|
758
786
|
.button-outline-class {
|
|
759
787
|
border: 1px solid rgba(var(--v-theme-primary));
|
|
760
788
|
}
|
|
789
|
+
|
|
790
|
+
.class-readonly {
|
|
791
|
+
pointer-events: none;
|
|
792
|
+
}
|
|
761
793
|
</style>
|
|
@@ -37,10 +37,15 @@
|
|
|
37
37
|
</v-dialog>
|
|
38
38
|
|
|
39
39
|
<v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
|
|
40
|
-
<VehicleForm :type="vehicleType" mode="
|
|
40
|
+
<VehicleForm :type="vehicleType" :mode="mode" :vehicle-data="selectedVehicleObject" :plate-number-id="selectedPlateNumberId" @back="handleBackToSelection"
|
|
41
41
|
@done="handleAddVehicleComplete" :org="org" :site="props.site" @close:all="handleCloseAll" />
|
|
42
42
|
</v-dialog>
|
|
43
43
|
|
|
44
|
+
<v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
|
|
45
|
+
<VehicleForm :type="vehicleType" :mode="mode" :vehicle-data="selectedPlateNumberObject" :plate-number-id="selectedPlateNumberId" @back="handleBackToSelection"
|
|
46
|
+
@done="handleAddVehicleComplete" :org="org" :site="props.site" @close:all="handleCloseAll" @close="handleCloseAll" />
|
|
47
|
+
</v-dialog>
|
|
48
|
+
|
|
44
49
|
<!-- <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
|
|
45
50
|
<VehicleForm :type="vehicleType" mode="edit" :vehicle-data="selectedVehicleObject"
|
|
46
51
|
@back="dialog.updateVehicle = false" @close="dialog.updateVehicle = false" @done="handleUpdateVehicleComplete"
|
|
@@ -58,7 +63,7 @@
|
|
|
58
63
|
<span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong></span>
|
|
59
64
|
<!-- <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" /> -->
|
|
60
65
|
<v-col cols="12">
|
|
61
|
-
<v-card flat outlined class="pa-2" border="sm black">
|
|
66
|
+
<v-card flat outlined class="pa-2" border="sm black" :loading="loadingPlateNumberDetails">
|
|
62
67
|
<v-data-table :items="selectedVehicleObject[key]" :headers="plateHeaders" hide-default-footer>
|
|
63
68
|
<template #item.status="{ value }">
|
|
64
69
|
<v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
|
|
@@ -66,22 +71,33 @@
|
|
|
66
71
|
</v-chip>
|
|
67
72
|
</template>
|
|
68
73
|
<template #item.action="{ item: plateNumberItem, value }">
|
|
69
|
-
<v-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
v-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
<v-menu>
|
|
75
|
+
<template #activator="{ props: menuProps }">
|
|
76
|
+
<v-btn icon="mdi-dots-vertical" v-bind="menuProps" flat size="x-small" />
|
|
77
|
+
</template>
|
|
78
|
+
<v-list density="compact">
|
|
79
|
+
<v-list-item v-if="(plateNumberItem as TPlateNumber)?.status == 'active'"
|
|
80
|
+
title="Edit Vehicle" prepend-icon="mdi-pencil" @click="handleEditVehicleAction(plateNumberItem as TPlateNumber)" />
|
|
81
|
+
<v-list-item
|
|
82
|
+
v-if="props.app === 'property_management_agency' && (plateNumberItem as TPlateNumber)?.status == 'pending'"
|
|
83
|
+
title="Approve" prepend-icon="mdi-check-circle" base-color="success"
|
|
84
|
+
@click="handleApproveVehicle(plateNumberItem as TPlateNumber)" />
|
|
85
|
+
<v-list-item v-if="(plateNumberItem as TPlateNumber)?.status == 'deleted'" title="Restore"
|
|
86
|
+
prepend-icon="mdi-restore" base-color="orange"
|
|
87
|
+
@click="handleRestoreVehicle(plateNumberItem as TPlateNumber)" />
|
|
88
|
+
<v-list-item
|
|
89
|
+
v-if="((plateNumberItem as TPlateNumber)?.type === 'whitelist' || (plateNumberItem as TPlateNumber)?.type === 'blocklist') && plateNumberItem?.status == 'active'"
|
|
90
|
+
:title="(plateNumberItem as TPlateNumber)?.type === 'blocklist' ? 'Whitelist' : 'Blocklist'"
|
|
91
|
+
prepend-icon="mdi-swap-horizontal"
|
|
92
|
+
:base-color="(plateNumberItem as TPlateNumber)?.type === 'blocklist' ? 'primary' : 'red'"
|
|
93
|
+
@click="handleUpdateType(plateNumberItem as TPlateNumber)" />
|
|
94
|
+
|
|
95
|
+
<v-list-item
|
|
96
|
+
v-if="canDeleteVehicle && (plateNumberItem as TPlateNumber)?.status == 'active'"
|
|
97
|
+
title="Delete" prepend-icon="mdi-delete" base-color="error"
|
|
98
|
+
@click="handleDeleteVehicleAction(plateNumberItem as TPlateNumber)" />
|
|
99
|
+
</v-list>
|
|
100
|
+
</v-menu>
|
|
85
101
|
</template>
|
|
86
102
|
</v-data-table>
|
|
87
103
|
<v-btn text="Add Vehicle Number" class="mt-6 text-capitalize" prepend-icon="mdi-plus"
|
|
@@ -94,7 +110,7 @@
|
|
|
94
110
|
<v-col v-else-if="selectedVehicleObject[key]" cols="12">
|
|
95
111
|
<span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ formatValues(key,
|
|
96
112
|
selectedVehicleObject[key])
|
|
97
|
-
|
|
113
|
+
}}</span>
|
|
98
114
|
</v-col>
|
|
99
115
|
</template>
|
|
100
116
|
</v-row>
|
|
@@ -168,26 +184,30 @@ const plateHeaders = [
|
|
|
168
184
|
|
|
169
185
|
|
|
170
186
|
const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
|
|
171
|
-
const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, updateVehicle } = useVehicle();
|
|
187
|
+
const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, updateVehicle, getVehicleById } = useVehicle();
|
|
172
188
|
|
|
173
189
|
const items = ref<Array<Record<string, any>>>([]);
|
|
174
190
|
const page = ref(1);
|
|
175
191
|
const pages = ref(0);
|
|
176
192
|
const pageRange = ref("-- - -- of --");
|
|
177
193
|
|
|
194
|
+
const mode = ref<"add" | "edit">("add");
|
|
195
|
+
|
|
178
196
|
const searchInput = ref("")
|
|
179
197
|
|
|
180
198
|
const loading = ref(false);
|
|
199
|
+
const loadingPlateNumberDetails = ref(false);
|
|
181
200
|
const deletingVehicle = ref(false);
|
|
182
201
|
const approvingVehicle = ref(false);
|
|
183
202
|
const restoringVehicle = ref(false);
|
|
184
203
|
const updatingType = ref(false);
|
|
185
204
|
|
|
186
205
|
const selectedVehicleId = ref<string | null>(null)
|
|
187
|
-
const vehicleType = ref<TVehicleType | null>(null);
|
|
206
|
+
const vehicleType = ref<TVehicleType | 'seasonpass' | null>(null);
|
|
188
207
|
const vehicleTypeFilter = ref<TVehicleType | null>(null);
|
|
189
208
|
|
|
190
209
|
const selectedPlateNumberObject = ref<TPlateNumber | null>(null);
|
|
210
|
+
const selectedPlateNumberId = ref<string | null>(null); // individual plate number
|
|
191
211
|
|
|
192
212
|
const message = ref("");
|
|
193
213
|
const messageColor = ref("");
|
|
@@ -255,6 +275,8 @@ const handleCloseAll = () => {
|
|
|
255
275
|
dialog.createVehicle = false;
|
|
256
276
|
dialog.updateVehicle = false;
|
|
257
277
|
dialog.showSelection = false;
|
|
278
|
+
dialog.updateVehicleType = false;
|
|
279
|
+
mode.value = "add";
|
|
258
280
|
vehicleType.value = null;
|
|
259
281
|
}
|
|
260
282
|
|
|
@@ -294,12 +316,29 @@ function addNewPlateNumbers() {
|
|
|
294
316
|
dialog.showSelection = true;
|
|
295
317
|
}
|
|
296
318
|
|
|
297
|
-
function handleEditVehicleAction() {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
319
|
+
async function handleEditVehicleAction(item: TPlateNumber) {
|
|
320
|
+
|
|
321
|
+
selectedPlateNumberId.value = item?._id as string;
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
loadingPlateNumberDetails.value = true;
|
|
325
|
+
const res = await getVehicleById(selectedPlateNumberId.value as string)
|
|
326
|
+
if(res?.seasonPassType){
|
|
327
|
+
vehicleType.value = "seasonpass";
|
|
328
|
+
} else {
|
|
329
|
+
vehicleType.value = item?.type || null;
|
|
330
|
+
}
|
|
331
|
+
selectedPlateNumberObject.value = res || null;
|
|
332
|
+
dialog.showMoreActions = false;
|
|
333
|
+
mode.value = "edit";
|
|
334
|
+
dialog.updateVehicle = true;
|
|
335
|
+
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error("Error fetching vehicle details:", error);
|
|
338
|
+
} finally {
|
|
339
|
+
loadingPlateNumberDetails.value = false;
|
|
340
|
+
}
|
|
341
|
+
|
|
303
342
|
}
|
|
304
343
|
|
|
305
344
|
function handleDeleteVehicleAction(item: TPlateNumber) {
|
|
@@ -354,6 +393,7 @@ function handleUpdatePage(newPageNum: number) {
|
|
|
354
393
|
}
|
|
355
394
|
|
|
356
395
|
function handleAddVehicleClick() {
|
|
396
|
+
mode.value = "add";
|
|
357
397
|
dialog.showSelection = true;
|
|
358
398
|
selectedVehicleId.value = null;
|
|
359
399
|
}
|
|
@@ -15,6 +15,7 @@ export default function useCustomerSite() {
|
|
|
15
15
|
);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
async function getAll({
|
|
19
20
|
page = 1,
|
|
20
21
|
search = "",
|
|
@@ -47,10 +48,20 @@ export default function useCustomerSite() {
|
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function getCustomerSites (org: string) {
|
|
52
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
53
|
+
`/api/customer-sites?page=1&search=&limit=10&sort=&order=asc&status=active&org=${org}`,
|
|
54
|
+
{
|
|
55
|
+
method: 'GET'
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
return {
|
|
51
61
|
add,
|
|
52
62
|
getAll,
|
|
53
63
|
addViaInvite,
|
|
54
64
|
getBySiteAsServiceProvider,
|
|
65
|
+
getCustomerSites
|
|
55
66
|
};
|
|
56
67
|
}
|
package/composables/useUser.ts
CHANGED
|
@@ -14,6 +14,30 @@ export default function useUser() {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function inviteUserClient({
|
|
18
|
+
email = '',
|
|
19
|
+
app = '',
|
|
20
|
+
role = '',
|
|
21
|
+
name = '',
|
|
22
|
+
org = '',
|
|
23
|
+
site = '',
|
|
24
|
+
siteName = ''
|
|
25
|
+
} = {}) {
|
|
26
|
+
return useNuxtApp().$api<Record<string, any>>('/api/auth/invite/user', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: {
|
|
29
|
+
email,
|
|
30
|
+
app,
|
|
31
|
+
role,
|
|
32
|
+
name,
|
|
33
|
+
org,
|
|
34
|
+
...(site && { siteId: site }),
|
|
35
|
+
...(siteName && { siteName })
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
17
41
|
function updateName(name = "") {
|
|
18
42
|
return useNuxtApp().$api<Record<string, any>>("/api/users/name", {
|
|
19
43
|
method: "PUT",
|
|
@@ -127,6 +151,7 @@ export default function useUser() {
|
|
|
127
151
|
createUserByInvite,
|
|
128
152
|
getById,
|
|
129
153
|
createUserByVerification,
|
|
130
|
-
getUserByEmail
|
|
154
|
+
getUserByEmail,
|
|
155
|
+
inviteUserClient
|
|
131
156
|
};
|
|
132
157
|
}
|