/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { Component } from 'react';
import Calendar from './Calendar';
import IntervalDates from './IntervalDates';
import { withRouterHooks } from '../utils/router';
import CountAndExportSettledMobile from './Buttons/CountAndExportSettledMobile';
import InputWithOperator from './InputWithOperator';
import { PERMISSIONS } from '../constants';
/**
* @module Filter
*/
/**
* @typedef {object} props
* @property {string|null} selectedMenuItem
* @property {boolean} [info]
* @property {boolean} [initialApiCall]
* @property {Array} filterFields
* @property {Array<view>} children
* @property {object} filters
* @property {object} [defaultFilter]
* @property {boolean} [allowEmpty] Allow call without filters
* @property {object} selectedItem values for dropdowns
* @property {Function} search Callback function after filter update
* @property {Function} setFilterData
* @property {Function} clearFilterData
*/
class Filter extends Component {
constructor(props) {
super(props);
/**
* @member {object}
* @property {object} filter
* @property {object} selectedItem Values of all dropdowns
*/
this.defaultState = {
filter: props.filters ? { ...props.filters } : {},
selectedItem: props.selectedItem ? { ...props.selectedItem } : {},
isSearch: false,
};
this.state = {
...this.defaultState,
hasExportCouponPermission:
typeof this.props.checkPermission === 'function'
? this.props.checkPermission(PERMISSIONS.EXPORT_COUPONS)
: false,
};
/**
* @member {boolean}
* @description Clear search only when filter was changed
*/
this.filteredBefore = false;
}
/**
* Load filter when section is changed
* Perform search if params exist in URL upon componentDidMount
*
* @returns {void}
*/
componentDidMount() {
if (this.props.initialApiCall) {
this.search();
} else {
this.handleFilterUrl();
}
}
/**
* Clear filter when section is changed
* Perform search when params are changed in URL
*
* @param {object} prevProp
* @returns {void}
*/
componentDidUpdate(prevProp) {
if (
this.props.selectedMenuItem !== prevProp.selectedMenuItem ||
this.props.clearFilters !== prevProp.clearFilters
) {
this.clear();
}
if (prevProp.location !== this.props.location) {
this.handleFilterUrl();
}
}
/**
* Handle filter params on back/forward browser button pressed
* Perform search when params are changed in URL
*
* @returns {void}
*/
handleFilterUrl = () => {
const filterFromUrl = Object.fromEntries(new URLSearchParams(this.props.location.search));
const keys = Object.keys(filterFromUrl);
if (!this.state.isSearch) {
if (keys.length > 0) {
const filterProps = this.props.filterProps ? this.props.filterProps : {};
if (Object.entries(filterFromUrl).toString() !== Object.entries(filterProps).toString()) {
const filter = { ...filterFromUrl };
const selectedItem = this.state.selectedItem;
const filterObj = this.props.filterFields.filter((field) => field.dropdown && keys.includes(field.field));
if (filterObj.length > 0) {
this.props.filterFields.filter((field, index) => {
if (field.dropdown && keys.includes(field.field)) {
const f = field.dropdown.find((d) => d.id === filterFromUrl[field.field]);
selectedItem[index] = f;
}
});
this.setState({
filter,
selectedItem,
});
} else {
this.setState({
filter,
selectedItem: {},
});
}
this.props.setFilterData({
filterFields: this.props.filterFields,
keys,
filter,
callback: this.props.search,
selectedTimezone: this.props.selectedTimezone,
});
}
} else {
this.props.clearFilterData();
this.props.search();
this.setState({
filter: this.props.defaultFilter ? { ...this.props.defaultFilter } : {},
selectedItem: {},
});
}
}
this.setState({ isSearch: false });
};
/**
* Search button
*
* @function
* @returns {void}
*/
search = () => {
this.setState({ isSearch: true });
const keys = Object.keys(this.state.filter);
if (keys.length > 0 || this.props.allowEmpty === true) {
this.filteredBefore = true;
const filter = this.state.filter;
this.props.setFilterData({
filterFields: this.props.filterFields,
keys,
filter,
callback: this.props.search,
selectedTimezone: this.props.selectedTimezone,
});
this.props.navigate({
pathname: this.props.location.pathname,
search: new URLSearchParams(filter).toString(),
});
}
};
/**
* Clear filter
*
* @function
* @returns {void}
*/
clear = () => {
if (this.filteredBefore) {
this.filteredBefore = false;
this.props.clearFilterData();
this.props.search();
}
this.setState({
filter: this.props.defaultFilter
? { ...this.props.defaultFilter }
: this.props.filters
? { ...this.props.filters }
: {},
selectedItem: this.props.selectedItem ? { ...this.props.selectedItem } : {},
});
this.props.navigate({
pathname: this.props.location.pathname,
search: '',
});
};
/**
* Change filter input field
*
* @function
* @param {string} fieldValue
* @param {string} field
* @param {number} [dropdownIndex]
* @returns {void}
*/
changeFilter = (fieldValue, field, dropdownIndex) => {
const filter = this.state.filter;
const selectedItem = this.state.selectedItem;
if (dropdownIndex !== undefined) {
const f = field.dropdown.find((d) => d.label === fieldValue);
filter[field.field] = f.id;
selectedItem[dropdownIndex] = f;
} else {
filter[field] = fieldValue;
}
this.setState({ filter, selectedItem });
if (this.props.resetExport) this.props.resetExport();
};
/**
* Render dropdown filter
*
* @function
* @param {object} field
* @param {number} i
* @returns {view}
*/
renderDropdown = (field, i) => {
const value = field.dropdown.find((f) => f.id === this.state.filter[field.field]);
return (
<select
className="form-control"
onChange={(e) => {
this.changeFilter(e.target.value, field, i);
}}
id={field.field}
value={value ? value.label : ''}
>
{field.dropdown.map((d) => (
<option key={d.label}>{d.label}</option>
))}
</select>
);
};
/**
* Render checkbox filter
*
* @function
* @param {object} field
* @returns {view}
*/
renderCheckbox = (field) => (
<div className="user__field" key={field.field}>
<label className="checkbox">
<input
type="checkbox"
checked={this.state.filter[field.field] ? 'checked' : ''}
onChange={() => {
this.changeFilter(this.state.filter[field.field] ? '' : 1, field.field);
}}
/>
<span className="checkbox-txt">Bonus</span>
</label>
</div>
);
/**
*
* @function
* @returns {boolean}
*/
checkForSpecificRequiredFilters = () => {
const booleanArr = this.props.mustBePopulatedFilters?.map((filter) => this.state.filter[filter?.field]);
return booleanArr?.every((elem) => !!elem);
};
/**
* Disable SearchBtn
*
* @function
* @returns {boolean}
*/
disableSearchBtn = () => {
const filterNames = Object.keys(this.state.filter);
const mandatoryFilters = filterNames?.filter((f) => !this.props.notMandatoryFilters?.includes(f));
// Run custom validations set in filter fields
for (let index = 0; index < this.props.filterFields.length; index += 1) {
if (typeof this.props.filterFields[index]?.isValid === 'function') {
const filterIsValid = this.props.filterFields[index].isValid(this.state.filter);
if (!filterIsValid) {
return true; // If any filter is invalid, disable immediately
}
}
}
if (this.props.additionalFiltersCheck) {
const checkSpecificFilters = this.checkForSpecificRequiredFilters();
if (!checkSpecificFilters) return true;
}
// Check for mandatory filters only if all filters are valid
for (let i = 0; i < mandatoryFilters.length; i += 1) {
if (this.state.filter[mandatoryFilters[i]]) {
return false;
}
}
return true; // Disable button if no mandatory filter
};
renderClearAndSearchButtons = () => (
<>
<button
id="searchBtn_filter"
type="button"
className="btn btn-primary"
onClick={this.search}
disabled={this.disableSearchBtn()}
>
Search
</button>
<button id="clearFiltersBtn_filter" type="button" className="btn btn-secondary" onClick={this.clear}>
Clear Filters
</button>
</>
);
changeInputWithOperator = (value, config) => {
// value is of type object { input: '', operator: ''}
// Ensure value.input is a number
let inputValue = parseInt(value.input);
if (Number.isNaN(inputValue)) inputValue = 0;
// Define min and max limits from config or default values
const minLimit = config.minimumValue;
const maxLimit = config.maximumValue;
const filter = this.state.filter;
filter[config.field] =
typeof config.formatValue === 'function' && value.input
? config.formatValue(Math.max(minLimit, Math.min(maxLimit, inputValue)))
: value.input;
filter[config.operatorField] = value.operator;
this.setState({ filter });
};
getInputValue = (config) =>
typeof config.getValue === 'function' && this.state.filter[config.field]
? config.getValue(parseInt(this.state.filter[config.field]))
: this.state.filter[config.field];
/**
* Render
*
* @returns {view}
*/
render() {
return (
<>
<div className="user__filter">
<div className="user__filter-fields">
{this.props.filterFields.map((field, i) =>
field.bonus ? (
this.renderCheckbox(field)
) : field.inputWithOperator ? (
<React.Fragment key={field.field}>
<InputWithOperator
inputValue={this.getInputValue(field) || ''}
operatorValue={this.state.filter[field.operatorField] || ''}
onChangeFilter={this.changeInputWithOperator}
config={field}
/>
</React.Fragment>
) : (
<div className="user__field" key={i}>
<label className="txt">{field.text}</label>
{field.dateInterval ? (
<IntervalDates
fields={field.fields}
filter={this.state.filter}
defaultFilter={this.props.defaultFilter}
changeFilter={this.changeFilter}
selectedTimezone={this.props.selectedTimezone}
dateFilterFields={this.props.dateFilterFields}
{...this.props}
/>
) : field.dateIntervalSecond ? (
<IntervalDates
fields={field.fields}
filter={this.state.filter}
defaultFilter={this.props.defaultFilter}
changeFilter={this.changeFilter}
selectedTimezone={this.props.selectedTimezone}
{...this.props}
dateFilterFields={this.props.dateFilterFieldsSecond}
/>
) : field.dateTime ? (
<Calendar
field={field.field}
filter={this.state.filter}
defaultFilter={this.props.defaultFilter}
changeFilter={this.changeFilter}
className="calendar"
actions
selectedTimezone={this.props.selectedTimezone}
/>
) : field.dropdown ? (
this.renderDropdown(field, i)
) : (
<input
type={field.isNumber ? 'number' : 'text'}
className="form-control"
id={`${field.field}_filter`}
placeholder="-"
value={this.state.filter[field.field] || ''}
onChange={(e) => {
this.changeFilter(e.target.value, field.field);
}}
/>
)}
</div>
)
)}
</div>
</div>
{this.props.info && (
<div className="user__info mt-20">*selected Information will be displayed in the table(s) below</div>
)}
{this.props.isSettledCouponsForMobile ? (
<div className="user__filter-button d-flex justify-content-between">
<div className="d-flex flow-right align-items-center">{this.renderClearAndSearchButtons()}</div>
{this.state.hasExportCouponPermission && (
<div className="d-flex flow-right align-items-center">
<CountAndExportSettledMobile />
</div>
)}
</div>
) : (
<div className="user__filter-button">
{this.renderClearAndSearchButtons()}
{this.props.children}
</div>
)}
</>
);
}
}
export default withRouterHooks(Filter);