@7365admin1/layer-common 1.11.32 → 1.11.33
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
CHANGED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
|
|
4
|
+
<!-- MAIN TABLE -->
|
|
5
|
+
<v-col cols="12">
|
|
6
|
+
<TableMain :headers="currentHeaders" :items="currentItems" :loading="loadingState" :page="currentPage"
|
|
7
|
+
:pages="currentPages" :pageRange="pageRange" :extension-height="120" :offset="300" show-header
|
|
8
|
+
@refresh="handleRefresh" @update:page="handleUpdatePage">
|
|
9
|
+
|
|
10
|
+
<!-- EXTENSION -->
|
|
11
|
+
<template #extension>
|
|
12
|
+
<v-row no-gutters class="w-100 d-flex flex-column ga-2 pt-2">
|
|
13
|
+
|
|
14
|
+
<v-tabs v-model="tab" class="w-100" height="32" @update:model-value="onTabChange">
|
|
15
|
+
<v-tab value="active">Active</v-tab>
|
|
16
|
+
<v-tab value="suspended">Suspended</v-tab>
|
|
17
|
+
<v-tab value="pending">Pending Invitation</v-tab>
|
|
18
|
+
</v-tabs>
|
|
19
|
+
|
|
20
|
+
<div class="px-3 py-2 w-100">
|
|
21
|
+
<v-text-field v-model="search" density="compact" placeholder="Search" clearable max-width="300"
|
|
22
|
+
append-inner-icon="mdi-magnify" hide-details @update:model-value="handleSearch" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
</v-row>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<!-- ACTIONS (ONLY ONE INVITE BUTTON HERE) -->
|
|
29
|
+
<template #actions>
|
|
30
|
+
<v-btn class="text-none" rounded="pill" variant="tonal" size="large" @click="openInviteDialog">
|
|
31
|
+
Invite Client
|
|
32
|
+
</v-btn>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<!-- CUSTOM CELL -->
|
|
36
|
+
<template #item.index="{ index }">
|
|
37
|
+
{{ (currentPage - 1) * 10 + index + 1 }}
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template #item.status="{ value }">
|
|
41
|
+
<v-chip size="small" variant="tonal">
|
|
42
|
+
{{ value }}
|
|
43
|
+
</v-chip>
|
|
44
|
+
</template>
|
|
45
|
+
<template #item.action="{ item }">
|
|
46
|
+
<v-btn v-if="tab === 'pending'" size="small" color="error" variant="tonal"
|
|
47
|
+
:loading="cancelLoadingId === item._id" :disabled="cancelLoadingId === item._id"
|
|
48
|
+
@click="cancelInvite(item)">
|
|
49
|
+
Cancel Invite
|
|
50
|
+
</v-btn>
|
|
51
|
+
</template>
|
|
52
|
+
</TableMain>
|
|
53
|
+
</v-col>
|
|
54
|
+
|
|
55
|
+
<!-- DIALOG -->
|
|
56
|
+
<v-dialog v-model="dialog" max-width="500">
|
|
57
|
+
<InvitationForm title="Invite Client" app="organization" @success="handleSuccess" @cancel="dialog = false" />
|
|
58
|
+
</v-dialog>
|
|
59
|
+
|
|
60
|
+
</v-row>
|
|
61
|
+
</template>
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import useOrg from '@7365admin1/layer-common/composables/useOrg'
|
|
64
|
+
import useRole from '@7365admin1/layer-common/composables/useRole'
|
|
65
|
+
import useVerification from '@7365admin1/layer-common/composables/useVerification'
|
|
66
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
67
|
+
|
|
68
|
+
const tab = ref<'active' | 'suspended' | 'pending'>('active')
|
|
69
|
+
const dialog = ref(false)
|
|
70
|
+
|
|
71
|
+
const { getAll } = useOrg()
|
|
72
|
+
const { getVerifications, cancelUserInvitation } = useVerification()
|
|
73
|
+
const { getRoleById } = useRole()
|
|
74
|
+
const roleCache = ref<Record<string, string>>({})
|
|
75
|
+
|
|
76
|
+
/* ================= STATE ================= */
|
|
77
|
+
const items = ref<any[]>([])
|
|
78
|
+
const loading = ref(false)
|
|
79
|
+
|
|
80
|
+
const page = ref(1)
|
|
81
|
+
const pages = ref(1)
|
|
82
|
+
const search = ref("")
|
|
83
|
+
async function resolveRoleName(id: string) {
|
|
84
|
+
if (!id) return "-"
|
|
85
|
+
|
|
86
|
+
if (roleCache.value[id]) return roleCache.value[id]
|
|
87
|
+
|
|
88
|
+
const res = await getRoleById(id)
|
|
89
|
+
|
|
90
|
+
const roleName = res?.name ?? "-"
|
|
91
|
+
|
|
92
|
+
roleCache.value[id] = roleName
|
|
93
|
+
|
|
94
|
+
return roleName
|
|
95
|
+
}
|
|
96
|
+
/* ================= HEADERS ================= */
|
|
97
|
+
const orgHeaders = [
|
|
98
|
+
{ title: "Organization Name", key: "name" },
|
|
99
|
+
{ title: "Site", key: "sites" },
|
|
100
|
+
{ title: "Plan Type", key: "plan" },
|
|
101
|
+
{ title: "Billing Cycle", key: "bill" },
|
|
102
|
+
{ title: "Subscription Start", key: "start" },
|
|
103
|
+
{ title: "Subscription End", key: "end" },
|
|
104
|
+
{ title: "Status", key: "status" },
|
|
105
|
+
{ title: "Action", key: "actions" },
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
const inviteHeaders = [
|
|
109
|
+
{ title: "Email", key: "email" },
|
|
110
|
+
{ title: "App", key: "app" },
|
|
111
|
+
{ title: "Role", key: "role" },
|
|
112
|
+
{ title: "Status", key: "status" },
|
|
113
|
+
{ title: "Action", key: "action" },
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
/* ================= COMPUTED ================= */
|
|
117
|
+
const currentHeaders = computed(() =>
|
|
118
|
+
tab.value === 'pending' ? inviteHeaders : orgHeaders
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const currentItems = computed(() => items.value)
|
|
122
|
+
const currentPage = computed(() => page.value)
|
|
123
|
+
const currentPages = computed(() => pages.value)
|
|
124
|
+
|
|
125
|
+
const loadingState = computed(() => loading.value)
|
|
126
|
+
const totalItems = ref(0)
|
|
127
|
+
/* ================= API ================= */
|
|
128
|
+
async function fetchData() {
|
|
129
|
+
loading.value = true
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (tab.value === 'pending') {
|
|
133
|
+
const res = await getVerifications({
|
|
134
|
+
status: "pending",
|
|
135
|
+
type: "user-invite",
|
|
136
|
+
page: page.value,
|
|
137
|
+
search: search.value,
|
|
138
|
+
app: "organization",
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
items.value = await Promise.all(
|
|
142
|
+
(res?.items || []).map(async (i: any) => ({
|
|
143
|
+
...i,
|
|
144
|
+
app: i.metadata?.app ?? "-",
|
|
145
|
+
role: await resolveRoleName(i.metadata?.role),
|
|
146
|
+
}))
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
pages.value = res?.pages || 1
|
|
150
|
+
totalItems.value = res?.total || items.value.length
|
|
151
|
+
} else {
|
|
152
|
+
const res = await getAll({
|
|
153
|
+
page: page.value,
|
|
154
|
+
search: search.value,
|
|
155
|
+
nature: tab.value === "active" ? "" : "suspended",
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
items.value = res?.data?.items || res?.items || []
|
|
159
|
+
|
|
160
|
+
pages.value = res?.data?.totalPages || res?.totalPages || 1
|
|
161
|
+
totalItems.value =
|
|
162
|
+
res?.data?.totalItems ||
|
|
163
|
+
res?.totalItems ||
|
|
164
|
+
items.value.length
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
loading.value = false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const pageRange = computed(() => {
|
|
171
|
+
if (!totalItems.value) return "0-0 of 0"
|
|
172
|
+
|
|
173
|
+
const pageSize = 10
|
|
174
|
+
|
|
175
|
+
const start = (page.value - 1) * pageSize + 1
|
|
176
|
+
const end = Math.min(page.value * pageSize, totalItems.value)
|
|
177
|
+
|
|
178
|
+
return `${start}-${end} of ${totalItems.value}`
|
|
179
|
+
})
|
|
180
|
+
/* ================= EVENTS ================= */
|
|
181
|
+
function onTabChange() {
|
|
182
|
+
page.value = 1
|
|
183
|
+
fetchData()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function handleUpdatePage(p: number) {
|
|
187
|
+
page.value = p
|
|
188
|
+
fetchData()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function handleRefresh() {
|
|
192
|
+
fetchData()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function handleSearch() {
|
|
196
|
+
page.value = 1
|
|
197
|
+
fetchData()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ================= DIALOG ================= */
|
|
201
|
+
function openInviteDialog() {
|
|
202
|
+
dialog.value = true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handleSuccess() {
|
|
206
|
+
dialog.value = false
|
|
207
|
+
fetchData()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ================= INIT ================= */
|
|
211
|
+
onMounted(() => {
|
|
212
|
+
fetchData()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const cancelLoadingId = ref<string | null>(null)
|
|
216
|
+
|
|
217
|
+
async function cancelInvite(item: any) {
|
|
218
|
+
try {
|
|
219
|
+
cancelLoadingId.value = item._id
|
|
220
|
+
|
|
221
|
+
await cancelUserInvitation(item._id)
|
|
222
|
+
|
|
223
|
+
await fetchData()
|
|
224
|
+
} finally {
|
|
225
|
+
cancelLoadingId.value = null
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<v-list>
|
|
4
4
|
<v-list-item>
|
|
5
5
|
<v-list-item-title class="text-h6 text-white">
|
|
6
|
-
{{ APP_NAME }}
|
|
6
|
+
{{ props.title || APP_NAME }}
|
|
7
7
|
</v-list-item-title>
|
|
8
8
|
</v-list-item>
|
|
9
9
|
<slot name="action" />
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
<script setup lang="ts">
|
|
37
37
|
const props = defineProps({
|
|
38
38
|
navigationItems: { type: Array<TNavigationItem>, required: true },
|
|
39
|
+
title: { type: String, default: "" }
|
|
39
40
|
});
|
|
40
41
|
|
|
41
42
|
const { drawer } = useLocal();
|