import React, { useEffect, useState } from "react"; import { SelectableList } from "./SelectableList"; import { Box, Text, UseInput } from "./Ink"; import { TextInput } from "./TextInput"; import { NumberInput } from "./NumberInput"; import { getApiKey } from "../../zygonApi"; import { DateFilter } from "./DateFilter"; const API_ENDPOINT = process.env.API_ENDPOINT ?? "https://api.zygon.tech"; export type Filter = { id: string; type: | "string" | "date" | "number" | "boolean" | "singleOption" | "multiOption" | "multiTextOption" | "json"; }; type OptionOperator = | "isAny" | "isNone" | "isSet" | "isNotSet" | "haveAny" | "haveNone" | "haveAll" | "areSet" | "areNotSet" | "anyContain" | "noneContain"; type TextOperator = "startsWith" | "endsWith" | "contains"; type DateOperator = | "lteDate" | "gteDate" | "betweenDate" | "today" | "inLessThan" | "inMoreThan" | "lessThanAgo" | "moreThanAgo" | "inTheNext" | "inTheLast"; type NumberOperator = "lteInt" | "gteInt" | "eqInt" | "betweenInt"; type HierarchyOperator = "isAbove" | "isBelow" | "managed" | "manages"; type BooleanOperator = "is"; type Operator = | OptionOperator | TextOperator | DateOperator | NumberOperator | HierarchyOperator | BooleanOperator; const operators = { string: ["startsWith", "endsWith", "contains"], number: ["lteInt", "gteInt", "eqInt", "betweenInt"], boolean: ["is"], singleOption: [ "isAny", "isNone", "isSet", "isNotSet", "haveAny", "haveNone", "haveAll", "areSet", "areNotSet", "anyContain", "noneContain", ], multiOption: [ "isAny", "isNone", "isSet", "isNotSet", "haveAny", "haveNone", "haveAll", "areSet", "areNotSet", "anyContain", "noneContain", ], multiTextOption: [ "isAny", "isNone", "isSet", "isNotSet", "haveAny", "haveNone", "haveAll", "areSet", "areNotSet", "anyContain", "noneContain", ], date: [ "lteDate", "gteDate", "betweenDate", "today", "inLessThan", "inMoreThan", "lessThanAgo", "moreThanAgo", "inTheNext", "inTheLast", ], json: [], } satisfies Record>; export type ConfiguredFilter = { id: string; filters?: Filter[]; operator?: Operator; negation?: boolean; value?: string; values?: string[]; dateMin?: Date; dateMax?: Date; valueMin?: number; valueMax?: number; relativeDays?: number; }; type FilterConfigurationProps = { width: number; height: number; filter: Filter; onFilterConfigured: (filter: ConfiguredFilter) => void; useInput: UseInput; }; const FilterConfiguration = ({ width, height, filter, onFilterConfigured, useInput, }: FilterConfigurationProps): React.JSX.Element => { const [configuredFilter, setConfiguredFilter] = useState(null); const [options, setOptions] = useState>( [], ); useEffect(() => { if ( ["singleOption", "multiOption", "multiTextOption"].includes(filter.type) ) { fetch(`${API_ENDPOINT}/zquery/filters/${filter.id}/options`, { method: "GET", headers: { Authorization: `Bearer ${getApiKey()}`, "Content-Type": "application/json", }, }) .then((response) => response.json()) .then((options) => setOptions(options as Array<{ id: string; label: string }>), ); } }, [filter.id]); if (!configuredFilter) { return ( Pick an operator: operator, label: (operator) => operator, }} onItemSelected={(operator) => setConfiguredFilter({ id: filter.id, operator, }) } useInput={useInput} /> ); } switch (filter.type) { case "string": return ( Enter your filter text: onFilterConfigured({ ...configuredFilter, value: text, }) } useInput={useInput} /> ); case "number": return ( Enter your filter{" "} {configuredFilter.operator === "betweenInt" ? configuredFilter.valueMin === undefined ? "minimum " : "maximum " : ""} value:{" "} configuredFilter.operator === "betweenInt" && configuredFilter.valueMin === undefined ? setConfiguredFilter({ ...configuredFilter, valueMin: value, }) : onFilterConfigured({ ...configuredFilter, [configuredFilter.operator === "lteInt" || configuredFilter.operator === "betweenInt" ? "valueMax" : "valueMin"]: value, }) } useInput={useInput} /> ); case "boolean": onFilterConfigured(configuredFilter); return <>; case "date": return ( onFilterConfigured(configuredFilter)} useInput={useInput} /> ); case "singleOption": case "multiOption": case "multiTextOption": return ( Pick your filter: option.id, label: (option) => option.label, }} onItemSelected={(option) => onFilterConfigured({ ...configuredFilter, values: [option.id], }) } useInput={useInput} /> ); case "json": onFilterConfigured(configuredFilter); return <>; } }; type FiltersPickerProps = { width: number; height: number; filters: Array; onFiltersSelection: (filters: Array) => void; useInput: UseInput; }; export const FiltersPicker = ({ width, height, filters, onFiltersSelection, useInput, }: FiltersPickerProps) => { const [currentlyEditingFilter, setCurrentlyEditingFilter] = useState(null); const [configuredFilters, setConfiguredFilters] = useState< Array >([]); useInput((input, key) => { if (key.return) { if (currentlyEditingFilter === null) { onFiltersSelection(configuredFilters); } } }); return ( <> {currentlyEditingFilter && ( { setConfiguredFilters([...configuredFilters, filter]); setCurrentlyEditingFilter(null); }} useInput={useInput} /> )} {!currentlyEditingFilter && ( filter.id, label: (filter) => filter.id }} onItemSelected={setCurrentlyEditingFilter} useInput={useInput} /> )} ); };