@7365admin1/layer-common 1.11.40 → 1.11.42
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 +13 -0
- package/components/AccessCardPreviewDialog.vue +164 -0
- package/components/AttachmentsViewer.vue +44 -15
- package/components/BulletinBoardForm.vue +11 -2
- package/components/BulletinBoardManagement.vue +2 -1
- package/components/ClientMain.vue +36 -22
- package/components/Input/DateTimePicker.vue +14 -1
- package/components/InvitationClientForm.vue +114 -60
- package/components/InvitationForm.vue +15 -4
- package/components/Layout/NavigationDrawer.vue +11 -15
- package/components/MemberMain.vue +9 -12
- package/components/TableMain.vue +1 -1
- package/components/VehicleForm.vue +4 -2
- package/components/VisitorManagement.vue +215 -628
- package/composables/useFile.ts +1 -1
- package/composables/useLocalAuth.ts +12 -0
- package/composables/useMember.ts +41 -14
- package/composables/useOrg.ts +49 -1
- package/composables/useUser.ts +46 -14
- package/package.json +1 -1
- package/types/nuxt-app.d.ts +7 -0
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
:rules="[requiredRule, emailRule]"
|
|
22
22
|
:loading="loading.verifyingEmail"
|
|
23
23
|
/>
|
|
24
|
+
<div v-if="existingUserMessage" class="text-subtitle-2 text-error mt-n3 mb-2">
|
|
25
|
+
{{ existingUserMessage }}
|
|
26
|
+
</div>
|
|
24
27
|
</v-col>
|
|
25
28
|
|
|
26
29
|
<!-- APP MULTI SELECT -->
|
|
@@ -37,7 +40,35 @@
|
|
|
37
40
|
density="comfortable"
|
|
38
41
|
:rules="[requiredRule]"
|
|
39
42
|
@update:model-value="handleUpdateApp"
|
|
40
|
-
|
|
43
|
+
>
|
|
44
|
+
<template #item="{ props: itemProps, item }">
|
|
45
|
+
<v-list-item
|
|
46
|
+
v-bind="itemProps"
|
|
47
|
+
:disabled="item.raw.disabled"
|
|
48
|
+
>
|
|
49
|
+
<template #prepend>
|
|
50
|
+
<v-checkbox-btn
|
|
51
|
+
:model-value="invite.app.includes(item.raw.value)"
|
|
52
|
+
:disabled="item.raw.disabled"
|
|
53
|
+
color="black"
|
|
54
|
+
density="comfortable"
|
|
55
|
+
/>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<template #title>
|
|
59
|
+
<span :class="item.raw.disabled ? 'text-disabled' : ''">
|
|
60
|
+
{{ item.raw.title }}
|
|
61
|
+
</span>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<template #subtitle>
|
|
65
|
+
<span v-if="item.raw.disabled" class="text-error text-caption">
|
|
66
|
+
Already a member
|
|
67
|
+
</span>
|
|
68
|
+
</template>
|
|
69
|
+
</v-list-item>
|
|
70
|
+
</template>
|
|
71
|
+
</v-autocomplete>
|
|
41
72
|
</v-col>
|
|
42
73
|
|
|
43
74
|
<!-- ROLE -->
|
|
@@ -65,21 +96,11 @@
|
|
|
65
96
|
/>
|
|
66
97
|
</v-col>
|
|
67
98
|
|
|
68
|
-
<!-- CREATE MORE -->
|
|
69
|
-
<v-col cols="12" class="mt-2">
|
|
70
|
-
<v-checkbox v-model="createMore" density="comfortable" hide-details>
|
|
71
|
-
<template #label>
|
|
72
|
-
<span class="text-subtitle-2 font-weight-bold">
|
|
73
|
-
Create more
|
|
74
|
-
</span>
|
|
75
|
-
</template>
|
|
76
|
-
</v-checkbox>
|
|
77
|
-
</v-col>
|
|
78
99
|
|
|
79
100
|
<!-- ERROR -->
|
|
80
101
|
<v-col cols="12" class="my-2 text-center">
|
|
81
102
|
<span class="text-subtitle-2 text-error">
|
|
82
|
-
{{
|
|
103
|
+
{{ submitError }}
|
|
83
104
|
</span>
|
|
84
105
|
</v-col>
|
|
85
106
|
|
|
@@ -116,14 +137,14 @@
|
|
|
116
137
|
</template>
|
|
117
138
|
|
|
118
139
|
<script setup lang="ts">
|
|
119
|
-
import { computed, ref, reactive, watchEffect } from 'vue'
|
|
140
|
+
import { computed, ref, reactive, watchEffect, watch } from 'vue'
|
|
120
141
|
import useCustomerSite from '../composables/useCustomerSite'
|
|
121
142
|
import useLocal from '../composables/useLocal'
|
|
122
143
|
import useRole from '../composables/useRole'
|
|
123
144
|
import useUser from '../composables/useUser'
|
|
124
145
|
import useUtils from '../composables/useUtils'
|
|
125
146
|
import useOrg from '../composables/useOrg'
|
|
126
|
-
import
|
|
147
|
+
import useMember from '../composables/useMember'
|
|
127
148
|
|
|
128
149
|
const APP = useRuntimeConfig().public.APP
|
|
129
150
|
|
|
@@ -156,7 +177,6 @@ const invite = ref({
|
|
|
156
177
|
isMemberInvite: false,
|
|
157
178
|
})
|
|
158
179
|
|
|
159
|
-
/* default app */
|
|
160
180
|
if (props.mode === "create") {
|
|
161
181
|
invite.value.app = props.app ? [props.app] : []
|
|
162
182
|
}
|
|
@@ -168,11 +188,14 @@ const APP_LIST = [
|
|
|
168
188
|
{ title: "Cleaning Services", value: "cleaning_services" },
|
|
169
189
|
{ title: "Mechanical & Electrical Services", value: "mechanical_electrical_services" },
|
|
170
190
|
{ title: "Pest Control Services", value: "pest_control_services" },
|
|
191
|
+
{ title: "Landscaping Services", value: "landscaping_services" },
|
|
171
192
|
{ title: "Pool Maintenance Services", value: "pool_maintenance_services" },
|
|
172
193
|
]
|
|
194
|
+
|
|
173
195
|
const { getByName } = useOrg()
|
|
174
196
|
const org = ref<any>(null)
|
|
175
|
-
|
|
197
|
+
|
|
198
|
+
watch(
|
|
176
199
|
() => props.org,
|
|
177
200
|
async (name) => {
|
|
178
201
|
if (!name) return
|
|
@@ -181,7 +204,14 @@ const org = ref<any>(null)
|
|
|
181
204
|
{ immediate: true }
|
|
182
205
|
)
|
|
183
206
|
|
|
184
|
-
const apps = computed(() =>
|
|
207
|
+
const apps = computed(() => {
|
|
208
|
+
return APP_LIST.map(app => {
|
|
209
|
+
const disabled = userAccess.value.some(
|
|
210
|
+
(a: any) => a.type === app.value
|
|
211
|
+
)
|
|
212
|
+
return { ...app, disabled }
|
|
213
|
+
})
|
|
214
|
+
})
|
|
185
215
|
|
|
186
216
|
/* ================= SITE ================= */
|
|
187
217
|
const { natureOfBusiness } = useLocal()
|
|
@@ -221,18 +251,14 @@ const { getRoles } = useRole()
|
|
|
221
251
|
|
|
222
252
|
const { data: roleData, refresh: refreshRoles } = await useLazyAsyncData(
|
|
223
253
|
"roles",
|
|
224
|
-
() =>
|
|
225
|
-
getRoles({
|
|
226
|
-
org: org.value?._id,
|
|
227
|
-
type: props.app,
|
|
228
|
-
limit: 50,
|
|
229
|
-
}),
|
|
254
|
+
() => getRoles({ org: org.value?._id, type: props.app, limit: 50 }),
|
|
230
255
|
{ watch: [() => props.app] }
|
|
231
256
|
)
|
|
232
257
|
|
|
233
258
|
watchEffect(() => {
|
|
234
259
|
if (roleData.value) roles.value = roleData.value.items
|
|
235
260
|
})
|
|
261
|
+
|
|
236
262
|
watch(
|
|
237
263
|
() => org.value?._id,
|
|
238
264
|
async (id) => {
|
|
@@ -240,6 +266,7 @@ watch(
|
|
|
240
266
|
await refreshRoles()
|
|
241
267
|
}
|
|
242
268
|
)
|
|
269
|
+
|
|
243
270
|
/* ================= EVENTS ================= */
|
|
244
271
|
async function handleUpdateApp(value: string[]) {
|
|
245
272
|
invite.value.role = ""
|
|
@@ -252,59 +279,86 @@ function handleUpdateSite(id: string) {
|
|
|
252
279
|
invite.value.siteName = obj?.title || ""
|
|
253
280
|
}
|
|
254
281
|
|
|
255
|
-
/* =================
|
|
256
|
-
const { inviteUserClient } = useUser()
|
|
282
|
+
/* ================= EMAIL CHECK ================= */
|
|
283
|
+
const { inviteUserClient, getUserByEmail } = useUser()
|
|
284
|
+
const { getAllByUserId } = useMember()
|
|
257
285
|
const { requiredRule, emailRule } = useUtils()
|
|
258
286
|
|
|
259
|
-
const
|
|
260
|
-
const
|
|
287
|
+
const existingUserMessage = ref("")
|
|
288
|
+
const submitError = ref("")
|
|
261
289
|
const disable = ref(false)
|
|
290
|
+
const existingUser = ref<any>(null)
|
|
291
|
+
const userAccess = ref<any[]>([])
|
|
262
292
|
|
|
263
|
-
|
|
264
|
-
|
|
293
|
+
let timeout: any = null
|
|
294
|
+
|
|
295
|
+
watch(
|
|
296
|
+
() => invite.value.email,
|
|
297
|
+
(email) => {
|
|
298
|
+
clearTimeout(timeout)
|
|
299
|
+
existingUserMessage.value = ""
|
|
300
|
+
existingUser.value = null
|
|
301
|
+
userAccess.value = []
|
|
302
|
+
|
|
303
|
+
if (!email || email.length < 3) return
|
|
304
|
+
|
|
305
|
+
timeout = setTimeout(async () => {
|
|
306
|
+
await checkEmail(email)
|
|
307
|
+
}, 400)
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async function checkEmail(email: string) {
|
|
312
|
+
loading.verifyingEmail = true
|
|
313
|
+
existingUserMessage.value = ""
|
|
265
314
|
|
|
266
315
|
try {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
role: invite.value.role,
|
|
275
|
-
// org: invite.value.org,
|
|
276
|
-
org: org.value?._id,
|
|
277
|
-
site: invite.value.site,
|
|
278
|
-
siteName: invite.value.siteName,
|
|
279
|
-
// isMemberInvite: invite.value.isMemberInvite,
|
|
280
|
-
})
|
|
281
|
-
)
|
|
282
|
-
)
|
|
316
|
+
const user = await getUserByEmail(email)
|
|
317
|
+
|
|
318
|
+
if (user?._id) {
|
|
319
|
+
existingUser.value = user
|
|
320
|
+
existingUserMessage.value = `User already exists: ${email}`
|
|
321
|
+
|
|
322
|
+
const memberRes = await getAllByUserId(user._id)
|
|
283
323
|
|
|
284
|
-
|
|
285
|
-
form.value?.reset()
|
|
286
|
-
invite.value = {
|
|
287
|
-
email: "",
|
|
288
|
-
app: [],
|
|
289
|
-
role: "",
|
|
290
|
-
org: org.value?._id || "",
|
|
291
|
-
site: "",
|
|
292
|
-
siteName: "",
|
|
293
|
-
isMemberInvite: false,
|
|
294
|
-
}
|
|
295
|
-
emit("success", false)
|
|
324
|
+
userAccess.value = memberRes?.items || []
|
|
296
325
|
} else {
|
|
297
|
-
|
|
326
|
+
existingUser.value = null
|
|
327
|
+
userAccess.value = []
|
|
298
328
|
}
|
|
329
|
+
} catch (e) {
|
|
330
|
+
existingUser.value = null
|
|
331
|
+
userAccess.value = []
|
|
332
|
+
} finally {
|
|
333
|
+
loading.verifyingEmail = false
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ================= SUBMIT ================= */
|
|
338
|
+
async function submit() {
|
|
339
|
+
loading.submittingForm = true
|
|
340
|
+
submitError.value = ""
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
await inviteUserClient({
|
|
344
|
+
email: invite.value.email,
|
|
345
|
+
app: invite.value.app,
|
|
346
|
+
role: invite.value.role,
|
|
347
|
+
org: org.value?._id,
|
|
348
|
+
site: invite.value.site,
|
|
349
|
+
siteName: invite.value.siteName,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
emit("success", true)
|
|
299
353
|
|
|
300
354
|
} catch (e: any) {
|
|
301
|
-
|
|
355
|
+
submitError.value = e?.response?._data?.message || "Error"
|
|
302
356
|
} finally {
|
|
303
357
|
loading.submittingForm = false
|
|
304
358
|
}
|
|
305
359
|
}
|
|
306
|
-
|
|
307
360
|
function cancel() {
|
|
308
361
|
emit("cancel")
|
|
309
362
|
}
|
|
310
363
|
</script>
|
|
364
|
+
|
|
@@ -190,6 +190,10 @@ const props = defineProps({
|
|
|
190
190
|
hideApp: {
|
|
191
191
|
type: Boolean,
|
|
192
192
|
default: false
|
|
193
|
+
},
|
|
194
|
+
inviteMember: {
|
|
195
|
+
type: Boolean,
|
|
196
|
+
default: false
|
|
193
197
|
}
|
|
194
198
|
});
|
|
195
199
|
|
|
@@ -377,20 +381,27 @@ function handleUpdateSite(siteId: string) {
|
|
|
377
381
|
invite.value.siteName = obj?.title || "";
|
|
378
382
|
}
|
|
379
383
|
|
|
380
|
-
const { inviteUser } = useUser();
|
|
384
|
+
const { inviteUser, inviteMemberClient } = useUser();
|
|
381
385
|
|
|
382
386
|
async function submit() {
|
|
383
387
|
loading.submittingForm = true;
|
|
388
|
+
|
|
384
389
|
try {
|
|
385
|
-
|
|
390
|
+
if (props.inviteMember) {
|
|
391
|
+
await inviteMemberClient(invite.value);
|
|
392
|
+
} else {
|
|
393
|
+
await inviteUser(invite.value);
|
|
394
|
+
}
|
|
386
395
|
|
|
387
396
|
if (createMore.value) {
|
|
388
397
|
form.value?.reset();
|
|
389
398
|
resetInvite();
|
|
390
399
|
emit("success", false);
|
|
391
|
-
} else
|
|
400
|
+
} else {
|
|
401
|
+
emit("success", true);
|
|
402
|
+
}
|
|
392
403
|
} catch (error: any) {
|
|
393
|
-
message.value = error.response
|
|
404
|
+
message.value = error.response?._data?.message || error.message;
|
|
394
405
|
} finally {
|
|
395
406
|
loading.submittingForm = false;
|
|
396
407
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
<!-- LayoutNavigationDrawer.vue -->
|
|
2
2
|
<template>
|
|
3
|
-
<v-navigation-drawer
|
|
4
|
-
v-model="drawer"
|
|
5
|
-
permanent
|
|
6
|
-
floating
|
|
7
|
-
class="bg-primary"
|
|
8
|
-
>
|
|
3
|
+
<v-navigation-drawer v-model="drawer" permanent floating class="bg-primary">
|
|
9
4
|
<v-list>
|
|
10
5
|
<v-list-item>
|
|
11
6
|
<v-list-item-title class="text-h6 text-white">
|
|
@@ -28,9 +23,10 @@
|
|
|
28
23
|
:title="item.title"
|
|
29
24
|
:icon="item.icon"
|
|
30
25
|
:route="item.route"
|
|
26
|
+
:children="item.children"
|
|
31
27
|
:link="item.link"
|
|
32
28
|
:class="{
|
|
33
|
-
'opacity-50 pointer-events-none': item.disabled
|
|
29
|
+
'opacity-50 pointer-events-none': item.disabled,
|
|
34
30
|
}"
|
|
35
31
|
@click="() => handleClick(item)"
|
|
36
32
|
/>
|
|
@@ -49,16 +45,16 @@
|
|
|
49
45
|
<script setup lang="ts">
|
|
50
46
|
const props = defineProps({
|
|
51
47
|
navigationItems: { type: Array, required: true },
|
|
52
|
-
title: { type: String, default: "" }
|
|
53
|
-
})
|
|
48
|
+
title: { type: String, default: "" },
|
|
49
|
+
});
|
|
54
50
|
|
|
55
|
-
const emit = defineEmits([
|
|
51
|
+
const emit = defineEmits(["navigate"]);
|
|
56
52
|
|
|
57
|
-
const { drawer } = useLocal()
|
|
58
|
-
const { APP_NAME } = useRuntimeConfig().public
|
|
53
|
+
const { drawer } = useLocal();
|
|
54
|
+
const { APP_NAME } = useRuntimeConfig().public;
|
|
59
55
|
|
|
60
56
|
const handleClick = (item: any) => {
|
|
61
|
-
if (item.disabled) return
|
|
62
|
-
emit(
|
|
63
|
-
}
|
|
57
|
+
if (item.disabled) return;
|
|
58
|
+
emit("navigate", item);
|
|
59
|
+
};
|
|
64
60
|
</script>
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
class="w-100"
|
|
50
50
|
>
|
|
51
51
|
<v-tab
|
|
52
|
-
v-for="tab in tabOptions"
|
|
52
|
+
v-for="(tab, i) in tabOptions"
|
|
53
53
|
:value="tab.status"
|
|
54
|
-
:key="
|
|
54
|
+
:key="i"
|
|
55
55
|
class="text-capitalize"
|
|
56
56
|
>
|
|
57
57
|
{{ tab.name }}
|
|
@@ -356,7 +356,7 @@ function toRoute(status: any) {
|
|
|
356
356
|
page.value = 1;
|
|
357
357
|
navigateTo({
|
|
358
358
|
name: routeName,
|
|
359
|
-
params: setRouteParams(normalizedRouteParams.value, { status
|
|
359
|
+
params: setRouteParams(normalizedRouteParams.value, { status }),
|
|
360
360
|
});
|
|
361
361
|
}
|
|
362
362
|
|
|
@@ -366,25 +366,26 @@ watch(
|
|
|
366
366
|
if (status && status !== selectedStatus.value) {
|
|
367
367
|
selectedStatus.value = status;
|
|
368
368
|
}
|
|
369
|
-
}
|
|
369
|
+
},
|
|
370
|
+
{ immediate: true }
|
|
370
371
|
);
|
|
371
372
|
|
|
372
373
|
const {
|
|
373
374
|
data: getAllReq,
|
|
374
375
|
refresh: getAll,
|
|
375
376
|
status: getAllReqStatus,
|
|
376
|
-
} =
|
|
377
|
-
|
|
377
|
+
} = useAsyncData(
|
|
378
|
+
() => `get-all-members-${props.status}-${props.orgId}`,
|
|
378
379
|
() =>
|
|
379
380
|
_getAll({
|
|
380
|
-
status:
|
|
381
|
+
status: props.status,
|
|
381
382
|
org: props.orgId,
|
|
382
383
|
search: headerSearch.value,
|
|
383
384
|
page: page.value,
|
|
384
385
|
type: props.type,
|
|
385
386
|
}),
|
|
386
387
|
{
|
|
387
|
-
watch: [
|
|
388
|
+
watch: [() => props.status, headerSearch],
|
|
388
389
|
}
|
|
389
390
|
);
|
|
390
391
|
|
|
@@ -398,10 +399,6 @@ watchEffect(() => {
|
|
|
398
399
|
}
|
|
399
400
|
});
|
|
400
401
|
|
|
401
|
-
watch(headerSearch, () => {
|
|
402
|
-
getAll();
|
|
403
|
-
});
|
|
404
|
-
|
|
405
402
|
const confirmDialog = ref(false);
|
|
406
403
|
const selectedMemberId = ref<string | null>(null);
|
|
407
404
|
const updateLoading = ref(false);
|
package/components/TableMain.vue
CHANGED
|
@@ -53,14 +53,15 @@
|
|
|
53
53
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
54
54
|
<InputLabel class="text-capitalize" title="NRIC" required />
|
|
55
55
|
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
56
|
-
:disabled="disablePrefilledInputs && type !== 'blocklist'" />
|
|
56
|
+
:disabled="disablePrefilledInputs && type !== 'blocklist' && prop.mode !== 'edit'" />
|
|
57
57
|
</v-col>
|
|
58
58
|
|
|
59
59
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
60
60
|
<v-row>
|
|
61
61
|
<v-col cols="12">
|
|
62
62
|
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
63
|
-
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]"
|
|
63
|
+
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]"
|
|
64
|
+
:disabled="disablePrefilledInputs && type !== 'blocklist' && prop.mode !== 'edit'" />
|
|
64
65
|
</v-col>
|
|
65
66
|
</v-row>
|
|
66
67
|
</v-col>
|
|
@@ -777,6 +778,7 @@ onMounted(() => {
|
|
|
777
778
|
vehicle.start = prop.vehicleData.start || '';
|
|
778
779
|
vehicle.plateNumber = prop.vehicleData.plateNumber
|
|
779
780
|
vehicle.peopleId = prop.vehicleData.peopleId || '';
|
|
781
|
+
vehicle.block = prop.vehicleData.block === 0 ? null : prop.vehicleData.block || null;
|
|
780
782
|
|
|
781
783
|
if(prop.type === "seasonpass"){
|
|
782
784
|
showSubscriptionDateOptions.value = true;
|