Create Rule page with validation
Fri May 23 2025 11:13:40 GMT+0000 (Coordinated Universal Time)
Saved by @krisha_joshi
import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { McButton, McInput } from '@maersk-global/mds-react-wrapper'; import { McSelect } from '@maersk-global/mds-react-wrapper/components-core/mc-select'; import { McOption } from '@maersk-global/mds-react-wrapper/components-core/mc-option'; import styles from '../styles/CreateRule.module.css'; import data from '../data/PnLGroup.json'; const CreateRules = () => { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('ruleInfo'); const [ruleData, setRuleData] = useState({ num: '', name: '', desc: '', custRefID: '', ruleGroup: '', isActive: 'Y', pnlGroup: '', }); const [steps, setSteps] = useState([ { stepNo: '', stepName: 'Single Step', StepDesc: '', stepType: 'S', preAggregatorColumns: '', sourceTable: '', sourceFilters: '', joinColumns: '', allocationColumns: '', driverTableID: '', driverWeightColumn: '', driverFilters: '', }, ]); const [errors, setErrors] = useState({ rule: {}, steps: [{}] }); // Extract PnL Group options from JSON, with fallback for empty data const pnlGroups = data.PnLGroups ? Object.keys(data.PnLGroups) : []; // Get Rule Group options based on selected PnL Group, with fallback const ruleGroups = ruleData.pnlGroup && data.PnLGroups[ruleData.pnlGroup] ? data.PnLGroups[ruleData.pnlGroup].RuleGroups || [] : []; const addStep = () => { setSteps((prevSteps) => [ ...prevSteps, { stepNo: '', stepName: '', StepDesc: '', stepType: '', preAggregatorColumns: '', sourceTable: '', sourceFilters: '', joinColumns: '', allocationColumns: '', driverTableID: '', driverWeightColumn: '', driverFilters: '', }, ]); setErrors((prevErrors) => ({ ...prevErrors, steps: [...prevErrors.steps, {}], })); }; const validateForm = () => { const newErrors = { rule: {}, steps: steps.map(() => ({})) }; let isValid = true; // Validate rule data (all fields are mandatory) Object.keys(ruleData).forEach((key) => { if (!ruleData[key]) { newErrors.rule[key] = 'This field is required'; isValid = false; } }); // Validate steps (all fields are mandatory) steps.forEach((step, index) => { const stepErrors = {}; Object.keys(step).forEach((key) => { if (!step[key]) { stepErrors[key] = 'This field is required'; isValid = false; } }); newErrors.steps[index] = stepErrors; }); setErrors(newErrors); return isValid; }; const handleInputChange = (e, stepIndex = null) => { const { name, value } = e.target; console.log(`Input changed: ${name} = ${value}${stepIndex !== null ? ` (Step ${stepIndex + 1})` : ''}`); if (stepIndex !== null) { setSteps((prevSteps) => { const newSteps = [...prevSteps]; newSteps[stepIndex] = { ...newSteps[stepIndex], [name]: value }; return newSteps; }); setErrors((prevErrors) => ({ ...prevErrors, steps: prevErrors.steps.map((stepErrors, i) => i === stepIndex ? { ...stepErrors, [name]: '' } : stepErrors ), })); } else { setRuleData((prevData) => ({ ...prevData, [name]: value, ...(name === 'pnlGroup' ? { ruleGroup: '' } : {}), })); setErrors((prevErrors) => ({ ...prevErrors, rule: { ...prevErrors.rule, [name]: '' }, })); } }; const handleSelectChange = (e) => { const { name, value } = e.target; console.log(`Select changed: ${name} = ${value}`); setRuleData((prevData) => ({ ...prevData, [name]: value, ...(name === 'pnlGroup' ? { ruleGroup: '' } : {}), })); setErrors((prevErrors) => ({ ...prevErrors, rule: { ...prevErrors.rule, [name]: '' }, })); }; const handleSave = async () => { if (!validateForm()) { console.log('Validation failed:', JSON.stringify(errors, null, 2)); alert('Please fill out all required fields.'); return; } // Parse comma-separated columns and filters const parseColumns = (input) => input.split(',').map((item) => item.trim()).filter((item) => item); const parseFilters = (input) => { const filters = input.split(';').map((item) => item.trim()).filter((item) => item); return filters.map((filter) => { const [name, filterType, values] = filter.split(':').map((item) => item.trim()); return { name, filterType, values }; }); }; const ruleJson = { rules: { rule: [ { num: ruleData.num, name: ruleData.name, desc: ruleData.desc, custRefID: ruleData.custRefID, ruleGroup: ruleData.ruleGroup, isActive: ruleData.isActive, Step: steps.map((step, index) => ({ stepNo: step.stepNo || `${ruleData.num}.${index + 1}`, stepName: step.stepName === 'Single Step' ? 'single' : 'multi', StepDesc: step.StepDesc, stepType: step.stepType, isActive: 'Y', SourceTable: { id: '1', Name: step.sourceTable, }, sourceFilters: { columns: parseFilters(step.sourceFilters), }, preAggregator: { columns: parseColumns(step.preAggregatorColumns), }, join: { columns: parseColumns(step.joinColumns), }, allocation: { columns: parseColumns(step.allocationColumns), }, driver: { driverTableID: step.driverTableID, driverWeightColumn: step.driverWeightColumn, driverFilters: { columns: parseFilters(step.driverFilters), }, }, })), }, ], }, }; console.log('Saving Rule Data:', JSON.stringify(ruleJson, null, 2)); try { const response = await fetch('/api/rules', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify(ruleJson), }); if (response.ok) { console.log('Rule created successfully'); alert('Rule created successfully!'); navigate('/'); } else { const errorText = await response.text(); console.error('Failed to create rule:', response.status, errorText); alert(`Failed to create rule: ${errorText || response.statusText}`); } } catch (error) { console.error('Error during API call:', error.message); alert('An error occurred while saving the rule. Please try again.'); } }; const handleCancel = () => { console.log('Cancelling rule creation'); navigate('/'); }; const renderTabContent = () => { console.log('Rendering tab:', activeTab); switch (activeTab) { case 'ruleInfo': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Rule Information</h3> <div className={styles.formGrid}> <div className={styles.gridItem}> <McSelect label="PnL Group" name="pnlGroup" value={ruleData.pnlGroup} optionselected={handleSelectChange} placeholder="Select a PnL Group" required invalid={!!errors.rule.pnlGroup} invalidmessage={errors.rule.pnlGroup} > {pnlGroups.map((group) => ( <McOption key={group} value={group}> {group} </McOption> ))} </McSelect> </div> <div className={styles.gridItem}> <McSelect label="Rule Group" name="ruleGroup" value={ruleData.ruleGroup} optionselected={handleSelectChange} placeholder={ruleGroups.length ? "Select a Rule Group" : "Select a PnL Group first"} required disabled={!ruleData.pnlGroup || !ruleGroups.length} invalid={!!errors.rule.ruleGroup} invalidmessage={errors.rule.ruleGroup} > {ruleGroups.map((group) => ( <McOption key={group} value={group}> {group} </McOption> ))} </McSelect> </div> </div> <div className={styles.inputGroup}> <McInput label="Rule Number" name="num" value={ruleData.num} input={handleInputChange} placeholder="Enter rule number" required invalid={!!errors.rule.num} invalidmessage={errors.rule.num} /> </div> <div className={styles.inputGroup}> <McInput label="Rule Name" name="name" value={ruleData.name} input={handleInputChange} placeholder="Enter rule name" required invalid={!!errors.rule.name} invalidmessage={errors.rule.name} /> </div> <div className={styles.inputGroup}> <McInput label="Description" name="desc" value={ruleData.desc} input={handleInputChange} placeholder="Enter rule description" multiline rows={3} required invalid={!!errors.rule.desc} invalidmessage={errors.rule.desc} /> </div> <div className={styles.inputGroup}> <McInput label="Customer Reference ID" name="custRefID" value={ruleData.custRefID} input={handleInputChange} placeholder="Enter customer reference ID" required invalid={!!errors.rule.custRefID} invalidmessage={errors.rule.custRefID} /> </div> <div className={styles.inputGroup}> <McSelect label="Is Active" name="isActive" value={ruleData.isActive} optionselected={handleSelectChange} required invalid={!!errors.rule.isActive} invalidmessage={errors.rule.isActive} > <McOption value="Y">Yes</McOption> <McOption value="N">No</McOption> </McSelect> </div> </div> ); case 'step': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Step Information</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput minutiae label="Step Number" name="stepNo" value={step.stepNo} input={(e) => handleInputChange(e, index)} placeholder={`Enter step number (e.g., ${ruleData.num}.${index + 1})`} required invalid={!!errors.steps[index].stepNo} invalidmessage={errors.steps[index].stepNo} /> </div> <div className={styles.inputGroup}> <McSelect label="Step Name" name="stepName" value={step.stepName} optionselected={(e) => handleInputChange(e, index)} required placeholder="Select Step Name" invalid={!!errors.steps[index].stepName} invalidmessage={errors.steps[index].stepName} > <McOption value="Single Step">Single Step</McOption> <McOption value="Multi Step">Multi Step</McOption> </McSelect> </div> <div className={styles.inputGroup}> <McInput label="Step Description" name="StepDesc" value={step.StepDesc} input={(e) => handleInputChange(e, index)} placeholder="Enter step description" multiline rows={3} required invalid={!!errors.steps[index].StepDesc} invalidmessage={errors.steps[index].StepDesc} /> </div> <div className={styles.inputGroup}> <McSelect label="Step Type" name="stepType" value={step.stepType} optionselected={(e) => handleInputChange(e, index)} required placeholder="Select Step Type" invalid={!!errors.steps[index].stepType} invalidmessage={errors.steps[index].stepType} > <McOption value="S">S</McOption> <McOption value="M">M</McOption> </McSelect> </div> </div> ))} <div className={styles.buttonContainer}> <McButton label="Add Step" appearance="secondary" click={addStep} className={styles.actionButton} /> </div> </div> ); case 'preAggregate': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Pre-Aggregate Columns</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput label="Pre-Aggregator Columns" name="preAggregatorColumns" value={step.preAggregatorColumns} input={(e) => handleInputChange(e, index)} placeholder="Enter columns (comma-separated)" multiline rows={3} required invalid={!!errors.steps[index].preAggregatorColumns} invalidmessage={errors.steps[index].preAggregatorColumns} /> </div> </div> ))} </div> ); case 'source': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Source Information</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput label="Source Table" name="sourceTable" value={step.sourceTable} input={(e) => handleInputChange(e, index)} placeholder="Enter source table name" required invalid={!!errors.steps[index].sourceTable} invalidmessage={errors.steps[index].sourceTable} /> </div> <div className={styles.inputGroup}> <McInput label="Source Filters" name="sourceFilters" value={step.sourceFilters} input={(e) => handleInputChange(e, index)} placeholder="Enter filters (e.g., PNL_LINE:IN:PnL.DVC.214,PnL.DVC.215;MOVE_TYPE:EQ:EX)" multiline rows={3} required invalid={!!errors.steps[index].sourceFilters} invalidmessage={errors.steps[index].sourceFilters} /> </div> </div> ))} </div> ); case 'join': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Join Columns</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput label="Join Columns" name="joinColumns" value={step.joinColumns} input={(e) => handleInputChange(e, index)} placeholder="Enter columns (comma-separated)" multiline rows={3} required invalid={!!errors.steps[index].joinColumns} invalidmessage={errors.steps[index].joinColumns} /> </div> </div> ))} </div> ); case 'allocation': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Allocation Columns</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput label="Allocation Columns" name="allocationColumns" value={step.allocationColumns} input={(e) => handleInputChange(e, index)} placeholder="Enter columns (comma-separated)" multiline rows={3} required invalid={!!errors.steps[index].allocationColumns} invalidmessage={errors.steps[index].allocationColumns} /> </div> </div> ))} </div> ); case 'driver': return ( <div className={styles.tabContent}> <h3 className={styles.sectionTitle}>Driver Information</h3> {steps.map((step, index) => ( <div key={index} className={styles.stepCase}> <h4>Step {index + 1}</h4> <div className={styles.inputGroup}> <McInput label="Driver Table ID" name="driverTableID" value={step.driverTableID} input={(e) => handleInputChange(e, index)} placeholder="Enter driver table ID" required invalid={!!errors.steps[index].driverTableID} invalidmessage={errors.steps[index].driverTableID} /> </div> <div className={styles.inputGroup}> <McInput label="Driver Weight Column" name="driverWeightColumn" value={step.driverWeightColumn} input={(e) => handleInputChange(e, index)} placeholder="Enter driver weight column" required invalid={!!errors.steps[index].driverWeightColumn} invalidmessage={errors.steps[index].driverWeightColumn} /> </div> <div className={styles.inputGroup}> <McInput label="Driver Filters" name="driverFilters" value={step.driverFilters} input={(e) => handleInputChange(e, index)} placeholder="Enter filters" multiline rows={3} required invalid={!!errors.steps[index].driverFilters} invalidmessage={errors.steps[index].driverFilters} /> </div> </div> ))} </div> ); default: return <div className={styles.tabContent}>No Tab Selected</div>; } }; return ( <div className={styles.pageWrapper}> <div className={styles.container}> <div className={styles.card}> <div className={styles.buttonContainer}> <McButton label="Back" appearance="neutral" click={handleCancel} className={styles.actionButton} /> <McButton label="Save" appearance="primary" click={handleSave} className={styles.actionButton} /> </div> <div className={styles.tabs}> <button className={`${styles.tabButton} ${activeTab === 'ruleInfo' ? styles.activeTab : ''}`} onClick={() => setActiveTab('ruleInfo')} > Rule Info </button> <button className={`${styles.tabButton} ${activeTab === 'step' ? styles.activeTab : ''}`} onClick={() => setActiveTab('step')} > Step </button> <button className={`${styles.tabButton} ${activeTab === 'preAggregate' ? styles.activeTab : ''}`} onClick={() => setActiveTab('preAggregate')} > Pre-Aggregate </button> <button className={`${styles.tabButton} ${activeTab === 'source' ? styles.activeTab : ''}`} onClick={() => setActiveTab('source')} > Source </button> <button className={`${styles.tabButton} ${activeTab === 'join' ? styles.activeTab : ''}`} onClick={() => setActiveTab('join')} > Join </button> <button className={`${styles.tabButton} ${activeTab === 'allocation' ? styles.activeTab : ''}`} onClick={() => setActiveTab('allocation')} > Allocation </button> <button className={`${styles.tabButton} ${activeTab === 'driver' ? styles.activeTab : ''}`} onClick={() => setActiveTab('driver')} > Driver </button> </div> {renderTabContent()} </div> </div> </div> ); }; export default CreateRules;
Comments