Dynamic Form - Copy this React, Tailwind Component to your project
import React, { useState, useEffect } from 'react'; import { Table, Button, Popup, Modal, Form, Header, Grid, Container, Checkbox, Message, } from 'semantic-ui-react'; import { useNavigate } from 'react-router-dom'; import { fetchFormData, submitForm, fetchFieldOptions, API_URL } from '../api'; // Adjust if necessary import { FaSort, FaSortUp, FaSortDown, FaCheck } from 'react-icons/fa'; const FormRenderer = ({ formType }) => { const [formData, setFormData] = useState(null); const [formValues, setFormValues] = useState({}); const [submittedData, setSubmittedData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [options, setOptions] = useState({}); const [showFormPopup, setShowFormPopup] = useState(false); const [editMode, setEditMode] = useState(false); const [currentlyEditing, setCurrentlyEditing] = useState(null); const [showFilterPopup, setShowFilterPopup] = useState(false); const [showColumnPopup, setShowColumnPopup] = useState(false); const [showChildFormPopup, setShowChildFormPopup] = useState(false); const [childFormData, setChildFormData] = useState(null); const [selectedColumns, setSelectedColumns] = useState({}); const [sortField, setSortField] = useState(null); const [sortDirection, setSortDirection] = useState('ascending'); const [filterValues, setFilterValues] = useState({}); const [inlineEditSubmitted, setInlineEditSubmitted] = useState(false); // New state variable const navigate = useNavigate(); useEffect(() => { const fetchData = async () => { try { const response = await fetchFormData(formType); setFormData(response); setLoading(false); const getResponse = await fetch(`${API_URL}/dynamic-info?formType=${formType}`); const data = await getResponse.json(); setSubmittedData(data); await fetchFieldOptionsForSelectFields(response.fields); const initialSelectedColumns = response.fields.reduce((acc, field) => { acc[field.name] = true; return acc; }, {}); setSelectedColumns(initialSelectedColumns); } catch (err) { setError('Failed to load form data'); setLoading(false); } }; fetchData(); }, [formType]); const fetchFieldOptionsForSelectFields = async (fields) => { const promises = fields.map(async (field) => { if (field.type === 'select') { if (field.optionsApi) { try { const response = await fetchFieldOptions(field.optionsApi); setOptions((prevOptions) => ({ ...prevOptions, [field.name]: response, })); } catch (error) { console.error(`Error fetching options for ${field.name}:`, error); } } else if (field.options) { setOptions((prevOptions) => ({ ...prevOptions, [field.name]: field.options, })); } } }); await Promise.all(promises); }; const handleChange = (e) => { const { name, value, type, checked } = e.target; // Handle checkbox logic if (type === 'checkbox') { setFormValues((prevValues) => { const currentValues = prevValues[name] || []; if (checked) { return { ...prevValues, [name]: [...currentValues, value], // Add the option if checked }; } else { return { ...prevValues, [name]: currentValues.filter((v) => v !== value), // Remove if unchecked }; } }); } else { setFormValues((prev) => ({ ...prev, [name]: value, })); } }; const handleFilterChange = (e) => { const { name, value } = e.target; setFilterValues((prev) => ({ ...prev, [name]: value })); }; const handleInlineEditSubmit = async (field) => { const value = formValues[field.name]; if (!value) { alert("Field value is empty, please enter a value before submitting."); return; } if (field.inlineEdit) { try { // Update the field const response = await fetch(`${API_URL}/dynamic-info?formType=${formType}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ formType, _id: currentlyEditing, fieldName: field.name, fieldValue: value }), }); if (response.ok) { alert('Field updated successfully!'); setInlineEditSubmitted(true); // Set the state when inline edit is submitted // Fetch the updated data const getResponse = await fetch(`${API_URL}/dynamic-info?formType=${formType}`); const updatedData = await getResponse.json(); // Rebind the updated data setSubmittedData(updatedData); // Assuming updatedData has the structure of submittedData setFormValues((prevValues) => ({ ...prevValues, [field.name]: value, // Update the specific field with the new value })); } else { throw new Error('Failed to update field'); } } catch (error) { alert('Error updating field: ' + error.message); } } }; const handleSubmit = async (e) => { e.preventDefault(); // Check if inline edit was submitted; if yes, exit early if (inlineEditSubmitted) { alert('HandleInlineEditSubmit has been called, cannot submit the form.'); return; } const submitData = { formType, formValues }; try { const response = await submitForm(submitData); if (editMode) { setSubmittedData((prev) => prev.map((item) => (item._id === response._id ? response : item)) ); } else { setSubmittedData((prev) => [...prev, response]); } alert('Form submitted successfully!'); setFormValues({}); setShowFormPopup(false); setEditMode(false); setInlineEditSubmitted(false); // Reset after successful form submission } catch (err) { alert('Error submitting form: ' + err.message); } }; const handleEdit = (row) => { setFormValues(row); setCurrentlyEditing(row._id); setEditMode(true); setShowFormPopup(true); }; const handleSort = (field) => { const direction = sortField === field && sortDirection === 'ascending' ? 'descending' : 'ascending'; const sortedData = [...submittedData].sort((a, b) => { return direction === 'ascending' ? (a[field] > b[field] ? 1 : -1) : (a[field] < b[field] ? 1 : -1); }); setSortField(field); setSortDirection(direction); setSubmittedData(sortedData); }; const handleColumnVisibilityChange = (field) => { setSelectedColumns((prev) => ({ ...prev, [field]: !prev[field], })); }; const renderField = (field) => { const { label, name, type, required, inlineEdit, options } = field; const value = formValues[name] || ''; if (!selectedColumns[name]) return null; return ( <Grid.Row key={name}> <Grid.Column width={8}> <label>{label}</label> </Grid.Column> <Grid.Column width={8}> <Popup content={`Default value: ${value}`} trigger={ (() => { switch (type) { case 'text': case 'email': case 'number': case 'tel': return ( <Form.Input type={type} name={name} value={value} onChange={handleChange} required={required} /> ); case 'select': return ( <select name={name} value={value} onChange={handleChange} required> <option value="">Select {label}</option> {options[name]?.map((option, index) => ( <option key={index} value={option}>{option}</option> ))} </select> ); case 'file': return ( <input type="file" name={name} onChange={handleChange} required /> ); case 'datetime': case 'date': return ( <input type="datetime-local" name={name} value={value} onChange={handleChange} required /> ); case 'textarea': return ( <Form.TextArea name={name} value={value} onChange={handleChange} required rows="4" style={{ width: '100%' }} placeholder="Enter your text here..." /> ); case 'checkbox': return ( <div> {options && options.length > 0 ? options.map((option, index) => ( <Checkbox key={index} label={option} name={name} value={option} checked={formValues[name]?.includes(option) || false} onChange={(e) => { const currentValues = formValues[name] || []; if (e.target.checked) { setFormValues((prev) => ({ ...prev, [name]: [...currentValues, option], // Add the option if checked })); } else { setFormValues((prev) => ({ ...prev, [name]: currentValues.filter((i) => i !== option), // Remove if unchecked })); } }} /> )) : ( <Checkbox label="Default Checkbox" name={name} checked={formValues[name]?.includes("Default Checkbox") || false} onChange={(e) => { const currentValues = formValues[name] || []; if (e.target.checked) { setFormValues((prev) => ({ ...prev, [name]: [...currentValues, "Default Checkbox"], // Add default checkbox if checked })); } else { setFormValues((prev) => ({ ...prev, [name]: currentValues.filter((i) => i !== "Default Checkbox"), // Remove if unchecked })); } }} /> )} </div> ); case 'childForm': return ( <Button onClick={() => handleOpenChildForm(field)}>Manage {label}</Button> ); default: return null; } })() } /> {/* Render the tick mark button for inline edits */} {inlineEdit && editMode && ( <Button icon onClick={() => handleInlineEditSubmit(field)} style={{ marginLeft: '10px' }}> <FaCheck /> </Button> )} </Grid.Column> </Grid.Row> ); }; const handleOpenChildForm = async (field) => { setShowChildFormPopup(true); setChildFormData(null); try { const response = await fetchFormData(field.linkedForm); setChildFormData(response); } catch (error) { console.error('Error fetching child form data:', error); } }; const renderChildFormPopup = () => ( <Modal open={showChildFormPopup} onClose={() => setShowChildFormPopup(false)}> <Header>Manage {formData?.formTitle}</Header> <Modal.Content> {childFormData && childFormData.fields.map(field => renderField(field))} <Button type="submit" primary>Submit Child Form</Button> <Button type="button" onClick={() => setShowChildFormPopup(false)}>Cancel</Button> </Modal.Content> </Modal> ); const renderFormPopup = () => ( <Modal open={showFormPopup} onClose={() => setShowFormPopup(false)}> <Header>{formData?.formTitle}</Header> <Modal.Content> <Form> <Grid> {formData.fields.map((field) => renderField(field))} </Grid> <Button type="submit" primary>{editMode ? 'Update' : 'Submit'}</Button> <Button type="button" onClick={() => setShowFormPopup(false)}>Cancel</Button> </Form> </Modal.Content> </Modal> ); const renderColumnPopup = () => ( <Modal open={showColumnPopup} onClose={() => setShowColumnPopup(false)}> <Header>Manage Columns</Header> <Modal.Content> {formData?.fields.map((field) => ( <Grid.Row key={field.name}> <Grid.Column width={8}> <label>{field.label}</label> </Grid.Column> <Grid.Column width={8}> <Checkbox toggle checked={selectedColumns[field.name]} onChange={() => handleColumnVisibilityChange(field.name)} /> </Grid.Column> </Grid.Row> ))} <Button primary onClick={() => setShowColumnPopup(false)}>Apply</Button> </Modal.Content> </Modal> ); const renderFilterPopup = () => ( <Modal open={showFilterPopup} onClose={() => setShowFilterPopup(false)}> <Header>Filter Options</Header> <Modal.Content> <Grid> {formData?.fields.map((field) => { const { label, name, type } = field; return ( <Grid.Row key={name}> <Grid.Column width={8}> <label>{label}</label> </Grid.Column> <Grid.Column width={8}> {type === 'text' || type === 'email' || type === 'number' || type === 'tel' ? ( <input type={type} name={name} value={filterValues[name] || ''} onChange={handleFilterChange} /> ) : type === 'select' ? ( <select name={name} value={filterValues[name] || ''} onChange={handleFilterChange}> <option value="">Select {label}</option> {options[name]?.map((option, index) => ( <option key={index} value={option}>{option}</option> ))} </select> ) : type === 'datetime' || type === 'date' ? ( <input type="datetime-local" name={name} value={filterValues[name] || ''} onChange={handleFilterChange} /> ) : type === 'daterange' ? ( <div> <input type="date" name={`${name}Start`} value={filterValues[`${name}Start`] || ''} onChange={handleFilterChange} /> <input type="date" name={`${name}End`} value={filterValues[`${name}End`] || ''} onChange={handleFilterChange} /> </div> ) : null} </Grid.Column> </Grid.Row> ); })} </Grid> <Button onClick={applyFilters} primary>Apply</Button> <Button onClick={() => setShowFilterPopup(false)}>Cancel</Button> <Button onClick={() => { setFilterValues({}); setShowFilterPopup(false); }}>Reset</Button> </Modal.Content> </Modal> ); const applyFilters = () => { alert('Filters applied!'); setShowFilterPopup(false); }; const renderSortIcon = (field) => { if (sortField === field) { return sortDirection === 'ascending' ? <FaSortUp /> : <FaSortDown />; } return <FaSort />; }; if (loading) { return <div>Loading form...</div>; } if (error) { return <div>{error}</div>; } return ( <div className="dashboard"> <Container> <Header as="h2">{formData?.formTitle}</Header> <Button onClick={() => setShowFormPopup(true)} primary>Add New</Button> <Button onClick={() => setShowColumnPopup(true)} primary style={{ marginLeft: '10px' }}> Manage Columns </Button> <div style={{ textAlign: 'right', marginBottom: '10px' }}> <Button onClick={() => setShowFilterPopup(true)} primary>Filter</Button> </div> {showFormPopup && renderFormPopup()} {showColumnPopup && renderColumnPopup()} {showFilterPopup && renderFilterPopup()} {showChildFormPopup && renderChildFormPopup()} <h3>Submitted Data</h3> <Table celled striped> <Table.Header> <Table.Row> {formData.fields.map((field) => selectedColumns[field.name] && ( <Table.HeaderCell key={field.name} onClick={() => handleSort(field.name)} style={{ cursor: 'pointer' }} > {field.label} {renderSortIcon(field.name)} </Table.HeaderCell> ))} <Table.HeaderCell>Actions</Table.HeaderCell> </Table.Row> </Table.Header> <Table.Body> {submittedData.map((data, index) => ( <Table.Row key={index}> {formData.fields.map((field) => selectedColumns[field.name] && ( <Table.Cell key={field.name}> {typeof data[field.name] === 'object' && data[field.name] !== null ? ( <div> {Object.entries(data[field.name]).map(([key, value]) => ( <div key={key}>{`${key}: ${value}`}</div> ))} </div> ) : ( data[field.name] )} </Table.Cell> ))} <Table.Cell> {formData.fields.map((field) => field.type === 'childForm' && ( <Button key={field.name} onClick={() => handleOpenChildForm(field)}>Manage</Button> ))} <Button onClick={() => handleEdit(data)}>Edit</Button> <Button>Delete</Button> </Table.Cell> </Table.Row> ))} </Table.Body> </Table> </Container> </div> ); }; export default FormRenderer; use this and generate beautiful ui with mock data