diff --git a/src/components/ai-item-history-trigger.tsx b/src/components/ai-item-history-trigger.tsx index 0095ac9..ab4d8da 100644 --- a/src/components/ai-item-history-trigger.tsx +++ b/src/components/ai-item-history-trigger.tsx @@ -4,94 +4,113 @@ * ai-item-history-trigger.tsx * * Per-line-item "Item History" popover trigger. Renders a small ⓘ - * icon button in the quote/order items table; click opens a popover - * showing the AI's analysis for that line (last quoted/sold price, - * stock status, supplier history, etc.). + * icon button in the sprint row; click fetches + * `GET /api/v1/quote_items/[id]/item-history` and renders the + * combined deterministic + AI history panel. * - * Shape of the data: same fluid-JSON rules as the document-level - * card. See `ai-insights-renderer.tsx` for the rendering contract. - * - * Implementation note: this project's `Button` component doesn't - * support `asChild`, so we style the `PopoverTrigger` directly - * (same pattern as `components/ui/duplicate-warning.tsx`). + * The fetch returns 404 when `ITEM_HISTORY_COMPARE_MODE === "none"`, + * in which case the trigger hides itself. */ + import { useState } from "react"; import { Info } from "lucide-react"; import { Popover, PopoverContent, - PopoverHeader, - PopoverTitle, PopoverTrigger, } from "@/components/ui/popover"; -import { - AiInsightsCard, - AiInsightsEmptyState, - type AiItemInsightsShape, -} from "./ai-insights-renderer"; +import { ItemHistoryPopover } from "@/components/item-history-popover"; +import type { ItemHistoryResponse } from "@/lib/types"; import { cn } from "@/lib/utils"; interface Props { /** PB id of the quote_item / order_item. */ itemId: string; - insights: AiItemInsightsShape | null | undefined; + /** + * Pre-loaded insights (optional). If supplied AND we don't have + * a fresh fetch yet, the popover renders from this immediately — + * the fetch then runs in the background and updates the view. + * Kept so the parent (sprint list) can still pass through the + * existing `ai_item_insights` for an instant first paint. + */ + insights?: Record | null | undefined; className?: string; } export function AiItemHistoryTrigger({ itemId, insights, className }: Props) { const [open, setOpen] = useState(false); - const hasInsights = - insights != null && - typeof insights === "object" && - Object.keys(insights).length > 0; + const [data, setData] = useState( + insights + ? { + deterministic: null, + ai: insights, + ai_updated_at: null, + generated_at: new Date().toISOString(), + } + : null + ); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [hidden, setHidden] = useState(false); + + const handleOpen = async (next: boolean) => { + setOpen(next); + if (!next || data || hidden) return; + setLoading(true); + setError(null); + try { + const res = await fetch(`/api/v1/quote_items/${itemId}/item-history`); + if (res.status === 404) { + // Mode is "none" — hide the trigger. + setHidden(true); + setOpen(false); + return; + } + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const body = (await res.json()) as ItemHistoryResponse; + setData(body); + if (body.error) setError(body.error); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load history"); + } finally { + setLoading(false); + } + }; + + if (hidden) return null; + + const hasData = data && (data.deterministic || data.ai); return ( - + - - - +
+ + Item History - - - {hasInsights ? ( - - ) : ( - - )} + +
+

Item id: {itemId}