Refactor ComponentPreview and RepeatingTable for Improved Data Handling and Performance
- Updated ComponentPreview.vue to enhance data binding for form fields, ensuring controlled updates to prevent circular reactivity. - Introduced new methods for updating group fields and list items, improving data management and validation. - Enhanced RepeatingTable.vue by replacing the LazyCellValue component with a SimpleCellValue for immediate data display, optimizing rendering performance. - Improved table structure and styling for better user experience, including sticky headers and custom scrollbar styles. - Added logic to initialize repeating tables with default data in form builder, ensuring consistent data handling across components. - Implemented checks to prevent unnecessary updates in form data, enhancing overall application performance.
This commit is contained in:
parent
eab2ca3647
commit
f024cc91dd
@ -184,7 +184,8 @@
|
|||||||
<template v-for="(field, fieldIndex) in component.props.fields" :key="fieldIndex">
|
<template v-for="(field, fieldIndex) in component.props.fields" :key="fieldIndex">
|
||||||
<FormKit :type="field.type" :label="field.label" :placeholder="field.placeholder"
|
<FormKit :type="field.type" :label="field.label" :placeholder="field.placeholder"
|
||||||
:name="`${component.props.name}.${groupIndex}.${field.name}`" :options="field.options"
|
:name="`${component.props.name}.${groupIndex}.${field.name}`" :options="field.options"
|
||||||
v-model="group[field.name]" />
|
:value="group[field.name]"
|
||||||
|
@input="updateGroupField(component.props.name, groupIndex, field.name, $event)" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -278,11 +279,12 @@
|
|||||||
:checked="isItemSelected(component.props.name, index)"
|
:checked="isItemSelected(component.props.name, index)"
|
||||||
@change="toggleItemSelection(component.props.name, index)"
|
@change="toggleItemSelection(component.props.name, index)"
|
||||||
class="mr-2 h-4 w-4 rounded border-gray-300" />
|
class="mr-2 h-4 w-4 rounded border-gray-300" />
|
||||||
<input :type="component.props.itemType || 'text'" v-model="safeGetField(component.props.name, previewFormData)[index]"
|
<input :type="component.props.itemType || 'text'"
|
||||||
|
:value="safeGetField(component.props.name, previewFormData)[index]"
|
||||||
:placeholder="component.props.placeholder"
|
:placeholder="component.props.placeholder"
|
||||||
:class="getItemInputClasses(component.props.name, index, item)"
|
:class="getItemInputClasses(component.props.name, index, item)"
|
||||||
@blur="validateItem(component.props.name, index, item)"
|
@blur="validateItem(component.props.name, index, item)"
|
||||||
@input="handleItemInput(component.props.name, index, $event.target.value)" />
|
@input="updateListItem(component.props.name, index, $event.target.value)" />
|
||||||
<div v-if="component.props.enableSorting"
|
<div v-if="component.props.enableSorting"
|
||||||
class="ml-2 cursor-move text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
class="ml-2 cursor-move text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<Icon name="material-symbols:drag-indicator" class="w-5 h-5" />
|
<Icon name="material-symbols:drag-indicator" class="w-5 h-5" />
|
||||||
@ -324,7 +326,7 @@
|
|||||||
<div v-else-if="component.type === 'repeating-table'" class="repeating-table-wrapper">
|
<div v-else-if="component.type === 'repeating-table'" class="repeating-table-wrapper">
|
||||||
<RepeatingTable
|
<RepeatingTable
|
||||||
:config="component.props"
|
:config="component.props"
|
||||||
:model-value="safeGetField(component.props.name, previewFormData) || []"
|
:model-value="getTableData(component.props.name)"
|
||||||
:is-preview="isPreview"
|
:is-preview="isPreview"
|
||||||
@update:model-value="updateTableData"
|
@update:model-value="updateTableData"
|
||||||
/>
|
/>
|
||||||
@ -646,8 +648,10 @@ onMounted(() => {
|
|||||||
const defaultItems = props.component.props.defaultItems;
|
const defaultItems = props.component.props.defaultItems;
|
||||||
|
|
||||||
if ((!currentFormData || currentFormData.length === 0) && defaultItems && defaultItems.length > 0) {
|
if ((!currentFormData || currentFormData.length === 0) && defaultItems && defaultItems.length > 0) {
|
||||||
const updatedData = { ...formStore.previewFormData, [listName]: [...defaultItems] };
|
nextTick(() => {
|
||||||
formStore.updatePreviewFormData(updatedData);
|
const updatedData = { ...formStore.previewFormData, [listName]: [...defaultItems] };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -677,8 +681,10 @@ onMounted(() => {
|
|||||||
initialGroups.push(newGroup);
|
initialGroups.push(newGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedData = { ...formStore.previewFormData, [groupName]: initialGroups };
|
nextTick(() => {
|
||||||
formStore.updatePreviewFormData(updatedData);
|
const updatedData = { ...formStore.previewFormData, [groupName]: initialGroups };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -690,11 +696,19 @@ watch(() => props.component.props.defaultItems, (newDefaultItems, oldDefaultItem
|
|||||||
const listName = props.component.props.name;
|
const listName = props.component.props.name;
|
||||||
if (!listName) return;
|
if (!listName) return;
|
||||||
|
|
||||||
|
// Check if defaultItems actually changed to prevent unnecessary updates
|
||||||
|
const newItemsStr = JSON.stringify(newDefaultItems);
|
||||||
|
const oldItemsStr = JSON.stringify(oldDefaultItems);
|
||||||
|
|
||||||
|
if (newItemsStr === oldItemsStr) return;
|
||||||
|
|
||||||
// Always update when defaultItems change, regardless of current form data
|
// Always update when defaultItems change, regardless of current form data
|
||||||
const items = newDefaultItems || [];
|
const items = newDefaultItems || [];
|
||||||
|
|
||||||
const updatedData = { ...formStore.previewFormData, [listName]: [...items] };
|
nextTick(() => {
|
||||||
formStore.updatePreviewFormData(updatedData);
|
const updatedData = { ...formStore.previewFormData, [listName]: [...items] };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, { deep: true, immediate: true });
|
}, { deep: true, immediate: true });
|
||||||
|
|
||||||
@ -702,7 +716,7 @@ watch(() => props.component.props.defaultItems, (newDefaultItems, oldDefaultItem
|
|||||||
watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
||||||
if (props.component.type === 'repeating-group') {
|
if (props.component.type === 'repeating-group') {
|
||||||
const groupName = props.component.props.name;
|
const groupName = props.component.props.name;
|
||||||
if (!groupName) return;
|
if (!groupName || newMinItems === oldMinItems) return;
|
||||||
|
|
||||||
const currentGroups = safeGetField(groupName, formStore.previewFormData);
|
const currentGroups = safeGetField(groupName, formStore.previewFormData);
|
||||||
const minItems = newMinItems || 1;
|
const minItems = newMinItems || 1;
|
||||||
@ -725,12 +739,51 @@ watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
|||||||
updatedGroups.push(newGroup);
|
updatedGroups.push(newGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedData = { ...formStore.previewFormData, [groupName]: updatedGroups };
|
nextTick(() => {
|
||||||
formStore.updatePreviewFormData(updatedData);
|
const updatedData = { ...formStore.previewFormData, [groupName]: updatedGroups };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, { deep: true, immediate: true });
|
}, { deep: true, immediate: true });
|
||||||
|
|
||||||
|
// Controlled update methods to prevent circular reactivity
|
||||||
|
const updateGroupField = (groupName, groupIndex, fieldName, newValue) => {
|
||||||
|
if (!props.isPreview) return;
|
||||||
|
|
||||||
|
const currentGroups = [...(safeGetField(groupName, formStore.previewFormData) || [])];
|
||||||
|
if (!currentGroups[groupIndex]) return;
|
||||||
|
|
||||||
|
// Only update if value actually changed
|
||||||
|
if (currentGroups[groupIndex][fieldName] === newValue) return;
|
||||||
|
|
||||||
|
currentGroups[groupIndex][fieldName] = newValue;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const updatedData = { ...formStore.previewFormData, [groupName]: currentGroups };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
emit('form-data-updated', updatedData);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateListItem = (listName, index, newValue) => {
|
||||||
|
if (!props.isPreview) return;
|
||||||
|
|
||||||
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
||||||
|
if (currentItems[index] === newValue) return; // No change
|
||||||
|
|
||||||
|
// Validate and handle duplicates
|
||||||
|
if (!checkDuplicates(listName, newValue, index)) return;
|
||||||
|
if (!validateItem(listName, index, newValue)) return;
|
||||||
|
|
||||||
|
currentItems[index] = newValue;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const updatedData = { ...formStore.previewFormData, [listName]: currentItems };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Repeating group and dynamic list functionality
|
// Repeating group and dynamic list functionality
|
||||||
const addGroupItem = () => {
|
const addGroupItem = () => {
|
||||||
if (!props.isPreview) return;
|
if (!props.isPreview) return;
|
||||||
@ -1181,13 +1234,48 @@ const updateListItems = (listName, newItems) => {
|
|||||||
formStore.updatePreviewFormData(updatedData);
|
formStore.updatePreviewFormData(updatedData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get table data safely and initialize if needed
|
||||||
|
const getTableData = (tableName) => {
|
||||||
|
if (!tableName) return [];
|
||||||
|
|
||||||
|
// Directly check the form store without using safeGetField to avoid warnings
|
||||||
|
const formData = formStore.previewFormData || {};
|
||||||
|
|
||||||
|
// If field exists and is an array, return it
|
||||||
|
if (formData.hasOwnProperty(tableName) && Array.isArray(formData[tableName])) {
|
||||||
|
return formData[tableName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If data doesn't exist, initialize it immediately (no nextTick needed for initial render)
|
||||||
|
const initialData = [];
|
||||||
|
const updatedFormData = { ...formData, [tableName]: initialData };
|
||||||
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
|
||||||
|
return initialData;
|
||||||
|
};
|
||||||
|
|
||||||
// Update table data for repeating-table component
|
// Update table data for repeating-table component
|
||||||
const updateTableData = (newData) => {
|
const updateTableData = (newData) => {
|
||||||
const tableName = props.component.props.name;
|
const tableName = props.component.props.name;
|
||||||
if (!tableName) return;
|
if (!tableName) return;
|
||||||
|
|
||||||
const updatedFormData = { ...formStore.previewFormData, [tableName]: newData };
|
// Ensure newData is always an array
|
||||||
formStore.updatePreviewFormData(updatedFormData);
|
const safeNewData = Array.isArray(newData) ? newData : [];
|
||||||
|
|
||||||
|
// Check if data actually changed to prevent unnecessary updates
|
||||||
|
const currentData = safeGetField(tableName, formStore.previewFormData) || [];
|
||||||
|
const currentDataStr = JSON.stringify(currentData);
|
||||||
|
const newDataStr = JSON.stringify(safeNewData);
|
||||||
|
|
||||||
|
if (currentDataStr === newDataStr) return;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const updatedFormData = { ...formStore.previewFormData, [tableName]: safeNewData };
|
||||||
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
|
||||||
|
// Emit the change for workflow page to sync with its local formData
|
||||||
|
emit('form-data-updated', updatedFormData);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Form Section Component
|
// Form Section Component
|
||||||
|
@ -70,9 +70,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="paginatedData.length > 0" class="table-wrapper">
|
<div v-if="paginatedData.length > 0" class="table-wrapper">
|
||||||
<div class="table-content" ref="tableContainer">
|
<div class="table-container" ref="tableContainer">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead class="table-header-row">
|
<thead class="table-header">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-if="config.showRowNumbers" class="row-number-header">#</th>
|
<th v-if="config.showRowNumbers" class="row-number-header">#</th>
|
||||||
<th
|
<th
|
||||||
@ -106,7 +106,7 @@
|
|||||||
:style="{ width: getColumnWidth(column) }"
|
:style="{ width: getColumnWidth(column) }"
|
||||||
>
|
>
|
||||||
<div class="cell-content">
|
<div class="cell-content">
|
||||||
<LazyCellValue
|
<SimpleCellValue
|
||||||
:value="record[column.name]"
|
:value="record[column.name]"
|
||||||
:column="column"
|
:column="column"
|
||||||
:record="record"
|
:record="record"
|
||||||
@ -298,8 +298,8 @@ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
|
||||||
// Lazy cell component for better performance
|
// Simple cell component that shows data immediately
|
||||||
const LazyCellValue = defineComponent({
|
const SimpleCellValue = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: [String, Number, Boolean, Object],
|
type: [String, Number, Boolean, Object],
|
||||||
@ -315,35 +315,7 @@ const LazyCellValue = defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const isVisible = ref(false)
|
|
||||||
const cellRef = ref(null)
|
|
||||||
|
|
||||||
const observer = ref(null)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (cellRef.value) {
|
|
||||||
observer.value = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
isVisible.value = true
|
|
||||||
observer.value?.disconnect()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ threshold: 0.1 }
|
|
||||||
)
|
|
||||||
observer.value.observe(cellRef.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
observer.value?.disconnect()
|
|
||||||
})
|
|
||||||
|
|
||||||
const formatValue = computed(() => {
|
const formatValue = computed(() => {
|
||||||
if (!isVisible.value) return ''
|
|
||||||
|
|
||||||
const value = props.value
|
const value = props.value
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
return '-'
|
return '-'
|
||||||
@ -370,13 +342,11 @@ const LazyCellValue = defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isVisible,
|
|
||||||
cellRef,
|
|
||||||
formatValue
|
formatValue
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<span ref="cellRef" class="cell-value">
|
<span class="cell-value">
|
||||||
{{ formatValue }}
|
{{ formatValue }}
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
@ -400,7 +370,7 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
// Reactive state
|
// Reactive state
|
||||||
const data = ref([...props.modelValue])
|
const data = ref([...(props.modelValue || [])])
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
const showDeleteConfirm = ref(false)
|
const showDeleteConfirm = ref(false)
|
||||||
@ -487,7 +457,7 @@ const isAddDisabled = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const showActions = computed(() => {
|
const showActions = computed(() => {
|
||||||
return (props.config.allowEdit || props.config.allowDelete) && !props.isPreview
|
return props.config.allowEdit || props.config.allowDelete
|
||||||
})
|
})
|
||||||
|
|
||||||
const showRecordCount = computed(() => {
|
const showRecordCount = computed(() => {
|
||||||
@ -543,23 +513,48 @@ const visiblePages = computed(() => {
|
|||||||
return rangeWithDots.filter((item, index, array) => array.indexOf(item) === index)
|
return rangeWithDots.filter((item, index, array) => array.indexOf(item) === index)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Guard to prevent recursive updates
|
||||||
|
const isUpdatingFromProps = ref(false)
|
||||||
|
|
||||||
// Watch for external data changes
|
// Watch for external data changes
|
||||||
watch(() => props.modelValue, (newValue) => {
|
watch(() => props.modelValue, (newValue) => {
|
||||||
data.value = [...newValue]
|
// Handle the case where newValue might be undefined/null
|
||||||
|
const safeNewValue = newValue || []
|
||||||
|
|
||||||
|
// Prevent circular updates by checking if data actually changed
|
||||||
|
const newDataStr = JSON.stringify(safeNewValue)
|
||||||
|
const currentDataStr = JSON.stringify(data.value)
|
||||||
|
|
||||||
|
if (newDataStr === currentDataStr) return
|
||||||
|
|
||||||
|
isUpdatingFromProps.value = true
|
||||||
|
data.value = [...safeNewValue]
|
||||||
// Clear caches when data changes
|
// Clear caches when data changes
|
||||||
columnCache.value.clear()
|
columnCache.value.clear()
|
||||||
recordKeys.value.clear()
|
recordKeys.value.clear()
|
||||||
}, { deep: true })
|
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFromProps.value = false
|
||||||
|
})
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
// Watch for internal data changes and emit
|
// Watch for internal data changes and emit (only when not updating from props)
|
||||||
watch(data, (newData) => {
|
watch(data, (newData, oldData) => {
|
||||||
emit('update:modelValue', [...newData])
|
if (isUpdatingFromProps.value) return
|
||||||
|
|
||||||
|
// Check if data actually changed
|
||||||
|
const newDataStr = JSON.stringify(newData)
|
||||||
|
const oldDataStr = JSON.stringify(oldData)
|
||||||
|
|
||||||
|
if (newDataStr === oldDataStr) return
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
emit('update:modelValue', [...newData])
|
||||||
|
})
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const openAddModal = () => {
|
const openAddModal = () => {
|
||||||
if (props.isPreview) return
|
|
||||||
|
|
||||||
editingIndex.value = null
|
editingIndex.value = null
|
||||||
formData.value = {}
|
formData.value = {}
|
||||||
|
|
||||||
@ -572,8 +567,6 @@ const openAddModal = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openEditModal = (record, index) => {
|
const openEditModal = (record, index) => {
|
||||||
if (props.isPreview) return
|
|
||||||
|
|
||||||
editingIndex.value = index
|
editingIndex.value = index
|
||||||
formData.value = { ...record }
|
formData.value = { ...record }
|
||||||
showModal.value = true
|
showModal.value = true
|
||||||
@ -606,8 +599,6 @@ const saveRecord = (formData) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteRecord = (index) => {
|
const deleteRecord = (index) => {
|
||||||
if (props.isPreview) return
|
|
||||||
|
|
||||||
if (props.config.confirmDelete) {
|
if (props.config.confirmDelete) {
|
||||||
deleteIndex.value = index
|
deleteIndex.value = index
|
||||||
showDeleteConfirm.value = true
|
showDeleteConfirm.value = true
|
||||||
@ -693,18 +684,56 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Performance optimizations */
|
/* Table Container with Sticky Header */
|
||||||
.table-content {
|
.table-container {
|
||||||
overflow-x: auto;
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
max-height: 500px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 600px;
|
scrollbar-width: thin;
|
||||||
will-change: transform;
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table {
|
.data-table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky Header */
|
||||||
|
.table-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollable Body */
|
||||||
|
.table-body {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for webkit browsers */
|
||||||
|
.table-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-header,
|
.column-header,
|
||||||
@ -875,7 +904,7 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
@apply overflow-x-auto;
|
@apply overflow-hidden w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-content {
|
.table-content {
|
||||||
@ -894,6 +923,16 @@ onUnmounted(() => {
|
|||||||
.row-number-header,
|
.row-number-header,
|
||||||
.actions-header {
|
.actions-header {
|
||||||
@apply px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider;
|
@apply px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-header:last-child,
|
||||||
|
.actions-header {
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-number-header {
|
.row-number-header {
|
||||||
@ -932,6 +971,18 @@ onUnmounted(() => {
|
|||||||
.data-cell,
|
.data-cell,
|
||||||
.actions-cell {
|
.actions-cell {
|
||||||
@apply px-6 py-4 whitespace-nowrap;
|
@apply px-6 py-4 whitespace-nowrap;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
border-right: 1px solid #f3f4f6;
|
||||||
|
background: white;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-cell:last-child,
|
||||||
|
.actions-cell {
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-number {
|
.row-number {
|
||||||
@ -939,11 +990,11 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cell-content {
|
.cell-content {
|
||||||
@apply flex items-center;
|
@apply flex items-center w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-value {
|
.cell-value {
|
||||||
@apply text-sm text-gray-900;
|
@apply text-sm text-gray-900 w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons-row {
|
.action-buttons-row {
|
||||||
|
@ -2579,6 +2579,10 @@ watchEffect(() => {
|
|||||||
case 'dynamic-list':
|
case 'dynamic-list':
|
||||||
newDefaults[component.props.name] = Array.isArray(component.props.defaultItems) ? [...component.props.defaultItems] : [];
|
newDefaults[component.props.name] = Array.isArray(component.props.defaultItems) ? [...component.props.defaultItems] : [];
|
||||||
break;
|
break;
|
||||||
|
case 'repeating-table':
|
||||||
|
// Initialize repeating table with empty array or default data
|
||||||
|
newDefaults[component.props.name] = Array.isArray(component.props.defaultData) ? [...component.props.defaultData] : [];
|
||||||
|
break;
|
||||||
case 'select':
|
case 'select':
|
||||||
case 'radio':
|
case 'radio':
|
||||||
if (Array.isArray(component.props.options) && component.props.options.length > 0) {
|
if (Array.isArray(component.props.options) && component.props.options.length > 0) {
|
||||||
@ -2674,25 +2678,32 @@ const handleFormKitInput = (formData, node) => {
|
|||||||
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make form data accessible to component previews (for UI rendering, not for triggering script engine)
|
|
||||||
watchEffect(() => {
|
|
||||||
if (formStore) { // Ensure formStore is available
|
|
||||||
formStore.updatePreviewFormData(previewFormData.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Watch for changes in previewFormData to trigger FormScriptEngine
|
// Watch for changes in previewFormData to trigger FormScriptEngine
|
||||||
|
// Only update store if data actually changed to prevent recursion
|
||||||
watch(() => previewFormData.value, (newData, oldData) => {
|
watch(() => previewFormData.value, (newData, oldData) => {
|
||||||
if (!isPreview.value) return; // Only in preview mode
|
if (!isPreview.value) return; // Only in preview mode
|
||||||
|
|
||||||
|
// Check if data actually changed to prevent unnecessary updates
|
||||||
|
const newDataStr = JSON.stringify(newData);
|
||||||
|
const oldDataStr = JSON.stringify(oldData);
|
||||||
|
|
||||||
|
if (newDataStr === oldDataStr) {
|
||||||
|
return; // No actual change, skip update
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[FormBuilder] previewFormData watcher triggered!');
|
console.log('[FormBuilder] previewFormData watcher triggered!');
|
||||||
console.log('[FormBuilder] New data:', JSON.parse(JSON.stringify(newData)));
|
console.log('[FormBuilder] New data:', newDataStr);
|
||||||
console.log('[FormBuilder] Old data:', oldData ? JSON.parse(JSON.stringify(oldData)) : 'undefined');
|
console.log('[FormBuilder] Old data:', oldDataStr);
|
||||||
|
|
||||||
// Update form store
|
// Use nextTick to avoid synchronous updates that can cause recursion
|
||||||
formStore.updatePreviewFormData(newData);
|
nextTick(() => {
|
||||||
|
// Only update store if the data is still different (hasn't been changed by something else)
|
||||||
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
const currentDataStr = JSON.stringify(previewFormData.value);
|
||||||
|
if (currentDataStr === newDataStr) {
|
||||||
|
formStore.updatePreviewFormData(newData);
|
||||||
|
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
||||||
|
}
|
||||||
|
});
|
||||||
}, { deep: true, immediate: false });
|
}, { deep: true, immediate: false });
|
||||||
|
|
||||||
const navigateToManage = () => {
|
const navigateToManage = () => {
|
||||||
|
@ -1214,7 +1214,7 @@ watch(currentStep, async (newStep) => {
|
|||||||
if (currentForm.value?.formComponents) {
|
if (currentForm.value?.formComponents) {
|
||||||
formStore.formComponents = currentForm.value.formComponents;
|
formStore.formComponents = currentForm.value.formComponents;
|
||||||
|
|
||||||
// Initialize repeating groups in form data
|
// Initialize repeating groups and tables in form data
|
||||||
const updatedFormData = { ...formData.value };
|
const updatedFormData = { ...formData.value };
|
||||||
currentForm.value.formComponents.forEach(component => {
|
currentForm.value.formComponents.forEach(component => {
|
||||||
if (component.type === 'repeating-group' && component.props?.name) {
|
if (component.type === 'repeating-group' && component.props?.name) {
|
||||||
@ -1240,6 +1240,13 @@ watch(currentStep, async (newStep) => {
|
|||||||
|
|
||||||
updatedFormData[groupName] = initialGroups;
|
updatedFormData[groupName] = initialGroups;
|
||||||
}
|
}
|
||||||
|
} else if (component.type === 'repeating-table' && component.props?.name) {
|
||||||
|
const tableName = component.props.name;
|
||||||
|
|
||||||
|
// If the field doesn't exist or is not an array, initialize it as empty array
|
||||||
|
if (!updatedFormData[tableName] || !Array.isArray(updatedFormData[tableName])) {
|
||||||
|
updatedFormData[tableName] = component.props.defaultData ? [...component.props.defaultData] : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user