@iservice365/layer-common 1.0.8 → 1.0.10

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.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 4cdf32b: Invitation form - revise app selection
8
+
9
+ ## 1.0.9
10
+
11
+ ### Patch Changes
12
+
13
+ - 681b0f4: Update dependencies - vuetify
14
+
3
15
  ## 1.0.8
4
16
 
5
17
  ### Patch Changes
@@ -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">{{ item.category }}</v-col>
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
- <v-row no-gutters class="fill-height" align="stretch">
3
- <v-col cols="12" md="3" class="fill-height">
4
- <ChatNavigation
5
- :title="'Feedbacks'"
6
- :items="items"
7
- @select="handleSelectFeedback"
8
- @search="_getFeedbacks"
9
- />
10
- </v-col>
11
-
12
- <v-col cols="12" md="6" class="fill-height">
13
- <ChatMessage />
14
- </v-col>
15
-
16
- <v-col cols="12" md="3" class="fill-height">
17
- <ChatInformation
18
- :item="feedback"
19
- :service-providers="_serviceProviders"
20
- @edit="openEditDialog"
21
- @mark-complete-request="showCompleteDialog = true"
22
- @delete="showDeleteDialog = true"
23
- />
24
- </v-col>
25
- </v-row>
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 = `${API_DO_STORAGE_ENDPOINT}/${uploadedId}`;
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
- return "/images/file-thumbnails/word.png";
172
- if (fileUrl.match(/\.(xls|xlsx)$/i))
173
- return "/images/file-thumbnails/excel.png";
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
@@ -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>
@@ -47,14 +48,13 @@
47
48
  :items="roles"
48
49
  item-title="name"
49
50
  item-value="_id"
50
- :rules="[requiredRule]"
51
+ :rules="[requiredRule]"
51
52
  density="comfortable"
52
53
  ></v-autocomplete>
53
54
  </v-col>
54
55
  </v-row>
55
56
  </v-col>
56
57
 
57
-
58
58
  <v-col v-if="hasSite" cols="12">
59
59
  <v-row no-gutters>
60
60
  <InputLabel class="text-capitalize" title="Site" />
@@ -64,6 +64,7 @@
64
64
  :items="sites"
65
65
  density="comfortable"
66
66
  :rules="[requiredRule]"
67
+ @update:model-value="handleUpdateSite"
67
68
  ></v-autocomplete>
68
69
  </v-col>
69
70
  </v-row>
@@ -118,7 +119,7 @@
118
119
  class="text-none"
119
120
  size="48"
120
121
  :disabled="!validForm"
121
- :loading="submitting"
122
+ :loading="loading.submittingForm"
122
123
  @click="submit"
123
124
  >
124
125
  Submit
@@ -174,9 +175,13 @@ const props = defineProps({
174
175
  const emit = defineEmits(["cancel", "success", "success:create-more"]);
175
176
 
176
177
  const validForm = ref(false);
177
- const form = ref<HTMLFormElement | null>(null)
178
+ const form = ref<HTMLFormElement | null>(null);
178
179
  const app = computed(() => useRuntimeConfig().public.APP ?? "");
179
- const submitting = ref(false);
180
+
181
+ const loading = reactive({
182
+ submittingForm: false,
183
+ verifyingEmail: false,
184
+ });
180
185
 
181
186
  const invite = ref<Record<string, any>>({
182
187
  email: "",
@@ -197,27 +202,36 @@ if (props.mode === "edit") {
197
202
  }
198
203
 
199
204
  const { natureOfBusiness } = useLocal();
205
+ const { orgNature } = useLocalSetup();
200
206
 
201
207
  const apps = computed(() => {
202
208
  const items = [];
203
209
  items.unshift({ title: "Organization", value: "organization" });
204
210
 
205
- if (props.app === "security_agency") {
206
- items.push({ title: "Security Agency", value: "security_agency" });
211
+ const _org = "security_agency";
212
+
213
+ if (props.app === _org || orgNature.value === _org) {
214
+ items.push({ title: "Security Agency", value: _org });
207
215
  }
208
216
 
209
- if (props.app === "cleaning_agency") {
210
- items.push({ title: "Cleaning Agency", value: "cleaning_agency" });
217
+ const _cleaning = "cleaning_agency";
218
+
219
+ if (props.app === _cleaning || orgNature.value === _cleaning) {
220
+ items.push({ title: "Cleaning Agency", value: _cleaning });
211
221
  }
212
222
 
213
- if (props.app === "property_manager") {
214
- items.push({ title: "Property Manager", value: "property_manager" });
223
+ const _property = "property_manager";
224
+
225
+ if (props.app === _property || orgNature.value === _property) {
226
+ items.push({ title: "Property Manager", value: _property });
215
227
  }
216
228
 
217
- if (props.app === "mechanical_electrical_services") {
229
+ const _mechanical = "mechanical_electrical_services";
230
+
231
+ if (props.app === _mechanical || orgNature.value === _mechanical) {
218
232
  items.push({
219
233
  title: "Mechanical & Electrical Services",
220
- value: "mechanical_electrical_services",
234
+ value: _mechanical,
221
235
  });
222
236
  }
223
237
 
@@ -234,8 +248,7 @@ const { getAll: getAllCustomerSite } = useCustomerSite();
234
248
 
235
249
  const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
236
250
  "get-sites-by-org",
237
- async () => await getAllCustomerSite({ org: props.org, limit: 50 }),
238
- { }
251
+ async () => await getAllCustomerSite({ org: props.org, limit: 50 })
239
252
  );
240
253
 
241
254
  watchEffect(() => {
@@ -269,17 +282,16 @@ watchEffect(() => {
269
282
  }
270
283
  });
271
284
 
272
- function handleUpdateApp(value: string){
285
+ function handleUpdateApp(value: string) {
273
286
  invite.value.role = "";
274
287
  invite.value.site = "";
275
288
  refreshRoles();
276
289
  }
277
290
 
278
-
279
291
  const createMore = ref(false);
280
292
  const disable = ref(false);
281
293
 
282
- const { requiredRule } = useUtils();
294
+ const { requiredRule, emailRule } = useUtils();
283
295
 
284
296
  const message = ref("");
285
297
 
@@ -290,10 +302,15 @@ function resetInvite() {
290
302
  message.value = "";
291
303
  }
292
304
 
305
+ function handleUpdateSite(siteId: string) {
306
+ const obj = sites.value.find((x) => x?.value === siteId);
307
+ invite.value.siteName = obj?.title || "";
308
+ }
309
+
293
310
  const { inviteUser } = useUser();
294
311
 
295
312
  async function submit() {
296
- submitting.value = true;
313
+ loading.submittingForm = true;
297
314
  try {
298
315
  await inviteUser(invite.value);
299
316
 
@@ -305,7 +322,7 @@ async function submit() {
305
322
  } catch (error: any) {
306
323
  message.value = error.response._data.message;
307
324
  } finally {
308
- submitting.value = false;
325
+ loading.submittingForm = false;
309
326
  }
310
327
  }
311
328
 
@@ -36,7 +36,7 @@
36
36
  <local-pagination
37
37
  v-model="page"
38
38
  :length="pages"
39
- @update:value="_getVerifications({ search: headerSearch })"
39
+ @update:value="getVerifications"
40
40
  />
41
41
  </v-row>
42
42
  </template>
@@ -244,7 +244,8 @@ const {
244
244
  page: page.value,
245
245
  status: props.status,
246
246
  search: headerSearch.value,
247
- type: "user-invite",
247
+ type: "user-invite,member-invite",
248
+ app: props.app
248
249
  })
249
250
  );
250
251
 
@@ -258,7 +259,7 @@ watchEffect(() => {
258
259
  }
259
260
  });
260
261
 
261
- watch([page, headerSearch], () => {
262
+ watch([headerSearch], () => {
262
263
  getVerifications();
263
264
  });
264
265
 
@@ -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
- v-if="canCreate"
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
- v-model="internalPage"
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
- :headers="headers"
55
- :items="items"
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>
@@ -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/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.8",
5
+ "version": "1.0.10",
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.7.3"
23
+ "vuetify": "^3.10.4"
24
24
  },
25
25
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
26
26
  "dependencies": {
package/types/local.d.ts CHANGED
@@ -7,6 +7,7 @@ declare type TToken = {
7
7
  declare type TNavigationRoute = {
8
8
  name: string;
9
9
  params?: TKeyValuePair;
10
+ query?: TKeyValuePair;
10
11
  };
11
12
 
12
13
  declare type TNavigationItem = {