From bc7daed98863a5d8acd2908e02da48dc3a75f3e2 Mon Sep 17 00:00:00 2001 From: Afiq Date: Thu, 7 Aug 2025 14:45:35 +0800 Subject: [PATCH] Enhance Info Display Component Functionality - Updated ComponentPreview.vue to include optional component labels and help text for improved user guidance. - Enhanced layout options for the info display component, adding support for side-by-side, grid, and horizontal layouts, with button functionality for fields. - Modified FormBuilderComponents.vue to update the description and default properties for the info display component, including new fields for button configuration. - Improved FormBuilderConfiguration.vue to add essential settings for the info display, including component title, name, label, help text, and layout style. - Enhanced FormBuilderFieldSettingsModal.vue to support configuration of information fields, including field type selection (text or button) and button-specific settings. - Added robust error handling for button click actions in the info display, ensuring a seamless user experience when interacting with links. - Refined styles across components for better visual consistency and usability. --- components/ComponentPreview.vue | 94 ++++++- components/FormBuilderComponents.vue | 17 +- components/FormBuilderConfiguration.vue | 166 ++++++++--- components/FormBuilderFieldSettingsModal.vue | 277 ++++++++++++++++++- 4 files changed, 505 insertions(+), 49 deletions(-) diff --git a/components/ComponentPreview.vue b/components/ComponentPreview.vue index d8bb8e9..17de7ae 100644 --- a/components/ComponentPreview.vue +++ b/components/ComponentPreview.vue @@ -66,6 +66,16 @@
+ +
+ +
+ + +
+ {{ component.props.help }} +
+
-
+ +
-
{{ field.label }}
-
{{ field.value }}
+
{{ field.label }}
+
+ + + + {{ field.value }} +
+ +
+
+
{{ field.label }}
+
+ + + + {{ field.value }} +
+
+
+ +
{{ field.label }}:
-
{{ field.value }}
+
+ + + + {{ field.value }} +
+
{{ field.label }}
-
{{ field.value }}
+
+ + + + {{ field.value }} +
@@ -1470,6 +1539,21 @@ const updateRepeatingGroupField = (groupName, groupIndex, fieldName, value) => { // Duplicate RepeatingGroupContainer definition removed +// Handle info display button clicks +const handleInfoButtonClick = (url, openInNewTab = false) => { + if (!url) return; + + try { + if (openInNewTab) { + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + window.location.href = url; + } + } catch (error) { + console.error('Error opening URL:', error); + } +}; + // Update table data for repeating-table component const updateTableData = (newData) => { const tableName = props.component.props.name; diff --git a/components/FormBuilderComponents.vue b/components/FormBuilderComponents.vue index feac1ac..f895ca1 100644 --- a/components/FormBuilderComponents.vue +++ b/components/FormBuilderComponents.vue @@ -707,17 +707,22 @@ const availableComponents = [ name: 'Info Display', category: 'Layout', icon: 'material-symbols:info-outline', - description: 'Display read-only information in key-value format', + description: 'Display read-only information in key-value format with optional buttons', defaultProps: { + name: 'info_display', title: 'Information', + help: 'Display important information in an organized format', fields: [ - { label: 'Customer Name', value: 'John Doe', key: 'customer_name' }, - { label: 'Email', value: 'john@example.com', key: 'customer_email' }, - { label: 'Phone', value: '+1-234-567-8900', key: 'customer_phone' } + { label: 'Customer Name', value: 'John Doe', key: 'customer_name', type: 'text' }, + { label: 'Email', value: 'john@example.com', key: 'customer_email', type: 'text' }, + { label: 'Phone', value: '+1-234-567-8900', key: 'customer_phone', type: 'text' }, + { label: 'View Profile', value: 'View Details', key: 'profile_link', type: 'button', url: 'https://example.com/profile', openInNewTab: true, icon: 'material-symbols:link' } ], - layout: 'vertical', // vertical, horizontal, grid + layout: 'side-by-side', // vertical, horizontal, grid, side-by-side showBorder: true, - backgroundColor: '#f9fafb' + backgroundColor: '#f9fafb', + width: '100%', + gridColumn: 'span 12' } }, { diff --git a/components/FormBuilderConfiguration.vue b/components/FormBuilderConfiguration.vue index 505cdb3..ed2b1c4 100644 --- a/components/FormBuilderConfiguration.vue +++ b/components/FormBuilderConfiguration.vue @@ -589,45 +589,79 @@ + + + + + +
@@ -2337,6 +2581,8 @@ if (this.element.querySelector('.file-upload')) { placeholder="e.g., required|email|length:3,50" /> + + @@ -2702,10 +2948,10 @@ const showField = (fieldName) => { if (!props.component) return false const fieldConfig = { - label: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'], - name: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'], + label: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'], + name: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'], placeholder: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'dynamic-list'], - help: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'], + help: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'], value: ['heading', 'paragraph', 'hidden'], width: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'heading', 'paragraph', 'form-section', 'info-display', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'], rows: ['textarea'], @@ -2720,7 +2966,7 @@ const showField = (fieldName) => { const hasOptions = computed(() => showField('options')) const hasSpecificSettings = computed(() => { if (!props.component) return false - const specificTypes = ['mask', 'otp', 'dropzone', 'range', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'] + const specificTypes = ['mask', 'otp', 'dropzone', 'range', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'] return specificTypes.includes(props.component.type) }) @@ -3210,6 +3456,29 @@ const removeCondition = (index) => { } } +// Info Display field management +const addInfoField = () => { + if (!configModel.value.fields) { + configModel.value.fields = [] + } + + configModel.value.fields.push({ + label: `Field ${configModel.value.fields.length + 1}`, + value: 'Value', + key: `field_${configModel.value.fields.length + 1}`, + type: 'text', // text, button + url: '', // for button type + openInNewTab: false, // for button type + icon: '' // for button type + }) +} + +const removeInfoField = (index) => { + if (configModel.value.fields && configModel.value.fields.length > index) { + configModel.value.fields.splice(index, 1) + } +} + const generateConditionalLogicCode = () => { if (!configModel.value.conditionalLogic || !configModel.value.conditionalLogic.conditions.length) { return '// No conditions defined'