@iservice365/layer-common 1.0.7 → 1.0.9
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/Chat/Information.vue +31 -3
- package/components/FeedbackDetail.vue +150 -24
- package/components/FeedbackMain.vue +1 -1
- package/components/Input/File.vue +15 -7
- package/components/InvitationForm.vue +104 -17
- package/components/InvitationMain.vue +17 -3
- package/components/TableMain.vue +12 -31
- package/composables/useLocalSetup.ts +1 -1
- package/composables/useUser.ts +10 -1
- package/composables/useVerification.ts +2 -1
- package/middleware/02.org.ts +2 -2
- package/package.json +2 -2
- package/plugins/secure-member.client.ts +9 -8
- package/types/local.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
</v-col>
|
|
20
20
|
|
|
21
21
|
<v-col cols="12" class="text-center mb-3" v-if="item">
|
|
22
|
-
<v-avatar
|
|
22
|
+
<!-- <v-avatar
|
|
23
23
|
size="48"
|
|
24
24
|
class="mb-1"
|
|
25
25
|
v-if="!item.attachments || !item.attachments.length"
|
|
@@ -50,7 +50,33 @@
|
|
|
50
50
|
v-for="(attachment, index) in item.attachments"
|
|
51
51
|
:key="index"
|
|
52
52
|
>
|
|
53
|
-
<v-img :src="attachment" cover class="w-100 h-100" />
|
|
53
|
+
<v-img :src="`/api/public/${attachment}`" cover class="w-100 h-100" />
|
|
54
|
+
</v-carousel-item>
|
|
55
|
+
</v-carousel> -->
|
|
56
|
+
<v-img
|
|
57
|
+
v-if="item.attachments.length === 1"
|
|
58
|
+
:src="`/api/public/${item.attachments[0]}`"
|
|
59
|
+
height="200"
|
|
60
|
+
cover
|
|
61
|
+
class="rounded mb-2"
|
|
62
|
+
/>
|
|
63
|
+
<v-carousel
|
|
64
|
+
v-else
|
|
65
|
+
hide-delimiter-background
|
|
66
|
+
height="200"
|
|
67
|
+
class="rounded mb-2"
|
|
68
|
+
show-arrows
|
|
69
|
+
cycle
|
|
70
|
+
>
|
|
71
|
+
<v-carousel-item
|
|
72
|
+
v-for="(attachment, index) in item.attachments"
|
|
73
|
+
:key="index"
|
|
74
|
+
>
|
|
75
|
+
<v-img
|
|
76
|
+
:src="`/api/public/${attachment}`"
|
|
77
|
+
cover
|
|
78
|
+
class="w-100 h-100"
|
|
79
|
+
/>
|
|
54
80
|
</v-carousel-item>
|
|
55
81
|
</v-carousel>
|
|
56
82
|
</v-col>
|
|
@@ -58,7 +84,9 @@
|
|
|
58
84
|
<v-col cols="12" v-if="item">
|
|
59
85
|
<v-row dense class="my-1">
|
|
60
86
|
<v-col cols="6" class="py-1"><strong>Category:</strong></v-col>
|
|
61
|
-
<v-col cols="6" class="py-1 text-right text-capitalize">{{
|
|
87
|
+
<v-col cols="6" class="py-1 text-right text-capitalize">{{
|
|
88
|
+
item.category
|
|
89
|
+
}}</v-col>
|
|
62
90
|
</v-row>
|
|
63
91
|
|
|
64
92
|
<v-divider />
|
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<v-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
2
|
+
<div class="feedback-detail-wrapper">
|
|
3
|
+
<v-row no-gutters class="fill-height">
|
|
4
|
+
|
|
5
|
+
<v-col cols="12" xl="3" lg="4" md="4" class="fill-height">
|
|
6
|
+
<div class="panel-container border-e">
|
|
7
|
+
<ChatNavigation
|
|
8
|
+
:title="'Feedbacks'"
|
|
9
|
+
:items="items"
|
|
10
|
+
@select="handleSelectFeedback"
|
|
11
|
+
@search="_getFeedbacks"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
</v-col>
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
<v-col cols="12" xl="6" lg="5" md="5" class="fill-height">
|
|
18
|
+
<div class="panel-container border-e">
|
|
19
|
+
<ChatMessage />
|
|
20
|
+
</div>
|
|
21
|
+
</v-col>
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
<v-col cols="12" xl="3" lg="3" md="3" class="fill-height">
|
|
25
|
+
<div class="panel-container">
|
|
26
|
+
<ChatInformation
|
|
27
|
+
:item="feedback"
|
|
28
|
+
:service-providers="_serviceProviders"
|
|
29
|
+
@edit="openEditDialog"
|
|
30
|
+
@mark-complete-request="showCompleteDialog = true"
|
|
31
|
+
@delete="showDeleteDialog = true"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</v-col>
|
|
35
|
+
</v-row>
|
|
36
|
+
</div>
|
|
26
37
|
|
|
27
38
|
<FeedbackForm
|
|
28
39
|
v-model="showCreateDialog"
|
|
@@ -463,3 +474,118 @@ async function submitFeedback(payload: TFeedbackUpdate) {
|
|
|
463
474
|
}
|
|
464
475
|
}
|
|
465
476
|
</script>
|
|
477
|
+
|
|
478
|
+
<style scoped>
|
|
479
|
+
.feedback-detail-wrapper {
|
|
480
|
+
height: calc(100vh - 80px); /* Account for header and padding */
|
|
481
|
+
max-height: calc(100vh - 80px);
|
|
482
|
+
overflow: hidden;
|
|
483
|
+
position: relative;
|
|
484
|
+
background-color: rgb(var(--v-theme-surface));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.panel-container {
|
|
488
|
+
height: 100%;
|
|
489
|
+
max-height: 100%;
|
|
490
|
+
overflow: hidden;
|
|
491
|
+
display: flex;
|
|
492
|
+
flex-direction: column;
|
|
493
|
+
background-color: rgb(var(--v-theme-surface));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.border-e {
|
|
497
|
+
border-right: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.fill-height {
|
|
501
|
+
height: 100%;
|
|
502
|
+
max-height: 100%;
|
|
503
|
+
overflow: hidden;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Ensure the root row doesn't create scrolling */
|
|
507
|
+
.v-row.fill-height {
|
|
508
|
+
margin: 0;
|
|
509
|
+
height: 100%;
|
|
510
|
+
max-height: 100%;
|
|
511
|
+
overflow: hidden;
|
|
512
|
+
align-items: stretch;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Better column spacing */
|
|
516
|
+
.v-col {
|
|
517
|
+
padding: 0;
|
|
518
|
+
display: flex;
|
|
519
|
+
flex-direction: column;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* Ensure responsive behavior */
|
|
523
|
+
@media (max-width: 1280px) {
|
|
524
|
+
.feedback-detail-wrapper {
|
|
525
|
+
height: calc(100vh - 72px);
|
|
526
|
+
max-height: calc(100vh - 72px);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
@media (max-width: 960px) {
|
|
531
|
+
.feedback-detail-wrapper {
|
|
532
|
+
height: calc(100vh - 64px); /* Mobile header height */
|
|
533
|
+
max-height: calc(100vh - 64px);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.border-e {
|
|
537
|
+
border-right: none;
|
|
538
|
+
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.v-col {
|
|
542
|
+
height: auto;
|
|
543
|
+
min-height: 300px;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* Individual component styling */
|
|
548
|
+
:deep(.chat-navigation) {
|
|
549
|
+
height: 100%;
|
|
550
|
+
overflow-y: auto;
|
|
551
|
+
background-color: rgb(var(--v-theme-surface));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
:deep(.chat-message) {
|
|
555
|
+
height: 100%;
|
|
556
|
+
overflow-y: auto;
|
|
557
|
+
background-color: rgb(var(--v-theme-surface));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
:deep(.chat-information) {
|
|
561
|
+
height: 100%;
|
|
562
|
+
overflow-y: auto;
|
|
563
|
+
background-color: rgb(var(--v-theme-surface));
|
|
564
|
+
padding: 16px;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/* Smooth scrollbars */
|
|
568
|
+
:deep(*::-webkit-scrollbar) {
|
|
569
|
+
width: 6px;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
:deep(*::-webkit-scrollbar-track) {
|
|
573
|
+
background: transparent;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
:deep(*::-webkit-scrollbar-thumb) {
|
|
577
|
+
background: rgba(var(--v-border-color), 0.3);
|
|
578
|
+
border-radius: 3px;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
:deep(*::-webkit-scrollbar-thumb:hover) {
|
|
582
|
+
background: rgba(var(--v-border-color), 0.5);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/* Remove default margins/padding that might cause issues */
|
|
586
|
+
:deep(.v-container) {
|
|
587
|
+
padding: 0;
|
|
588
|
+
margin: 0;
|
|
589
|
+
max-width: none;
|
|
590
|
+
}
|
|
591
|
+
</style>
|
|
@@ -424,7 +424,7 @@ async function handleFileAdded(file: File) {
|
|
|
424
424
|
|
|
425
425
|
const uploadedId = res?.id;
|
|
426
426
|
if (uploadedId) {
|
|
427
|
-
const url = `${
|
|
427
|
+
const url = `${uploadedId}`;
|
|
428
428
|
_feedback.value.attachments = _feedback.value.attachments ?? [];
|
|
429
429
|
_feedback.value.attachments.push(url);
|
|
430
430
|
}
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
class="d-flex align-center pa-2 mr-2 mb-2 rounded bg-white border-sm"
|
|
61
61
|
>
|
|
62
62
|
<div class="mr-3">
|
|
63
|
-
<v-img
|
|
63
|
+
<!-- <v-img
|
|
64
64
|
v-if="!localErroredImages.includes(file)"
|
|
65
65
|
:src="file"
|
|
66
66
|
width="40"
|
|
@@ -76,6 +76,13 @@
|
|
|
76
76
|
height="40"
|
|
77
77
|
class="rounded"
|
|
78
78
|
cover
|
|
79
|
+
/> -->
|
|
80
|
+
<v-img
|
|
81
|
+
:src="getThumbnail(file)"
|
|
82
|
+
width="40"
|
|
83
|
+
height="40"
|
|
84
|
+
class="rounded"
|
|
85
|
+
cover
|
|
79
86
|
/>
|
|
80
87
|
</div>
|
|
81
88
|
|
|
@@ -166,12 +173,13 @@ function onImageError(file: string) {
|
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
function getThumbnail(fileUrl: string): string {
|
|
169
|
-
if (fileUrl.endsWith(".pdf")) return "mdi-file-pdf-outline";
|
|
170
|
-
if (fileUrl.match(/\.(doc|docx)$/i))
|
|
171
|
-
|
|
172
|
-
if (fileUrl.match(/\.(xls|xlsx)$/i))
|
|
173
|
-
|
|
174
|
-
return "/images/file-thumbnails/file.png";
|
|
176
|
+
// if (fileUrl.endsWith(".pdf")) return "mdi-file-pdf-outline";
|
|
177
|
+
// if (fileUrl.match(/\.(doc|docx)$/i))
|
|
178
|
+
// return "/images/file-thumbnails/word.png";
|
|
179
|
+
// if (fileUrl.match(/\.(xls|xlsx)$/i))
|
|
180
|
+
// return "/images/file-thumbnails/excel.png";
|
|
181
|
+
// return "/images/file-thumbnails/file.png";
|
|
182
|
+
return `/api/public/${fileUrl}`
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
// Modified to try to display the friendly name
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</v-row>
|
|
9
9
|
</v-toolbar>
|
|
10
10
|
<v-card-text style="max-height: 100vh; overflow-y: auto">
|
|
11
|
-
<v-form v-model="validForm" :disabled="disable">
|
|
11
|
+
<v-form v-model="validForm" ref="form" :disabled="disable">
|
|
12
12
|
<v-row no-gutters>
|
|
13
13
|
<v-col cols="12" class="mt-2">
|
|
14
14
|
<v-row no-gutters>
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
<v-text-field
|
|
18
18
|
v-model="invite.email"
|
|
19
19
|
density="comfortable"
|
|
20
|
-
:rules="[requiredRule]"
|
|
20
|
+
:rules="[requiredRule, emailRule]"
|
|
21
|
+
:loading="loading.verifyingEmail"
|
|
21
22
|
></v-text-field>
|
|
22
23
|
</v-col>
|
|
23
24
|
</v-row>
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
density="comfortable"
|
|
33
34
|
:rules="[requiredRule]"
|
|
34
35
|
:items="apps"
|
|
36
|
+
@update:model-value="handleUpdateApp"
|
|
35
37
|
></v-autocomplete>
|
|
36
38
|
</v-col>
|
|
37
39
|
</v-row>
|
|
@@ -46,7 +48,24 @@
|
|
|
46
48
|
:items="roles"
|
|
47
49
|
item-title="name"
|
|
48
50
|
item-value="_id"
|
|
51
|
+
:rules="[requiredRule]"
|
|
52
|
+
density="comfortable"
|
|
53
|
+
></v-autocomplete>
|
|
54
|
+
</v-col>
|
|
55
|
+
</v-row>
|
|
56
|
+
</v-col>
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
<v-col v-if="hasSite" cols="12">
|
|
60
|
+
<v-row no-gutters>
|
|
61
|
+
<InputLabel class="text-capitalize" title="Site" />
|
|
62
|
+
<v-col cols="12">
|
|
63
|
+
<v-autocomplete
|
|
64
|
+
v-model="invite.site"
|
|
65
|
+
:items="sites"
|
|
49
66
|
density="comfortable"
|
|
67
|
+
:rules="[requiredRule]"
|
|
68
|
+
@update:model-value="handleUpdateSite"
|
|
50
69
|
></v-autocomplete>
|
|
51
70
|
</v-col>
|
|
52
71
|
</v-row>
|
|
@@ -101,6 +120,7 @@
|
|
|
101
120
|
class="text-none"
|
|
102
121
|
size="48"
|
|
103
122
|
:disabled="!validForm"
|
|
123
|
+
:loading="loading.submittingForm"
|
|
104
124
|
@click="submit"
|
|
105
125
|
>
|
|
106
126
|
Submit
|
|
@@ -126,9 +146,9 @@ const props = defineProps({
|
|
|
126
146
|
type: Object,
|
|
127
147
|
default: () => ({}),
|
|
128
148
|
},
|
|
129
|
-
|
|
149
|
+
app: {
|
|
130
150
|
type: String,
|
|
131
|
-
default: "
|
|
151
|
+
default: "organization",
|
|
132
152
|
},
|
|
133
153
|
org: {
|
|
134
154
|
type: String,
|
|
@@ -156,16 +176,24 @@ const props = defineProps({
|
|
|
156
176
|
const emit = defineEmits(["cancel", "success", "success:create-more"]);
|
|
157
177
|
|
|
158
178
|
const validForm = ref(false);
|
|
179
|
+
const form = ref<HTMLFormElement | null>(null)
|
|
159
180
|
const app = computed(() => useRuntimeConfig().public.APP ?? "");
|
|
160
181
|
|
|
182
|
+
const loading = reactive({
|
|
183
|
+
submittingForm: false,
|
|
184
|
+
verifyingEmail: false
|
|
185
|
+
})
|
|
186
|
+
|
|
161
187
|
const invite = ref<Record<string, any>>({
|
|
162
188
|
email: "",
|
|
163
|
-
app: "
|
|
189
|
+
app: "",
|
|
164
190
|
role: "",
|
|
165
191
|
org: "",
|
|
192
|
+
site: "",
|
|
193
|
+
siteName: "",
|
|
166
194
|
});
|
|
167
195
|
|
|
168
|
-
invite.value.app = app
|
|
196
|
+
invite.value.app = props.app;
|
|
169
197
|
invite.value.org = props.org ?? "";
|
|
170
198
|
|
|
171
199
|
if (props.mode === "edit") {
|
|
@@ -177,16 +205,65 @@ if (props.mode === "edit") {
|
|
|
177
205
|
const { natureOfBusiness } = useLocal();
|
|
178
206
|
|
|
179
207
|
const apps = computed(() => {
|
|
180
|
-
const items =
|
|
208
|
+
const items = [];
|
|
181
209
|
items.unshift({ title: "Organization", value: "organization" });
|
|
210
|
+
|
|
211
|
+
if (props.app === "security_agency") {
|
|
212
|
+
items.push({ title: "Security Agency", value: "security_agency" });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (props.app === "cleaning_agency") {
|
|
216
|
+
items.push({ title: "Cleaning Agency", value: "cleaning_agency" });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (props.app === "property_manager") {
|
|
220
|
+
items.push({ title: "Property Manager", value: "property_manager" });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (props.app === "mechanical_electrical_services") {
|
|
224
|
+
items.push({
|
|
225
|
+
title: "Mechanical & Electrical Services",
|
|
226
|
+
value: "mechanical_electrical_services",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
182
230
|
return items;
|
|
183
231
|
});
|
|
184
232
|
|
|
233
|
+
const hasSite = computed(() => {
|
|
234
|
+
return natureOfBusiness.map((i) => i.value).includes(invite.value.app);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const sites = ref<Array<Record<string, any>>>([]);
|
|
238
|
+
|
|
239
|
+
const { getAll: getAllCustomerSite } = useCustomerSite();
|
|
240
|
+
|
|
241
|
+
const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
242
|
+
"get-sites-by-org",
|
|
243
|
+
async () => await getAllCustomerSite({ org: props.org, limit: 50 }),
|
|
244
|
+
{ }
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
watchEffect(() => {
|
|
248
|
+
if (siteData.value) {
|
|
249
|
+
sites.value = siteData.value.items.map((i: any) => ({
|
|
250
|
+
title: i.name,
|
|
251
|
+
value: i.site,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
watchEffect(() => {
|
|
257
|
+
if (hasSite.value) {
|
|
258
|
+
refreshSiteData();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
185
262
|
const roles = ref<Array<Record<string, any>>>([]);
|
|
186
263
|
|
|
187
264
|
const { getRoles } = useRole();
|
|
188
265
|
|
|
189
|
-
const { data: RolesData } = await useLazyAsyncData(
|
|
266
|
+
const { data: RolesData, refresh: refreshRoles } = await useLazyAsyncData(
|
|
190
267
|
"get-roles-by-type",
|
|
191
268
|
() => getRoles({ org: props.org, type: app.value, limit: 50 }),
|
|
192
269
|
{ watch: [app] }
|
|
@@ -198,17 +275,17 @@ watchEffect(() => {
|
|
|
198
275
|
}
|
|
199
276
|
});
|
|
200
277
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
278
|
+
function handleUpdateApp(value: string){
|
|
279
|
+
invite.value.role = "";
|
|
280
|
+
invite.value.site = "";
|
|
281
|
+
refreshRoles();
|
|
282
|
+
}
|
|
283
|
+
|
|
207
284
|
|
|
208
285
|
const createMore = ref(false);
|
|
209
286
|
const disable = ref(false);
|
|
210
287
|
|
|
211
|
-
const { requiredRule } = useUtils();
|
|
288
|
+
const { requiredRule, emailRule } = useUtils();
|
|
212
289
|
|
|
213
290
|
const message = ref("");
|
|
214
291
|
|
|
@@ -219,18 +296,28 @@ function resetInvite() {
|
|
|
219
296
|
message.value = "";
|
|
220
297
|
}
|
|
221
298
|
|
|
299
|
+
function handleUpdateSite(siteId: string){
|
|
300
|
+
const obj = sites.value.find( x => x?.value === siteId)
|
|
301
|
+
invite.value.siteName = obj?.title || ""
|
|
302
|
+
}
|
|
303
|
+
|
|
222
304
|
const { inviteUser } = useUser();
|
|
223
305
|
|
|
306
|
+
|
|
224
307
|
async function submit() {
|
|
308
|
+
loading.submittingForm = true;
|
|
225
309
|
try {
|
|
226
310
|
await inviteUser(invite.value);
|
|
227
|
-
emit("success");
|
|
228
311
|
|
|
229
312
|
if (createMore.value) {
|
|
313
|
+
form.value?.reset();
|
|
230
314
|
resetInvite();
|
|
231
|
-
|
|
315
|
+
emit("success", false);
|
|
316
|
+
} else emit("success", true);
|
|
232
317
|
} catch (error: any) {
|
|
233
318
|
message.value = error.response._data.message;
|
|
319
|
+
} finally {
|
|
320
|
+
loading.submittingForm = false;
|
|
234
321
|
}
|
|
235
322
|
}
|
|
236
323
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<local-pagination
|
|
37
37
|
v-model="page"
|
|
38
38
|
:length="pages"
|
|
39
|
-
@update:value="
|
|
39
|
+
@update:value="getVerifications"
|
|
40
40
|
/>
|
|
41
41
|
</v-row>
|
|
42
42
|
</template>
|
|
@@ -142,7 +142,9 @@
|
|
|
142
142
|
title="Invite member"
|
|
143
143
|
:org="org"
|
|
144
144
|
@cancel="dialogMember = false"
|
|
145
|
+
@success="handleSuccess"
|
|
145
146
|
@invited="getVerifications()"
|
|
147
|
+
:app="props.app"
|
|
146
148
|
/>
|
|
147
149
|
</v-dialog>
|
|
148
150
|
</v-row>
|
|
@@ -150,6 +152,10 @@
|
|
|
150
152
|
|
|
151
153
|
<script setup lang="ts">
|
|
152
154
|
const props = defineProps({
|
|
155
|
+
app: {
|
|
156
|
+
type: String,
|
|
157
|
+
default: "organization",
|
|
158
|
+
},
|
|
153
159
|
route: {
|
|
154
160
|
type: String,
|
|
155
161
|
default: "index",
|
|
@@ -238,7 +244,8 @@ const {
|
|
|
238
244
|
page: page.value,
|
|
239
245
|
status: props.status,
|
|
240
246
|
search: headerSearch.value,
|
|
241
|
-
type: "user-invite",
|
|
247
|
+
type: "user-invite,member-invite",
|
|
248
|
+
app: props.app
|
|
242
249
|
})
|
|
243
250
|
);
|
|
244
251
|
|
|
@@ -252,7 +259,7 @@ watchEffect(() => {
|
|
|
252
259
|
}
|
|
253
260
|
});
|
|
254
261
|
|
|
255
|
-
watch([
|
|
262
|
+
watch([headerSearch], () => {
|
|
256
263
|
getVerifications();
|
|
257
264
|
});
|
|
258
265
|
|
|
@@ -282,6 +289,13 @@ async function onConfirmCancel() {
|
|
|
282
289
|
cancelLoading.value = false;
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
function handleSuccess(closeForm: boolean) {
|
|
293
|
+
getVerifications();
|
|
294
|
+
if (closeForm) {
|
|
295
|
+
dialogMember.value = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
285
299
|
watchEffect(() => {
|
|
286
300
|
if (!props.viewInvitations) {
|
|
287
301
|
useRouter().back();
|
package/components/TableMain.vue
CHANGED
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
<v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
|
|
5
5
|
<v-row no-gutters>
|
|
6
6
|
<slot name="actions">
|
|
7
|
-
<v-btn
|
|
8
|
-
|
|
9
|
-
class="text-none"
|
|
10
|
-
rounded="pill"
|
|
11
|
-
variant="tonal"
|
|
12
|
-
size="large"
|
|
13
|
-
@click="emits('create')"
|
|
14
|
-
>
|
|
7
|
+
<v-btn v-if="canCreate" class="text-none" rounded="pill" variant="tonal" size="large"
|
|
8
|
+
@click="emits('create')">
|
|
15
9
|
{{ createLabel }}
|
|
16
10
|
</v-btn>
|
|
17
11
|
</slot>
|
|
@@ -20,13 +14,7 @@
|
|
|
20
14
|
|
|
21
15
|
<!-- Table Card -->
|
|
22
16
|
<v-col cols="12">
|
|
23
|
-
<v-card
|
|
24
|
-
width="100%"
|
|
25
|
-
variant="outlined"
|
|
26
|
-
border="thin"
|
|
27
|
-
rounded="lg"
|
|
28
|
-
:loading="loading"
|
|
29
|
-
>
|
|
17
|
+
<v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
|
|
30
18
|
<!-- Toolbar -->
|
|
31
19
|
<v-toolbar density="compact" color="grey-lighten-4">
|
|
32
20
|
<template #prepend>
|
|
@@ -40,27 +28,20 @@
|
|
|
40
28
|
<span class="mr-2 text-caption text-fontgray">
|
|
41
29
|
{{ pageRange }}
|
|
42
30
|
</span>
|
|
43
|
-
<local-pagination
|
|
44
|
-
|
|
45
|
-
:length="pages"
|
|
46
|
-
@update:value="emits('update:page', internalPage)"
|
|
47
|
-
/>
|
|
31
|
+
<local-pagination v-model="internalPage" :length="pages"
|
|
32
|
+
@update:value="emits('update:page', internalPage)" />
|
|
48
33
|
</v-row>
|
|
49
34
|
</template>
|
|
35
|
+
|
|
36
|
+
<template v-if="$slots.extension" #extension>
|
|
37
|
+
<slot name="extension" />
|
|
38
|
+
</template>
|
|
50
39
|
</v-toolbar>
|
|
51
40
|
|
|
52
41
|
<!-- Data Table -->
|
|
53
|
-
<v-data-table
|
|
54
|
-
|
|
55
|
-
:
|
|
56
|
-
:item-value="itemValue"
|
|
57
|
-
:items-per-page="itemsPerPage"
|
|
58
|
-
fixed-header
|
|
59
|
-
hide-default-footer
|
|
60
|
-
hide-default-header
|
|
61
|
-
@click:row="(_: any, data: any) => emits('row-click', data)"
|
|
62
|
-
style="max-height: calc(100vh - (200px))"
|
|
63
|
-
>
|
|
42
|
+
<v-data-table :headers="headers" :items="items" :item-value="itemValue" :items-per-page="itemsPerPage"
|
|
43
|
+
fixed-header hide-default-footer hide-default-header
|
|
44
|
+
@click:row="(_: any, data: any) => emits('row-click', data)" style="max-height: calc(100vh - (200px))">
|
|
64
45
|
<template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
|
|
65
46
|
<slot :name="slotName" v-bind="slotProps" />
|
|
66
47
|
</template>
|
package/composables/useUser.ts
CHANGED
|
@@ -5,10 +5,12 @@ export default function useUser() {
|
|
|
5
5
|
role = "",
|
|
6
6
|
name = "",
|
|
7
7
|
org = "",
|
|
8
|
+
site="",
|
|
9
|
+
siteName="",
|
|
8
10
|
} = {}) {
|
|
9
11
|
return useNuxtApp().$api<Record<string, any>>("/api/auth/invite", {
|
|
10
12
|
method: "POST",
|
|
11
|
-
body: { email, app, role, name, org },
|
|
13
|
+
body: { email, app, role, name, org, ...(site && {siteId: site}), ...(siteName && {siteName}) },
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -106,6 +108,12 @@ export default function useUser() {
|
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
function getUserByEmail(email = "") {
|
|
112
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/users/email/${email}`, {
|
|
113
|
+
method: "GET"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
109
117
|
return {
|
|
110
118
|
inviteUser,
|
|
111
119
|
updateName,
|
|
@@ -119,5 +127,6 @@ export default function useUser() {
|
|
|
119
127
|
createUserByInvite,
|
|
120
128
|
getById,
|
|
121
129
|
createUserByVerification,
|
|
130
|
+
getUserByEmail
|
|
122
131
|
};
|
|
123
132
|
}
|
|
@@ -5,6 +5,7 @@ export default function useVerification() {
|
|
|
5
5
|
search = "",
|
|
6
6
|
page = 1,
|
|
7
7
|
email = "",
|
|
8
|
+
app = ""
|
|
8
9
|
} = {}): Promise<{
|
|
9
10
|
items: TMiniVerification[];
|
|
10
11
|
pages: number;
|
|
@@ -12,7 +13,7 @@ export default function useVerification() {
|
|
|
12
13
|
}> {
|
|
13
14
|
return useNuxtApp().$api("/api/verifications", {
|
|
14
15
|
method: "GET",
|
|
15
|
-
query: { status, search, page, type, email },
|
|
16
|
+
query: { status, search, page, type, email, app },
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
19
|
|
package/middleware/02.org.ts
CHANGED
|
@@ -4,12 +4,12 @@ const hexSchema = z
|
|
|
4
4
|
.string()
|
|
5
5
|
.regex(/^[0-9a-fA-F]{24}$/, "Invalid organization ID");
|
|
6
6
|
|
|
7
|
-
export default defineNuxtRouteMiddleware(async (to) => {
|
|
7
|
+
export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
8
8
|
if (import.meta.server) return;
|
|
9
9
|
|
|
10
10
|
const { organization, org } = to.params;
|
|
11
11
|
|
|
12
|
-
if (!hexSchema.safeParse(organization
|
|
12
|
+
if (!hexSchema.safeParse(organization || org).success) {
|
|
13
13
|
return navigateTo(
|
|
14
14
|
{ name: "require-organization-membership" },
|
|
15
15
|
{ replace: true }
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@iservice365/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.9",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "nuxi dev .playground",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"typescript": "^5.8.3",
|
|
21
21
|
"vite-plugin-vuetify": "^2.0.4",
|
|
22
22
|
"vue": "latest",
|
|
23
|
-
"vuetify": "^3.
|
|
23
|
+
"vuetify": "^3.10.4"
|
|
24
24
|
},
|
|
25
25
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
|
26
26
|
"dependencies": {
|
|
@@ -6,7 +6,7 @@ export default defineNuxtPlugin(() => {
|
|
|
6
6
|
|
|
7
7
|
const { userAppRole, id, orgNature } = useLocalSetup();
|
|
8
8
|
|
|
9
|
-
router.afterEach((to) => {
|
|
9
|
+
router.afterEach(async (to) => {
|
|
10
10
|
const isMember = to.meta?.memberOnly;
|
|
11
11
|
|
|
12
12
|
if (!isMember) return;
|
|
@@ -19,11 +19,12 @@ export default defineNuxtPlugin(() => {
|
|
|
19
19
|
|
|
20
20
|
const userId = computed(() => useCookie("user").value ?? "");
|
|
21
21
|
|
|
22
|
-
const { data: userMemberData, error: userMemberError } =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const { data: userMemberData, error: userMemberError } =
|
|
23
|
+
await useLazyAsyncData(
|
|
24
|
+
"get-member-by-id",
|
|
25
|
+
() => getByUserType(userId.value, APP, org.value),
|
|
26
|
+
{ watch: [userId] }
|
|
27
|
+
);
|
|
27
28
|
|
|
28
29
|
watchEffect(() => {
|
|
29
30
|
if (userMemberError.value) {
|
|
@@ -42,7 +43,7 @@ export default defineNuxtPlugin(() => {
|
|
|
42
43
|
}
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
const { data: getOrgByIdReq } = useLazyAsyncData(
|
|
46
|
+
const { data: getOrgByIdReq } = await useLazyAsyncData(
|
|
46
47
|
"get-org-by-id",
|
|
47
48
|
() => getById(org.value),
|
|
48
49
|
{ watch: [org] }
|
|
@@ -56,7 +57,7 @@ export default defineNuxtPlugin(() => {
|
|
|
56
57
|
|
|
57
58
|
const roleId = computed(() => userMemberData.value?.role ?? "");
|
|
58
59
|
|
|
59
|
-
const { data: getRoleByIdReq } = useLazyAsyncData(
|
|
60
|
+
const { data: getRoleByIdReq } = await useLazyAsyncData(
|
|
60
61
|
"get-role-by-id",
|
|
61
62
|
() => getRoleById(roleId.value),
|
|
62
63
|
{ watch: [roleId], immediate: false }
|