@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.
@@ -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
- {{ message }}
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 { watch } from 'vue'
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
- watch(
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(() => APP_LIST)
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
- /* ================= SUBMIT ================= */
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 message = ref("")
260
- const createMore = ref(false)
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
- async function submit() {
264
- loading.submittingForm = true
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 apps = invite.value.app || []
268
-
269
- await Promise.all(
270
- apps.map(app =>
271
- inviteUserClient({
272
- email: invite.value.email,
273
- app,
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
- if (createMore.value) {
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
- emit("success", true)
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
- message.value = e?.response?._data?.message || "Error"
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
- await inviteUser(invite.value);
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 emit("success", true);
400
+ } else {
401
+ emit("success", true);
402
+ }
392
403
  } catch (error: any) {
393
- message.value = error.response._data.message;
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(['navigate'])
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('navigate', item)
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="tab.status"
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: obj.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
- } = useLazyAsyncData(
377
- "get-all-members",
377
+ } = useAsyncData(
378
+ () => `get-all-members-${props.status}-${props.orgId}`,
378
379
  () =>
379
380
  _getAll({
380
- status: currentStatus.value,
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: [page, currentStatus],
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);
@@ -5,7 +5,7 @@
5
5
  <v-row no-gutters>
6
6
  <slot name="actions">
7
7
  <v-col cols="12">
8
- <v-row no-gutters>
8
+ <v-row no-gutters class="ga-2">
9
9
  <v-btn
10
10
  v-if="canCreate"
11
11
  class="text-none"
@@ -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;