diff --git a/components/ComponentPreview.vue b/components/ComponentPreview.vue
index 17de7ae..6176210 100644
--- a/components/ComponentPreview.vue
+++ b/components/ComponentPreview.vue
@@ -482,7 +482,7 @@
-
+
console.log('[ComponentPreview] Table debug data:', data)"
+ @mounted="() => console.log('[ComponentPreview] RepeatingTable mounted with data:', getTableData(component.props.name))"
/>
@@ -1411,12 +1413,13 @@ const getTableData = (tableName) => {
return currentData;
}
- // If data doesn't exist, initialize it immediately
+ // If data doesn't exist, initialize it
const initialData = [];
- const updatedFormData = { ...formStore.previewFormData, [tableName]: initialData };
- formStore.updatePreviewFormData(updatedFormData);
-
- console.log('[ComponentPreview] getTableData: initialized new data for:', tableName);
+ nextTick(() => {
+ const updatedFormData = { ...formStore.previewFormData, [tableName]: initialData };
+ formStore.updatePreviewFormData(updatedFormData);
+ console.log('[ComponentPreview] getTableData: initialized new data for:', tableName);
+ });
return initialData;
};
@@ -2513,6 +2516,47 @@ onMounted(() => {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
}
+/* Table Loading State Styles */
+.table-loading-state {
+ @apply border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm w-full;
+ min-width: 0;
+ max-width: 100%;
+ box-sizing: border-box;
+}
+
+.loading-header {
+ @apply p-4 md:p-6 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-200;
+}
+
+.loading-title {
+ @apply text-lg font-semibold text-gray-800 leading-tight mb-1;
+}
+
+.loading-subtitle {
+ @apply text-sm text-gray-600 leading-relaxed;
+}
+
+.loading-table {
+ @apply p-4;
+}
+
+.loading-row {
+ @apply flex gap-4 mb-3;
+}
+
+.loading-header-row {
+ @apply border-b border-gray-200 pb-3 mb-4;
+}
+
+.loading-cell {
+ @apply flex-1 h-6 bg-gray-200 rounded animate-pulse;
+ min-width: 80px;
+}
+
+.loading-header-row .loading-cell {
+ @apply h-4 bg-gray-300;
+}
+
/* Custom HTML Component Styles */
.custom-html-wrapper {
width: 100%;
diff --git a/components/RepeatingTable.vue b/components/RepeatingTable.vue
index 8bd8bd8..3b5946c 100644
--- a/components/RepeatingTable.vue
+++ b/components/RepeatingTable.vue
@@ -106,11 +106,10 @@
:style="{ width: getColumnWidth(column), minWidth: getColumnWidth(column) }"
>
-
+
+
+ {{ formatCellValue(record[column.name], column) }}
+
@@ -222,7 +221,7 @@
type="form"
:actions="false"
v-model="formData"
- @submit="saveRecord"
+ @submit="(formData) => { console.log('[RepeatingTable] Form submitted with:', formData); saveRecord(formData); }"
class="record-form"
>
@@ -298,59 +297,7 @@ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import { useDebounceFn } from '@vueuse/core'
-// Simple cell component that shows data immediately
-const SimpleCellValue = defineComponent({
- props: {
- value: {
- type: [String, Number, Boolean, Object],
- default: null
- },
- column: {
- type: Object,
- required: true
- },
- record: {
- type: Object,
- required: true
- }
- },
- setup(props) {
- const formatValue = computed(() => {
- const value = props.value
- if (value === null || value === undefined || value === '') {
- return '-'
- }
-
- if (props.column.type === 'date') {
- return new Date(value).toLocaleDateString()
- }
-
- if (props.column.type === 'time') {
- return value
- }
-
- if (props.column.type === 'checkbox') {
- return value ? 'Yes' : 'No'
- }
-
- if (props.column.type === 'select' && props.column.options) {
- const option = props.column.options.find(opt => opt.value === value)
- return option ? option.label : value
- }
-
- return value.toString()
- })
-
- return {
- formatValue
- }
- },
- template: `
-
- {{ formatValue }}
-
- `
-})
+// SimpleCellValue component removed - using direct template rendering instead
const props = defineProps({
config: {
@@ -367,7 +314,7 @@ const props = defineProps({
}
})
-const emit = defineEmits(['update:modelValue'])
+const emit = defineEmits(['update:modelValue', 'debug-data', 'mounted'])
// Reactive state
const data = ref([...(props.modelValue || [])])
@@ -380,6 +327,9 @@ const formData = ref({})
const formId = ref(`table-form-${uuidv4()}`)
const tableContainer = ref(null)
+// Debug flag
+const debug = ref(true)
+
// Pagination state
const currentPage = ref(1)
const pageSize = ref(10)
@@ -401,15 +351,20 @@ watch(searchQuery, (newQuery) => {
// Computed properties with memoization
const filteredData = computed(() => {
- if (!searchQuery.value) return data.value
+ if (!searchQuery.value) {
+ console.log('[RepeatingTable] filteredData (no search):', data.value)
+ return data.value
+ }
const query = searchQuery.value.toLowerCase()
- return data.value.filter(record => {
+ const result = data.value.filter(record => {
return props.config.columns.some(column => {
const value = record[column.name]
return value && value.toString().toLowerCase().includes(query)
})
})
+ console.log('[RepeatingTable] filteredData (with search):', result)
+ return result
})
// Column virtualization for large datasets
@@ -466,12 +421,11 @@ const getColumnWidth = (column) => {
return width
}
-// Record key generation for better Vue rendering
+// Record key generation for better Vue rendering - production safe
const getRecordKey = (record, index) => {
- if (!recordKeys.value.has(record)) {
- recordKeys.value.set(record, `record-${index}-${Date.now()}`)
- }
- return recordKeys.value.get(record)
+ // Use a more reliable key generation that works in production
+ const recordId = record.id || record._id || `record-${index}`
+ return `${recordId}-${index}`
}
const isAddDisabled = computed(() => {
@@ -494,7 +448,15 @@ const totalPages = computed(() => {
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
- return filteredData.value.slice(start, end)
+ const result = filteredData.value.slice(start, end)
+ console.log('[RepeatingTable] paginatedData computed:', {
+ start,
+ end,
+ filteredDataLength: filteredData.value.length,
+ resultLength: result.length,
+ result: result
+ })
+ return result
})
const startRecord = computed(() => {
@@ -538,7 +500,7 @@ const visiblePages = computed(() => {
// Guard to prevent recursive updates
const isUpdatingFromProps = ref(false)
-// Watch for external data changes
+// Watch for external data changes with SSR safety
watch(() => props.modelValue, (newValue) => {
// Handle the case where newValue might be undefined/null
const safeNewValue = newValue || []
@@ -549,6 +511,12 @@ watch(() => props.modelValue, (newValue) => {
if (newDataStr === currentDataStr) return
+ // Prevent hydration issues by ensuring updates happen on client side
+ if (process.server) {
+ data.value = [...safeNewValue]
+ return
+ }
+
isUpdatingFromProps.value = true
data.value = [...safeNewValue]
// Clear caches when data changes
@@ -564,14 +532,20 @@ watch(() => props.modelValue, (newValue) => {
watch(data, (newData, oldData) => {
if (isUpdatingFromProps.value) return
+ // Prevent emissions during SSR to avoid hydration issues
+ if (process.server) return
+
// Check if data actually changed
const newDataStr = JSON.stringify(newData)
const oldDataStr = JSON.stringify(oldData)
if (newDataStr === oldDataStr) return
+ console.log('[RepeatingTable] Data changed, emitting update:', newData)
+
nextTick(() => {
emit('update:modelValue', [...newData])
+ emit('debug-data', newData)
})
}, { deep: true })
@@ -585,6 +559,7 @@ const openAddModal = () => {
formData.value[column.name] = getDefaultValue(column.type)
})
+ console.log('[RepeatingTable] openAddModal - initialized formData:', formData.value)
showModal.value = true
}
@@ -608,15 +583,36 @@ const submitForm = () => {
}
}
-const saveRecord = (formData) => {
+const saveRecord = (submittedFormData) => {
+ console.log('[RepeatingTable] saveRecord called with:', submittedFormData)
+ console.log('[RepeatingTable] Current data before save:', data.value)
+
if (editingIndex.value !== null) {
// Update existing record
- data.value[editingIndex.value] = { ...formData }
+ data.value[editingIndex.value] = {
+ ...submittedFormData,
+ id: data.value[editingIndex.value].id || `record-${editingIndex.value}-${Date.now()}`
+ }
+ console.log('[RepeatingTable] Updated record at index:', editingIndex.value, 'with data:', submittedFormData)
} else {
- // Add new record
- data.value.push({ ...formData })
+ // Add new record with unique ID
+ const newRecord = {
+ ...submittedFormData,
+ id: `record-${data.value.length}-${Date.now()}`
+ }
+ data.value.push(newRecord)
+ console.log('[RepeatingTable] Added new record with data:', newRecord)
}
+ console.log('[RepeatingTable] Data after save:', data.value)
+
+ // Force reactivity update in production
+ nextTick(() => {
+ // Trigger a reactive update by reassigning the array
+ data.value = [...data.value]
+ console.log('[RepeatingTable] Forced reactivity update, data:', data.value)
+ })
+
closeModal()
}
@@ -698,6 +694,34 @@ const goToPage = (page) => {
currentPage.value = page
}
+// Format cell value directly in template
+const formatCellValue = (value, column) => {
+ console.log(`[formatCellValue] Column: ${column.name}, Value:`, value, 'Type:', typeof value)
+
+ if (value === null || value === undefined || value === '') {
+ return '-'
+ }
+
+ if (column.type === 'date') {
+ return new Date(value).toLocaleDateString()
+ }
+
+ if (column.type === 'time') {
+ return value
+ }
+
+ if (column.type === 'checkbox') {
+ return value ? 'Yes' : 'No'
+ }
+
+ if (column.type === 'select' && column.options) {
+ const option = column.options.find(opt => opt.value === value)
+ return option ? option.label : value
+ }
+
+ return value.toString()
+}
+
// Calculate minimum table width based on columns
const calculateMinTableWidth = computed(() => {
if (!props.config.columns) return 'auto'
@@ -729,6 +753,9 @@ const calculateMinTableWidth = computed(() => {
// Debug table width on mount
onMounted(() => {
+ console.log('[RepeatingTable] Component mounted with data:', data.value)
+ emit('mounted')
+
nextTick(() => {
if (tableContainer.value) {
const table = tableContainer.value.querySelector('.data-table')
diff --git a/plugins/form-hydration.client.js b/plugins/form-hydration.client.js
new file mode 100644
index 0000000..8824ee0
--- /dev/null
+++ b/plugins/form-hydration.client.js
@@ -0,0 +1,39 @@
+export default defineNuxtPlugin(() => {
+ // This plugin runs only on the client side to handle form data hydration
+ const { $formStore } = useNuxtApp()
+
+ // Ensure form store data is properly initialized on client hydration
+ if (process.client) {
+ // Add any additional client-side form data initialization logic here
+ console.log('[FormHydration] Client-side form data hydration plugin loaded')
+
+ // Handle any form data that needs to be restored after hydration
+ const restoreFormData = () => {
+ try {
+ // Check if there's any persisted form data that needs restoration
+ const persistedData = sessionStorage.getItem('form-builder-data')
+ if (persistedData && $formStore) {
+ const parsed = JSON.parse(persistedData)
+ if (parsed.previewFormData) {
+ $formStore.updatePreviewFormData(parsed.previewFormData)
+ console.log('[FormHydration] Restored form data from session storage')
+ }
+ }
+ } catch (error) {
+ console.warn('[FormHydration] Failed to restore form data:', error)
+ }
+ }
+
+ // Restore data after page load
+ window.addEventListener('load', restoreFormData)
+
+ // Clean up
+ return {
+ provide: {
+ formHydration: {
+ restoreFormData
+ }
+ }
+ }
+ }
+})
diff --git a/stores/formBuilder.js b/stores/formBuilder.js
index 7a3dc4c..3d6501c 100644
--- a/stores/formBuilder.js
+++ b/stores/formBuilder.js
@@ -1002,9 +1002,21 @@ export const useFormBuilderStore = defineStore('formBuilder', {
});
},
- // Update preview form data
+ // Update preview form data - SSR safe
updatePreviewFormData(data) {
this.previewFormData = { ...data };
+
+ // Only persist to sessionStorage on client side to prevent hydration issues
+ if (process.client) {
+ try {
+ sessionStorage.setItem('form-builder-data', JSON.stringify({
+ previewFormData: this.previewFormData,
+ timestamp: Date.now()
+ }));
+ } catch (error) {
+ console.warn('[FormStore] Failed to persist form data:', error);
+ }
+ }
},
// Optimize the grid layout by analyzing the current components
|