import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { ChevronDown, Plus, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import type { FieldErrors, PricingFieldKey } from "./pricingOverrideSheet"; import { PRICING_FIELDS } from "./pricingOverrideSheet"; type GroupKey = "chat" | "embedding" | "rerank" | "audio" | "image" | "video" | "ocr"; const PRICING_GROUPS: { key: GroupKey; label: string }[] = [ { key: "chat", label: "Chat / Text / Responses" }, { key: "embedding", label: "Embedding" }, { key: "rerank", label: "Rerank" }, { key: "audio", label: "Audio" }, { key: "image", label: "Image" }, { key: "video", label: "Video" }, { key: "ocr", label: "OCR" }, ]; const REQUEST_TYPE_TO_CATEGORY: Record = { chat_completion: "chat", text_completion: "chat", responses: "chat", embedding: "embedding", rerank: "rerank", speech: "audio", transcription: "audio", image_generation: "image", image_variation: "image", image_edit: "image", video_generation: "video", video_remix: "video", ocr: "ocr", }; interface PricingFieldSelectorProps { values: Partial>; errors: FieldErrors; selectedRequestTypes?: string[]; onChange: (key: PricingFieldKey, value: string) => void; onFieldInteraction?: () => void; } export function PricingFieldSelector({ values, errors, selectedRequestTypes, onChange, onFieldInteraction }: PricingFieldSelectorProps) { const [search, setSearch] = useState(""); const [openGroups, setOpenGroups] = useState>(new Set(["chat"])); const [activeFields, setActiveFields] = useState>( () => new Set(PRICING_FIELDS.filter((f) => values[f.key] != null && values[f.key]!.trim() !== "").map((f) => f.key)), ); // Sync active fields to exactly the set of keys that have non-empty values. // This handles both loading new overrides (adds keys) and clearing the patch (removes stale keys). useEffect(() => { setActiveFields(new Set(PRICING_FIELDS.filter((f) => values[f.key] != null && values[f.key]!.trim() !== "").map((f) => f.key))); }, [values]); // Derive active categories from selected request types const activeCategories = useMemo | null>(() => { if (!selectedRequestTypes || selectedRequestTypes.length === 0) return null; const cats = new Set(); for (const rt of selectedRequestTypes) { const cat = REQUEST_TYPE_TO_CATEGORY[rt]; if (cat) cats.add(cat); } return cats.size > 0 ? cats : null; }, [selectedRequestTypes]); const trimmedSearch = search.trim().toLowerCase(); const isSearching = trimmedSearch.length > 0; const filteredFields = useMemo(() => { if (!isSearching) return null; return PRICING_FIELDS.filter((f) => f.label.toLowerCase().includes(trimmedSearch) || f.key.toLowerCase().includes(trimmedSearch)); }, [isSearching, trimmedSearch]); // Fields visible per group when not searching, respecting activeCategories filter const visibleGroupedFields = useMemo( () => PRICING_GROUPS.map((group) => { const fields = PRICING_FIELDS.filter((f) => { if (f.group !== group.key) return false; if (activeCategories === null) return true; return (f.requestTypeGroups as readonly string[]).some((rg) => activeCategories.has(rg as GroupKey)); }); return { ...group, fields }; }).filter((g) => g.fields.length > 0), [activeCategories], ); const toggleGroup = (key: GroupKey) => { setOpenGroups((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); }; const activateField = (key: PricingFieldKey) => { setActiveFields((prev) => new Set([...prev, key])); }; const deactivateField = (key: PricingFieldKey) => { setActiveFields((prev) => { const next = new Set(prev); next.delete(key); return next; }); onFieldInteraction?.(); onChange(key, ""); }; const handleInputChange = (key: PricingFieldKey, value: string) => { onFieldInteraction?.(); onChange(key, value); }; const renderFieldRow = (field: { key: PricingFieldKey; label: string }) => { const isActive = activeFields.has(field.key); const hasValue = values[field.key]?.trim(); const error = errors[field.key]; if (!isActive) { return ( ); } return (
{field.label}
handleInputChange(field.key, e.target.value)} placeholder="0.0" /> {error &&

{error}

}
); }; return (
setSearch(e.target.value)} className="h-9" data-testid="pricing-field-search" />
{isSearching ? (
{filteredFields!.length === 0 ? (
No fields match “{search}”
) : ( filteredFields!.map((field) => renderFieldRow(field)) )}
) : (
{visibleGroupedFields.length === 0 ? (
No pricing fields for the selected request types
) : ( visibleGroupedFields.map((group) => { const isOpen = openGroups.has(group.key); const valueCount = group.fields.filter((f) => values[f.key]?.trim()).length; return (
{isOpen && (
{group.fields.map((field) => renderFieldRow(field))}
)}
); }) )}
)}
); }