"use client"; import { IconBug, IconClipboardList, IconGitPullRequest, IconLink } from "@tabler/icons-react"; import type { EntityReferenceSearchError } from "@/hooks/use-entity-reference-search"; import type { EntityReference, EntityReferenceGroupStatus, EntityReferenceSearchGroup, } from "@/lib/types/entity-reference"; import { selectableEntityReferences, visibleEntityReferenceGroups, } from "./entity-reference-groups"; import { PopupMenu, PopupMenuItem, useMenuItemRefs } from "./popup-menu"; import { useTranslation } from "react-i18next"; import { t } from "@/lib/i18n"; export { selectableEntityReferences, visibleEntityReferenceGroups, } from "./entity-reference-groups"; type EntityReferenceMenuProps = { isOpen: boolean; clientRect?: (() => DOMRect | null) | null; groups: EntityReferenceSearchGroup[]; query: string; selectedIndex: number; isSearching: boolean; error: EntityReferenceSearchError | null; onRetry: () => void; onSelect: (reference: EntityReference) => void; onClose: () => void; setSelectedIndex: (index: number) => void; }; // Catalog keys, not copy: this table is built at module load, so a `t()` here // would freeze at the boot locale. Keys resolve at render. const GROUP_STATUS_LABEL_KEYS: Partial> = { unauthorized: "task:groupStatusUnauthorized", rate_limited: "task:groupStatusRateLimited", timeout: "task:groupStatusTimeout", upstream_error: "task:groupStatusUpstreamError", unsupported_scope: "task:groupStatusUnsupportedScope", }; function groupStatusLabel( translate: (key: string) => string, status: EntityReferenceGroupStatus, ): string { const key = GROUP_STATUS_LABEL_KEYS[status]; return key ? translate(key) : translate("task:providerUnavailable"); } function entityReferenceIcon(kind: string) { if (kind === "task") return ; if (kind === "issue" || kind === "work_item") return ; if (kind === "pull_request" || kind === "merge_request") { return ; } return ; } function entityReferenceEmptyState( query: string, isSearching: boolean, error: EntityReferenceSearchError | null, onRetry: () => void, ) { if (!query.trim()) return t("task:typeToSearchWorkItems"); if (isSearching) return t("task:searchingWorkItems"); if (error) { return (
{error.message}
); } return t("task:noWorkItemsFound"); } export function EntityReferenceMenu({ isOpen, clientRect, groups, query, selectedIndex, isSearching, error, onRetry, onSelect, onClose, setSelectedIndex, }: EntityReferenceMenuProps) { const { t } = useTranslation(); const { setItemRef } = useMenuItemRefs(selectedIndex); const selectable = selectableEntityReferences(groups); let itemIndex = 0; const visibleGroups = visibleEntityReferenceGroups(groups); const hasContent = visibleGroups.length > 0; return ( {entityReferenceEmptyState(query, isSearching, error, onRetry)} } > {visibleGroups.map((group) => (
{group.display_name} {group.kind_label}
{group.status === "ok" ? ( group.results.map((reference) => { const index = itemIndex++; return ( onSelect(reference)} onMouseEnter={() => setSelectedIndex(index)} itemRef={setItemRef(index)} /> ); }) ) : (
{groupStatusLabel(t, group.status)}
)}
))} {isSearching && selectable.length > 0 && (
{t("task:updating")}
)}
); }