filters form

This commit is contained in:
Ildar Kamalov
2025-01-15 16:42:26 +03:00
parent c389d88aae
commit 92c004d15d
3 changed files with 205 additions and 188 deletions

View File

@@ -0,0 +1,96 @@
import React from 'react';
import classNames from 'classnames';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
const getIconsData = (homepage: string, source: string) => [
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
];
const renderIcons = (iconsData: { iconName: string; href: string; className?: string }[]) =>
iconsData.map(({ iconName, href, className = '' }) => (
<a
key={iconName}
href={href}
target="_blank"
rel="noopener noreferrer"
className={classNames('d-flex align-items-center', className)}>
<svg className="icon icon--15 mr-1 icon--gray">
<use xlinkHref={`#${iconName}`} />
</svg>
</a>
));
type Filter = {
categoryId: string;
homepage: string;
source: string;
name: string;
};
type Category = {
name: string;
description: string;
};
type Props = {
categories: Record<string, Category>;
filters: Record<string, Filter>;
selectedSources: Record<string, boolean>;
};
export const FiltersList = ({ categories, filters, selectedSources }: Props) => {
const { t } = useTranslation();
const { register } = useFormContext();
return (
<>
{Object.entries(categories).map(([categoryId, category]) => {
const categoryFilters = Object.entries(filters)
.filter(([, filter]) => filter.categoryId === categoryId)
.map(([key, filter]) => ({ ...filter, id: key }));
return (
<div key={category.name} className="modal-body__item">
<h6 className="font-weight-bold mb-1">{t(category.name)}</h6>
<p className="mb-3">{t(category.description)}</p>
{categoryFilters.map((filter) => {
const { homepage, source, name, id } = filter;
const isSelected = selectedSources[source];
const iconsData = getIconsData(homepage, source);
return (
<div key={name} className="d-flex align-items-center pb-1">
<label className="checkbox checkbox--settings">
<span className="checkbox__marker" />
<input
id={id}
type="checkbox"
className="checkbox__input"
disabled={isSelected}
{...register(id)}
/>
<span className="checkbox__label">
<span className="checkbox__label-text">
<span className="checkbox__label-title">{t(name)}</span>
</span>
</span>
</label>
{renderIcons(iconsData)}
</div>
);
})}
</div>
);
})}
</>
);
};

View File

@@ -1,130 +1,45 @@
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import classNames from 'classnames';
import { useForm, Controller, FormProvider } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { CheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE } from '../../helpers/constants';
import filtersCatalog from '../../helpers/filters/filters';
import { FiltersList } from './FiltersList';
const getIconsData = (homepage: any, source: any) => [
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
];
type FormValues = {
enabled: boolean;
name: string;
url: string;
};
const renderIcons = (iconsData: any) =>
iconsData.map(({ iconName, href, className = '' }: any) => (
<a
key={iconName}
href={href}
target="_blank"
rel="noopener noreferrer"
className={classNames('d-flex align-items-center', className)}>
<svg className="icon icon--15 mr-1 icon--gray">
<use xlinkHref={`#${iconName}`} />
</svg>
</a>
));
interface renderCheckboxFieldProps {
input: {
name: string;
value: string;
checked: boolean;
onChange: (...args: unknown[]) => unknown;
};
disabled: boolean;
}
const renderCheckboxField = (props: renderCheckboxFieldProps) => (
<CheckboxField
{...props}
meta={{ touched: false, error: null }}
input={{
...props.input,
checked: props.disabled || props.input.checked,
}}
/>
);
const renderFilters = ({ categories, filters }: any, selectedSources: any, t: any) =>
Object.keys(categories).map((categoryId) => {
const category = categories[categoryId];
const categoryFilters: any = [];
Object.keys(filters)
.sort()
.forEach((key) => {
const filter = filters[key];
filter.id = key;
if (filter.categoryId === categoryId) {
categoryFilters.push(filter);
}
});
return (
<div key={category.name} className="modal-body__item">
<h6 className="font-weight-bold mb-1">{t(category.name)}</h6>
<p className="mb-3">{t(category.description)}</p>
{categoryFilters.map((filter) => {
const { homepage, source, name } = filter;
const isSelected = Object.prototype.hasOwnProperty.call(selectedSources, source);
const iconsData = getIconsData(homepage, source);
return (
<div key={name} className="d-flex align-items-center pb-1">
<Field
name={filter.id}
type="checkbox"
component={renderCheckboxField}
placeholder={t(name)}
disabled={isSelected}
/>
{renderIcons(iconsData)}
</div>
);
})}
</div>
);
});
interface FormProps {
t: (...args: unknown[]) => string;
closeModal: (...args: unknown[]) => unknown;
handleSubmit: (...args: unknown[]) => string;
type Props = {
closeModal: (...args: unknown[]) => void;
onSubmit: (...args: unknown[]) => void;
processingAddFilter: boolean;
processingConfigFilter: boolean;
whitelist?: boolean;
modalType: string;
toggleFilteringModal: (...args: unknown[]) => unknown;
selectedSources?: object;
}
toggleFilteringModal: (...args: unknown[]) => void;
selectedSources?: Record<string, boolean>;
initialValues?: FormValues;
};
const Form = (props: FormProps) => {
const {
t,
closeModal,
handleSubmit,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
} = props;
export const Form = ({
closeModal,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
onSubmit,
initialValues,
}: Props) => {
const { t } = useTranslation();
const methods = useForm({ defaultValues: initialValues });
const { handleSubmit, control } = methods;
const openModal = (modalType: any, timeout = MODAL_OPEN_TIMEOUT) => {
toggleFilteringModal();
@@ -136,72 +51,86 @@ const Form = (props: FormProps) => {
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
return (
<form onSubmit={handleSubmit}>
<div className="modal-body modal-body--filters">
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE && (
<div className="d-flex justify-content-around">
<button
onClick={openFilteringListModal}
className="btn btn-success btn-standard mr-2 btn-large">
{t('choose_from_list')}
</button>
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="modal-body modal-body--filters">
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE && (
<div className="d-flex justify-content-around">
<button
onClick={openFilteringListModal}
className="btn btn-success btn-standard mr-2 btn-large">
{t('choose_from_list')}
</button>
<button onClick={openAddFiltersModal} className="btn btn-primary btn-standard">
{t('add_custom_list')}
</button>
</div>
)}
{modalType === MODAL_TYPE.CHOOSE_FILTERING_LIST && renderFilters(filtersCatalog, selectedSources, t)}
{modalType !== MODAL_TYPE.CHOOSE_FILTERING_LIST && modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<>
<div className="form__group">
<Field
id="name"
name="name"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_name_hint')}
normalizeOnBlur={(data: any) => data.trim()}
/>
<button onClick={openAddFiltersModal} className="btn btn-primary btn-standard">
{t('add_custom_list')}
</button>
</div>
)}
{modalType === MODAL_TYPE.CHOOSE_FILTERING_LIST && (
<FiltersList
categories={filtersCatalog.categories}
filters={filtersCatalog.filters}
selectedSources={selectedSources}
/>
)}
{modalType !== MODAL_TYPE.CHOOSE_FILTERING_LIST && modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<>
<div className="form__group">
<Controller
name="name"
control={control}
render={({ field }) => (
<input
{...field}
type="text"
className="form-control"
placeholder={t('enter_name_hint')}
onBlur={(e) => field.onChange(e.target.value.trim())}
/>
)}
/>
</div>
<div className="form__group">
<Field
id="url"
name="url"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_url_or_path_hint')}
validate={[validateRequiredValue, validatePath]}
normalizeOnBlur={(data: any) => data.trim()}
/>
</div>
<div className="form__group">
<Controller
name="url"
control={control}
rules={{ validate: { validateRequiredValue, validatePath } }}
render={({ field }) => (
<input
{...field}
type="text"
className="form-control"
placeholder={t('enter_url_or_path_hint')}
onBlur={(e) => field.onChange(e.target.value.trim())}
/>
)}
/>
</div>
<div className="form__description">
{whitelist ? t('enter_valid_allowlist') : t('enter_valid_blocklist')}
</div>
</>
)}
</div>
<div className="form__description">
{whitelist ? t('enter_valid_allowlist') : t('enter_valid_blocklist')}
</div>
</>
)}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeModal}>
{t('cancel_btn')}
</button>
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<button
type="submit"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}>
{t('save_btn')}
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeModal}>
{t('cancel_btn')}
</button>
)}
</div>
</form>
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<button
type="submit"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}>
{t('save_btn')}
</button>
)}
</div>
</form>
</FormProvider>
);
};
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.FILTER })])(Form);

View File

@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
import { MODAL_TYPE } from '../../helpers/constants';
import Form from './Form';
import { Form } from './Form';
import '../ui/Modal.css';
import { getMap } from '../../helpers/helpers';
@@ -75,25 +75,15 @@ class Modal extends Component<ModalProps> {
render() {
const {
isOpen,
processingAddFilter,
processingConfigFilter,
handleSubmit,
modalType,
currentFilterData,
whitelist,
toggleFilteringModal,
filters,
t,
filtersCatalog,
} = this.props;
@@ -117,6 +107,8 @@ class Modal extends Component<ModalProps> {
const title = t(getTitle(modalType, whitelist));
console.log(modalType, initialValues);
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered"