@7365admin1/layer-common 1.11.51 → 1.11.53

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,18 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.53
4
+
5
+ ### Patch Changes
6
+
7
+ - bb3d683: update endpoint for customer site
8
+ - bb3d683: update layer common
9
+
10
+ ## 1.11.52
11
+
12
+ ### Patch Changes
13
+
14
+ - 11b93db: fix site org filters invitation module
15
+
3
16
  ## 1.11.51
4
17
 
5
18
  ### Patch Changes
@@ -483,6 +483,122 @@
483
483
  </v-list-item>
484
484
  </draggable>
485
485
  </section>
486
+
487
+ <v-col cols="12" class="mt-10">
488
+ <p class="header-style font-weight-bold mb-5 mt-md-0 ml-md-4">
489
+ Other Settings
490
+ </p>
491
+ <v-row align="center" class="border pa-md-4" no-gutters>
492
+ <v-col cols="9" class="d-flex align-center text-body-2">
493
+ <v-row no-gutters>
494
+ <v-col cols="12" class="header-style mt-5 mt-md-0">
495
+ Monthly Booking limit per unit
496
+ </v-col>
497
+ <v-col cols="12" class="text-body-2 text-medium-emphasis">
498
+ Sets the maximum number of bookings per unit
499
+ </v-col>
500
+ </v-row>
501
+ </v-col>
502
+ <v-col cols="3" class="d-flex align-center justify-end">
503
+ <v-switch
504
+ v-model="facility.isBookingUnitLimitEnabled"
505
+ class="d-flex align-center"
506
+ color="primary"
507
+ hide-details
508
+ @update:model-value="onToggleBookingUnitLimit"
509
+ />
510
+ </v-col>
511
+ <v-col cols="12" class="mt-4">
512
+ <v-number-input
513
+ v-model="facility.bookingUnitLimit"
514
+ :reverse="false"
515
+ controlVariant="default"
516
+ label="Monthly booking limit per unit"
517
+ :hideInput="false"
518
+ :inset="false"
519
+ :min="1"
520
+ :max="500"
521
+ hide-details
522
+ :disabled="!facility.isBookingUnitLimitEnabled"
523
+ />
524
+ </v-col>
525
+ </v-row>
526
+
527
+ <v-row align="center" class="border pa-md-4 mt-4" no-gutters>
528
+ <v-col cols="9" class="d-flex align-center text-body-2">
529
+ <v-row no-gutters>
530
+ <v-col cols="12" class="header-style mt-5 mt-md-0">
531
+ Maximum No. of Guest
532
+ </v-col>
533
+ <v-col cols="12" class="text-body-2 text-medium-emphasis">
534
+ The maximum number of guests refers to the highest allowable
535
+ capacity a venue can safely and comfortably accommodate for an
536
+ event.
537
+ </v-col>
538
+ </v-row>
539
+ </v-col>
540
+ <v-col cols="3" class="d-flex align-center justify-end">
541
+ <v-switch
542
+ v-model="facility.isMaxNumberGuestLimitEnabled"
543
+ class="d-flex align-center"
544
+ color="primary"
545
+ hide-details
546
+ @update:model-value="onToggleMaxNumberGuestLimit"
547
+ />
548
+ </v-col>
549
+ <v-col cols="12" class="mt-4">
550
+ <v-number-input
551
+ v-model="facility.maxNumberGuest"
552
+ :reverse="false"
553
+ controlVariant="default"
554
+ label="Maximum no. of guests"
555
+ :hideInput="false"
556
+ :inset="false"
557
+ :min="1"
558
+ :max="500"
559
+ hide-details
560
+ :disabled="!facility.isMaxNumberGuestLimitEnabled"
561
+ />
562
+ </v-col>
563
+ </v-row>
564
+
565
+ <v-row align="center" class="border pa-md-4 mt-4" no-gutters>
566
+ <v-col cols="9" class="d-flex align-center text-body-2">
567
+ <v-row no-gutters>
568
+ <v-col cols="12" class="header-style mt-5 mt-md-0">
569
+ Cancel allowed up to (Days prior)
570
+ </v-col>
571
+ <v-col cols="12" class="text-body-2 text-medium-emphasis">
572
+ Number of days before a scheduled booking that a user is allowed
573
+ to cancel.
574
+ </v-col>
575
+ </v-row>
576
+ </v-col>
577
+ <v-col cols="3" class="d-flex align-center justify-end">
578
+ <v-switch
579
+ v-model="facility.isCancelAllowedLimitEnabled"
580
+ class="d-flex align-center"
581
+ color="primary"
582
+ hide-details
583
+ @update:model-value="onToggleCancelAllowedLimit"
584
+ />
585
+ </v-col>
586
+ <v-col cols="12" class="mt-4">
587
+ <v-number-input
588
+ v-model="facility.daysAllowedCancel"
589
+ :reverse="false"
590
+ controlVariant="default"
591
+ label="Cancel allowed up to (Days prior)"
592
+ :hideInput="false"
593
+ :inset="false"
594
+ :min="1"
595
+ :max="500"
596
+ hide-details
597
+ :disabled="!facility.isCancelAllowedLimitEnabled"
598
+ />
599
+ </v-col>
600
+ </v-row>
601
+ </v-col>
486
602
  </v-col>
487
603
 
488
604
  <!-- remarks -->
@@ -896,6 +1012,36 @@ const confirmDelete = () => {
896
1012
  const handlePolicy = (newContent: string) => {
897
1013
  facility.value.bookingPolicy = newContent;
898
1014
  };
1015
+
1016
+ function onToggleBookingUnitLimit(val: any) {
1017
+ if (val) {
1018
+ isBookingUnitLimitEnabled.value = true;
1019
+ facility.value.bookingUnitLimit = 1;
1020
+ } else {
1021
+ isBookingUnitLimitEnabled.value = false;
1022
+ facility.value.bookingUnitLimit = null;
1023
+ }
1024
+ }
1025
+
1026
+ function onToggleMaxNumberGuestLimit(val: any) {
1027
+ if (val) {
1028
+ isMaxNumberGuestLimitEnabled.value = true;
1029
+ facility.value.maxNumberGuest = 1;
1030
+ } else {
1031
+ isMaxNumberGuestLimitEnabled.value = false;
1032
+ facility.value.maxNumberGuest = null;
1033
+ }
1034
+ }
1035
+
1036
+ function onToggleCancelAllowedLimit(val: any) {
1037
+ if (val) {
1038
+ isCancelAllowedLimitEnabled.value = true;
1039
+ facility.value.daysAllowedCancel = 1;
1040
+ } else {
1041
+ isCancelAllowedLimitEnabled.value = false;
1042
+ facility.value.daysAllowedCancel = null;
1043
+ }
1044
+ }
899
1045
  </script>
900
1046
 
901
1047
  <style scoped>
@@ -22,7 +22,7 @@
22
22
  fab
23
23
  icon
24
24
  density="comfortable"
25
- @click="_getVerifications({ search: headerSearch, status })"
25
+ @click="getVerifications()"
26
26
  >
27
27
  <v-icon>mdi-refresh</v-icon>
28
28
  </v-btn>
@@ -210,8 +210,6 @@ const headers = [
210
210
  },
211
211
  ];
212
212
 
213
- const status = (useRoute().params.status as string) ?? "";
214
-
215
213
  const { getVerifications: _getVerifications, cancelUserInvitation } =
216
214
  useVerification();
217
215
 
@@ -248,6 +246,8 @@ const {
248
246
  search: headerSearch.value,
249
247
  type: "user-invite,member-invite",
250
248
  app: props.app,
249
+ orgId: props.org,
250
+ siteId: props.siteId,
251
251
  })
252
252
  );
253
253
 
@@ -7,85 +7,164 @@
7
7
  </span>
8
8
  </v-row>
9
9
  </v-toolbar>
10
+ <v-col v-if="prop.mode !== 'edit'" cols="12" class="px-6 pt-4 pb-0">
11
+ <InputLabel title="Form Template" />
12
+ <v-list
13
+ density="compact"
14
+ lines="one"
15
+ max-height="160"
16
+ style="overflow-y: auto; border: 1px solid rgba(0,0,0,0.18); border-radius: 4px;"
17
+ class="pa-0"
18
+ >
19
+ <v-list-item
20
+ v-if="availableTemplateNames.length === 0"
21
+ disabled
22
+ title="All templates have already been created"
23
+ class="text-caption text-medium-emphasis"
24
+ />
25
+ <v-list-item
26
+ v-for="name in availableTemplateNames"
27
+ :key="name"
28
+ :title="name"
29
+ :active="selectedTemplate === name"
30
+ active-color="primary"
31
+ class="text-caption"
32
+ @click="selectedTemplate = name; applyTemplate(name)"
33
+ />
34
+ </v-list>
35
+ </v-col>
36
+ <v-divider v-if="prop.mode !== 'edit'" class="mt-3" />
37
+
10
38
  <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
11
39
  <v-form v-model="validForm" :disabled="disable">
12
40
  <v-col cols="12" class="px-6">
13
- <InputLabel class="text-capitalize" title="Attachment" />
14
- <v-select
15
- v-model="onlineForm.attachment"
16
- :items="attachments"
17
- item-title="name"
18
- item-value="attachment"
19
- density="comfortable"
20
- type="text"
21
- />
41
+ <InputLabel class="text-capitalize" title="Attachment (Optional)" />
42
+
43
+ <div v-if="onlineForm.attachment" class="d-flex align-center" style="gap:8px">
44
+ <v-card
45
+ variant="outlined"
46
+ border="thin"
47
+ class="px-3 flex-grow-1 text-caption"
48
+ height="40"
49
+ :href="getFileUrl(onlineForm.attachment)"
50
+ target="_blank"
51
+ >
52
+ <v-row no-gutters class="fill-height" align="center" style="gap:6px">
53
+ <v-icon size="15">mdi-file-document-outline</v-icon>
54
+ {{ attachmentFileName || 'View attachment' }}
55
+ <v-icon size="13" class="ml-auto">mdi-open-in-new</v-icon>
56
+ </v-row>
57
+ </v-card>
58
+ <v-btn icon variant="text" size="small" density="comfortable" :disabled="disable" @click="clearAttachment">
59
+ <v-icon size="17">mdi-close</v-icon>
60
+ </v-btn>
61
+ </div>
62
+
63
+ <div
64
+ v-else
65
+ class="upload-drop-zone"
66
+ :class="{ 'drag-over': isDragOver, 'is-uploading': isUploading }"
67
+ @dragover.prevent="isDragOver = true"
68
+ @dragleave.prevent="isDragOver = false"
69
+ @drop.prevent="handleDrop"
70
+ @click="fileInputRef?.click()"
71
+ >
72
+ <v-progress-circular v-if="isUploading" indeterminate size="24" width="2" color="primary" class="mb-1" />
73
+ <v-icon v-else size="26" :color="isDragOver ? 'primary' : undefined" class="mb-1 text-medium-emphasis">
74
+ mdi-cloud-upload-outline
75
+ </v-icon>
76
+ <span class="text-caption text-medium-emphasis">
77
+ {{ isUploading ? 'Uploading…' : 'Drop file here or click to browse' }}
78
+ </span>
79
+ <input
80
+ ref="fileInputRef"
81
+ type="file"
82
+ accept=".pdf,application/pdf,image/*"
83
+ style="display:none"
84
+ @change="handleFileInputChange"
85
+ />
86
+ </div>
87
+
88
+ <span v-if="attachmentError" class="text-caption text-error d-block mt-1">{{ attachmentError }}</span>
22
89
  </v-col>
23
90
 
24
91
  <v-col cols="12" class="px-6">
25
92
  <InputLabel class="text-capitalize" title="Name" />
26
93
  <v-text-field
27
- v-model.trim="onlineForm.name"
94
+ v-model.trim="onlineForm.formType"
28
95
  density="comfortable"
29
96
  ></v-text-field>
30
97
  </v-col>
31
98
 
32
99
  <template v-for="(inputLabel, inputLabelIndex) in onlineForm.inputs">
100
+ <v-row class="px-6 mb-0" align="center" no-gutters>
101
+ <v-col>
102
+ <span class="text-caption font-weight-medium text-medium-emphasis">
103
+ Field {{ inputLabelIndex + 1 }}
104
+ </span>
105
+ </v-col>
106
+ <v-col cols="auto">
107
+ <v-btn
108
+ icon
109
+ size="small"
110
+ variant="text"
111
+ color="error"
112
+ :disabled="onlineForm.inputs.length === 1"
113
+ @click="onlineForm.inputs.splice(inputLabelIndex, 1)"
114
+ >
115
+ <v-icon size="18">mdi-delete-outline</v-icon>
116
+ </v-btn>
117
+ </v-col>
118
+ </v-row>
33
119
  <v-row class="px-6">
34
120
  <v-col cols="6">
35
- <InputLabel
36
- class="text-capitalize"
37
- :title="`Label ${+inputLabelIndex + 1}`"
38
- />
121
+ <InputLabel title="Label" />
39
122
  <v-text-field
40
123
  v-model.trim="inputLabel.label"
41
124
  density="comfortable"
42
- ></v-text-field>
125
+ />
43
126
  </v-col>
44
127
  <v-col cols="6">
45
- <InputLabel
46
- class="text-capitalize"
47
- :title="`Data Type ${+inputLabelIndex + 1}`"
48
- />
128
+ <InputLabel title="Data Type" />
49
129
  <v-select v-model="inputLabel.dataType" :items="dataTypes" />
50
130
  </v-col>
51
- <v-col cols="6">
52
- <InputLabel
53
- class="text-capitalize"
54
- :title="`Required ${+inputLabelIndex + 1}`"
131
+ <v-col v-if="inputLabel.dataType === 'Select'" cols="12">
132
+ <InputLabel title="Options" />
133
+ <v-combobox
134
+ v-model="inputLabel.options"
135
+ chips
136
+ closable-chips
137
+ multiple
138
+ density="comfortable"
139
+ variant="outlined"
140
+ placeholder="Type an option and press Enter"
141
+ hint="Press Enter after each option to add it"
142
+ persistent-hint
55
143
  />
56
- <v-checkbox v-model="inputLabel.required" />
57
144
  </v-col>
58
145
  <v-col cols="6">
59
- <InputLabel
60
- class="text-capitalize"
61
- :title="`Multiple ${+inputLabelIndex + 1}`"
62
- />
63
- <v-checkbox v-model="inputLabel.multiple" />
146
+ <InputLabel title="Required" />
147
+ <v-checkbox v-model="inputLabel.required" />
64
148
  </v-col>
65
149
  <v-col cols="6">
66
- <v-btn
67
- block
68
- color="red"
69
- variant="text"
70
- class="text-none font-weight-bold"
71
- text="Remove"
72
- :disabled="onlineForm.inputs.length === 1"
73
- @click="onlineForm.inputs.splice(inputLabelIndex, 1)"
74
- />
150
+ <InputLabel title="Multiple" />
151
+ <v-checkbox v-model="inputLabel.multiple" />
75
152
  </v-col>
76
153
  </v-row>
77
154
  <v-divider class="my-3" />
78
155
  </template>
79
156
 
80
- <v-col cols="12" class="px-6">
157
+ <v-col cols="12" class="px-6 pb-4">
81
158
  <v-btn
82
159
  block
160
+ variant="outlined"
83
161
  color="primary"
84
- variant="text"
85
- class="text-none font-weight-bold"
86
- text="Add"
162
+ class="text-none"
163
+ prepend-icon="mdi-plus"
87
164
  @click="addNewInput"
88
- ></v-btn>
165
+ >
166
+ Add Field
167
+ </v-btn>
89
168
  </v-col>
90
169
 
91
170
  <v-col cols="12">
@@ -123,12 +202,7 @@
123
202
  color="black"
124
203
  class="text-none"
125
204
  size="48"
126
- :disabled="
127
- !validForm ||
128
- disable ||
129
- onlineForm.attachment.length == 0 ||
130
- onlineForm.name == ''
131
- "
205
+ :disabled="!validForm || disable || onlineForm.formType == ''"
132
206
  @click="submit"
133
207
  :loading="disable"
134
208
  >
@@ -140,6 +214,10 @@
140
214
  </v-card>
141
215
  </template>
142
216
  <script setup lang="ts">
217
+ import useFile from "../composables/useFile";
218
+ import useOnlineFormConfiguration from "../composables/useOnlineFormConfiguration";
219
+ import useOnlineFormTemplates from "../composables/useOnlineFormTemplates";
220
+
143
221
  const prop = defineProps({
144
222
  mode: {
145
223
  type: String,
@@ -168,25 +246,43 @@ const emit = defineEmits(["cancel", "success"]);
168
246
  const validForm = ref(false);
169
247
  const disable = ref(false);
170
248
  const message = ref("");
249
+ const selectedTemplate = ref<string | null>(null);
250
+
251
+ const { formTemplates, templateNames } = useOnlineFormTemplates();
252
+
253
+ function applyTemplate(name: string | null) {
254
+ if (!name) return;
255
+ const template = formTemplates[name];
256
+ if (!template) return;
257
+ onlineForm.value.formType = name;
258
+ onlineForm.value.inputs = template.map((f) => ({ ...f }));
259
+ }
171
260
 
172
261
  const route = useRoute();
173
262
  const siteId = route.params.site as string;
174
263
  const orgId = route.params.org as string;
175
264
 
176
- const attachments = ref<any>([
177
- {
178
- _id: "",
179
- name: "",
180
- attachment: "",
181
- type: "",
182
- },
183
- ]);
265
+ const { addFile, getFileUrl } = useFile();
266
+ const { create: _add, updateById: _update, getAll: _getAll } = useOnlineFormConfiguration();
267
+
268
+ const { data: existingFormsData } = useLazyAsyncData(
269
+ `online-form-configurations-filter-${siteId}-${Date.now()}`,
270
+ () => _getAll({ site: siteId, org: orgId, limit: 100 })
271
+ );
272
+
273
+ const availableTemplateNames = computed(() => {
274
+ const existing = new Set(existingFormsData.value?.items?.map((f: any) => f.formType) ?? []);
275
+ return templateNames.filter((name) => !existing.has(name));
276
+ });
184
277
 
185
- const { getDocumentsBySiteId: _getAllDocuments } = useDocument();
186
- const { add: _add, updateOnlineFormById: _update } = useOnlineForm();
278
+ const isDragOver = ref(false);
279
+ const isUploading = ref(false);
280
+ const attachmentError = ref("");
281
+ const attachmentFileName = ref("");
282
+ const fileInputRef = ref<HTMLInputElement | null>(null);
187
283
 
188
284
  const onlineForm = ref<Record<string, any>>({
189
- name: "",
285
+ formType: "",
190
286
  attachment: "",
191
287
  inputs: [
192
288
  {
@@ -207,37 +303,43 @@ const dataTypes = ref([
207
303
  "Checkbox",
208
304
  ]);
209
305
 
210
- const {
211
- data: getDocumentReq,
212
- refresh: getDocuments,
213
- status: getAllReqStatus,
214
- } = useLazyAsyncData("get-all-documents", () =>
215
- _getAllDocuments(siteId, "Online Form")
216
- );
217
-
218
- watchEffect(() => {
219
- if (getDocumentReq.value) {
220
- attachments.value = getDocumentReq.value;
221
- }
222
- });
223
-
224
306
  if (prop.mode === "edit") {
225
307
  onlineForm.value.attachment = prop.onlineForm.attachment;
226
- onlineForm.value.name = prop.onlineForm.name;
308
+ onlineForm.value.formType = prop.onlineForm.formType;
227
309
  onlineForm.value.inputs = prop.onlineForm.inputs;
228
310
  }
229
311
 
230
- watch(
231
- () => onlineForm.value.attachment,
232
- (newVal) => {
233
- if (newVal) {
234
- const val = attachments.value.find(
235
- (attachment: any) => attachment.attachment === newVal
236
- );
237
- onlineForm.value.name = val.name;
238
- }
312
+ function handleDrop(e: DragEvent) {
313
+ isDragOver.value = false;
314
+ const file = e.dataTransfer?.files[0];
315
+ if (file) uploadAttachment(file);
316
+ }
317
+
318
+ function handleFileInputChange(e: Event) {
319
+ const file = (e.target as HTMLInputElement).files?.[0];
320
+ if (file) uploadAttachment(file);
321
+ }
322
+
323
+ async function uploadAttachment(file: File) {
324
+ isUploading.value = true;
325
+ attachmentError.value = "";
326
+ try {
327
+ const res = await addFile(file);
328
+ onlineForm.value.attachment = res.id;
329
+ attachmentFileName.value = file.name;
330
+ } catch (e: any) {
331
+ attachmentError.value = e?.message || "Upload failed. Please try again.";
332
+ } finally {
333
+ isUploading.value = false;
239
334
  }
240
- );
335
+ }
336
+
337
+ function clearAttachment() {
338
+ onlineForm.value.attachment = "";
339
+ attachmentFileName.value = "";
340
+ attachmentError.value = "";
341
+ if (fileInputRef.value) fileInputRef.value.value = "";
342
+ }
241
343
 
242
344
  function cancel() {
243
345
  emit("cancel");
@@ -249,6 +351,7 @@ function addNewInput() {
249
351
  dataType: "",
250
352
  required: true,
251
353
  multiple: false,
354
+ options: [],
252
355
  });
253
356
  }
254
357
 
@@ -257,26 +360,32 @@ async function submit() {
257
360
  disable.value = true;
258
361
  try {
259
362
  if (prop.mode === "add") {
260
- const payload = {
261
- name: onlineForm.value.name,
262
- attachment: onlineForm.value.attachment,
263
- inputs: onlineForm.value.inputs,
363
+ const fields: Record<string, string> = Object.fromEntries(
364
+ onlineForm.value.inputs.map((input: any, i: number) => [String(i), JSON.stringify(input)])
365
+ );
366
+ if (onlineForm.value.attachment) fields["attachment"] = onlineForm.value.attachment;
367
+
368
+ await _add({
369
+ formType: onlineForm.value.formType,
370
+ fields,
264
371
  status: "active",
265
372
  org: orgId,
266
373
  site: siteId,
267
- };
268
-
269
- await _add(payload);
374
+ });
270
375
  }
271
376
 
272
377
  if (prop.mode === "edit") {
273
- const payload = {
274
- name: onlineForm.value.name,
275
- attachment: onlineForm.value.attachment,
276
- inputs: onlineForm.value.inputs,
277
- };
378
+ const fields: Record<string, string> = Object.fromEntries(
379
+ onlineForm.value.inputs.map((input: any, i: number) => [String(i), JSON.stringify(input)])
380
+ );
381
+ if (onlineForm.value.attachment) fields["attachment"] = onlineForm.value.attachment;
278
382
 
279
- await _update(prop.onlineForm._id ?? "", payload);
383
+ await _update(prop.onlineForm._id ?? "", {
384
+ _id: prop.onlineForm._id ?? "",
385
+ formType: onlineForm.value.formType,
386
+ fields,
387
+ status: onlineForm.value.status || "active",
388
+ });
280
389
  }
281
390
 
282
391
  emit("success");
@@ -288,3 +397,28 @@ async function submit() {
288
397
  }
289
398
  }
290
399
  </script>
400
+
401
+ <style scoped>
402
+ .upload-drop-zone {
403
+ border: 2px dashed rgba(0, 0, 0, 0.2);
404
+ border-radius: 8px;
405
+ padding: 18px 12px;
406
+ display: flex;
407
+ flex-direction: column;
408
+ align-items: center;
409
+ justify-content: center;
410
+ cursor: pointer;
411
+ min-height: 80px;
412
+ transition: border-color 0.2s, background-color 0.2s;
413
+ user-select: none;
414
+ }
415
+ .upload-drop-zone:hover,
416
+ .upload-drop-zone.drag-over {
417
+ border-color: rgb(var(--v-theme-primary));
418
+ background-color: rgba(var(--v-theme-primary), 0.04);
419
+ }
420
+ .upload-drop-zone.is-uploading {
421
+ pointer-events: none;
422
+ opacity: 0.65;
423
+ }
424
+ </style>