@7365admin1/layer-common 1.11.36 → 1.11.38

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
@@ -1,5 +1,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.38
4
+
5
+ ### Patch Changes
6
+
7
+ - 79f3f90: Update Layer-common
8
+
9
+ ## 1.11.37
10
+
11
+ ### Patch Changes
12
+
13
+ - 100370d: Update layer-common version
14
+
3
15
  ## 1.11.36
4
16
 
5
17
  ### Patch Changes
@@ -1,16 +1,12 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
-
4
- <!-- MAIN TABLE -->
5
3
  <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
4
+ <TableMain :headers="currentHeaders" :items="items" :loading="loading" :page="page" :pages="pages"
5
+ :pageRange="pageRange" :items-per-page="-1" :extension-height="120" :offset="300" show-header
8
6
  @refresh="handleRefresh" @update:page="handleUpdatePage">
9
-
10
7
  <!-- EXTENSION -->
11
8
  <template #extension>
12
9
  <v-row no-gutters class="w-100 d-flex flex-column ga-2 pt-2">
13
-
14
10
  <v-tabs v-model="tab" class="w-100" height="32" @update:model-value="onTabChange">
15
11
  <v-tab value="active">Active</v-tab>
16
12
  <v-tab value="suspended">Suspended</v-tab>
@@ -21,27 +17,30 @@
21
17
  <v-text-field v-model="search" density="compact" placeholder="Search" clearable max-width="300"
22
18
  append-inner-icon="mdi-magnify" hide-details @update:model-value="handleSearch" />
23
19
  </div>
24
-
25
20
  </v-row>
26
21
  </template>
27
22
 
28
- <!-- ACTIONS (ONLY ONE INVITE BUTTON HERE) -->
23
+ <!-- ACTIONS -->
29
24
  <template #actions>
30
- <v-btn class="text-none" rounded="pill" variant="tonal" size="large" @click="openInviteDialog">
25
+ <v-btn v-if="tab !== 'suspended'" class="text-none" rounded="pill" variant="tonal" size="large"
26
+ @click="openInviteDialog">
31
27
  Invite Client
32
28
  </v-btn>
33
29
  </template>
34
30
 
35
- <!-- CUSTOM CELL -->
36
- <template #item.index="{ index }">
31
+ <!-- INDEX CELL -->
32
+ <!-- <template #item.index="{ index }">
37
33
  {{ (currentPage - 1) * 10 + index + 1 }}
38
- </template>
34
+ </template> -->
39
35
 
36
+ <!-- STATUS CELL -->
40
37
  <template #item.status="{ value }">
41
- <v-chip size="small" variant="tonal">
38
+ <v-chip size="small" :color="value === 'suspended' ? 'error' : 'success'" variant="flat">
42
39
  {{ value }}
43
40
  </v-chip>
44
41
  </template>
42
+
43
+ <!-- ACTION CELL (pending only) -->
45
44
  <template #item.action="{ item }">
46
45
  <v-btn v-if="tab === 'pending'" size="small" color="error" variant="tonal"
47
46
  :loading="cancelLoadingId === item._id" :disabled="cancelLoadingId === item._id"
@@ -56,64 +55,66 @@
56
55
  <v-dialog v-model="dialog" max-width="500">
57
56
  <InvitationClientForm title="Invite Client" @success="handleSuccess" @cancel="dialog = false" />
58
57
  </v-dialog>
59
- s
60
58
  </v-row>
61
59
  </template>
62
- <script setup lang="ts">
63
60
 
64
- import { computed, onMounted, ref, watch } from 'vue'
61
+ <script setup lang="ts">
62
+ import { computed, onMounted, ref } from 'vue'
65
63
  import useOrg from '../composables/useOrg'
66
64
  import useVerification from '../composables/useVerification'
67
65
  import useRole from '../composables/useRole'
68
66
  import InvitationClientForm from './InvitationClientForm.vue'
69
-
67
+ import useCustomerSite from '../composables/useCustomerSite'
68
+ import useSubscription from '../composables/useSubscription'
70
69
 
71
70
  const tab = ref<'active' | 'suspended' | 'pending'>('active')
72
71
  const dialog = ref(false)
73
72
 
74
73
  const { getAll } = useOrg()
75
74
  const { getVerifications, cancelUserInvitation } = useVerification()
75
+ const { getCustomerSites: getCustomerSitesByOrgId } = useCustomerSite()
76
+ const { getByOrgId: getSubscriptionByOrgId } = useSubscription()
76
77
  const { getRoleById } = useRole()
77
78
  const roleCache = ref<Record<string, string>>({})
78
79
 
79
80
  /* ================= STATE ================= */
80
81
  const items = ref<any[]>([])
81
82
  const loading = ref(false)
82
-
83
83
  const page = ref(1)
84
84
  const pages = ref(1)
85
- const search = ref("")
86
- async function resolveRoleName(id: string) {
87
- if (!id) return "-"
88
-
89
- if (roleCache.value[id]) return roleCache.value[id]
90
-
91
- const res = await getRoleById(id)
92
-
93
- const roleName = res?.name ?? "-"
85
+ const search = ref('')
86
+ const totalItems = ref(0)
87
+ const cancelLoadingId = ref<string | null>(null)
94
88
 
95
- roleCache.value[id] = roleName
96
89
 
97
- return roleName
98
- }
90
+ const APP_LIST = [
91
+ { title: "Organization", value: "organization" },
92
+ { title: "Security Agency", value: "security_agency" },
93
+ { title: "Cleaning Services", value: "cleaning_services" },
94
+ { title: "Property Management Agency", value: "property_management_agency" },
95
+ { title: "Mechanical & Electrical Services", value: "mechanical_electrical_services" },
96
+ { title: "Pest Control Services", value: "pest_control_services" },
97
+ { title: "Landscaping Services", value: "landscaping_services" },
98
+ { title: "Pool Maintenance Services", value: "pool_maintenance_services" },
99
+ ]
99
100
  /* ================= HEADERS ================= */
100
101
  const orgHeaders = [
101
- { title: "Organization Name", key: "name" },
102
- { title: "Site", key: "sites" },
103
- { title: "Plan Type", key: "plan" },
104
- { title: "Billing Cycle", key: "bill" },
105
- { title: "Subscription Start", key: "start" },
106
- { title: "Subscription End", key: "end" },
107
- { title: "Status", key: "status" },
108
- { title: "Action", key: "actions" },
102
+ { title: 'Organization Name', key: 'name' },
103
+ { title: 'Sites', key: 'sites' },
104
+ { title: 'Plan Type', key: 'planType' },
105
+ { title: 'Billing Cycle', key: 'billingCycle' },
106
+ { title: 'Subscription Start', key: 'subscriptionStart' },
107
+ { title: 'Subscription End', key: 'subscriptionEnd' },
108
+ { title: 'Status', key: 'status' },
109
+ { title: 'Actions', key: 'actions' },
109
110
  ]
110
111
 
111
112
  const inviteHeaders = [
112
- { title: "Email", key: "email" },
113
- { title: "App", key: "app" },
114
- { title: "Role", key: "role" },
115
- { title: "Status", key: "status" },
116
- { title: "Action", key: "action" },
113
+ { title: 'Email', key: 'email' },
114
+ { title: 'App', key: 'app' },
115
+ { title: 'Role', key: 'role' },
116
+ { title: 'Status', key: 'status' },
117
+ { title: 'Action', key: 'action' },
117
118
  ]
118
119
 
119
120
  /* ================= COMPUTED ================= */
@@ -121,65 +122,176 @@ const currentHeaders = computed(() =>
121
122
  tab.value === 'pending' ? inviteHeaders : orgHeaders
122
123
  )
123
124
 
124
- const currentItems = computed(() => items.value)
125
- const currentPage = computed(() => page.value)
126
- const currentPages = computed(() => pages.value)
125
+ const pageRange = computed(() => {
126
+ if (!totalItems.value) return '0-0 of 0'
127
+ const pageSize = 10
128
+ const start = (page.value - 1) * pageSize + 1
129
+ const end = Math.min(page.value * pageSize, totalItems.value)
130
+ return `${start}-${end} of ${totalItems.value}`
131
+ })
132
+
133
+ /* ================= HELPERS ================= */
134
+ function formatDate(date?: string) {
135
+ if (!date) return '-'
136
+ return new Date(date).toLocaleDateString('en-US')
137
+ }
138
+
139
+ async function resolveRoleName(id: string) {
140
+ if (!id) return '-'
141
+ if (roleCache.value[id]) return roleCache.value[id]
142
+ const res = await getRoleById(id)
143
+ const roleName = res?.name ?? '-'
144
+ roleCache.value[id] = roleName
145
+ return roleName
146
+ }
147
+
148
+ /* ================= FETCH ================= */
149
+ async function fetchActive() {
150
+ const res = await getAll({ page: page.value, search: search.value })
151
+ const orgs = res?.data?.items || res?.items || []
152
+
153
+ const mapped = await Promise.all(
154
+ orgs.map(async (org: any) => {
155
+ let sitesCount = 0
156
+ let planType = '-'
157
+ let billingCycle = '-'
158
+ let subscriptionStart = '-'
159
+ let subscriptionEnd = '-'
160
+
161
+ try {
162
+ const siteRes = await getCustomerSitesByOrgId(org._id)
163
+ sitesCount =
164
+ siteRes?.data?.totalItems ||
165
+ siteRes?.totalItems ||
166
+ siteRes?.data?.items?.length ||
167
+ siteRes?.items?.length ||
168
+ 0
169
+
170
+ const subRes = await getSubscriptionByOrgId(org._id)
171
+ const subscription = subRes?.data || subRes
172
+ if (subscription) {
173
+ planType = subscription?.paidType || '-'
174
+ billingCycle = subscription?.billingCycle || '-'
175
+ subscriptionStart = formatDate(subscription?.createdAt)
176
+ subscriptionEnd = formatDate(subscription?.nextBillingDate)
177
+ }
178
+ } catch (e) {
179
+ console.error('Error mapping org:', org._id, e)
180
+ }
181
+
182
+ return {
183
+ _id: org._id,
184
+ ...org,
185
+ sites: `${sitesCount} Sites`,
186
+ planType,
187
+ billingCycle,
188
+ subscriptionStart,
189
+ subscriptionEnd,
190
+ status: org?.status || 'Active',
191
+ }
192
+ })
193
+ )
194
+
195
+ items.value = mapped
196
+ pages.value = res?.data?.totalPages || res?.totalPages || 1
197
+ totalItems.value = res?.data?.totalItems || res?.totalItems || mapped.length
198
+ }
199
+
200
+ async function fetchSuspended() {
201
+ const res = await getAll({
202
+ page: page.value,
203
+ search: search.value,
204
+ // status: 'suspended',
205
+ })
206
+ const orgs = res?.data?.items || res?.items || []
207
+
208
+ const mapped = await Promise.all(
209
+ orgs.map(async (org: any) => {
210
+ let sitesCount = 0
211
+ let planType = '-'
212
+ let billingCycle = '-'
213
+ let subscriptionStart = '-'
214
+ let subscriptionEnd = '-'
215
+
216
+ try {
217
+ const siteRes = await getCustomerSitesByOrgId(org._id)
218
+ sitesCount =
219
+ siteRes?.data?.totalItems ||
220
+ siteRes?.totalItems ||
221
+ siteRes?.data?.items?.length ||
222
+ siteRes?.items?.length ||
223
+ 0
224
+
225
+ const subRes = await getSubscriptionByOrgId(org._id)
226
+ const subscription = subRes?.data || subRes
227
+ if (subscription) {
228
+ planType = subscription?.paidType || '-'
229
+ billingCycle = subscription?.billingCycle || '-'
230
+ subscriptionStart = formatDate(subscription?.createdAt)
231
+ subscriptionEnd = formatDate(subscription?.nextBillingDate)
232
+ }
233
+ } catch (e) {
234
+ console.error('Error mapping suspended org:', org._id, e)
235
+ }
236
+
237
+ return {
238
+ _id: org._id,
239
+ ...org,
240
+ sites: `${sitesCount} Sites`,
241
+ planType,
242
+ billingCycle,
243
+ subscriptionStart,
244
+ subscriptionEnd,
245
+ status: org?.status || 'suspended',
246
+ }
247
+ })
248
+ )
249
+
250
+ items.value = mapped
251
+ pages.value = res?.data?.totalPages || res?.totalPages || 1
252
+ totalItems.value = res?.data?.totalItems || res?.totalItems || mapped.length
253
+ }
254
+
255
+ async function fetchPending() {
256
+ const res = await getVerifications({
257
+ status: 'pending',
258
+ type: 'user-invite',
259
+ page: page.value,
260
+ search: search.value,
261
+ // app: 'organization',
262
+ })
263
+
264
+ items.value = await Promise.all(
265
+ (res?.items || []).map(async (i: any) => ({
266
+ ...i,
267
+ app:
268
+ APP_LIST.find(a => a.value === i.metadata?.app)?.title ||
269
+ i.metadata?.app ||
270
+ '-',
271
+ role: await resolveRoleName(i.metadata?.role),
272
+ }))
273
+ )
274
+
275
+ pages.value = res?.pages || 1
276
+ totalItems.value = res?.total || items.value.length
277
+ }
127
278
 
128
- const loadingState = computed(() => loading.value)
129
- const totalItems = ref(0)
130
- /* ================= API ================= */
131
279
  async function fetchData() {
132
280
  loading.value = true
133
-
134
281
  try {
135
- if (tab.value === 'pending') {
136
- const res = await getVerifications({
137
- status: "pending",
138
- type: "user-invite",
139
- page: page.value,
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
282
+ if (tab.value === 'suspended') {
283
+ items.value = []
284
+ pages.value = 1
285
+ totalItems.value = 0
286
+ return
168
287
  }
288
+ if (tab.value === 'active') await fetchActive()
289
+ else await fetchPending()
169
290
  } finally {
170
291
  loading.value = false
171
292
  }
172
293
  }
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
294
 
181
- return `${start}-${end} of ${totalItems.value}`
182
- })
183
295
  /* ================= EVENTS ================= */
184
296
  function onTabChange() {
185
297
  page.value = 1
@@ -210,22 +322,19 @@ function handleSuccess() {
210
322
  fetchData()
211
323
  }
212
324
 
213
- /* ================= INIT ================= */
214
- onMounted(() => {
215
- fetchData()
216
- })
217
-
218
- const cancelLoadingId = ref<string | null>(null)
219
-
325
+ /* ================= CANCEL INVITE ================= */
220
326
  async function cancelInvite(item: any) {
221
327
  try {
222
328
  cancelLoadingId.value = item._id
223
-
224
329
  await cancelUserInvitation(item._id)
225
-
226
330
  await fetchData()
227
331
  } finally {
228
332
  cancelLoadingId.value = null
229
333
  }
230
334
  }
335
+
336
+ /* ================= INIT ================= */
337
+ onMounted(() => {
338
+ fetchData()
339
+ })
231
340
  </script>
@@ -119,17 +119,18 @@
119
119
  import { computed, ref, reactive, watchEffect } from 'vue'
120
120
  import useCustomerSite from '../composables/useCustomerSite'
121
121
  import useLocal from '../composables/useLocal'
122
- import { useLocalSetup } from '../composables/useLocalSetup'
123
122
  import useRole from '../composables/useRole'
124
123
  import useUser from '../composables/useUser'
125
124
  import useUtils from '../composables/useUtils'
125
+ import useOrg from '../composables/useOrg'
126
+ import { watch } from 'vue'
126
127
 
127
128
  const APP = useRuntimeConfig().public.APP
128
129
 
129
130
  /* ================= PROPS ================= */
130
131
  const props = defineProps({
131
132
  title: { type: String, default: "Invite Form" },
132
- app: { type: String, default: "organization" },
133
+ app: { type: String, default: "" },
133
134
  org: { type: String, default: "" },
134
135
  mode: { type: String, default: "create" },
135
136
  })
@@ -162,17 +163,23 @@ if (props.mode === "create") {
162
163
 
163
164
  /* ================= APP LIST ================= */
164
165
  const APP_LIST = [
165
- { title: "Organization", value: "organization" },
166
+ { title: "Property Management Agency", value: "property_management_agency" },
166
167
  { title: "Security Agency", value: "security_agency" },
167
168
  { title: "Cleaning Services", value: "cleaning_services" },
168
- { title: "Property Management Agency", value: "property_management_agency" },
169
169
  { title: "Mechanical & Electrical Services", value: "mechanical_electrical_services" },
170
170
  { title: "Pest Control Services", value: "pest_control_services" },
171
- { title: "Landscaping Services", value: "landscaping_services" },
172
171
  { title: "Pool Maintenance Services", value: "pool_maintenance_services" },
173
172
  ]
174
-
175
- const { orgNature } = useLocalSetup()
173
+ const { getByName } = useOrg()
174
+ const org = ref<any>(null)
175
+ watch(
176
+ () => props.org,
177
+ async (name) => {
178
+ if (!name) return
179
+ org.value = await getByName(name)
180
+ },
181
+ { immediate: true }
182
+ )
176
183
 
177
184
  const apps = computed(() => APP_LIST)
178
185
 
@@ -188,7 +195,7 @@ const { getAll: getAllCustomerSite } = useCustomerSite()
188
195
 
189
196
  const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
190
197
  "sites",
191
- () => getAllCustomerSite({ org: props.org, limit: 50 })
198
+ () => getAllCustomerSite({ org: org.value?._id, limit: 50 })
192
199
  )
193
200
 
194
201
  watchEffect(() => {
@@ -200,9 +207,13 @@ watchEffect(() => {
200
207
  }
201
208
  })
202
209
 
203
- watchEffect(() => {
204
- if (hasSite.value) refreshSiteData()
205
- })
210
+ watch(
211
+ () => org.value?._id,
212
+ async (id) => {
213
+ if (!id) return
214
+ await refreshSiteData()
215
+ }
216
+ )
206
217
 
207
218
  /* ================= ROLE ================= */
208
219
  const roles = ref<any[]>([])
@@ -211,18 +222,24 @@ const { getRoles } = useRole()
211
222
  const { data: roleData, refresh: refreshRoles } = await useLazyAsyncData(
212
223
  "roles",
213
224
  () =>
214
- getRoles({
215
- org: props.org,
216
- type: props.app,
217
- limit: 50,
218
- }),
225
+ getRoles({
226
+ org: org.value?._id,
227
+ type: props.app,
228
+ limit: 50,
229
+ }),
219
230
  { watch: [() => props.app] }
220
231
  )
221
232
 
222
233
  watchEffect(() => {
223
234
  if (roleData.value) roles.value = roleData.value.items
224
235
  })
225
-
236
+ watch(
237
+ () => org.value?._id,
238
+ async (id) => {
239
+ if (!id) return
240
+ await refreshRoles()
241
+ }
242
+ )
226
243
  /* ================= EVENTS ================= */
227
244
  async function handleUpdateApp(value: string[]) {
228
245
  invite.value.role = ""
@@ -236,7 +253,7 @@ function handleUpdateSite(id: string) {
236
253
  }
237
254
 
238
255
  /* ================= SUBMIT ================= */
239
- const { inviteUser } = useUser()
256
+ const { inviteUserClient } = useUser()
240
257
  const { requiredRule, emailRule } = useUtils()
241
258
 
242
259
  const message = ref("")
@@ -251,14 +268,15 @@ async function submit() {
251
268
 
252
269
  await Promise.all(
253
270
  apps.map(app =>
254
- inviteUser({
271
+ inviteUserClient({
255
272
  email: invite.value.email,
256
273
  app,
257
274
  role: invite.value.role,
258
- org: invite.value.org,
275
+ // org: invite.value.org,
276
+ org: org.value?._id,
259
277
  site: invite.value.site,
260
278
  siteName: invite.value.siteName,
261
- isMemberInvite: invite.value.isMemberInvite,
279
+ // isMemberInvite: invite.value.isMemberInvite,
262
280
  })
263
281
  )
264
282
  )
@@ -269,7 +287,7 @@ async function submit() {
269
287
  email: "",
270
288
  app: [],
271
289
  role: "",
272
- org: props.org,
290
+ org: org.value?._id || "",
273
291
  site: "",
274
292
  siteName: "",
275
293
  isMemberInvite: false,