Preview:
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;
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter