import { Select, Textfield } from "@digdir/designsystemet-react"; import { PencilIcon } from "@navikt/aksel-icons"; import { type DynamicValue, type FieldMetadata, type FilterExpression, type FilterOperator, getDynamicValueLabel, getDynamicValuesForField, getLocalizedString, getOperatorArity, getValueType, normalizeOperator, } from "@olenbetong/appframe-core"; import { Combobox } from "@olenbetong/appframe-ds"; import { ChipListInput } from "./ChipListInput.js"; import type { FilterEditorSize, FilterValueOption } from "./types.js"; export type FilterValueEditorProps = { expression: FilterExpression; field?: FieldMetadata; options?: FilterValueOption[]; onChange: (expression: FilterExpression) => void; readOnly?: boolean; "data-size"?: FilterEditorSize; }; /** The `input` type used for a field's scalar editor. */ function getInputType(field: FieldMetadata | undefined): "text" | "number" | "date" | "datetime-local" { switch (field?.type) { case "number": return "number"; case "date": return "date"; case "datetime": return "datetime-local"; default: return "text"; } } function toStringValue(value: FilterExpression["value"]): string { if (value === null || value === undefined) return ""; if (value instanceof Date) return value.toISOString(); return String(value); } function toArrayValue(value: FilterExpression["value"], length?: number): string[] { let array = Array.isArray(value) ? value.map(toStringValue) : []; if (length === undefined) return array; // Pair editors always render two inputs, even before both are filled in. return Array.from({ length }, (_, index) => array[index] ?? ""); } /** * The value part of a filter row. Which editor is rendered depends on the * operator's arity, the field's type, and whether the value is a dynamic * ("special") value such as `Today` or `CurrentUser`. */ export function FilterValueEditor({ expression, field, options, onChange, readOnly, "data-size": size = "sm", }: FilterValueEditorProps) { let operator = normalizeOperator(expression.operator); let arity = getOperatorArity(operator); let isDynamic = expression.valueType === "special"; let dynamicValues = field ? getDynamicValuesForField(field) : []; let inputType = getInputType(field); function setValue(value: FilterExpression["value"]) { onChange({ ...expression, value }); } function setDynamic(enabled: boolean) { if (enabled) { onChange({ ...expression, value: dynamicValues[0] ?? null, valueType: "special" }); } else { onChange({ ...expression, value: arity === "pair" ? ["", ""] : arity === "list" ? [] : "", valueType: field ? getValueType(field, operator as FilterOperator) : "string", }); } } if (arity === "none") return null; let editor: React.ReactNode; if (isDynamic) { editor = ( ); } else if (arity === "list") { editor = options ? ( option.label ?? option.value} isOptionEqualToValue={(a, b) => a.value === b.value} value={toArrayValue(expression.value).map( (item) => options.find((option) => option.value === item) ?? { value: item }, )} onChange={(_event, selected) => setValue(selected.map((option) => option.value))} /> ) : ( ); } else if (arity === "pair") { let [from, to] = toArrayValue(expression.value, 2); editor = (
setValue([event.target.value, to])} /> {getLocalizedString("and")} setValue([from, event.target.value])} />
); } else if (options) { let current = toStringValue(expression.value); editor = ( option.label ?? option.value} isOptionEqualToValue={(a, b) => a.value === b.value} value={current ? (options.find((option) => option.value === current) ?? { value: current }) : null} onChange={(_event, selected) => setValue(selected?.value ?? "")} /> ); } else { editor = ( setValue(event.target.value)} /> ); } // Dynamic values are resolved server side, so they can only replace the whole // value. Pairs would need one toggle per side, which the Windows editor does // not offer either. let canToggleDynamic = !readOnly && dynamicValues.length > 0 && arity === "single"; return (
{editor} {canToggleDynamic && ( )}
); }