pgo-ui 1.1.13 → 1.1.16
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/dist/Radio-C69Y0KPR.js +4 -0
- package/dist/index-DJX1IK1P.js +66771 -0
- package/dist/index.es.js +73 -66711
- package/dist/index.umd.js +75 -76
- package/dist/pgo-ui.css +1 -1
- package/package.json +1 -1
- package/src/components/pgo/AppBar.vue +1 -1
- package/src/components/pgo/DataTable.vue +15 -6
- package/src/components/pgo/forms/DynamicForm.vue +642 -442
- package/src/pgo-components/lib/componentConfig.js +100 -16
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Modal
|
|
3
|
-
v-model="open"
|
|
2
|
+
<Modal
|
|
3
|
+
v-model="open"
|
|
4
4
|
persistent
|
|
5
5
|
padding="p-3"
|
|
6
6
|
:bg="bg"
|
|
7
|
-
:width="width"
|
|
8
7
|
>
|
|
9
8
|
<Banner
|
|
10
9
|
v-if="form?.banner"
|
|
11
10
|
v-bind="form?.banner"
|
|
12
|
-
bannerClass="mb-4"
|
|
11
|
+
bannerClass="mb-4"
|
|
13
12
|
/>
|
|
14
|
-
<Form
|
|
13
|
+
<Form
|
|
15
14
|
v-model="valid"
|
|
16
15
|
ref="formRef"
|
|
17
|
-
>
|
|
18
|
-
<input
|
|
16
|
+
>
|
|
17
|
+
<input
|
|
19
18
|
v-if="props.mode === 'edit' && props.editItemId"
|
|
20
19
|
v-model="formData.id"
|
|
21
20
|
type="hidden"
|
|
22
21
|
/>
|
|
23
|
-
<!-- Render grouped fields -->
|
|
24
|
-
|
|
22
|
+
<!-- Render grouped fields -->
|
|
23
|
+
|
|
25
24
|
<!-- A0111904 -->
|
|
26
25
|
<template v-if="form.groups">
|
|
27
26
|
<template v-for="(group, id) in form.groups" :key="id">
|
|
@@ -30,11 +29,11 @@
|
|
|
30
29
|
<div v-if="group.title" :class="['absolute top-0 -translate-y-1/2 bg-inherit px-2 text-sm font-semibold mb-2', lang === 'dv' ? 'right-4' : 'left-4']">
|
|
31
30
|
{{ group.title ? useLanguageSelected(group.title, lang) : '' }}
|
|
32
31
|
</div>
|
|
33
|
-
<template
|
|
34
|
-
v-for="(field, index) in getFieldsByGroup(group.name)"
|
|
32
|
+
<template
|
|
33
|
+
v-for="(field, index) in getFieldsByGroup(group.name)"
|
|
35
34
|
:key="field?.key || index"
|
|
36
35
|
>
|
|
37
|
-
<div
|
|
36
|
+
<div
|
|
38
37
|
v-if="field && field.key && shouldShowField(field)"
|
|
39
38
|
:class="columnSpansMap[field.columnSpan] || columnSpansMap[1]"
|
|
40
39
|
>
|
|
@@ -49,15 +48,15 @@
|
|
|
49
48
|
</div>
|
|
50
49
|
</div>
|
|
51
50
|
</template>
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
<!-- MOVE: Render ungrouped fields ONCE after ALL groups -->
|
|
54
53
|
<div v-if="getUngroupedFields().length > 0" class="mb-4">
|
|
55
|
-
<div :class="['grid', gridColsMap[form.numberOfColumns] || gridColsMap[2], 'gap-
|
|
56
|
-
<template
|
|
57
|
-
v-for="(field, index) in getUngroupedFields()"
|
|
54
|
+
<div :class="['grid', gridColsMap[form.numberOfColumns] || gridColsMap[2], 'gap-2']">
|
|
55
|
+
<template
|
|
56
|
+
v-for="(field, index) in getUngroupedFields()"
|
|
58
57
|
:key="field?.key || index"
|
|
59
58
|
>
|
|
60
|
-
<div
|
|
59
|
+
<div
|
|
61
60
|
v-if="field && field.key && shouldShowField(field)"
|
|
62
61
|
:class="columnSpansMap[field.columnSpan] || columnSpansMap[1]"
|
|
63
62
|
>
|
|
@@ -72,14 +71,14 @@
|
|
|
72
71
|
</div>
|
|
73
72
|
</div>
|
|
74
73
|
</template>
|
|
75
|
-
|
|
74
|
+
|
|
76
75
|
<!-- If no groups, render all fields -->
|
|
77
|
-
<div v-else :class="['grid', gridColsMap[form.numberOfColumns] || gridColsMap[2], 'gap-
|
|
78
|
-
<template
|
|
79
|
-
v-for="(field, index) in form.fields"
|
|
76
|
+
<div v-else :class="['grid', gridColsMap[form.numberOfColumns] || gridColsMap[2], 'gap-2']">
|
|
77
|
+
<template
|
|
78
|
+
v-for="(field, index) in form.fields"
|
|
80
79
|
:key="field?.key || index"
|
|
81
80
|
>
|
|
82
|
-
<div
|
|
81
|
+
<div
|
|
83
82
|
v-if="field && field.key && shouldShowField(field)"
|
|
84
83
|
:class="columnSpansMap[field.columnSpan] || columnSpansMap[1]"
|
|
85
84
|
>
|
|
@@ -94,42 +93,37 @@
|
|
|
94
93
|
</div>
|
|
95
94
|
</Form>
|
|
96
95
|
<template #footer>
|
|
97
|
-
<div class="flex justify-end gap-2">
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
label="buttons.draft"
|
|
103
|
-
color="primary"
|
|
104
|
-
:loading="isSubmitting && clickedButton === 'draft'"
|
|
105
|
-
:disabled="isSubmitting || !valid"
|
|
106
|
-
@click="handleSubmit('draft')"
|
|
107
|
-
/>
|
|
108
|
-
|
|
109
|
-
<!-- UPDATE: visible after draft saved or opened in edit mode -->
|
|
110
|
-
<Button
|
|
111
|
-
v-if="formMode === 'edit'"
|
|
112
|
-
label="buttons.update"
|
|
113
|
-
color="warning"
|
|
114
|
-
:loading="isSubmitting && clickedButton === 'update'"
|
|
115
|
-
:disabled="isSubmitting || !isFormDirty || !valid"
|
|
96
|
+
<div class="flex justify-end gap-2 ">
|
|
97
|
+
<Button v-if="mode == 'edit'"
|
|
98
|
+
label="buttons.update"
|
|
99
|
+
color="primary"
|
|
100
|
+
:loading="isSubmitting"
|
|
116
101
|
@click="handleSubmit('update')"
|
|
117
102
|
/>
|
|
103
|
+
<template v-else>
|
|
104
|
+
<Button
|
|
105
|
+
v-if="items || draftSubmitted"
|
|
106
|
+
label="buttons.submit"
|
|
107
|
+
color="primary"
|
|
108
|
+
:loading="isSubmitting"
|
|
109
|
+
:disabled="!valid || isSubmitting"
|
|
110
|
+
|
|
111
|
+
@click="handleSubmit('submitted')"
|
|
112
|
+
/>
|
|
113
|
+
<Button
|
|
114
|
+
label="buttons.draft"
|
|
115
|
+
color="primary"
|
|
116
|
+
:loading="isSubmitting"
|
|
117
|
+
:disabled="!valid || isSubmitting || draftSubmitted"
|
|
118
|
+
|
|
119
|
+
@click="handleSubmit('draft')"
|
|
120
|
+
/>
|
|
121
|
+
<!-- <Button v-if="items" label="buttons.submit" color="primary" @click="handleSubmit('submit')"/> -->
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
<Button
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
color="primary"
|
|
124
|
-
:loading="isSubmitting && clickedButton === 'submit'"
|
|
125
|
-
:disabled="isSubmitting || !valid"
|
|
126
|
-
@click="handleSubmit('submit')"
|
|
127
|
-
/>
|
|
128
|
-
|
|
129
|
-
<!-- CLOSE: always visible -->
|
|
130
|
-
<Button
|
|
131
|
-
label="buttons.close"
|
|
132
|
-
color="gray"
|
|
123
|
+
</template>
|
|
124
|
+
<Button
|
|
125
|
+
label="buttons.close"
|
|
126
|
+
color="gray"
|
|
133
127
|
:disabled="isSubmitting"
|
|
134
128
|
@click="handleClose"
|
|
135
129
|
/>
|
|
@@ -144,31 +138,27 @@
|
|
|
144
138
|
import Modal from '../Modal.vue'
|
|
145
139
|
import { Banner, LabelField } from '../index'
|
|
146
140
|
import { gridColsMap, columnSpansMap, useLanguageSelected, initializeFunctions } from '@/pgo-components/lib/componentConfig';
|
|
147
|
-
|
|
141
|
+
|
|
148
142
|
const rules = inject('validationRules')
|
|
149
143
|
// const baseUrl = inject('baseUrl')
|
|
150
144
|
const { language } = inject('i18n')
|
|
151
145
|
|
|
152
146
|
const valid = ref(false);
|
|
153
147
|
const formId = ref(null);
|
|
154
|
-
const relationId = ref(null);
|
|
155
148
|
const open = ref(true);
|
|
156
149
|
const formRef = ref(null);
|
|
157
150
|
const isSubmitting = ref(false);
|
|
158
151
|
const draftSubmitted = ref(false);
|
|
159
152
|
const clickedButton = ref('');
|
|
160
|
-
const isPreFilled = ref(false)
|
|
161
|
-
const originalFormData = ref({})
|
|
162
|
-
|
|
163
153
|
const emit = defineEmits([
|
|
164
|
-
'update:modelValue',
|
|
165
|
-
'submit:success',
|
|
166
|
-
'submit:error',
|
|
154
|
+
'update:modelValue',
|
|
155
|
+
'submit:success',
|
|
156
|
+
'submit:error',
|
|
167
157
|
'close',
|
|
168
158
|
'field:event',
|
|
169
159
|
'view-pdf'
|
|
170
160
|
]);
|
|
171
|
-
|
|
161
|
+
|
|
172
162
|
const props = defineProps({
|
|
173
163
|
modelValue: { type: Boolean, default: false },
|
|
174
164
|
form: { type: Object, required: true },
|
|
@@ -177,19 +167,17 @@
|
|
|
177
167
|
crudLink:{ type: String },
|
|
178
168
|
editItemId: { type: [String, Number], default: null },
|
|
179
169
|
mode: { type: String, default: 'create', enum: ['create', 'edit'] },
|
|
180
|
-
|
|
170
|
+
|
|
181
171
|
bg: { type: String, default: 'bg-background-color' },
|
|
182
172
|
});
|
|
183
173
|
|
|
184
174
|
const api = inject('api');
|
|
185
175
|
const snackbar = inject('snackbar');
|
|
186
176
|
|
|
187
|
-
// Initialize form data from fields
|
|
177
|
+
// Initialize form data from fields
|
|
188
178
|
const formData = reactive({});
|
|
189
179
|
const FormDataList = ref({});
|
|
190
180
|
|
|
191
|
-
const formMode = ref(props.mode)
|
|
192
|
-
|
|
193
181
|
// Initialize variables as reactive refs
|
|
194
182
|
const formVariables = reactive({});
|
|
195
183
|
|
|
@@ -199,18 +187,15 @@
|
|
|
199
187
|
|
|
200
188
|
// Enhanced evaluation context that includes props and mode
|
|
201
189
|
const createEvaluationContext = () => {
|
|
202
|
-
|
|
203
|
-
formData,
|
|
204
|
-
formVariables,
|
|
205
|
-
variables: formVariables,
|
|
206
|
-
props,
|
|
207
|
-
mode: formMode.value,
|
|
208
|
-
formType: formType.value,
|
|
209
|
-
isEdit: formMode.value === 'edit',
|
|
210
|
-
isCreate: formMode.value === 'create',
|
|
211
|
-
...formVariables,
|
|
190
|
+
return {
|
|
212
191
|
...formData,
|
|
213
|
-
|
|
192
|
+
...formVariables,
|
|
193
|
+
mode: props.mode,
|
|
194
|
+
formType: formType.value,
|
|
195
|
+
props: props,
|
|
196
|
+
isEdit: props.mode === 'edit',
|
|
197
|
+
isCreate: props.mode === 'create'
|
|
198
|
+
};
|
|
214
199
|
};
|
|
215
200
|
|
|
216
201
|
// const fileList = ref([
|
|
@@ -250,84 +235,56 @@
|
|
|
250
235
|
return formData[fieldKey];
|
|
251
236
|
};
|
|
252
237
|
|
|
253
|
-
// Helper function to set field value
|
|
238
|
+
// Helper function to set field value
|
|
254
239
|
const setFieldValue = (fieldKey, value) => {
|
|
255
|
-
formData
|
|
240
|
+
if (formData.hasOwnProperty(fieldKey)) {
|
|
241
|
+
formData[fieldKey] = value;
|
|
242
|
+
}
|
|
256
243
|
};
|
|
257
244
|
|
|
258
|
-
const isFormDirty = computed(() => {
|
|
259
|
-
if (!isPreFilled.value) return false
|
|
260
|
-
return Object.keys(originalFormData.value).some(key => {
|
|
261
|
-
const original = originalFormData.value[key]
|
|
262
|
-
const current = formData[key]
|
|
263
|
-
// Handle null/undefined equality
|
|
264
|
-
if (original === null || original === undefined) {
|
|
265
|
-
return current !== null && current !== undefined && current !== ''
|
|
266
|
-
}
|
|
267
|
-
// Handle objects/arrays
|
|
268
|
-
if (typeof original === 'object') {
|
|
269
|
-
return JSON.stringify(original) !== JSON.stringify(current)
|
|
270
|
-
}
|
|
271
|
-
return original !== current
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
|
|
275
245
|
// Initialize form data with default values or items data
|
|
276
|
-
const initializeFormData = () => {
|
|
246
|
+
const initializeFormData = () => {
|
|
247
|
+
// Add safety check for form.fields
|
|
277
248
|
if (!props.form.fields || !Array.isArray(props.form.fields)) {
|
|
278
249
|
return;
|
|
279
250
|
}
|
|
280
|
-
|
|
251
|
+
|
|
281
252
|
props.form.fields.forEach(field => {
|
|
282
253
|
if (!field || !field.key) return;
|
|
283
254
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
255
|
+
if (props.items && props.items[field.key] !== undefined) {
|
|
256
|
+
formData[field.key] = props.items[field.key];
|
|
257
|
+
} else if (field.default !== undefined) {
|
|
258
|
+
formData[field.key] = field.default;
|
|
259
|
+
} else if (field.value !== undefined) {
|
|
260
|
+
formData[field.key] = field.value;
|
|
261
|
+
} else {
|
|
262
|
+
// Set default value based on type
|
|
263
|
+
switch (field.type) {
|
|
264
|
+
case 'number':
|
|
265
|
+
case 'integer':
|
|
266
|
+
formData[field.key] = null;
|
|
267
|
+
break;
|
|
268
|
+
case 'boolean':
|
|
269
|
+
formData[field.key] = false;
|
|
270
|
+
break;
|
|
271
|
+
case 'array':
|
|
272
|
+
formData[field.key] = null;
|
|
273
|
+
break;
|
|
274
|
+
case 'object':
|
|
275
|
+
formData[field.key] = null;
|
|
276
|
+
break;
|
|
277
|
+
case 'string':
|
|
278
|
+
default:
|
|
279
|
+
if (field.inputType === 'checkbox') {
|
|
280
|
+
formData[field.key] = false; // Default checkboxes to false
|
|
281
|
+
} else {
|
|
282
|
+
formData[field.key] = null;
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
295
285
|
}
|
|
296
286
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
// 2. Use field default value
|
|
300
|
-
if (field.default !== undefined) {
|
|
301
|
-
formData[field.key] = field.default
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// 3. Use field value
|
|
306
|
-
if (field.value !== undefined) {
|
|
307
|
-
formData[field.key] = field.value
|
|
308
|
-
return
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// 4. Set type-based default (fallback)
|
|
312
|
-
switch (field.type) {
|
|
313
|
-
case 'number':
|
|
314
|
-
case 'integer':
|
|
315
|
-
formData[field.key] = null
|
|
316
|
-
break
|
|
317
|
-
case 'boolean':
|
|
318
|
-
formData[field.key] = false
|
|
319
|
-
break
|
|
320
|
-
case 'array':
|
|
321
|
-
case 'object':
|
|
322
|
-
formData[field.key] = null
|
|
323
|
-
break
|
|
324
|
-
case 'string':
|
|
325
|
-
default:
|
|
326
|
-
formData[field.key] = field.inputType === 'checkbox' ? false : null
|
|
327
|
-
break
|
|
328
|
-
}
|
|
329
287
|
});
|
|
330
|
-
|
|
331
288
|
// console.log('Initialized form data:', formData);
|
|
332
289
|
};
|
|
333
290
|
|
|
@@ -342,35 +299,23 @@ const initializeFormData = () => {
|
|
|
342
299
|
|
|
343
300
|
const initializeAllFunctions = () => {
|
|
344
301
|
if (props.form.functions) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
context[`arg${index}`] = arg
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
// Pass function body directly — NOT wrapped with return()
|
|
362
|
-
// Function bodies can have multiple statements and returns
|
|
363
|
-
return safeEval(functionBody, context)
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.warn(`Error executing function "${name}":`, error.message)
|
|
366
|
-
console.warn('Function body was:', functionBody)
|
|
367
|
-
return null
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
console.warn(`Error compiling function "${name}":`, error.message)
|
|
302
|
+
// Create context that the functions will have access to
|
|
303
|
+
const functionContext = {
|
|
304
|
+
formData,
|
|
305
|
+
formVariables,
|
|
306
|
+
snackbar,
|
|
307
|
+
api,
|
|
308
|
+
emit,
|
|
309
|
+
props,
|
|
310
|
+
console,
|
|
311
|
+
variables: formVariables,
|
|
312
|
+
this: {
|
|
313
|
+
variables: formVariables
|
|
372
314
|
}
|
|
373
|
-
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Use the global initializeFunctions helper
|
|
318
|
+
compiledFunctions.value = initializeFunctions(props.form.functions, functionContext);
|
|
374
319
|
}
|
|
375
320
|
};
|
|
376
321
|
|
|
@@ -378,18 +323,273 @@ const initializeFormData = () => {
|
|
|
378
323
|
const executeFunction = (functionName, ...args) => {
|
|
379
324
|
if (compiledFunctions.value[functionName]) {
|
|
380
325
|
try {
|
|
381
|
-
return compiledFunctions.value[functionName](...args)
|
|
326
|
+
return compiledFunctions.value[functionName](...args);
|
|
382
327
|
} catch (error) {
|
|
383
|
-
console.
|
|
384
|
-
|
|
328
|
+
console.error(`Error executing function ${functionName}:`, error);
|
|
329
|
+
snackbar?.show({
|
|
330
|
+
message: `Function ${functionName} execution failed`,
|
|
331
|
+
variant: 'error'
|
|
332
|
+
});
|
|
385
333
|
}
|
|
386
334
|
} else {
|
|
387
|
-
console.warn(`Function
|
|
388
|
-
|
|
335
|
+
console.warn(`Function ${functionName} not found`);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Function to get fields by group name
|
|
340
|
+
const getFieldsByGroup = (groupName) => {
|
|
341
|
+
if (!props.form.fields || !Array.isArray(props.form.fields)) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
return props.form.fields.filter(field => field && field.group === groupName);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const shouldShowGroup = (group) => {
|
|
348
|
+
// Add null check for group
|
|
349
|
+
if (!group) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// If no condition, always show the group
|
|
354
|
+
if (!group.condition) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// Normalize the condition string (trim spaces)
|
|
360
|
+
let expression = group.condition.trim();
|
|
361
|
+
|
|
362
|
+
// Create enhanced evaluation context
|
|
363
|
+
const context = createEvaluationContext();
|
|
364
|
+
|
|
365
|
+
// Helper function to safely evaluate expressions
|
|
366
|
+
const evaluateCondition = (expr) => {
|
|
367
|
+
// Handle various condition patterns:
|
|
368
|
+
|
|
369
|
+
// Pattern 1: Simple variable check "variableName" -> check if variable is truthy
|
|
370
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expr)) {
|
|
371
|
+
return !!context[expr];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Pattern 2: Negation "!variableName"
|
|
375
|
+
if (/^!/.test(expr)) {
|
|
376
|
+
const variableName = expr.substring(1).trim();
|
|
377
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(variableName)) {
|
|
378
|
+
return !context[variableName];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Pattern 3: Complex expression with &&, ||, ===, !==, >, <, etc.
|
|
383
|
+
// Create variable declarations from context
|
|
384
|
+
const varDeclarations = Object.keys(context)
|
|
385
|
+
.map(key => {
|
|
386
|
+
const value = context[key];
|
|
387
|
+
// Properly stringify different value types
|
|
388
|
+
if (value === null || value === undefined) {
|
|
389
|
+
return `const ${key} = null;`;
|
|
390
|
+
} else if (typeof value === 'string') {
|
|
391
|
+
return `const ${key} = "${value.replace(/"/g, '\\"')}";`;
|
|
392
|
+
} else if (typeof value === 'boolean') {
|
|
393
|
+
return `const ${key} = ${value};`;
|
|
394
|
+
} else if (typeof value === 'number') {
|
|
395
|
+
return `const ${key} = ${value};`;
|
|
396
|
+
} else {
|
|
397
|
+
return `const ${key} = ${JSON.stringify(value)};`;
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
.join('\n');
|
|
401
|
+
|
|
402
|
+
// Create and execute the function with the context
|
|
403
|
+
const fn = new Function(varDeclarations + `\nreturn (${expr});`);
|
|
404
|
+
return fn();
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Evaluate the condition expression
|
|
408
|
+
return !!evaluateCondition(expression);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.warn(`Error evaluating group condition "${group.condition}":`, error.message);
|
|
411
|
+
// Fallback: check if the condition is a simple variable reference
|
|
412
|
+
const simpleVariable = group.condition.trim().replace(/^!/, '');
|
|
413
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(simpleVariable)) {
|
|
414
|
+
const context = createEvaluationContext();
|
|
415
|
+
const value = context[simpleVariable];
|
|
416
|
+
return group.condition.trim().startsWith('!') ? !value : !!value;
|
|
417
|
+
}
|
|
418
|
+
return true; // Show group by default if evaluation fails
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// Function to get ungrouped fields
|
|
423
|
+
const getUngroupedFields = () => {
|
|
424
|
+
if (!props.form.fields || !Array.isArray(props.form.fields)) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
return props.form.fields.filter(field => field && !field.group);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Function to check if field should be shown based on condition
|
|
431
|
+
const shouldShowField = (field) => {
|
|
432
|
+
// Add null check for field
|
|
433
|
+
if (!field) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (field.hidden === true) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!field.condition) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
// Normalize the condition string (trim spaces)
|
|
447
|
+
let expression = field.condition.trim();
|
|
448
|
+
|
|
449
|
+
// Create enhanced evaluation context
|
|
450
|
+
const context = createEvaluationContext();
|
|
451
|
+
|
|
452
|
+
// Helper function to safely evaluate expressions
|
|
453
|
+
const evaluateCondition = (expr) => {
|
|
454
|
+
// Handle various condition patterns:
|
|
455
|
+
|
|
456
|
+
// Pattern 1: Simple field check "fieldName" -> check if field is truthy
|
|
457
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expr)) {
|
|
458
|
+
return !!context[expr];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Pattern 2: Negation "!fieldName"
|
|
462
|
+
if (/^!/.test(expr)) {
|
|
463
|
+
const fieldName = expr.substring(1).trim();
|
|
464
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(fieldName)) {
|
|
465
|
+
return !context[fieldName];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Pattern 3: Complex expression with &&, ||, ===, !==, >, <, etc.
|
|
470
|
+
// Create variable declarations from context
|
|
471
|
+
const varDeclarations = Object.keys(context)
|
|
472
|
+
.map(key => {
|
|
473
|
+
const value = context[key];
|
|
474
|
+
// Properly stringify different value types
|
|
475
|
+
if (value === null || value === undefined) {
|
|
476
|
+
return `const ${key} = null;`;
|
|
477
|
+
} else if (typeof value === 'string') {
|
|
478
|
+
return `const ${key} = "${value.replace(/"/g, '\\"')}";`;
|
|
479
|
+
} else if (typeof value === 'boolean') {
|
|
480
|
+
return `const ${key} = ${value};`;
|
|
481
|
+
} else if (typeof value === 'number') {
|
|
482
|
+
return `const ${key} = ${value};`;
|
|
483
|
+
} else {
|
|
484
|
+
return `const ${key} = ${JSON.stringify(value)};`;
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
.join('\n');
|
|
488
|
+
|
|
489
|
+
// Create and execute the function with the context
|
|
490
|
+
const fn = new Function(varDeclarations + `\nreturn (${expr});`);
|
|
491
|
+
return fn();
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Evaluate the condition expression
|
|
495
|
+
return !!evaluateCondition(expression);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.warn(`Error evaluating condition "${field.condition}":`, error.message);
|
|
498
|
+
// Fallback: check if the condition is a simple field reference
|
|
499
|
+
const simpleField = field.condition.trim().replace(/^!/, '');
|
|
500
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(simpleField)) {
|
|
501
|
+
const context = createEvaluationContext();
|
|
502
|
+
const value = context[simpleField];
|
|
503
|
+
return field.condition.trim().startsWith('!') ? !value : !!value;
|
|
504
|
+
}
|
|
505
|
+
return true; // Show field by default if evaluation fails
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const componentMap = computed(() => {
|
|
510
|
+
const map = {}
|
|
511
|
+
const usedTypes = new Set(props.form.fields.map(f => f.inputType?.toLowerCase()))
|
|
512
|
+
|
|
513
|
+
if (usedTypes.has('search') || usedTypes.has('inputsearch')) {
|
|
514
|
+
map['search'] = defineAsyncComponent(() => import('../SearchBox.vue'))
|
|
515
|
+
map['searchbox'] = defineAsyncComponent(() => import('../SearchBox.vue'))
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (usedTypes.has('select')) {
|
|
519
|
+
map['select'] = defineAsyncComponent(() => import('../inputs/Select.vue'))
|
|
520
|
+
}
|
|
521
|
+
if (usedTypes.has('textarea')) {
|
|
522
|
+
map['textarea'] = defineAsyncComponent(() => import('../inputs/Textarea.vue'))
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (usedTypes.has('textfield') || usedTypes.has('text') || usedTypes.has('string')) {
|
|
526
|
+
map['textfield'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
|
|
527
|
+
map['text'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
|
|
528
|
+
map['string'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (usedTypes.has('datepicker') || usedTypes.has('date')) {
|
|
532
|
+
map['datepicker'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
|
|
533
|
+
map['date'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (usedTypes.has('chipgroup')) {
|
|
537
|
+
map['chipgroup'] = defineAsyncComponent(() => import('../buttons/ChipGroup.vue'))
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (usedTypes.has('checkbox')) {
|
|
541
|
+
map['checkbox'] = defineAsyncComponent(() => import('../inputs/Checkbox.vue'))
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (usedTypes.has('radio')) {
|
|
545
|
+
map['radio'] = defineAsyncComponent(() => import('../inputs/Radio.vue'))
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (usedTypes.has('numberfield') || usedTypes.has('number')) {
|
|
549
|
+
map['numberfield'] = defineAsyncComponent(() => import('../inputs/NumberField.vue'))
|
|
550
|
+
map['number'] = defineAsyncComponent(() => import('../inputs/NumberField.vue'))
|
|
551
|
+
}
|
|
552
|
+
if (usedTypes.has('file') || usedTypes.has('filefield')) {
|
|
553
|
+
map['file'] = defineAsyncComponent(() => import('../inputs/FileUpload.vue'))
|
|
554
|
+
map['filefield'] = defineAsyncComponent(() => import('../inputs/FileUpload.vue'))
|
|
555
|
+
}
|
|
556
|
+
if (usedTypes.has('label') || usedTypes.has('labelfield')) {
|
|
557
|
+
map['label'] = defineAsyncComponent(() => import('../inputs/LabelField.vue'))
|
|
558
|
+
map['labelfield'] = defineAsyncComponent(() => import('../inputs/LabelField.vue'))
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return map
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
const getFieldComponent = (type) => {
|
|
565
|
+
const normalizedType = type?.toLowerCase() || 'textfield'
|
|
566
|
+
return componentMap.value[normalizedType] || componentMap.value['textfield']
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Helper function to evaluate JavaScript expressions with context
|
|
570
|
+
const evaluateExpression = (expression, context) => {
|
|
571
|
+
if (typeof expression !== 'string') {
|
|
572
|
+
return expression; // Return as-is if not a string
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// If it's a simple variable name, return the context value
|
|
576
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expression)) {
|
|
577
|
+
return context[expression];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
// Create function with context variables as parameters
|
|
582
|
+
const contextKeys = Object.keys(context);
|
|
583
|
+
const contextValues = Object.values(context);
|
|
584
|
+
const func = new Function(...contextKeys, `return ${expression}`);
|
|
585
|
+
return func(...contextValues);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
console.warn('Error evaluating expression:', expression, error);
|
|
588
|
+
return false;
|
|
389
589
|
}
|
|
390
590
|
};
|
|
391
591
|
|
|
392
|
-
// Simplified getFieldProps function
|
|
592
|
+
// Simplified getFieldProps function
|
|
393
593
|
const getFieldProps = (field) => {
|
|
394
594
|
if (!field) {
|
|
395
595
|
return {};
|
|
@@ -397,158 +597,269 @@ const initializeFormData = () => {
|
|
|
397
597
|
|
|
398
598
|
// Create evaluation context
|
|
399
599
|
const context = createEvaluationContext();
|
|
400
|
-
|
|
401
|
-
const {
|
|
402
|
-
type,
|
|
403
|
-
key,
|
|
404
|
-
inputType,
|
|
405
|
-
columnSpan,
|
|
406
|
-
rules,
|
|
407
|
-
condition,
|
|
408
|
-
group,
|
|
409
|
-
events,
|
|
600
|
+
|
|
601
|
+
const {
|
|
602
|
+
type,
|
|
603
|
+
key,
|
|
604
|
+
inputType,
|
|
605
|
+
columnSpan,
|
|
606
|
+
rules,
|
|
607
|
+
condition,
|
|
608
|
+
group,
|
|
609
|
+
events,
|
|
410
610
|
hidden,
|
|
411
|
-
|
|
412
|
-
|
|
611
|
+
disabled,
|
|
612
|
+
reaonly,
|
|
613
|
+
value,
|
|
614
|
+
default: defaultValue,
|
|
615
|
+
...restProps
|
|
413
616
|
} = field;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (typeof field.disabled === 'string') {
|
|
419
|
-
try {
|
|
420
|
-
cleanProps.disabled = !!safeEval(`return (${field.disabled});`, context)
|
|
421
|
-
} catch (error) {
|
|
422
|
-
console.warn(`Error evaluating disabled for field "${field.key}":`, error.message)
|
|
423
|
-
cleanProps.disabled = false
|
|
424
|
-
}
|
|
425
|
-
} else if (typeof field.disabled === 'boolean') {
|
|
426
|
-
cleanProps.disabled = field.disabled
|
|
617
|
+
|
|
618
|
+
// Skip hidden fields
|
|
619
|
+
if (hidden) {
|
|
620
|
+
return {};
|
|
427
621
|
}
|
|
428
622
|
|
|
623
|
+
// Start with fieldKey
|
|
624
|
+
const cleanProps = {
|
|
625
|
+
fieldKey: key
|
|
626
|
+
};
|
|
627
|
+
|
|
429
628
|
// Add relationId for file upload components
|
|
430
629
|
if (field.inputType === 'file' || field.inputType === 'filefield') {
|
|
431
|
-
cleanProps.relationId =
|
|
432
|
-
cleanProps
|
|
433
|
-
cleanProps
|
|
434
|
-
cleanProps.fileList = FormDataList.value?.[field.dataKey ?? field.key];
|
|
630
|
+
cleanProps.relationId = formId.value || props.editItemId;
|
|
631
|
+
// cleanProps.onuploadSuccess = fetchData();
|
|
632
|
+
// cleanProps.onviewPdf = emit('view-pdf');
|
|
633
|
+
// cleanProps.fileList = FormDataList.value?.[ field.dataKey ?? field.key ];
|
|
634
|
+
|
|
635
|
+
// cleanProps.relationId = formId.value || props.editItemId;
|
|
636
|
+
cleanProps.onUploadSuccess = () => handleUploadSuccess();
|
|
637
|
+
cleanProps['onView-pdf'] = (pdfData) => emit('view-pdf', pdfData);
|
|
638
|
+
cleanProps.fileList = FormDataList.value?.[ field.dataKey ?? field.key ];
|
|
639
|
+
// cleanProps.fileList = fileList.value;
|
|
435
640
|
}
|
|
436
641
|
|
|
437
|
-
|
|
438
|
-
|
|
642
|
+
// Add all other props directly (assuming they're clean from backend)
|
|
643
|
+
// Add all other props and evaluate expressions
|
|
644
|
+
Object.entries(restProps).forEach(([propKey, value]) => {
|
|
645
|
+
// Skip empty/null/undefined values
|
|
646
|
+
if (value === '' || value === null || value === undefined) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Filter out any event-like properties that are strings
|
|
651
|
+
if (propKey.startsWith('on') && typeof value === 'string') {
|
|
652
|
+
console.warn(`Filtering out string event handler: ${propKey}`);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Evaluate expressions for specific props
|
|
657
|
+
if (typeof value === 'string' && (
|
|
658
|
+
propKey === 'disabled' ||
|
|
659
|
+
propKey === 'readonly' ||
|
|
660
|
+
propKey === 'required' ||
|
|
661
|
+
propKey === 'visible' ||
|
|
662
|
+
propKey === 'hidden'
|
|
663
|
+
)) {
|
|
664
|
+
// Check if it looks like a JavaScript expression
|
|
665
|
+
if (value.includes('===') || value.includes('!==') ||
|
|
666
|
+
value.includes('==') || value.includes('!=') ||
|
|
667
|
+
value.includes('&&') || value.includes('||') ||
|
|
668
|
+
value.includes('?') || value.includes(':')) {
|
|
669
|
+
cleanProps[propKey] = evaluateExpression(value, context);
|
|
670
|
+
} else if (context.hasOwnProperty(value)) {
|
|
671
|
+
// Simple variable reference
|
|
672
|
+
cleanProps[propKey] = context[value];
|
|
673
|
+
} else {
|
|
674
|
+
cleanProps[propKey] = value;
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
cleanProps[propKey] = value;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
439
680
|
|
|
681
|
+
if (disabled !== undefined) {
|
|
682
|
+
if (typeof disabled === 'string') {
|
|
683
|
+
cleanProps.disabled = evaluateExpression(disabled, context);
|
|
684
|
+
} else {
|
|
685
|
+
cleanProps.disabled = disabled;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Dynamically handle events configuration
|
|
690
|
+
if (events && typeof events === 'object') {
|
|
691
|
+
Object.keys(events).forEach(eventName => {
|
|
692
|
+
const functionName = events[eventName];
|
|
693
|
+
|
|
694
|
+
// Convert event name to Vue prop format (onEventName)
|
|
695
|
+
let vueEventName;
|
|
696
|
+
|
|
697
|
+
if (eventName.includes(':')) {
|
|
698
|
+
vueEventName = 'on' + eventName
|
|
699
|
+
.split(':')
|
|
700
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
701
|
+
.join('');
|
|
702
|
+
} else if (eventName.includes('-')) {
|
|
703
|
+
vueEventName = 'on' + eventName
|
|
704
|
+
.split('-')
|
|
705
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
706
|
+
.join('');
|
|
707
|
+
} else {
|
|
708
|
+
vueEventName = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Create the event handler
|
|
712
|
+
cleanProps[vueEventName] = (data) => {
|
|
713
|
+
executeFunction(functionName, data);
|
|
714
|
+
};
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Handle item-text, item-value, item-title transformations
|
|
719
|
+
if (cleanProps.itemText) {
|
|
720
|
+
cleanProps['item-text'] = cleanProps.itemText;
|
|
721
|
+
delete cleanProps.itemText;
|
|
722
|
+
}
|
|
723
|
+
if (cleanProps.itemValue) {
|
|
724
|
+
cleanProps['item-value'] = cleanProps.itemValue;
|
|
725
|
+
delete cleanProps.itemValue;
|
|
726
|
+
}
|
|
727
|
+
if (cleanProps.itemTitle) {
|
|
728
|
+
cleanProps['item-title'] = cleanProps.itemTitle;
|
|
729
|
+
delete cleanProps.itemTitle;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return cleanProps;
|
|
733
|
+
};
|
|
734
|
+
|
|
440
735
|
const parseRules = (rulesString) => {
|
|
441
736
|
if (!rulesString || !rules) return [];
|
|
737
|
+
|
|
442
738
|
if (Array.isArray(rulesString)) return rulesString;
|
|
443
|
-
|
|
739
|
+
|
|
444
740
|
if (typeof rulesString === 'string') {
|
|
445
741
|
try {
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
742
|
+
const cleanString = rulesString.replace(/[\[\]]/g, '').trim();
|
|
743
|
+
const ruleNames = cleanString.split(',').map(r => r.trim());
|
|
744
|
+
|
|
745
|
+
return ruleNames.map(ruleName => {
|
|
746
|
+
const match = ruleName.match(/rules\.(\w+)(\(.*\))?/);
|
|
747
|
+
if (match) {
|
|
748
|
+
const [, funcName, args] = match;
|
|
749
|
+
|
|
750
|
+
if (rules[funcName]) {
|
|
751
|
+
if (args) {
|
|
752
|
+
const argValues = args.slice(1, -1).split(',').map(arg => {
|
|
753
|
+
arg = arg.trim();
|
|
754
|
+
return isNaN(arg) ? arg.replace(/['"]/g, '') : Number(arg);
|
|
755
|
+
});
|
|
756
|
+
return rules[funcName](...argValues);
|
|
757
|
+
}
|
|
758
|
+
return rules[funcName];
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}).filter(Boolean);
|
|
459
763
|
} catch (e) {
|
|
460
764
|
console.error('Failed to parse rules:', rulesString, e);
|
|
461
765
|
return [];
|
|
462
766
|
}
|
|
463
767
|
}
|
|
464
|
-
|
|
768
|
+
|
|
465
769
|
return [];
|
|
466
770
|
};
|
|
467
771
|
|
|
468
772
|
const handleSubmit = async (buttonType) => {
|
|
469
|
-
clickedButton.value = buttonType
|
|
470
|
-
|
|
471
|
-
const { valid: isValid } = await formRef.value?.validate()
|
|
773
|
+
clickedButton.value = buttonType;
|
|
774
|
+
const { valid: isValid } = await formRef.value?.validate();
|
|
472
775
|
if (!isValid) {
|
|
473
|
-
snackbar?.show({ message: 'Please fix validation errors', variant: 'error' })
|
|
474
|
-
return
|
|
776
|
+
snackbar?.show({ message: 'Please fix validation errors', variant: 'error' });
|
|
777
|
+
return;
|
|
475
778
|
}
|
|
476
779
|
|
|
477
780
|
if (!api) {
|
|
478
|
-
|
|
479
|
-
|
|
781
|
+
console.error('API not available');
|
|
782
|
+
snackbar?.show({ message: 'API not configured', variant: 'error' });
|
|
783
|
+
return;
|
|
480
784
|
}
|
|
481
785
|
|
|
482
786
|
if (!props.form?.crudLink) {
|
|
483
|
-
|
|
484
|
-
|
|
787
|
+
console.error('No crudLink defined in form config');
|
|
788
|
+
snackbar?.show({ message: 'Form configuration error', variant: 'error' });
|
|
789
|
+
return;
|
|
485
790
|
}
|
|
486
791
|
|
|
487
|
-
isSubmitting.value = true
|
|
792
|
+
isSubmitting.value = true;
|
|
488
793
|
|
|
489
794
|
try {
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// ── First save: POST with status=draft ────────────────────────
|
|
497
|
-
submitData.status = 'draft'
|
|
498
|
-
const createLink = props.form?.createLink || props.form?.crudLink
|
|
499
|
-
response = await api.post(createLink, submitData)
|
|
500
|
-
|
|
501
|
-
// Transition to edit mode
|
|
502
|
-
formId.value = (response?.data?.uuid || response?.uuid) ?? (response?.data?.id || response?.id)
|
|
503
|
-
formData.id = response?.data?.id || response?.id
|
|
504
|
-
relationId.value = response?.data?.id || response?.id
|
|
505
|
-
formMode.value = 'edit' // ← trigger button swap
|
|
506
|
-
|
|
507
|
-
await fetchData() // load saved data back
|
|
508
|
-
|
|
509
|
-
// snackbar?.show({ message: response?.data?.message || 'Draft saved', variant: 'success' })
|
|
510
|
-
// Do NOT close form
|
|
511
|
-
emit('submit:success', response?.message)
|
|
512
|
-
|
|
513
|
-
} else if (buttonType === 'update') {
|
|
514
|
-
// ── Update: PUT, do not close ─────────────────────────────────
|
|
515
|
-
submitData.status = 'draft'
|
|
516
|
-
//submitData.id = currentId
|
|
517
|
-
const updateLink = props.form?.updateLink || props.form?.crudLink
|
|
518
|
-
response = await api.put(`${updateLink}/${currentId}`, submitData)
|
|
519
|
-
|
|
520
|
-
await fetchData()
|
|
521
|
-
|
|
522
|
-
// snackbar?.show({ message: response?.data?.message || 'Updated successfully', variant: 'success' })
|
|
523
|
-
// Do NOT close form
|
|
524
|
-
emit('submit:success', response?.message)
|
|
525
|
-
|
|
526
|
-
} else if (buttonType === 'submit') {
|
|
527
|
-
// ── Submit: PUT with status=submitted, then close ─────────────
|
|
528
|
-
submitData.status = 'submitted'
|
|
529
|
-
//submitData.id = currentId
|
|
530
|
-
const updateLink = props.form?.updateLink || props.form?.crudLink
|
|
531
|
-
response = await api.put(`${updateLink}/${currentId}`, submitData)
|
|
532
|
-
|
|
533
|
-
// snackbar?.show({ message: response?.data?.message || 'Submitted successfully', variant: 'success' })
|
|
534
|
-
emit('submit:success', response?.message)
|
|
535
|
-
handleClose()
|
|
536
|
-
formRef.value?.reset()
|
|
795
|
+
// Filter out non-database fields before submission
|
|
796
|
+
const submitData = getDbFieldsOnly(formData);
|
|
797
|
+
if (clickedButton.value === 'draft') {
|
|
798
|
+
submitData.status = 'draft';
|
|
799
|
+
}else {
|
|
800
|
+
submitData.status = 'submitted';
|
|
537
801
|
}
|
|
538
|
-
|
|
802
|
+
if (formId.value) {
|
|
803
|
+
submitData.id = formId.value; // Ensure ID is included for updates
|
|
804
|
+
}
|
|
805
|
+
// const isFullUrl = /^https?:\/\//i.test(props.form?.crudLink)
|
|
806
|
+
// let url = isFullUrl ? props.form?.crudLink : baseUrl + '/' + props.form?.crudLink
|
|
807
|
+
let completeUrl = ''
|
|
808
|
+
|
|
809
|
+
if ((props.mode === 'edit' && props.editItemId) || clickedButton.value === 'submitted') {
|
|
810
|
+
const editLink = props.form?.updateLink || props.form?.crudLink;
|
|
811
|
+
// const isFullUrl = /^https?:\/\//i.test(editLink)
|
|
812
|
+
// let url = isFullUrl ? editLink : baseUrl + '/' + editLink
|
|
813
|
+
let url = editLink
|
|
814
|
+
url += `/${submitData.id}`;
|
|
815
|
+
completeUrl = api.put(url, submitData); // Use filtered data
|
|
816
|
+
} else {
|
|
817
|
+
|
|
818
|
+
const createLink = props.form?.createLink || props.form?.crudLink;
|
|
819
|
+
// const isFullUrl = /^https?:\/\//i.test(createLink)
|
|
820
|
+
// let url = isFullUrl ? createLink : baseUrl + '/' + createLink
|
|
821
|
+
let url = createLink
|
|
822
|
+
completeUrl = api.post(url, submitData); // Use filtered data
|
|
823
|
+
}
|
|
824
|
+
const response = await completeUrl;
|
|
825
|
+
|
|
826
|
+
const successMessage = response.data?.message || response.message || 'Created successfully';
|
|
827
|
+
snackbar?.show({ message: successMessage, variant: 'success' });
|
|
828
|
+
|
|
829
|
+
formId.value = response?.data?.id || formData.id || props.editItemId || null; // Update formId with response ID if available
|
|
830
|
+
// submitData.id = formId.value; // Ensure submitData has the correct ID for any subsequent operations
|
|
831
|
+
console.log('Form submission response:', response);
|
|
832
|
+
|
|
833
|
+
if (clickedButton.value === 'draft') {
|
|
834
|
+
draftSubmitted.value = true;
|
|
835
|
+
}
|
|
836
|
+
if (clickedButton.value !== 'draft') {
|
|
837
|
+
emit('submit:success', response.message);
|
|
838
|
+
handleClose();
|
|
839
|
+
formRef.value?.reset();
|
|
840
|
+
}
|
|
841
|
+
|
|
539
842
|
} catch (error) {
|
|
540
|
-
console.error('Form submission error:', error)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
843
|
+
console.error('Form submission error:', error);
|
|
844
|
+
const errorMessage = error.response?.data?.message || 'Form submission failed';
|
|
845
|
+
snackbar?.show({ message: errorMessage, variant: 'error' });
|
|
544
846
|
if (error.response?.status === 422 || error.response?.data?.errors) {
|
|
545
|
-
|
|
546
|
-
|
|
847
|
+
const backendErrors = error.response.data.errors;
|
|
848
|
+
snackbar?.show({ message: backendErrors, variant: 'error' });
|
|
849
|
+
|
|
850
|
+
// if (formRef.value?.setErrors) {
|
|
851
|
+
formRef.value.setErrors(backendErrors);
|
|
852
|
+
// } else {
|
|
853
|
+
// console.error('formRef.value.setErrors not available')
|
|
854
|
+
// }
|
|
855
|
+
|
|
856
|
+
const errorMessage = error.response.data.message || 'Validation failed';
|
|
857
|
+
emit('submit:error', error);
|
|
547
858
|
}
|
|
548
859
|
} finally {
|
|
549
|
-
isSubmitting.value = false
|
|
860
|
+
isSubmitting.value = false;
|
|
550
861
|
}
|
|
551
|
-
}
|
|
862
|
+
};
|
|
552
863
|
|
|
553
864
|
// Helper function to filter out non-database fields
|
|
554
865
|
const getDbFieldsOnly = (data) => {
|
|
@@ -561,7 +872,7 @@ const initializeFormData = () => {
|
|
|
561
872
|
.filter(field => field && field.NotDbField === true)
|
|
562
873
|
.map(field => field.key);
|
|
563
874
|
|
|
564
|
-
|
|
875
|
+
console.log('Non-DB fields to exclude:', nonDbFields);
|
|
565
876
|
|
|
566
877
|
// Create new object with only database fields
|
|
567
878
|
const dbOnlyData = {};
|
|
@@ -571,112 +882,45 @@ const initializeFormData = () => {
|
|
|
571
882
|
}
|
|
572
883
|
});
|
|
573
884
|
|
|
574
|
-
|
|
575
|
-
|
|
885
|
+
console.log('Original form data:', data);
|
|
886
|
+
console.log('Filtered DB-only data:', dbOnlyData);
|
|
576
887
|
|
|
577
888
|
return dbOnlyData;
|
|
578
889
|
};
|
|
579
|
-
// Helper to get nested value by dot notation key e.g. "submissionDetails.form_ref_number"
|
|
580
|
-
const getNestedValue = (obj, path) => {
|
|
581
|
-
if (!path || !obj) return undefined
|
|
582
|
-
return path.split('.').reduce((acc, key) => {
|
|
583
|
-
if (acc === null || acc === undefined) return undefined
|
|
584
|
-
return acc[key]
|
|
585
|
-
}, obj)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
890
|
// import dataList from '../../../pgo-components/services/data.json'
|
|
589
891
|
const fetchData = async () => {
|
|
590
892
|
// FormDataList.value = dataList
|
|
591
893
|
// props.form.fields.forEach(field => {
|
|
592
|
-
// if (
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
// // Try dot notation first (e.g. "submissionDetails.form_ref_number")
|
|
597
|
-
// if (fieldKey.includes('.')) {
|
|
598
|
-
// const nestedVal = getNestedValue(FormDataList.value, fieldKey)
|
|
599
|
-
// if (nestedVal !== undefined) {
|
|
600
|
-
// formData[field.key] = nestedVal
|
|
601
|
-
// return
|
|
602
|
-
// }
|
|
603
|
-
// }
|
|
604
|
-
|
|
605
|
-
// // Fallback: flat key lookup
|
|
606
|
-
// if (FormDataList.value[fieldKey] !== undefined) {
|
|
607
|
-
// formData[field.key] = FormDataList.value[fieldKey]
|
|
608
|
-
// }
|
|
894
|
+
// if (dataList[field.key] !== undefined) {
|
|
895
|
+
// formData[field.key] = dataList[field.key]
|
|
896
|
+
// }
|
|
609
897
|
// })
|
|
610
|
-
|
|
611
|
-
// await nextTick()
|
|
612
|
-
// if (formRef.value?.validate) {
|
|
613
|
-
// const result = await formRef.value.validate()
|
|
614
|
-
// valid.value = result?.valid ?? false
|
|
615
|
-
// }
|
|
616
|
-
// originalFormData.value = JSON.parse(JSON.stringify({ ...formData }))
|
|
617
|
-
// isPreFilled.value = true // mark as pre-filled after fetchData
|
|
618
898
|
// console.log('Form Fetched data:', FormDataList.value);
|
|
619
899
|
// return;
|
|
620
900
|
|
|
621
|
-
|
|
622
|
-
|
|
901
|
+
if (props.mode !== 'edit' || !props.editItemId || clickedButton.value !== 'draft') {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
623
904
|
|
|
624
905
|
try {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
for (const [key, value] of Object.entries(rawParams)) {
|
|
634
|
-
flattened[key] = (typeof value === 'object' && value !== null)
|
|
635
|
-
? JSON.stringify(value)
|
|
636
|
-
: value
|
|
637
|
-
}
|
|
638
|
-
// url = `${url}?${new URLSearchParams(flattened).toString()}`
|
|
639
|
-
url = `${url}?payload=${encodeURIComponent(JSON.stringify(rawParams))}`
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const response = await api.get(`${url}`)
|
|
644
|
-
// const response = await api.get(`${url}/${props.editItemId}`)
|
|
645
|
-
|
|
646
|
-
// console.log('Fetched data from API:', response);
|
|
647
|
-
|
|
906
|
+
// console.log('form:', props.form);
|
|
907
|
+
const showLink = props.form?.showLink || props.form?.crudLink;
|
|
908
|
+
// const isFullUrl = /^https?:\/\//i.test(showLink)
|
|
909
|
+
// let url = isFullUrl ? showLink : baseUrl + '/' + showLink
|
|
910
|
+
let url = showLink;
|
|
911
|
+
|
|
912
|
+
const response = await api.get(`${url}/${props.editItemId}`)
|
|
913
|
+
|
|
648
914
|
if (response) {
|
|
649
915
|
const fetchedData = response
|
|
650
|
-
FormDataList.value = response || response.data || {}
|
|
651
|
-
formData.id =
|
|
652
|
-
|
|
653
|
-
|
|
916
|
+
FormDataList.value = response || response.data || {};
|
|
917
|
+
formData.id = props.editItemId
|
|
918
|
+
|
|
654
919
|
props.form.fields.forEach(field => {
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
const fieldKey = field.key
|
|
658
|
-
|
|
659
|
-
if (fieldKey.includes('.')) {
|
|
660
|
-
const nestedVal = getNestedValue(fetchedData, fieldKey)
|
|
661
|
-
if (nestedVal !== undefined) {
|
|
662
|
-
formData[field.key] = nestedVal
|
|
663
|
-
return
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (fetchedData[fieldKey] !== undefined) {
|
|
668
|
-
formData[field.key] = fetchedData[fieldKey]
|
|
920
|
+
if (fetchedData[field.key] !== undefined) {
|
|
921
|
+
formData[field.key] = fetchedData[field.key]
|
|
669
922
|
}
|
|
670
923
|
})
|
|
671
|
-
// console.log('Form data after fetch:', formData);
|
|
672
|
-
await nextTick()
|
|
673
|
-
if (formRef.value?.validate) {
|
|
674
|
-
const result = await formRef.value.validate()
|
|
675
|
-
valid.value = result?.valid ?? false
|
|
676
|
-
}
|
|
677
|
-
originalFormData.value = JSON.parse(JSON.stringify({ ...formData }))
|
|
678
|
-
isPreFilled.value = true // mark as pre-filled after fetchData
|
|
679
|
-
|
|
680
924
|
}
|
|
681
925
|
} catch (error) {
|
|
682
926
|
snackbar?.show({ message: 'Error fetching data', variant: 'error' })
|
|
@@ -684,20 +928,17 @@ const initializeFormData = () => {
|
|
|
684
928
|
}
|
|
685
929
|
}
|
|
686
930
|
|
|
687
|
-
|
|
688
931
|
const handleClose = () => {
|
|
689
|
-
open.value = false
|
|
690
|
-
isSubmitting.value = false
|
|
691
|
-
formRef.value?.reset()
|
|
692
|
-
valid.value = false
|
|
693
|
-
|
|
694
|
-
formMode.value = props.mode // ← reset on close
|
|
695
|
-
originalFormData.value = {}
|
|
696
|
-
formId.value = null
|
|
697
|
-
emit('close', false)
|
|
932
|
+
open.value = false;
|
|
933
|
+
isSubmitting.value = false;
|
|
934
|
+
formRef.value?.reset();
|
|
935
|
+
valid.value = false;
|
|
936
|
+
emit('close', false);
|
|
698
937
|
};
|
|
699
938
|
|
|
700
939
|
const handleUploadSuccess = () => {
|
|
940
|
+
// After a successful file upload, refetch the item data to get the latest file list
|
|
941
|
+
// showUploads.value = true
|
|
701
942
|
fetchData();
|
|
702
943
|
};
|
|
703
944
|
// Initialize on mount
|
|
@@ -717,7 +958,6 @@ const initializeFormData = () => {
|
|
|
717
958
|
// Watch for form changes
|
|
718
959
|
watch(() => props.form, (newForm) => {
|
|
719
960
|
if (newForm) {
|
|
720
|
-
initializeFormData();
|
|
721
961
|
if (newForm.variables) {
|
|
722
962
|
initializeVariables();
|
|
723
963
|
}
|
|
@@ -725,10 +965,9 @@ const initializeFormData = () => {
|
|
|
725
965
|
initializeAllFunctions();
|
|
726
966
|
}
|
|
727
967
|
// Only fetch data if we're in edit mode and have the necessary data
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
968
|
+
if (props.mode === 'edit' && props.editItemId && newForm.crudLink) {
|
|
969
|
+
fetchData();
|
|
970
|
+
}
|
|
732
971
|
}
|
|
733
972
|
}, { deep: true });
|
|
734
973
|
|
|
@@ -743,9 +982,7 @@ const initializeFormData = () => {
|
|
|
743
982
|
|
|
744
983
|
// Also watch for mode changes
|
|
745
984
|
watch(() => props.mode, (newMode) => {
|
|
746
|
-
formMode.value = newMode
|
|
747
985
|
if (newMode === 'edit' && props.editItemId) {
|
|
748
|
-
formId.value = props.editItemId
|
|
749
986
|
fetchData()
|
|
750
987
|
} else if (newMode === 'create') {
|
|
751
988
|
initializeFormData()
|
|
@@ -753,14 +990,13 @@ const initializeFormData = () => {
|
|
|
753
990
|
})
|
|
754
991
|
|
|
755
992
|
// Initialize on mount
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
// })
|
|
993
|
+
onMounted(() => {
|
|
994
|
+
// if (props.mode === 'edit' && props.editItemId) {
|
|
995
|
+
// // fetchData()
|
|
996
|
+
// } else {
|
|
997
|
+
// initializeFormData()
|
|
998
|
+
// }
|
|
999
|
+
})
|
|
764
1000
|
|
|
765
1001
|
watch(() => props.modelValue, (newVal) => {
|
|
766
1002
|
// open.value = newVal;
|
|
@@ -773,43 +1009,7 @@ const initializeFormData = () => {
|
|
|
773
1009
|
if (!newVal) {
|
|
774
1010
|
formRef.value?.reset();
|
|
775
1011
|
valid.value = false; // Reset valid state when modal closes
|
|
776
|
-
|
|
1012
|
+
console.log('form open', formRef.value.isValid, valid.value);
|
|
777
1013
|
}
|
|
778
1014
|
});
|
|
779
|
-
</script>
|
|
780
|
-
|
|
781
|
-
<script>
|
|
782
|
-
const safeEval = (expression, context) => {
|
|
783
|
-
const functionKeys = []
|
|
784
|
-
const functionValues = []
|
|
785
|
-
const varDeclarations = []
|
|
786
|
-
|
|
787
|
-
Object.keys(context)
|
|
788
|
-
.filter(key => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key))
|
|
789
|
-
.forEach(key => {
|
|
790
|
-
const value = context[key]
|
|
791
|
-
|
|
792
|
-
if (typeof value === 'function') {
|
|
793
|
-
functionKeys.push(key)
|
|
794
|
-
functionValues.push(value)
|
|
795
|
-
} else if (value === null || value === undefined) {
|
|
796
|
-
varDeclarations.push(`var ${key} = null;`)
|
|
797
|
-
} else if (typeof value === 'object') {
|
|
798
|
-
try {
|
|
799
|
-
varDeclarations.push(`var ${key} = ${JSON.stringify(value)};`)
|
|
800
|
-
} catch {
|
|
801
|
-
varDeclarations.push(`var ${key} = null;`)
|
|
802
|
-
}
|
|
803
|
-
} else if (typeof value === 'string') {
|
|
804
|
-
varDeclarations.push(`var ${key} = ${JSON.stringify(value)};`)
|
|
805
|
-
} else {
|
|
806
|
-
varDeclarations.push(`var ${key} = ${value};`)
|
|
807
|
-
}
|
|
808
|
-
})
|
|
809
|
-
|
|
810
|
-
// expression is used as-is — caller decides whether to add "return (...)"
|
|
811
|
-
const body = varDeclarations.join('\n') + `\n${expression}`
|
|
812
|
-
const fn = new Function(...functionKeys, body)
|
|
813
|
-
return fn(...functionValues)
|
|
814
|
-
}
|
|
815
|
-
</script>
|
|
1015
|
+
</script>
|