import { Combobox } from "@tailor-platform/app-shell"; import { useCallback } from "react"; import { useClient } from "urql"; import { graphql } from "@/graphql"; import { cn } from "@/lib/utils"; const SiteSearchQuery = graphql(` query SiteSearch($query: SiteQueryInput, $first: Int) { sites(query: $query, first: $first) { edges { node { id name type } } } } `); export interface SiteRef { id: string; name: string; type?: string | null; } const formatSiteLabel = (s: SiteRef): string => (s.type ? `${s.name} (${s.type})` : s.name); const DEBOUNCE_MS = 300; const PAGE_SIZE = 30; interface SiteComboboxProps { value: SiteRef | null; onValueChange: (site: SiteRef | null) => void; placeholder?: string; className?: string; disabled?: boolean; /** * Optional Site id to exclude from the result list. Used to keep Source / * Destination from picking the same record on a transfer order form. */ excludeId?: string | null; /** * Optional Site.type values to exclude from the result list. Used on the * transfer order form to keep Source / Destination from forming an * unsupported pairing (e.g. Warehouse → Warehouse). */ excludeTypes?: readonly string[]; } /** * Async searchable combobox for selecting Sites. Replaces the previous * StorageLocation picker on transfer orders, since the native (erp-kit 0.34) * TransferOrder is keyed at the Site level, not the StorageLocation level. */ export const SiteCombobox = ({ value, onValueChange, placeholder = "Search sites...", className, disabled, excludeId, excludeTypes, }: SiteComboboxProps) => { const client = useClient(); const fetcher = useCallback( async (input: string | null, { signal }: { signal: AbortSignal }) => { const selectedLabel = value ? formatSiteLabel(value) : null; const searchText = input === selectedLabel ? null : input; const textFilter = searchText ? { regex: `(?i)${searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}` } : undefined; const queryParts: Record = {}; if (textFilter) queryParts.name = textFilter; if (excludeId) queryParts.id = { nin: [excludeId] }; if (excludeTypes && excludeTypes.length > 0) { queryParts.type = { nin: [...excludeTypes] }; } const query = Object.keys(queryParts).length > 0 ? queryParts : undefined; const result = await client .query( SiteSearchQuery, { query, first: PAGE_SIZE }, { fetchOptions: { signal }, requestPolicy: "network-only" }, ) .toPromise(); return result.data?.sites.edges.map((e) => e.node) ?? []; }, [client, value, excludeId, excludeTypes], ); return ( ({ label: formatSiteLabel(s), key: s.id })} placeholder={placeholder} className={cn("[&_[data-slot=combobox-input]]:pr-14", className)} disabled={disabled} /> ); };