import { useRef, useState } from "react"; import { Box } from "../../../ui"; import { Button, Notice, SelectButton, TextField, type SelectControl, } from "../../../components"; import { useShortcut } from "../../../public/react"; import type { ScreenCriterion, ScreenFieldDefinition, ScreenOperator, } from "../../../api-client/equity-screener"; import { parseCriterion } from "./model"; export function CriterionEditor({ fields, value, onSave, onCancel, focused, width, }: { fields: ScreenFieldDefinition[]; value: ScreenCriterion | null; onSave: (criterion: ScreenCriterion) => void; onCancel: () => void; focused: boolean; width: number; }) { const [fieldId, setFieldId] = useState( value?.field ?? (fields.find((row) => row.id === "trailingPE") ?? fields.find((row) => row.kind === "number") ?? fields[0]!).id, ); const field = fields.find((row) => row.id === fieldId)!; const [operator, setOperator] = useState( value?.op ?? field.operators[0]!, ); const [text, setText] = useState( value && "value" in value ? Array.isArray(value.value) ? value.value.join(", ") : String(value.value) : "", ); const [error, setError] = useState(null); const [active, setActive] = useState("field"); const fieldControl = useRef(null), operatorControl = useRef(null); const ring = [ "field", "operator", ...(["present", "missing"].includes(operator) ? [] : ["value"]), "save", "cancel", ]; const save = () => { try { onSave(parseCriterion(field, operator, text)); } catch (error) { setError(error instanceof Error ? error.message : "Invalid criterion."); } }; useShortcut( (event) => { if (event.name === "tab") { event.preventDefault(); event.stopPropagation(); setActive((current) => ring[(ring.indexOf(current) + (event.shift ? -1 : 1) + ring.length) % ring.length]!); } else if (event.name === "escape") { event.preventDefault(); event.stopPropagation(); onCancel(); } else if ( ["enter", "return"].includes(event.name ?? "") && !event.targetEditable ) { event.preventDefault(); event.stopPropagation(); if (active === "field") fieldControl.current?.open(); else if (active === "operator") operatorControl.current?.open(); else if (active === "cancel") onCancel(); else save(); } }, { enabled: focused, allowEditable: true, phase: "before", scope: "equity-screen-criterion", }, ); return ( ({ value: row.id, label: row.label }))} variant="field" width={Math.min(40, width - 4)} onFocus={() => setActive("field")} controlRef={fieldControl} onChange={(id) => { setFieldId(id); const next = fields.find((row) => row.id === id)!; setOperator(next.operators[0]!); setError(null); }} /> ({ value: op, label: { gte: "At least", lte: "At most", gt: "Above", lt: "Below", eq: "Equals", between: "Between", in: "One of", present: "Available", missing: "Unavailable", }[op], }))} variant="field" width={Math.min(30, width - 4)} onChange={setOperator} onFocus={() => setActive("operator")} controlRef={operatorControl} /> {!["present", "missing"].includes(operator) ? ( setActive("value")} onSubmit={save} /> ) : null} {error ? {error} : null}