components/MenuItems/Bonus/EditableTable.js

import React, { Component } from 'react';
import SVGComponent from '../../SVG/SVGComponent';
import { SVG_ICONS } from '../../../constants';
import DropdownTable from './DropdownTable';
/**
 * @module MI-Bonus/EditableTable
 */
/**
 * @typedef {object} props
 * @property {boolean} edit
 * @property {string} currency
 * @property {Array} tableRows
 * @property {Array} fields
 * @property {Function} save
 * @property {Function} setNavigationInfo set navigation info for unsaved data
 * @property {Function} setTableInfo set modified table data
 */
export default class EditableTable extends Component {
  constructor(props) {
    super(props);
    /**
     * @member {object}
     * @property {number|null} editRow
     * @property {Array} tableRows
     * @property {object} previousRow
     */
    this.state = {
      editRow: null,
      tableRows: this.getStartArray(props.tableRows),
      previousRow: null,
      hadChanges: false,
    };
  }

  /**
   * update check for modified fields
   *
   * @function
   * @param {object} prevProps
   * @param {object} prevState
   * @returns {void}
   */
  componentDidUpdate(prevProps, prevState) {
    if (prevState.hadChanges !== this.state.hadChanges) {
      this.props.setNavigationInfo({ hasFieldDataModified: this.state.hadChanges });
    }
    if (prevProps.tableRows !== this.props.tableRows) {
      this.setState({
        tableRows: this.getStartArray(this.props.tableRows),
      });
    }
  }

  /**
   * clear navigation info
   *
   * @returns {void}
   */
  componentWillUnmount() {
    this.props.setNavigationInfo({});
  }

  /**
   * Revert changes to beggining values
   *
   * @function
   * @returns {void}
   */
  setInitalData = () => {
    this.setState({
      tableRows: this.getStartArray(this.props.tableRows),
      editRow: null,
      previousRow: null,
      hadChanges: false,
    });
  };

  /**
   * Return data from table
   *
   * @param newData
   * @function
   * @returns {Array}
   */
  getTableData = (newData) => {
    let data = [...newData];
    if (!this.state.editingRow && this.state.editRow !== null) {
      data = this.clearChanges();
    }
    return data;
  };

  /**
   * Return beggining values of a table
   *
   * @function
   * @param {Array} tableRows
   * @returns {Array}
   */
  getStartArray = (tableRows) => {
    const newArray = [];
    tableRows.map((bonus) => {
      newArray.push({ ...bonus });
    });
    return newArray;
  };

  /**
   * Open Edit mode
   *
   * @function
   * @param {number} editRow
   * @returns {void}
   */
  openEditMode = (editRow) => {
    if (editRow !== null) {
      this.clearChanges();
    }
    this.setState({
      editRow,
      previousRow: { ...this.state.tableRows[editRow] },
      editingRow: true,
    });
  };

  /**
   * Close edit mode
   *
   * @function
   * @param {boolean} isEdited
   * @param {object} bonus
   * @returns {void}
   */
  closeEditMode = (isEdited, bonus) => {
    if (!isEdited) {
      this.clearChanges();
    } else {
      // eslint-disable-next-line no-param-reassign
      bonus.edited = true;
      this.setState({
        editRow: null,
        previousRow: null,
        hadChanges: true,
        editingRow: false,
      });
    }
  };

  /**
   * Revert changes
   *
   * @function
   * @param {boolean} hasNewChanges
   * @returns {void}
   */
  clearChanges = (hasNewChanges) => {
    const data = [...this.state.tableRows];
    if (!hasNewChanges) {
      data[this.state.editRow] = this.state.previousRow;
    }
    this.setState({
      tableRows: data,
      editRow: null,
      previousRow: null,
      editingRow: false,
      hadChanges: false,
    });
    return data;
  };

  /**
   * Change input field
   *
   * @function
   * @param {string} value
   * @param {string} field
   * @param {boolean} [isNumber]
   * @returns {void}
   */
  changeField = (value, field, isNumber) => {
    const newSettings = JSON.parse(JSON.stringify(this.state.tableRows));
    const newValue = isNumber ? parseInt(value.replace(/[,.]/g, '')) || 0 : value;
    newSettings[this.state.editRow][field] = newValue;
    this.setState({
      tableRows: newSettings,
      hadChanges: true,
    });
    this.setTableInfo(newSettings);
  };

  /**
   * Retrieve table data
   * @param newData
   * @function
   * @returns {Array}
   */
  setTableInfo = (newData) => {
    const data = this.getTableData(newData);
    // eslint-disable-next-line no-param-reassign
    data.map((d) => {
      d.edited = false;
    });
    this.props.setTableInfo(data);
    return data;
  };

  /**
   * Call API to save data
   *
   * @function
   * @returns {void}
   */
  save = () => {
    const data = this.setTableInfo(this.state.tableRows);
    this.props.save(data);
    this.setState({
      hadChanges: false,
      editRow: null,
      previousRow: null,
      editingRow: false,
    });
  };

  /**
   * Revert data to previous one
   *
   * @function
   * @returns {void}
   */
  cancel = () => {
    this.setInitalData();
  };

  /**
   * Render
   *
   * @returns {view}
   */
  render() {
    const disabled = !this.state.hadChanges || this.state.editRow !== null;
    return (
      <>
        <div className="table-wrapper mt-20">
          <div className="table-responsive  table-fix-10">
            <table className="table-style table-s">
              <thead>
                <tr>
                  {this.props.fields.map((field) => (
                    <th key={field.label}>{field.label}</th>
                  ))}
                  {this.props.edit && <th>Actions</th>}
                </tr>
              </thead>
              <tbody>
                {this.state.tableRows.map((bonus, row) => (
                  <tr
                    className={`table-style-row ${this.state.editRow === row && 'edit-mode'} ${
                      bonus.edited && 'edited'
                    }`}
                    // eslint-disable-next-line react/no-array-index-key
                    key={row}
                  >
                    {this.props.fields.map((field, col) => (
                      // eslint-disable-next-line react/no-array-index-key
                      <td key={col}>
                        {field.isEditable ? (
                          field.dropdown ? (
                            <DropdownTable bonus={bonus} field={field} changeField={this.changeField} />
                          ) : (
                            <div className="table-style-row-input">
                              <input
                                id={`bonusSettings-${row}-${col}`}
                                value={field.getValue(bonus, field.field)}
                                onChange={(e) => {
                                  this.changeField(e.target.value, field.field, field.isNumber);
                                }}
                              />
                              {field.currency && (
                                <div className="table-style-row-input-curency">{this.props.currency}</div>
                              )}
                            </div>
                          )
                        ) : null}
                        <div className={field.isEditable ? 'table-style-row-value' : ''}>
                          {field.getValue(bonus, field.field)} {field.currency && this.props.currency}
                        </div>
                      </td>
                    ))}
                    {this.props.edit && (
                      <td>
                        <div className="table-style-row-btn-wrap">
                          <SVGComponent
                            className="icon-xxs cancel-btn"
                            src={`${SVG_ICONS.utility}#cross`}
                            onClick={this.closeEditMode.bind(this, false)}
                          />
                          <SVGComponent
                            className="icon-xxs ml-2 confirm-btn"
                            src={`${SVG_ICONS.utility}#tick`}
                            onClick={this.closeEditMode.bind(this, true, bonus)}
                          />
                        </div>
                        <button
                          id={`edit-${row}`}
                          type="button"
                          className="btn btn-edit"
                          onClick={this.openEditMode.bind(this, row)}
                        >
                          Edit
                        </button>
                      </td>
                    )}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
        <hr className="line-spacer mt-20" />
        <div className="d-flex justify-content-end mt-20">
          <button
            id="bonusSettings_save"
            type="button"
            className="btn btn-primary"
            onClick={this.save}
            disabled={disabled}
          >
            Save
          </button>
          <button
            id="bonusSettins_cancel"
            type="button"
            className="btn btn-secondary ml-3"
            onClick={this.cancel}
            disabled={disabled}
          >
            Cancel
          </button>
        </div>
      </>
    );
  }
}