admin_search-options_src_components_fieldOptions.js

import { _x } from '@wordpress/i18n';

import {
	BaseControl,
	RadioControl,
	ToggleControl,
} from '@wordpress/components';

import Select from './select';
import { useEffect, useState } from '@wordpress/element';

export default function FieldOptions( props ) {
	const { editField, onChange } = props;

	const [ option, setOption ] = useState( editField.options || {} );

	const prepareDataSources = function( data ) {
		return data.map( ( source ) => {
			return {
				text: source.label,
				value: source.id,
			};
		} );
	};

	const prepareFacets = function( facetDistribution ) {
		const result = {};
		Object.entries( facetDistribution ).map( ( [ key, value ] ) => {
			result[ key ] = Object.entries( value ).map( ( [ key, value ] ) => {
				return {
					text: key,
					value: key,
				};
			} );
		} );

		return result;
	};

	const prepareFacetsOptions = function( source ) {
		if ( ! source ) {
			return [];
		}
		const sourceName = source.split( '/' ).slice( -1 )[ 0 ];
		return facetsOptions[ sourceName ];
	};

	const dataSources = Object.values( findstr.settingsIndexableFields ) || [];

	const [ facets, setFacets ] = useState( [] );
	const [ facetsOptions, setFacetsOptions ] = useState( {} );

	useEffect( () => {
		//call meilisearch
		findstr.index.search( '', { facets: [ '*' ] } ).then( ( response ) => {
			setFacetsOptions( prepareFacets( response.facetDistribution ) );
			const facets = Object.entries( response.facetDistribution ).map(
				( [ key, value ] ) => {
					return key;
				}
			);
			setFacets( facets );
		} );
	}, [] );

	/**
	 * Filters the field options.
	 *
	 * Structure:
	 * - allFields: All fields available for all field types.
	 * - [fieldType]: The field type specific options. (checkbox, dropdown, toggle, etc.)
	 *   - [OptionKey]: This is the option key.
	 *     - [type]: How the option should be rendered. (select, radio, toggle)
	 *     - [label]: The label of the option.
	 *     - [default]: The default value of the option.
	 *     - [data]: The data used to render the option.
	 *     - [conditions]: The conditions that need to be met for the option to be rendered.
	 *
	 *
	 * @hook findstr.fieldOptions
	 * @param {Object} options   All available options.
	 * @param {Object} editField The field being edited.
	 * @param {Object} option    The current options.
	 *
	 * @return {Object} The field options.
	 */
	const options = findstr.hooks.applyFilters(
		'fieldOptions',
		{
			allFields: {
				sourceName: {
					type: 'select',
					data: prepareDataSources(
						dataSources.filter( ( source ) => {
							return source.filterable;
						} )
					),
					label: _x( 'Source', 'edit field form', 'findstr' ),
					conditions: {
						fieldType: [ 'checkbox', 'dropdown', 'toggle' ],
					},
				},
				excludeOptions: {
					type: 'select',
					conditions: {
						fieldType: [ 'checkbox', 'dropdown' ],
					},
					isMulti: true,
					data: prepareFacetsOptions( editField.options.sourceName ),
					label: _x( 'Exclude options', 'edit field form', 'findstr' ),
				},
			},
			search: {
				hideMagnifier: {
					type: 'toggle',
					label: _x( 'Hide magnifier', 'edit field form', 'findstr' ),
					default: false,
				},
				disableSearchAsYouType: {
					type: 'toggle',
					label: _x(
						'Disable search as you type',
						'edit field form',
						'findstr'
					),
					default: false,
				},
			},
			checkbox: {
				hideFieldTitle: {
					type: 'toggle',
					label: _x( 'Hide field title', 'edit field form', 'findstr' ),
					default: false,
				},
				multiselect: {
					type: 'toggle',
					label: _x( 'Multi select', 'edit field form', 'findstr' ),
					default: false,
				},
				multiselectLogic: {
					conditions: { multiselect: true },
					type: 'radio',
					label: _x(
						'Multiple values Behavior',
						'edit field form',
						'findstr'
					),
					default: 'and',
					data: [
						{ label: 'AND', value: 'AND' },
						{ label: 'OR', value: 'OR' },
					],
				},
				hideEmpty: {
					type: 'toggle',
					label: _x(
						'Hide options with zero results',
						'edit field form',
						'findstr'
					),
					default: false,
					conditions: {
						multiselect: true,
					},
				},
				showCount: {
					type: 'toggle',
					label: _x( 'Show count', 'edit field form', 'findstr' ),
					default: false,
				},
				disableCountUpdate: {
					type: 'toggle',
					label: _x(
						'Disable count update',
						'edit field form',
						'findstr'
					),
					default: false,
				},
			},
			dropdown: {
				multiselect: {
					type: 'toggle',
					label: _x( 'Multi select', 'edit field form', 'findstr' ),
					default: false,
				},
				hideEmpty: {
					type: 'toggle',
					label: _x(
						'Hide options with zero results',
						'edit field form',
						'findstr'
					),
					default: false,
					conditions: {
						multiselect: true,
					},
				},
				showCount: {
					type: 'toggle',
					label: _x( 'Show count', 'edit field form', 'findstr' ),
					default: false,
				},
				multiselectLogic: {
					conditions: { multiselect: true },
					type: 'radio',
					label: _x(
						'Multiple values Behavior',
						'edit field form',
						'findstr'
					),
					default: 'and',
					data: [
						{ label: 'AND', value: 'AND' },
						{ label: 'OR', value: 'OR' },
					],
				},
			},
			sort: {
				sortableFields: {
					type: 'select',
					isMulti: true,
					data: prepareDataSources(
						dataSources.filter( ( source ) => source.sortable )
					),
					label: _x( 'Sortable sources', 'edit field form', 'findstr' ),
				},
			},
			pagination: {
				infiniteScroll: {
					type: 'toggle',
					label: _x( 'Infinite scroll', 'edit field form', 'findstr' ),
					default: false,
				},
			},
			loadMore: {
				infiniteScroll: {
					type: 'toggle',
					label: _x( 'Infinite scroll', 'edit field form', 'findstr' ),
					default: false,
				},
				showProgressBar: {
					type: 'toggle',
					label: _x(
						'Show progress bar',
						'edit field form',
						'findstr'
					),
					default: false,
				},
				showPagesNumber: {
					type: 'toggle',
					label: _x(
						'Show pages number',
						'edit field form',
						'findstr'
					),
					default: false,
				},
			},
			toggle: {
				targetValue: {
					type: 'select',
					isMulti: true,
					tomselect: {
						create: true,
						persist: true,
					},
					data: [],
					label: _x( 'Target values', 'edit field form', 'findstr' ),
				},
			},
			datepicker: {
				sourceName: {
					type: 'select',
					data: facets,
					label: _x( 'Source', 'edit field form', 'findstr' ),
				},
				inline: {
					type: 'toggle',
					label: _x( 'Inline', 'edit field form', 'findstr' ),
					default: false,
				},
				showEvents: {
					type: 'toggle',
					label: _x( 'Show events', 'edit field form', 'findstr' ),
					default: false,
				},
				displayButtons: {
					type: 'select',
					label: _x( 'Display buttons', 'edit field form', 'findstr' ),
					default: [],
					data: [
						{ text: 'Today', value: 'today' },
						{ text: 'Clear', value: 'clear' },
					],
					isMulti: true,
				},
				dateType: {
					type: 'select',
					data: [
						{ text: 'Single date', value: 'single' },
						{ text: 'Date range', value: 'range' },
					],
					tomselect: {},
					label: _x( 'Behavior', 'edit field form', 'findstr' ),
					default: 'single',
				},
				singleDateBehavior: {
					conditions: { dateType: 'single' },
					type: 'radio',
					label: _x( 'Behavior', 'edit field form', 'findstr' ),
					data: [
						{
							label: _x(
								'Filter at the date',
								'datepicker option field',
								'findstr'
							),
							value: 'current',
						},
						{
							label: _x(
								'Filter after date',
								'datepicker option field',
								'findstr'
							),
							value: 'after',
						},
						{
							label: _x(
								'Filter before date',
								'datepicker option field',
								'findstr'
							),
							value: 'before',
						},
					],
					default: 'current',
				},
			},
			reset: {
				autoHide: {
					type: 'toggle',
					label: _x(
						'Hide button when no reset is needed',
						'edit field form',
						'findstr'
					),
					default: false,
				},
			},
		},
		editField,
		option
	);

	function fieldMeetConditions( editField, conditions ) {
		if ( conditions ) {
			for ( const [ condition_key, condition_value ] of Object.entries(
				conditions
			) ) {
				//if field exists
				if (
					editField[ condition_key ] &&
					condition_value.includes( editField[ condition_key ] )
				) {
					return true;
				}
				if (
					editField?.options[ condition_key ] &&
					editField.options[ condition_key ] === condition_value
				) {
					return true;
				}

				if (
					! editField?.options[ condition_key ] &&
					options?.[ editField.fieldType ]?.[ condition_key ]?.default &&
					options?.[ editField.fieldType ]?.[ condition_key ]?.default ===
						condition_value
				) {
					return true;
				}

				if ( editField.options[ condition_key ] !== condition_value ) {
					return false;
				}
			}
		}
		return true;
	}

	useEffect( () => {
		if ( typeof onChange === 'function' ) {
			onChange( option );
		}
	}, [ option ] );

	function renderOptions( key, value ) {
		if ( fieldMeetConditions( editField, value.conditions ) ) {
			switch ( value.type ) {
				case 'radio':
					return (
						<li key={ key }>
							<RadioControl
								label={ value.label }
								selected={
									editField.options[ key ] ?? value.default
								}
								options={ value.data }
								onChange={ ( value ) => {
									setOption( { ...option, [ key ]: value } );
								} }
							/>
						</li>
					);
				case 'toggle':
					return (
						<li key={ key }>
							<ToggleControl
								label={ value.label }
								checked={
									editField.options[ key ] ?? value.default
								}
								onChange={ ( value ) => {
									setOption( { ...option, [ key ]: value } );
								} }
							/>
						</li>
					);
				case 'select':
					const plugins = [ 'drag_drop' ];
					if ( value.isMulti ) {
						plugins.push( 'remove_button' );
					}

					return (
						<li key={ key }>
							<BaseControl id={ key } label={ value.label }>
								<Select
									id={ key }
									tomselect={ value.tomselect }
									options={ value.data }
									value={
										editField.options[ key ] ?? value.default
									}
									isMulti={ value.isMulti ?? false }
									plugins={ plugins }
									name={ key }
									onChange={ ( value ) => {
										setOption( { ...option, [ key ]: value } );
									} }
								/>
							</BaseControl>
						</li>
					);
				case 'number':
					return (
						<li key={ key }>
							<BaseControl id={ key } label={ value.label }>
								<input
									type="number"
									id={ key }
									value={ ( editField.options[ key ] ?? value.default ) || 0 }
									min={ value.min ?? undefined }
									max={ value.max ?? undefined }
									step={ value.step ?? undefined }
									onChange={ ( event ) => {
										setOption( {
											...option,
											[ key ]: parseFloat( event.target.value ),
										} );
									} }
								/>
							</BaseControl>
						</li>
					);
			}
		}
	}

	let fieldOptions = Object.entries( options.allFields );

	if ( options[ editField.fieldType ] ) {
		fieldOptions = [
			...fieldOptions,
			...Object.entries( options[ editField.fieldType ] ),
		];
	}

	return (
		<div className="options">
			<ul>
				{ fieldOptions.map( ( [ key, value ] ) => {
					return renderOptions( key, value );
				} ) }
			</ul>
		</div>
	);
}