components/IntervalDates.js

/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable no-underscore-dangle */

import React, { Component } from 'react';
import {
  getDateForCalendar,
  getDateForFilter,
  getYesterday,
  getDateFromString,
  getDateForIntervalFilter,
  getCountryNewDate,
} from '../utils/common';
import SVGComponent from './SVG/SVGComponent';
import { SVG_ICONS, MANIPULATIONS } from '../constants';
import Calendar from './Calendar';
import validateFields from '../utils/validation';

let ACTIVE_FILTER = [];

/**
 * @module IntervalDates
 */
/**
 * @typedef {object} props
 * @property {Array} fields
 * @property {object} filter
 * @property {object} defaultFilter
 * @property {Function} changeFilter
 */
export default class IntervalDates extends Component {
  constructor(props) {
    super(props);
    /**
     * @member {object}
     * @description Properties has _ prefix to avoid matching filter keys
     * with component properties.
     * @property {boolean} isOpen
     * @property {string} _fromDate
     * @property {string} _toDate
     * @property {string} _fromTime
     * @property {string} _toTime
     * @property {object} filter
     * @property {number} activeFilter
     * @property {boolean} isCustomOpen
     */
    const yesterday = getDateForCalendar(getYesterday(this.props.selectedTimezone));
    const today = getDateForCalendar(getCountryNewDate(this.props.selectedTimezone));
    ACTIVE_FILTER = ['30#minutes ago', '5#hours ago', '24#hours ago', `Today#${today}`, `Yesterday#${yesterday}`];
    const fromDate = props.filter[props.fields[0]];
    const toDate = props.filter[props.fields[1]];
    this.state = this.getInintalState(fromDate, toDate, props);
  }

  /**
   * Update state on clear filter
   *
   * @param {object} prevProps
   * @returns {void}
   */
  componentDidUpdate(prevProps) {
    if (
      this.props.filter[this.props.fields[0]] !== prevProps.filter[prevProps.fields[0]] ||
      this.props.filter[this.props.fields[1]] !== prevProps.filter[prevProps.fields[1]]
    ) {
      const fromDate = this.props.filter[this.props.fields[0]];
      const toDate = this.props.filter[this.props.fields[1]];
      // eslint-disable-next-line react/no-did-update-set-state
      this.setState(this.getInintalState(fromDate, toDate, this.props));
    }
  }

  /**
   * Calculate state for parsed interval date
   *
   * @function
   * @param {string} from
   * @param {string} to
   * @param {object} props
   * @returns {object}
   */
  getInintalState = (from, to, props) => {
    let splitChar = ' ';
    if (from?.includes('T')) {
      splitChar = 'T';
    }
    const fromDate = from ? from.split(splitChar) : null;
    const toDate = to ? to.split(splitChar) : null;
    return {
      isOpen: false,
      _fromDate: fromDate ? getCountryNewDate(this.props.selectedTimezone, fromDate[0]) : '',
      _fromTime: fromDate ? fromDate[1] : '',
      _toDate: toDate ? getCountryNewDate(this.props.selectedTimezone, toDate[0]) : '',
      _toTime: toDate ? toDate[1] : '',
      filter: {
        _fromTime: fromDate ? fromDate[1] : '',
        _toTime: toDate ? toDate[1] : '',
        [props.fields[0]]: fromDate
          ? getDateForFilter(getCountryNewDate(this.props.selectedTimezone, fromDate[0]))
          : '',
        [props.fields[1]]: toDate ? getDateForFilter(getCountryNewDate(this.props.selectedTimezone, toDate[0])) : '',
      },
      activeFilter: 3,
      isCustomOpen: false,
      errorMgs: {},
    };
  };

  /**
   * Toggle calendar
   *
   * @function
   * @returns {void}
   */
  toggleCalendarPopup = () => {
    this.setState({
      isOpen: !this.state.isOpen,
      isCustomOpen: false,
      errorMgs: {},
    });
  };

  /**
   * Set default filter
   *
   * @function
   * @returns {void}
   */
  reset = () => {
    const fromDate = this.props.defaultFilter[this.props.fields[0]];
    const toDate = this.props.defaultFilter[this.props.fields[1]];
    this.setState(this.getInintalState(fromDate, toDate, this.props));
    this.applay(MANIPULATIONS.RESET_DATE_AND_TIME);
  };

  /**
   * Discard changes
   *
   * @function
   * @returns {void}
   */
  cancel = () => {
    if (this.state.errorMgs?.fromDate) return;

    this.setState({
      isOpen: false,
      filter: {
        _fromTime: this.state._fromTime,
        _toTime: this.state._toTime,
        [this.props.fields[0]]: this.state._fromDate ? getDateForFilter(this.state._fromDate) : '',
        [this.props.fields[1]]: this.state._toDate ? getDateForFilter(this.state._toDate) : '',
      },
      activeFilter: 4,
      isCustomOpen: false,
      errorMgs: {},
    });
  };

  /**
   * Adjust Date and Time According to Custom Option
   *
   * @param {string} type
   * @function
   */
  adjustDateAndTimeAccordingToCustomOption = (type) => {
    let fromDate = this.state.filter[this.props.fields[0]];
    let toDate = this.state.filter[this.props.fields[1]];

    if (type === MANIPULATIONS.RESET_DATE_AND_TIME) {
      fromDate = '';
      toDate = '';
    }

    this.props.changeFilter(fromDate ? `${fromDate} ${this.state.filter._fromTime}` : '', this.props.fields[0]);
    this.props.changeFilter(toDate ? `${toDate} ${this.state.filter._toTime}` : '', this.props.fields[1]);

    const validationParams = [
      {
        label: 'To date',
        fieldName: 'toDate',
        value: `${toDate} ${this.state.filter._toTime}`,
        rules: ['mandatory', 'greaterThan'],
        comparatorValue: `${fromDate} ${this.state.filter._fromTime}`,
      },
    ];

    // allow search by date for last 3 months only if this variable is present
    if (this.props.restrictSearchTo3Months) {
      const allowSearchFromDate = new Date();
      allowSearchFromDate.setMonth(allowSearchFromDate.getMonth() - 3);

      validationParams.push({
        label: 'From date',
        fieldName: 'fromDate',
        value: `${fromDate} ${this.state.filter._fromTime}`,
        rules: ['greaterThan'],
        comparatorValue: `${getDateForFilter(allowSearchFromDate)}`,
      });
    }

    validateFields(validationParams)
      .then(() => {
        this.setState({
          isOpen: false,
          _fromDate: fromDate ? getCountryNewDate(this.props.selectedTimezone, fromDate) : '',
          _toDate: toDate ? getCountryNewDate(this.props.selectedTimezone, toDate) : '',
          _fromTime: this.state.filter._fromTime,
          _toTime: this.state.filter._toTime,
          activeFilter: 4,
          isCustomOpen: false,
        });
      })
      .catch((errorMgs) => {
        this.setState({
          errorMgs,
        });
      });
  };

  /**
   * Change dates
   *
   * @function
   * @param {string} type
   * @returns {void}
   */
  adjustDateAndTimeAccordingToCalendarPopup = (type) => {
    const filter = ACTIVE_FILTER[this.state.activeFilter].split('#');
    let from = '';
    let to = '';
    let date;
    if (filter[1] === 'minutes ago') {
      const minutes = filter[0];
      date = getCountryNewDate(this.props.selectedTimezone);
      to = getDateForIntervalFilter(date);
      date.setMinutes(date.getMinutes() - minutes);
      from = getDateForIntervalFilter(date);
    } else if (filter[1] === 'hours ago') {
      const hours = filter[0];
      date = getCountryNewDate(this.props.selectedTimezone);
      to = getDateForIntervalFilter(date);
      date.setHours(date.getHours() - hours);
      from = getDateForIntervalFilter(date);
    } else {
      date = getDateFromString(filter[1]);
      from = `${date} 00:00`;
      to = `${date} 23:59`;
    }
    if (type === MANIPULATIONS.RESET_DATE_AND_TIME) {
      from = '';
      to = '';
    }
    this.props.changeFilter(from, this.props.fields[0]);
    this.props.changeFilter(to, this.props.fields[1]);
    this.setState(this.getInintalState(from, to, this.props));
  };

  /**
   * Change dates
   *
   * @function
   * @param {string} type
   * @returns {void}
   */
  applay = (type) => {
    if (this.state.isCustomOpen) {
      this.adjustDateAndTimeAccordingToCustomOption(type);
    } else {
      this.adjustDateAndTimeAccordingToCalendarPopup(type);
    }
  };

  /**
   * Change quick filter
   *
   * @function
   * @param {number} index
   * @returns {void}
   */
  changeFilter = (index) => {
    this.setState({
      activeFilter: index,
    });
  };

  /**
   * Change custom date filter
   *
   * @function
   * @param {string} fieldValue
   * @param {string} field
   * @returns {void}
   */
  changeFilterDate = (fieldValue, field) => {
    const temp = this.state.filter;
    temp[field] = fieldValue;
    this.setState({
      filter: temp,
      errorMgs: {},
    });
  };

  /**
   * Toggle custom filters
   *
   * @function
   * @returns {void}
   */
  toggleCustomFilter = () => {
    if (!this.state.isCustomOpen && !this.state.filter._fromTime && !this.state.filter._toTime) {
      const today = getDateFromString(getDateForCalendar(getCountryNewDate(this.props.selectedTimezone)));
      const updatedFilters = { ...this.state.filter };
      updatedFilters._fromTime = '00:00';
      updatedFilters._toTime = '23:59';
      if (this.props.dateFilterFields) {
        this.props.dateFilterFields.forEach((field) => {
          updatedFilters[field] = today;
        });
      }
      this.setState({
        isCustomOpen: !this.state.isCustomOpen,
        filter: updatedFilters,
      });
    } else {
      this.setState({
        isCustomOpen: !this.state.isCustomOpen,
      });
    }
  };

  /**
   * Enable / Disable Apply Button for date and time filter
   *
   * @function
   * @returns {boolean}
   */
  disableApplyBtn = () => {
    const isDateEmpty = this.props.dateFilterFields?.some((l) => !this.state.filter[l]);
    return this.state.isCustomOpen && (isDateEmpty || !this.state.filter._fromTime || !this.state.filter._toTime);
  };

  /**
   * Render
   *
   * @returns {view}
   */
  render() {
    const fromDate = getDateForCalendar(this.state._fromDate);
    const toDate = getDateForCalendar(this.state._toDate);
    return (
      <>
        <div className="main calendar">
          <div className="calendar__wrapper" onClick={this.toggleCalendarPopup}>
            <div className="calendar__input-date">
              <div className="calendar__input-date-label">From</div>
              <div className="calendar__input-date-value">{fromDate || '-'}</div>
              <div className="calendar__input-date-value">{this.state._fromTime}</div>
            </div>
            <div className="calendar__input-date">
              <div className="calendar__input-date-label">To</div>
              <div className="calendar__input-date-value">{toDate || '-'}</div>
              <div className="calendar__input-date-value">{this.state._toTime}</div>
            </div>
          </div>
          <i className="icon-calendar" onClick={this.toggleCalendarPopup} />
          <div className={`main calendar__popup ${this.state.isOpen && 'open'} `}>
            <div className="p-15">
              <div className="calendar__popup-heading">
                <h3>Select time range</h3>
                <SVGComponent
                  className="icon-xxs calendar__popup-close"
                  src={`${SVG_ICONS.utility}#close`}
                  onClick={this.toggleCalendarPopup}
                />
              </div>
            </div>
            <div className="d-flex">
              <div>
                <div className="calendar__popup-option">
                  {ACTIVE_FILTER.map((f, i) => {
                    const filter = f.split('#');
                    return (
                      <div
                        key={f}
                        className={`calendar__popup-row ${
                          this.state.activeFilter === i && !this.state.isCustomOpen ? 'active' : ''
                        }`}
                        onClick={this.changeFilter.bind(this, i)}
                      >
                        <div className="mr-4 w-30">{filter[0]}</div>
                        <span>{filter[1]}</span>
                      </div>
                    );
                  })}
                </div>
                <div className="calendar__popup-row mt-3 calendar-trigger" onClick={this.toggleCustomFilter}>
                  <span>Enter custom range</span>
                  <SVGComponent className="icon-xxs next-icon ml-4" src={`${SVG_ICONS.pagination}#arrow`} />
                </div>
              </div>
              <div className={`calendar__popup-custom-field ${this.state.isCustomOpen && 'open'}`}>
                <div className="mb-2">From</div>
                <Calendar
                  field={this.props.fields[0]}
                  filter={this.state.filter}
                  defaultFilter={this.props.defaultFilter}
                  changeFilter={this.changeFilterDate}
                  className="calendar__popup-input-style has-calendar mt-2"
                  sub
                  selectedTimezone={this.props.selectedTimezone}
                />
                {this.state.errorMgs.fromDate}
                <input
                  type="time"
                  className="calendar__popup-input-style mt-2"
                  value={this.state.filter._fromTime}
                  onChange={(e) => {
                    this.changeFilterDate(e.target.value, '_fromTime');
                  }}
                />
                <div className="mb-2 mt-2">To</div>
                <Calendar
                  field={this.props.fields[1]}
                  filter={this.state.filter}
                  defaultFilter={this.props.defaultFilter}
                  changeFilter={this.changeFilterDate}
                  className="calendar__popup-input-style has-calendar mt-2"
                  sub
                  selectedTimezone={this.props.selectedTimezone}
                />
                {this.state.errorMgs.toDate}
                <input
                  type="time"
                  className="calendar__popup-input-style mt-2"
                  value={this.state.filter._toTime}
                  onChange={(e) => {
                    this.changeFilterDate(e.target.value, '_toTime');
                  }}
                />
              </div>
            </div>
            <div className="p-15">
              <div className="calendar__popup-button">
                <button type="button" className="btn btn-secondary mr-auto" onClick={this.reset}>
                  Reset
                </button>
                <button type="button" className="btn btn-secondary ml-10" onClick={this.cancel}>
                  Cancel
                </button>
                <button
                  type="button"
                  className="btn btn-primary ml-10"
                  onClick={this.applay}
                  disabled={this.disableApplyBtn()}
                >
                  Apply
                </button>
              </div>
            </div>
          </div>
        </div>
        {this.state.isOpen && <div className="overlay-mask bg-grey open" />}
      </>
    );
  }
}