import "./FilterEditor.css";
import { type FilterTree, getLocalizedString, toFilterTree } from "@olenbetong/appframe-core";
import clsx from "clsx";
import { useMemo, useState } from "react";
import { FilterGroupEditor } from "./FilterGroupEditor.js";
import { FilterStringField } from "./FilterStringField.js";
import type { FilterEditorProps } from "./types.js";
/**
* The criteria tree of the filter builder. Groups can be nested and each group
* combines its items with either `and` or `or`, matching the advanced filter
* editor in the Windows client.
*
* Works both controlled (`value` + `onChange`) and uncontrolled
* (`defaultValue`). Saving and sharing filters is handled by `FilterBuilder`,
* which composes this component.
*
* @example
* ```jsx
*
* ```
*/
export function FilterEditor({
fields,
value,
defaultValue,
onChange,
readOnly,
showFilterString,
viewName,
getFieldOptions,
className,
"data-size": size = "sm",
}: FilterEditorProps) {
let [uncontrolled, setUncontrolled] = useState(() => toFilterTree(defaultValue));
let isControlled = value !== undefined;
// Normalizing on every render would create a new tree each time, which
// retriggers the filter string conversion.
let controlled = useMemo(() => toFilterTree(value), [value]);
let filter = isControlled ? controlled : uncontrolled;
let visibleFields = useMemo(() => fields.filter((field) => !field.excludeFromFilter), [fields]);
function update(next: FilterTree) {
if (!isControlled) setUncontrolled(next);
onChange?.(next);
}
return (
{filter.mode === "and"
? getLocalizedString("Find rows that match all of these criteria:")
: getLocalizedString("Find rows that match one or more of these criteria:")}