Remove and add more button included CREATE PAGE
Mon May 26 2025 18:39:09 GMT+0000 (Coordinated Universal Time)
Saved by @krisha_joshi
import React, { useState, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { McButton, McInput, McMultiSelect, McSelect } from '@maersk-global/mds-react-wrapper';
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';
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong. Please refresh the page.</div>;
}
return this.props.children;
}
}
const CreateRules = () => {
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('ruleInfo');
const [isLoading, setIsLoading] = useState(false);
const [ruleData, setRuleData] = useState({
num: '',
name: '',
desc: '',
custRefID: '',
ruleGroup: '',
isActive: 'Y',
pnlGroup: '',
});
const [steps, setSteps] = useState([
{
stepNo: '',
stepName: 'Single Step',
stepDesc: '',
stepType: 'S',
preAggregatorColumns: [],
sourceTableID: '',
sourceFilterSets: [{ filters: [], operator: '', values: [] }],
joinColumns: [],
allocationColumns: [],
driverTableID: '',
driverWeightColumn: '',
driverFilterSets: [{ filters: [], operator: '', values: [] }],
},
]);
const [errors, setErrors] = useState({ rule: {}, steps: [{ sourceFilterSets: [{}], driverFilterSets: [{}] }] });
const pnLGroups = data.PnLGroups && typeof data.PnLGroups === 'object'
? Object.keys(data.PnLGroups)
: [];
const ruleGroups = ruleData.pnlGroup && data.PnLGroups[ruleData.pnlGroup]
? data.PnLGroups[ruleData.pnlGroup].RuleGroups || []
: [];
const sourceFilterOptions = [
{ value: 'Source_Filter_1', label: 'Source Filter 1' },
{ value: 'Source_Filter_2', label: 'Source Filter 2' },
{ value: 'Source_Filter_3', label: 'Source Filter 3' },
];
const sourceValueOptions = [
{ value: 'Source_Value_1', label: 'Source Value 1' },
{ value: 'Source_Value_2', label: 'Source Value 2' },
{ value: 'Source_Value_3', label: 'Source Value 3' },
];
const preAggregatorOptions = [
{ value: 'column1', label: 'Column 1' },
{ value: 'column2', label: 'Column 2' },
{ value: 'column3', label: 'Column 3' },
];
const joinColumnsOptions = [
{ value: 'join_col1', label: 'Join Column 1' },
{ value: 'join_col2', label: 'Join Column 2' },
{ value: 'join_col3', label: 'Join Column 3' },
];
const allocationColumnsOptions = [
{ value: 'alloc_col1', label: 'Allocation Column 1' },
{ value: 'alloc_col2', label: 'Allocation Column 2' },
{ value: 'alloc_col3', label: 'Allocation Column 3' },
];
const driverFilterOptions = [
{ value: 'Driver_Type_1', label: 'Driver Type: Type 1' },
{ value: 'Driver_Type_2', label: 'Driver Type: Type 2' },
{ value: 'Driver_Status_Active', label: 'Driver Status: Active' },
];
const driverValueOptions = [
{ value: 'Driver_Value_1', label: 'Driver Value 1' },
{ value: 'Driver_Value_2', label: 'Driver Value 2' },
{ value: 'Driver_Value_3', label: 'Driver Value 3' },
];
const operatorOptions = useMemo(() => [
{ value: 'IN', label: 'IN' },
{ value: 'NOT IN', label: 'NOT IN' },
{ value: 'EQ', label: 'EQ' },
{ value: 'NTEQ', label: 'NTEQ' },
{ value: 'IS NULL', label: 'IS NULL' },
{ value: 'GT', label: 'GT' },
{ value: 'LT', label: 'LT' },
{ value: 'GTEQ', label: 'GTEQ' },
{ value: 'LTEQ', label: 'LTEQ' },
{ value: 'BETWEEN', label: 'BETWEEN' },
{ value: 'NOT BETWEEN', label: 'NOT BETWEEN' },
{ value: 'LIKE', label: 'LIKE' },
], []);
const addStep = useCallback(() => {
setSteps((prevSteps) => [
...prevSteps,
{
stepNo: '',
stepName: 'Single Step',
stepDesc: '',
stepType: 'S',
preAggregatorColumns: [],
sourceTableID: '',
sourceFilterSets: [{ filters: [], operator: '', values: [] }],
joinColumns: [],
allocationColumns: [],
driverTableID: '',
driverWeightColumn: '',
driverFilterSets: [{ filters: [], operator: '', values: [] }],
},
]);
setErrors((prevErrors) => ({
...prevErrors,
steps: [...prevErrors.steps, { sourceFilterSets: [{}], driverFilterSets: [{}] }],
}));
}, []);
const removeStep = useCallback((index) => {
if (steps.length === 1) {
alert('At least one step is required.');
return;
}
setSteps((prevSteps) => prevSteps.filter((_, i) => i !== index));
setErrors((prevErrors) => ({
...prevErrors,
steps: prevErrors.steps.filter((_, i) => i !== index),
}));
}, [steps.length]);
const addFilterSet = useCallback((stepIndex, type) => {
setSteps((prevSteps) => {
const newSteps = [...prevSteps];
const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
newSteps[stepIndex] = {
...newSteps[stepIndex],
[filterKey]: [...newSteps[stepIndex][filterKey], { filters: [], operator: '', values: [] }],
};
return newSteps;
});
setErrors((prevErrors) => {
const newStepsErrors = [...prevErrors.steps];
const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
newStepsErrors[stepIndex] = {
...newStepsErrors[stepIndex],
[filterErrorsKey]: [...newStepsErrors[stepIndex][filterErrorsKey], {}],
};
return { ...prevErrors, steps: newStepsErrors };
});
}, []);
const removeFilterSet = useCallback((stepIndex, filterIndex, type) => {
setSteps((prevSteps) => {
const newSteps = [...prevSteps];
const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
newSteps[stepIndex] = {
...newSteps[stepIndex],
[filterKey]: newSteps[stepIndex][filterKey].filter((_, i) => i !== filterIndex),
};
return newSteps;
});
setErrors((prevErrors) => {
const newStepsErrors = [...prevErrors.steps];
const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
newStepsErrors[stepIndex] = {
...newStepsErrors[stepIndex],
[filterErrorsKey]: newStepsErrors[stepIndex][filterErrorsKey].filter((_, i) => i !== filterIndex),
};
return { ...prevErrors, steps: newStepsErrors };
});
}, []);
const validateForm = useCallback(() => {
try {
const newErrors = { rule: {}, steps: steps.map(() => ({ sourceFilterSets: [], driverFilterSets: [] })) };
let isValid = true;
if (!ruleData.num) {
newErrors.rule.num = 'Rule Number is required';
isValid = false;
} else if (!/^[a-zA-Z0-9]+$/.test(ruleData.num)) {
newErrors.rule.num = 'Rule Number must be alphanumeric';
isValid = false;
}
if (!ruleData.name) {
newErrors.rule.name = 'Rule Name is required';
isValid = false;
}
if (!ruleData.desc) {
newErrors.rule.desc = 'Description is required';
isValid = false;
}
if (!ruleData.custRefID) {
newErrors.rule.custRefID = 'Customer Reference ID is required';
isValid = false;
}
if (!ruleData.pnlGroup) {
newErrors.rule.pnlGroup = 'PnL Group is required';
isValid = false;
}
if (!ruleData.ruleGroup) {
newErrors.ruleGroup = 'Rule Group is required';
isValid = false;
}
if (!ruleData.isActive) {
newErrors.rule.isActive = 'Active status is required';
isValid = false;
}
const stepNumbers = new Set();
steps.forEach((step, index) => {
const stepErrors = { sourceFilterSets: step.sourceFilterSets.map(() => ({})),
driverFilterSets: step.driverFilterSets.map(() => ({})) };
if (!step.stepNo) {
stepErrors.stepNo = 'Step Number is required';
isValid = false;
} else if (stepNumbers.has(step.stepNo)) {
stepErrors.stepNo = 'Step Number must be unique';
isValid = false;
} else {
stepNumbers.add(step.stepNo);
}
if (!step.stepName) {
stepErrors.stepName = 'Step Name is required';
isValid = false;
}
if (!step.stepDesc) {
stepErrors.stepDesc = 'Step Description is required';
isValid = false;
}
if (!step.stepType) {
stepErrors.stepType = 'Step Type is required';
isValid = false;
}
if (!step.preAggregatorColumns.length) {
stepErrors.preAggregatorColumns = 'Pre-Aggregator Columns is required';
isValid = false;
}
if (!step.sourceTableID) {
stepErrors.sourceTableID = 'Source Table ID is required';
isValid = false;
}
if (!step.sourceFilterSets.some(set => set.filters.length && set.operator && set.values.length)) {
stepErrors.sourceFilterSets[0].filters = 'At least one complete Source Filter set is required';
isValid = false;
}
step.sourceFilterSets.forEach((set, filterIndex) => {
if (set.filters.length || set.operator || set.values.length) {
if (!set.filters.length) {
stepErrors.sourceFilterSets[filterIndex].filters = 'Source Filters is required';
isValid = false;
}
if (!set.operator) {
stepErrors.sourceFilterSets[filterIndex].operator = 'Source Operator is required';
isValid = false;
}
if (!set.values.length) {
stepErrors.sourceFilterSets[filterIndex].values = 'Source Values is required';
isValid = false;
}
}
});
if (!step.joinColumns.length) {
stepErrors.joinColumns = 'Join Columns is required';
isValid = false;
}
if (!step.allocationColumns.length) {
stepErrors.allocationColumns = 'Allocation Columns is required';
isValid = false;
}
if (!step.driverTableID) {
stepErrors.driverTableID = 'Driver Table ID is required';
isValid = false;
}
if (!step.driverWeightColumn) {
stepErrors.driverWeightColumn = 'Driver Weight Column is required';
isValid = false;
}
if (!step.driverFilterSets.some(set => set.filters.length && set.operator && set.values.length)) {
stepErrors.driverFilterSets[0].filters = 'At least one complete Driver Filter set is required';
isValid = false;
}
step.driverFilterSets.forEach((set, filterIndex) => {
if (set.filters.length || set.operator || set.values.length) {
if (!set.filters.length) {
stepErrors.driverFilterSets[filterIndex].filters = 'Driver Filters is required';
isValid = false;
}
if (!set.operator) {
stepErrors.driverFilterSets[filterIndex].operator = 'Driver Operator is required';
isValid = false;
}
if (!set.values.length) {
stepErrors.driverFilterSets[filterIndex].values = 'Driver Values is required';
isValid = false;
}
}
});
newErrors.steps[index] = stepErrors;
});
setErrors(newErrors);
return isValid;
} catch (error) {
alert('An error occurred during form validation. Please try again.');
return false;
}
}, [ruleData, steps]);
const parseColumns = (input) => input;
const parseFilters = (filterSets) => {
return filterSets
.filter(set => set.filters.length && set.operator && set.values.length)
.map(set => ({
name: set.filters,
filterType: set.operator,
values: set.values,
}));
};
const handleInputChange = useCallback((e, stepIndex = null) => {
const { name, value } = e.target;
if (stepIndex !== null) {
setSteps((prevSteps) => {
const newSteps = [...prevSteps];
newSteps[stepIndex] = { ...newSteps[stepIndex], [name]: value };
return newSteps;
});
setErrors((prevErrors) => {
const newStepsErrors = [...prevErrors.steps];
newStepsErrors[stepIndex] = {
...newStepsErrors[stepIndex],
[name]: '',
};
return { ...prevErrors, steps: newStepsErrors };
});
} else {
setRuleData((prevData) => ({
...prevData,
[name]: value,
...(name === 'pnlGroup' ? { ruleGroup: '' } : {}),
}));
setErrors((prevErrors) => ({
...prevErrors,
rule: { ...prevErrors.rule, [name]: '' },
}));
}
}, []);
const handleFilterSetChange = useCallback((e, stepIndex, filterIndex, type, field) => {
const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
const value = field === 'operator' ? e.target.value : e.detail.map(option => option.value);
setSteps((prevSteps) => {
const newSteps = [...prevSteps];
newSteps[stepIndex] = {
...newSteps[stepIndex],
[filterKey]: newSteps[stepIndex][filterKey].map((set, i) =>
i === filterIndex ? { ...set, [field]: value } : set
),
};
return newSteps;
});
setErrors((prevErrors) => {
const newStepsErrors = [...prevErrors.steps];
const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
newStepsErrors[stepIndex] = {
...newStepsErrors[stepIndex],
[filterErrorsKey]: newStepsErrors[stepIndex][filterErrorsKey].map((setErrors, i) =>
i === filterIndex ? { ...setErrors, [field]: '' } : setErrors
),
};
return { ...prevErrors, steps: newStepsErrors };
});
}, []);
const resetForm = useCallback(() => {
setRuleData({
num: '',
name: '',
desc: '',
custRefID: '',
ruleGroup: '',
isActive: 'Y',
pnlGroup: '',
});
setSteps([
{
stepNo: '',
stepName: 'Single Step',
stepDesc: '',
stepType: 'S',
preAggregatorColumns: [],
sourceTableID: '',
sourceFilterSets: [{ filters: [], operator: '', values: [] }],
joinColumns: [],
allocationColumns: [],
driverTableID: '',
driverWeightColumn: '',
driverFilterSets: [{ filters: [], operator: '', values: [] }],
},
]);
setErrors({ rule: {}, steps: [{ sourceFilterSets: [{}], driverFilterSets: [{}] }] });
setActiveTab('ruleInfo');
}, []);
const handleSave = useCallback(async () => {
if (!validateForm()) {
alert('Please fill out all required fields.');
return;
}
setIsLoading(true);
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: step.sourceTableID,
Name: step.sourceTableID,
},
sourceFilters: {
columns: parseFilters(step.sourceFilterSets),
},
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.driverFilterSets),
},
},
})),
},
],
},
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch('/api/rules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(ruleJson),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (response.ok) {
alert('Rule created successfully!');
resetForm();
navigate('/');
} else {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
alert(`Failed to create rule: ${errorData.message || response.statusText}`);
}
} catch (error) {
if (error.name === 'AbortError') {
alert('Request timed out. Please try again.');
} else {
alert('An error occurred while saving the rule. Please try again.');
}
} finally {
setIsLoading(false);
}
}, [validateForm, ruleData, steps, resetForm, navigate]);
const handleCancel = useCallback(() => {
navigate('/');
}, [navigate]);
const renderTabContent = () => {
switch (activeTab) {
case 'ruleInfo':
return (
<div className={styles.tabContent}>
{Object.values(errors.rule).some((error) => error) && (
<div className={styles.errorSummary}>
<h4>Please fix the following errors:</h4>
<ul>
{Object.entries(errors.rule).map(([key, error]) => error && (
<li key={key}>{error}</li>
))}
</ul>
</div>
)}
<h3 className={styles.sectionTitle}>Rule Information</h3>
<div className={styles.formGrid}>
<div className={styles.gridItem}>
<McSelect
label="PnL Group"
name="pnlGroup"
value={ruleData.pnlGroup}
input={handleInputChange}
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}
input={handleInputChange}
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>
);
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 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
<div className={styles.inputGroup}>
<McInput
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}
input={(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}
input={(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.stepButtonContainer}>
<McButton
label="Add Step"
appearance="secondary"
click={addStep}
className={styles.actionButton}
/>
{steps.length > 1 && (
<McButton
label="Remove Step"
appearance="neutral"
click={() => removeStep(steps.length - 1)}
className={styles.actionButton}
/>
)}
</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 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
<div className={styles.inputGroup}>
<McInput
label="Source Table ID"
name="sourceTableID"
value={step.sourceTableID}
input={(e) => handleInputChange(e, index)}
placeholder="Enter source table ID"
required
invalid={!!errors.steps[index].sourceTableID}
invalidmessage={errors.steps[index].sourceTableID}
/>
</div>
{step.sourceFilterSets.map((filterSet, filterIndex) => (
<div key={filterIndex} className={styles.filterRow}>
<McMultiSelect
label="Source Filters"
name="sourceFilters"
value={filterSet.filters}
optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'filters')}
placeholder="Select source filters"
required
invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.filters}
invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.filters}
>
{sourceFilterOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
<McSelect
label="Source Operator"
name="sourceOperator"
value={filterSet.operator}
input={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'operator')}
placeholder="Select an operator"
required
invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.operator}
invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.operator}
>
{operatorOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McSelect>
<div className={styles.filterValueContainer}>
<McMultiSelect
label="Source Values"
name="sourceValues"
value={filterSet.values}
optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'values')}
placeholder="Select source values"
required
invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.values}
invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.values}
>
{sourceValueOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
{step.sourceFilterSets.length > 1 && (
<McButton
label="Remove"
appearance="neutral"
click={() => removeFilterSet(index, filterIndex, 'source')}
className={styles.removeButton}
/>
)}
</div>
</div>
))}
<div className={styles.stepButtonContainer}>
<McButton
label="Add More"
appearance="secondary"
click={() => addFilterSet(index, 'source')}
className={styles.actionButton}
/>
</div>
</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 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
<div className={styles.inputGroup}>
<McMultiSelect
label="Pre-Aggregator Columns"
name="preAggregatorColumns"
value={step.preAggregatorColumns}
optionselected={(e) => handleMultiSelectChange(e, index, 'preAggregatorColumns')}
placeholder="Select pre-aggregator columns"
required
invalid={!!errors.steps[index].preAggregatorColumns}
invalidmessage={errors.steps[index].preAggregatorColumns}
>
{preAggregatorOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
</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 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
<div className={styles.inputGroup}>
<McMultiSelect
label="Join Columns"
name="joinColumns"
value={step.joinColumns}
optionselected={(e) => handleMultiSelectChange(e, index, 'joinColumns')}
placeholder="Select join columns"
required
invalid={!!errors.steps[index].joinColumns}
invalidmessage={errors.steps[index].joinColumns}
>
{joinColumnsOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
</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 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
<div className={styles.inputGroup}>
<McMultiSelect
label="Allocation Columns"
name="allocationColumns"
value={step.allocationColumns}
optionselected={(e) => handleMultiSelectChange(e, index, 'allocationColumns')}
placeholder="Select allocation columns"
required
invalid={!!errors.steps[index].allocationColumns}
invalidmessage={errors.steps[index].allocationColumns}
>
{allocationColumnsOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
</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 style={{ color: '#35B0CB' }}>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>
{step.driverFilterSets.map((filterSet, filterIndex) => (
<div key={filterIndex} className={styles.filterRow}>
<McMultiSelect
label="Driver Filters"
name="driverFilters"
value={filterSet.filters}
optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'filters')}
placeholder="Select driver filters"
required
invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.filters}
invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.filters}
>
{driverFilterOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
<McSelect
label="Driver Operator"
name="driverOperator"
value={filterSet.operator}
input={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'operator')}
placeholder="Select an operator"
required
invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.operator}
invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.operator}
>
{operatorOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McSelect>
<div className={styles.filterValueContainer}>
<McMultiSelect
label="Driver Values"
name="driverValues"
value={filterSet.values}
optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'values')}
placeholder="Select driver values"
required
invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.values}
invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.values}
>
{driverValueOptions.map((option) => (
<McOption key={option.value} value={option.value}>
{option.label}
</McOption>
))}
</McMultiSelect>
{step.driverFilterSets.length > 1 && (
<McButton
label="Remove"
appearance="neutral"
click={() => removeFilterSet(index, filterIndex, 'driver')}
className={styles.removeButton}
/>
)}
</div>
</div>
))}
<div className={styles.stepButtonContainer}>
<McButton
label="Add More"
appearance="secondary"
click={() => addFilterSet(index, 'driver')}
className={styles.actionButton}
/>
</div>
</div>
))}
</div>
);
default:
return <div className={styles.tabContent}>No Tab Selected</div>;
}
};
return (
<ErrorBoundary>
<div className={styles.pageWrapper}>
{isLoading && (
<div className={styles.loader}>Loading...</div>
)}
<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}
loading={isLoading}
disabled={isLoading}
/>
</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 === 'source' ? styles.activeTab : ''}`}
onClick={() => setActiveTab('source')}
>
Source
</button>
<button
className={`${styles.tabButton} ${activeTab === 'preAggregate' ? styles.activeTab : ''}`}
onClick={() => setActiveTab('preAggregate')}
>
Pre-Aggregate
</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>
</ErrorBoundary>
);
};
export default CreateRules;



Comments