<commit_before>import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { translate as $t, UNKNOWN_OPERATION_TYPE, NONE_CATEGORY_ID } from '../../helpers';
import { get, actions } from '../../store';

import AmountInput from '../ui/amount-input';
import DatePicker from '../ui/date-picker';
import FoldablePanel from '../ui/foldable-panel';

const SearchCategorySelect = connect(state => {
    return {
        defaultValue: get.searchFields(state).categoryId,
        categories: get.categories(state)
    };
}, dispatch => {
    return {
        handleChange(event) {
            actions.setSearchField(dispatch, 'categoryId', event.target.value);
        }
    };
})(props => {
    let { defaultValue, categories, handleChange } = props;

    let noneCategory = categories.find(cat => cat.id === NONE_CATEGORY_ID);
    categories = categories.filter(cat => cat.id !== NONE_CATEGORY_ID);

    let options = [
        <option
          key="_"
          value="">
            { $t('client.search.any_category') }
        </option>,
        <option
          key={ noneCategory.id }
          value={ noneCategory.id }>
            { noneCategory.title }
        </option>
    ].concat(categories.map(cat => (
        <option
          key={ cat.id }
          value={ cat.id }>
            { cat.title }
        </option>
    )));

    return (
        <select
          className="form-control"
          id={ props.id }
          defaultValue={ defaultValue }
          onChange={ handleChange }>
            { options }
        </select>
    );
});

SearchCategorySelect.propTypes = {
    // A string to link the input to a label for exemple.
    id: PropTypes.string
};

const MinDatePicker = connect((state, props) => {
    return {
        defaultValue: get.searchFields(state).dateLow,
        maxDate: get.searchFields(state).dateHigh,
        ref: props.refCb
    };
}, dispatch => {
    return {
        onSelect(dateLow) {
            actions.setSearchField(dispatch, 'dateLow', dateLow);
        }
    };
})(DatePicker);

const MaxDatePicker = connect((state, props) => {
    return {
        defaultValue: get.searchFields(state).dateHigh,
        minDate: get.searchFields(state).dateLow,
        ref: props.refCb
    };
}, dispatch => {
    return {
        onSelect(dateHigh) {
            actions.setSearchField(dispatch, 'dateHigh', dateHigh);
        }
    };
})(DatePicker);

class SearchComponent extends React.Component {
    constructor(props) {
        super(props);
        this.handleClearSearchNoClose = this.handleClearSearch.bind(this, false);
        this.handleClearSearchAndClose = this.handleClearSearch.bind(this, true);

        this.searchForm = null;
        this.lowAmountInput = null;
        this.highAmountInput = null;
        this.lowDatePicker = null;
        this.highDatePicker = null;
    }

    handleClearSearch(close, event) {
        this.searchForm.reset();
        this.lowAmountInput.clear();
        this.highAmountInput.clear();
        this.lowDatePicker.clear();
        this.highDatePicker.clear();
        this.props.resetAll();
        if (close)
            this.refSearchPanel.handleToggleExpand();
        event.preventDefault();
    }

    componentWillUnmount() {
        this.props.resetAll(false);
    }

    render() {
        let unknownType = this.props.types.find(type => type.name === UNKNOWN_OPERATION_TYPE);

        // Types are not sorted.
        let types = [unknownType].concat(this.props.types.filter(type =>
            type.name !== UNKNOWN_OPERATION_TYPE
        ));

        let typeOptions = [
            <option
              key="_"
              value="">
                { $t('client.search.any_type') }
            </option>
        ].concat(
            types.map(type => (
                <option
                  key={ type.name }
                  value={ type.name }>
                    { $t(`client.${type.name}`) }
                </option>
            ))
        );

        let handleKeyword = event => {
            this.props.setKeywords(event.target.value);
        };
        let handleOperationType = event => {
            this.props.setType(event.target.value);
        };
        let handleAmountLow = value => {
            this.props.setAmountLow(Number.isNaN(value) ? null : value);
        };
        let handleAmountHigh = value => {
            this.props.setAmountHigh(Number.isNaN(value) ? null : value);
        };

        let refSearchPanel = node => {
            this.refSearchPanel = node;
        };
        let refSearchForm = node => {
            this.searchForm = node;
        };
        let refLowAmountInput = node => {
            this.lowAmountInput = node;
        };
        let refHighAmountInput = node => {
            this.highAmountInput = node;
        };
        let refLowDatePicker = node => {
            this.lowDatePicker = node;
        };
        let refHighDatePicker = node => {
            this.highDatePicker = node;
        };

        return (
            <FoldablePanel
              title={ $t('client.search.title') }
              initiallyExpanded={ this.props.displaySearchDetails }
              ref={ refSearchPanel }>
                <form ref={ refSearchForm }>
                    <div className="form-group">
                        <label htmlFor="keywords">
                            { $t('client.search.keywords') }
                        </label>
                        <input
                          type="text"
                          className="form-control"
                          onKeyUp={ handleKeyword }
                          id="keywords"
                        />
                    </div>

                    <div className="form-horizontal">
                        <div className="form-group">
                            <div className="col-xs-4 col-md-2">
                                <label htmlFor="category-selector">
                                    { $t('client.search.category') }
                                </label>
                            </div>
                            <div className="col-xs-8 col-md-5">
                                <SearchCategorySelect id="category-selector" />
                            </div>
                            <div className="col-xs-4 col-md-1">
                                <label htmlFor="type-selector">
                                    { $t('client.search.type') }
                                </label>
                            </div>
                            <div className="col-xs-8 col-md-4">
                                <select
                                  className="form-control"
                                  id="type-selector"
                                  onChange={ handleOperationType }>
                                    { typeOptions }
                                </select>
                            </div><commit_msg>Fixes #673: Use onChange in the search keywords field to work around a Firefox misbehavior;<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { translate as $t, UNKNOWN_OPERATION_TYPE, NONE_CATEGORY_ID } from '../../helpers';
import { get, actions } from '../../store';

import AmountInput from '../ui/amount-input';
import DatePicker from '../ui/date-picker';
import FoldablePanel from '../ui/foldable-panel';

const SearchCategorySelect = connect(state => {
    return {
        defaultValue: get.searchFields(state).categoryId,
        categories: get.categories(state)
    };
}, dispatch => {
    return {
        handleChange(event) {
            actions.setSearchField(dispatch, 'categoryId', event.target.value);
        }
    };
})(props => {
    let { defaultValue, categories, handleChange } = props;

    let noneCategory = categories.find(cat => cat.id === NONE_CATEGORY_ID);
    categories = categories.filter(cat => cat.id !== NONE_CATEGORY_ID);

    let options = [
        <option
          key="_"
          value="">
            { $t('client.search.any_category') }
        </option>,
        <option
          key={ noneCategory.id }
          value={ noneCategory.id }>
            { noneCategory.title }
        </option>
    ].concat(categories.map(cat => (
        <option
          key={ cat.id }
          value={ cat.id }>
            { cat.title }
        </option>
    )));

    return (
        <select
          className="form-control"
          id={ props.id }
          defaultValue={ defaultValue }
          onChange={ handleChange }>
            { options }
        </select>
    );
});

SearchCategorySelect.propTypes = {
    // A string to link the input to a label for exemple.
    id: PropTypes.string
};

const MinDatePicker = connect((state, props) => {
    return {
        defaultValue: get.searchFields(state).dateLow,
        maxDate: get.searchFields(state).dateHigh,
        ref: props.refCb
    };
}, dispatch => {
    return {
        onSelect(dateLow) {
            actions.setSearchField(dispatch, 'dateLow', dateLow);
        }
    };
})(DatePicker);

const MaxDatePicker = connect((state, props) => {
    return {
        defaultValue: get.searchFields(state).dateHigh,
        minDate: get.searchFields(state).dateLow,
        ref: props.refCb
    };
}, dispatch => {
    return {
        onSelect(dateHigh) {
            actions.setSearchField(dispatch, 'dateHigh', dateHigh);
        }
    };
})(DatePicker);

class SearchComponent extends React.Component {
    constructor(props) {
        super(props);
        this.handleClearSearchNoClose = this.handleClearSearch.bind(this, false);
        this.handleClearSearchAndClose = this.handleClearSearch.bind(this, true);

        this.searchForm = null;
        this.lowAmountInput = null;
        this.highAmountInput = null;
        this.lowDatePicker = null;
        this.highDatePicker = null;
    }

    handleClearSearch(close, event) {
        this.searchForm.reset();
        this.lowAmountInput.clear();
        this.highAmountInput.clear();
        this.lowDatePicker.clear();
        this.highDatePicker.clear();
        this.props.resetAll();
        if (close)
            this.refSearchPanel.handleToggleExpand();
        event.preventDefault();
    }

    componentWillUnmount() {
        this.props.resetAll(false);
    }

    render() {
        let unknownType = this.props.types.find(type => type.name === UNKNOWN_OPERATION_TYPE);

        // Types are not sorted.
        let types = [unknownType].concat(this.props.types.filter(type =>
            type.name !== UNKNOWN_OPERATION_TYPE
        ));

        let typeOptions = [
            <option
              key="_"
              value="">
                { $t('client.search.any_type') }
            </option>
        ].concat(
            types.map(type => (
                <option
                  key={ type.name }
                  value={ type.name }>
                    { $t(`client.${type.name}`) }
                </option>
            ))
        );

        let handleKeyword = event => {
            this.props.setKeywords(event.target.value);
        };
        let handleOperationType = event => {
            this.props.setType(event.target.value);
        };
        let handleAmountLow = value => {
            this.props.setAmountLow(Number.isNaN(value) ? null : value);
        };
        let handleAmountHigh = value => {
            this.props.setAmountHigh(Number.isNaN(value) ? null : value);
        };

        let refSearchPanel = node => {
            this.refSearchPanel = node;
        };
        let refSearchForm = node => {
            this.searchForm = node;
        };
        let refLowAmountInput = node => {
            this.lowAmountInput = node;
        };
        let refHighAmountInput = node => {
            this.highAmountInput = node;
        };
        let refLowDatePicker = node => {
            this.lowDatePicker = node;
        };
        let refHighDatePicker = node => {
            this.highDatePicker = node;
        };

        return (
            <FoldablePanel
              title={ $t('client.search.title') }
              initiallyExpanded={ this.props.displaySearchDetails }
              ref={ refSearchPanel }>
                <form ref={ refSearchForm }>
                    <div className="form-group">
                        <label htmlFor="keywords">
                            { $t('client.search.keywords') }
                        </label>
                        <input
                          type="text"
                          className="form-control"
                          onChange={ handleKeyword }
                          id="keywords"
                        />
                    </div>

                    <div className="form-horizontal">
                        <div className="form-group">
                            <div className="col-xs-4 col-md-2">
                                <label htmlFor="category-selector">
                                    { $t('client.search.category') }
                                </label>
                            </div>
                            <div className="col-xs-8 col-md-5">
                                <SearchCategorySelect id="category-selector" />
                            </div>
                            <div className="col-xs-4 col-md-1">
                                <label htmlFor="type-selector">
                                    { $t('client.search.type') }
                                </label>
                            </div>
                            <div className="col-xs-8 col-md-4">
                                <select
                                  className="form-control"
                                  id="type-selector"
                                  onChange={ handleOperationType }>
                                    { typeOptions }
                                </select>
                            </div>
{'fraction_non_alphanumeric': 0.11895461970030538, 'fraction_numerical': 0.0013493359846601803, 'mean_word_length': 1.4067680738335326, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3732172', 'n_tokens_mistral': 3661, 'n_tokens_neox': 3573, 'n_words': 667}