@7365admin1/layer-common 3.0.2 → 3.0.5

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.
@@ -2,60 +2,90 @@
2
2
  <v-card width="100%">
3
3
  <v-toolbar>
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-h5 text-capitalize">
6
- {{ prop.mode }} Online Form
5
+ <span class="font-weight-bold text-h5">
6
+ {{ prop.mode === "edit" ? "Edit" : "Add" }} Online Form
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
+ <v-card-text style="max-height: 76vh; overflow-y: auto" class="pa-0">
39
12
  <v-form v-model="validForm" :disabled="disable">
40
- <v-col cols="12" class="px-6">
41
- <InputLabel class="text-capitalize" title="Attachment (Optional)" />
42
13
 
43
- <div v-if="onlineForm.attachment" class="d-flex align-center" style="gap:8px">
14
+ <!-- Form Type -->
15
+ <v-col cols="12" class="px-6 pt-4 pb-0">
16
+ <InputLabel title="Form Type" />
17
+
18
+ <!-- Add mode: selectable list -->
19
+ <v-list
20
+ v-if="prop.mode !== 'edit'"
21
+ density="compact"
22
+ lines="one"
23
+ max-height="220"
24
+ style="overflow-y: auto; border: 1px solid rgba(0,0,0,0.18); border-radius: 4px;"
25
+ class="pa-0"
26
+ >
27
+ <v-list-item
28
+ v-if="availableFormTypes.length === 0"
29
+ disabled
30
+ title="All forms have already been added for this site"
31
+ class="text-caption text-medium-emphasis"
32
+ />
33
+ <v-list-item
34
+ v-for="name in availableFormTypes"
35
+ :key="name"
36
+ :title="name"
37
+ :active="selectedFormType === name"
38
+ active-color="primary"
39
+ class="text-caption"
40
+ @click="selectedFormType = name"
41
+ />
42
+ </v-list>
43
+
44
+ <!-- Edit mode: read-only label -->
45
+ <v-chip v-else color="primary" variant="tonal" size="small">
46
+ {{ prop.onlineForm.formType }}
47
+ </v-chip>
48
+ </v-col>
49
+
50
+ <v-divider class="mt-4" />
51
+
52
+ <!-- Status (edit mode only) -->
53
+ <v-col v-if="prop.mode === 'edit'" cols="12" class="px-6 pt-3 pb-0">
54
+ <InputLabel title="Status" />
55
+ <v-select
56
+ v-model="formStatus"
57
+ :items="['active', 'inactive']"
58
+ density="comfortable"
59
+ />
60
+ </v-col>
61
+
62
+ <!-- Attachment -->
63
+ <v-col cols="12" class="px-6 pt-3 pb-4">
64
+ <InputLabel title="Attachment (Optional)" />
65
+
66
+ <div v-if="attachmentId" class="d-flex align-center" style="gap: 8px">
44
67
  <v-card
45
68
  variant="outlined"
46
69
  border="thin"
47
70
  class="px-3 flex-grow-1 text-caption"
48
71
  height="40"
49
- :href="getFileUrl(onlineForm.attachment)"
72
+ :href="getFileUrl(attachmentId)"
50
73
  target="_blank"
51
74
  >
52
- <v-row no-gutters class="fill-height" align="center" style="gap:6px">
75
+ <v-row no-gutters class="fill-height" align="center" style="gap: 6px">
53
76
  <v-icon size="15">mdi-file-document-outline</v-icon>
54
- {{ attachmentFileName || 'View attachment' }}
77
+ {{ attachmentFileName || "View attachment" }}
55
78
  <v-icon size="13" class="ml-auto">mdi-open-in-new</v-icon>
56
79
  </v-row>
57
80
  </v-card>
58
- <v-btn icon variant="text" size="small" density="comfortable" :disabled="disable" @click="clearAttachment">
81
+ <v-btn
82
+ icon
83
+ variant="text"
84
+ size="small"
85
+ density="comfortable"
86
+ :disabled="disable"
87
+ @click="clearAttachment"
88
+ >
59
89
  <v-icon size="17">mdi-close</v-icon>
60
90
  </v-btn>
61
91
  </div>
@@ -69,117 +99,45 @@
69
99
  @drop.prevent="handleDrop"
70
100
  @click="fileInputRef?.click()"
71
101
  >
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">
102
+ <v-progress-circular
103
+ v-if="isUploading"
104
+ indeterminate
105
+ size="24"
106
+ width="2"
107
+ color="primary"
108
+ class="mb-1"
109
+ />
110
+ <v-icon
111
+ v-else
112
+ size="26"
113
+ :color="isDragOver ? 'primary' : undefined"
114
+ class="mb-1 text-medium-emphasis"
115
+ >
74
116
  mdi-cloud-upload-outline
75
117
  </v-icon>
76
118
  <span class="text-caption text-medium-emphasis">
77
- {{ isUploading ? 'Uploading…' : 'Drop file here or click to browse' }}
119
+ {{ isUploading ? "Uploading…" : "Drop file here or click to browse" }}
78
120
  </span>
79
121
  <input
80
122
  ref="fileInputRef"
81
123
  type="file"
82
124
  accept=".pdf,application/pdf,image/*"
83
- style="display:none"
125
+ style="display: none"
84
126
  @change="handleFileInputChange"
85
127
  />
86
128
  </div>
87
129
 
88
- <span v-if="attachmentError" class="text-caption text-error d-block mt-1">{{ attachmentError }}</span>
89
- </v-col>
90
-
91
- <v-col cols="12" class="px-6">
92
- <InputLabel class="text-capitalize" title="Name" />
93
- <v-text-field
94
- v-model.trim="onlineForm.formType"
95
- density="comfortable"
96
- ></v-text-field>
130
+ <span v-if="attachmentError" class="text-caption text-error d-block mt-1">
131
+ {{ attachmentError }}
132
+ </span>
97
133
  </v-col>
98
134
 
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>
119
- <v-row class="px-6">
120
- <v-col cols="6">
121
- <InputLabel title="Label" />
122
- <v-text-field
123
- v-model.trim="inputLabel.label"
124
- density="comfortable"
125
- />
126
- </v-col>
127
- <v-col cols="6">
128
- <InputLabel title="Data Type" />
129
- <v-select v-model="inputLabel.dataType" :items="dataTypes" />
130
- </v-col>
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
143
- />
144
- </v-col>
145
- <v-col cols="6">
146
- <InputLabel title="Required" />
147
- <v-checkbox v-model="inputLabel.required" />
148
- </v-col>
149
- <v-col cols="6">
150
- <InputLabel title="Multiple" />
151
- <v-checkbox v-model="inputLabel.multiple" />
152
- </v-col>
153
- </v-row>
154
- <v-divider class="my-3" />
155
- </template>
156
-
157
- <v-col cols="12" class="px-6 pb-4">
158
- <v-btn
159
- block
160
- variant="outlined"
161
- color="primary"
162
- class="text-none"
163
- prepend-icon="mdi-plus"
164
- @click="addNewInput"
165
- >
166
- Add Field
167
- </v-btn>
168
- </v-col>
169
-
170
- <v-col cols="12">
171
- <v-row no-gutters>
172
- <v-col cols="12" class="text-center">
173
- <span
174
- class="text-none text-subtitle-2 font-weight-medium text-error"
175
- >
176
- {{ message }}
177
- </span>
178
- </v-col>
179
- </v-row>
135
+ <v-col v-if="message" cols="12" class="px-6">
136
+ <span class="text-caption text-error">{{ message }}</span>
180
137
  </v-col>
181
138
  </v-form>
182
139
  </v-card-text>
140
+
183
141
  <v-toolbar density="compact">
184
142
  <v-row no-gutters>
185
143
  <v-col cols="6">
@@ -189,7 +147,7 @@
189
147
  variant="text"
190
148
  class="text-none"
191
149
  size="48"
192
- @click="cancel"
150
+ @click="emit('cancel')"
193
151
  >
194
152
  Cancel
195
153
  </v-btn>
@@ -202,9 +160,9 @@
202
160
  color="black"
203
161
  class="text-none"
204
162
  size="48"
205
- :disabled="!validForm || disable || onlineForm.formType == ''"
206
- @click="submit"
163
+ :disabled="!canSubmit || disable"
207
164
  :loading="disable"
165
+ @click="submit"
208
166
  >
209
167
  {{ prop.mode === "edit" ? "Update" : "Submit" }}
210
168
  </v-btn>
@@ -216,99 +174,54 @@
216
174
  <script setup lang="ts">
217
175
  import useFile from "../composables/useFile";
218
176
  import useOnlineFormConfiguration from "../composables/useOnlineFormConfiguration";
219
- import useOnlineFormTemplates from "../composables/useOnlineFormTemplates";
177
+ import useOnlineFormPages from "../composables/useOnlineFormPages";
220
178
 
221
179
  const prop = defineProps({
222
- mode: {
223
- type: String,
224
- default: "add",
225
- },
180
+ mode: { type: String, default: "add" },
226
181
  onlineForm: {
227
- type: Object as PropType<TOnlineForm>,
228
- default: () => ({
229
- name: "",
230
- attachment: "",
231
- inputs: [
232
- {
233
- label: "",
234
- dataType: "",
235
- required: true,
236
- multiple: false,
237
- },
238
- ],
239
- status: "active",
240
- }),
182
+ type: Object as PropType<Record<string, any>>,
183
+ default: () => ({}),
241
184
  },
242
185
  });
243
186
 
244
187
  const emit = defineEmits(["cancel", "success"]);
245
188
 
246
- const validForm = ref(false);
247
- const disable = ref(false);
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
- }
189
+ const { formPages } = useOnlineFormPages();
190
+ const { create: _add, updateById: _update, getAll: _getAll } = useOnlineFormConfiguration();
191
+ const { addFile, getFileUrl } = useFile();
260
192
 
261
193
  const route = useRoute();
262
194
  const siteId = route.params.site as string;
263
195
  const orgId = route.params.org as string;
264
196
 
265
- const { addFile, getFileUrl } = useFile();
266
- const { create: _add, updateById: _update, getAll: _getAll } = useOnlineFormConfiguration();
197
+ // --- form type selection (add mode) ---
198
+ const selectedFormType = ref("");
267
199
 
268
200
  const { data: existingFormsData } = useLazyAsyncData(
269
- `online-form-configurations-filter-${siteId}-${Date.now()}`,
201
+ `online-form-config-existing-${siteId}`,
270
202
  () => _getAll({ site: siteId, org: orgId, status: "active", limit: 100 })
271
203
  );
272
204
 
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));
205
+ const availableFormTypes = computed(() => {
206
+ const existing = new Set(
207
+ existingFormsData.value?.items?.map((f: any) => f.formType) ?? []
208
+ );
209
+ return Object.keys(formPages)
210
+ .filter((name) => !existing.has(name))
211
+ .sort((a, b) => a.localeCompare(b));
276
212
  });
277
213
 
214
+ // --- status (edit mode) ---
215
+ const formStatus = ref<string>(prop.onlineForm?.status || "active");
216
+
217
+ // --- attachment ---
218
+ const attachmentId = ref<string>(prop.onlineForm?.attachment || prop.onlineForm?.fields?.attachment || "");
219
+ const attachmentFileName = ref("");
220
+ const attachmentError = ref("");
278
221
  const isDragOver = ref(false);
279
222
  const isUploading = ref(false);
280
- const attachmentError = ref("");
281
- const attachmentFileName = ref("");
282
223
  const fileInputRef = ref<HTMLInputElement | null>(null);
283
224
 
284
- const onlineForm = ref<Record<string, any>>({
285
- formType: "",
286
- attachment: "",
287
- inputs: [
288
- {
289
- label: "Name",
290
- dataType: "Text Input",
291
- required: true,
292
- multiple: false,
293
- },
294
- ],
295
- status: "active",
296
- });
297
-
298
- const dataTypes = ref([
299
- "Text Input",
300
- "Text Area",
301
- "Date Input",
302
- "Select",
303
- "Checkbox",
304
- ]);
305
-
306
- if (prop.mode === "edit") {
307
- onlineForm.value.attachment = prop.onlineForm.attachment;
308
- onlineForm.value.formType = prop.onlineForm.formType;
309
- onlineForm.value.inputs = prop.onlineForm.inputs;
310
- }
311
-
312
225
  function handleDrop(e: DragEvent) {
313
226
  isDragOver.value = false;
314
227
  const file = e.dataTransfer?.files[0];
@@ -325,7 +238,7 @@ async function uploadAttachment(file: File) {
325
238
  attachmentError.value = "";
326
239
  try {
327
240
  const res = await addFile(file);
328
- onlineForm.value.attachment = res.id;
241
+ attachmentId.value = res.id;
329
242
  attachmentFileName.value = file.name;
330
243
  } catch (e: any) {
331
244
  attachmentError.value = e?.message || "Upload failed. Please try again.";
@@ -335,63 +248,52 @@ async function uploadAttachment(file: File) {
335
248
  }
336
249
 
337
250
  function clearAttachment() {
338
- onlineForm.value.attachment = "";
251
+ attachmentId.value = "";
339
252
  attachmentFileName.value = "";
340
253
  attachmentError.value = "";
341
254
  if (fileInputRef.value) fileInputRef.value.value = "";
342
255
  }
343
256
 
344
- function cancel() {
345
- emit("cancel");
346
- }
257
+ // --- submit ---
258
+ const validForm = ref(false);
259
+ const disable = ref(false);
260
+ const message = ref("");
347
261
 
348
- function addNewInput() {
349
- onlineForm.value.inputs.push({
350
- label: "",
351
- dataType: "",
352
- required: true,
353
- multiple: false,
354
- options: [],
355
- });
262
+ const canSubmit = computed(() =>
263
+ prop.mode === "edit" ? true : selectedFormType.value !== ""
264
+ );
265
+
266
+ function buildFields(formType: string): Record<string, string> {
267
+ const def = formPages[formType];
268
+ const fields: Record<string, string> = {};
269
+ if (def) fields.blocks = JSON.stringify(def.blocks);
270
+ if (attachmentId.value) fields.attachment = attachmentId.value;
271
+ return fields;
356
272
  }
357
273
 
358
274
  async function submit() {
359
- // console.log("submit", onlineForm.value);
360
275
  disable.value = true;
276
+ message.value = "";
361
277
  try {
362
278
  if (prop.mode === "add") {
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
279
  await _add({
369
- formType: onlineForm.value.formType,
370
- fields,
280
+ formType: selectedFormType.value,
281
+ fields: buildFields(selectedFormType.value),
371
282
  status: "active",
372
283
  org: orgId,
373
284
  site: siteId,
374
285
  });
375
- }
376
-
377
- if (prop.mode === "edit") {
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;
382
-
286
+ } else {
287
+ const formType = prop.onlineForm.formType as string;
383
288
  await _update(prop.onlineForm._id ?? "", {
384
- _id: prop.onlineForm._id ?? "",
385
- formType: onlineForm.value.formType,
386
- fields,
387
- status: onlineForm.value.status || "active",
289
+ formType,
290
+ fields: buildFields(formType),
291
+ status: formStatus.value,
388
292
  });
389
293
  }
390
-
391
294
  emit("success");
392
295
  } catch (error: any) {
393
- message.value =
394
- error.response?._data?.message || "Failed to create document";
296
+ message.value = error?.response?._data?.message || "Failed to save form";
395
297
  } finally {
396
298
  disable.value = false;
397
299
  }