// Vrai-Prix — méthode du coût : tiroir latéral générique + fiche « Source » d'un prix // (source, date, URL, valeur observée, unité d'origine, conversion, facteur régional, méthode, synchro, confiance). "use client"; import { useEffect } from "react"; import type { ItemPrice, Provenance } from "@/lib/cost/types"; import { UNIT_LABELS } from "@/lib/cost/units"; import { fmtDate, KindIcon, kindLabel } from "./Provenance"; import { money, num } from "@/components/avendre/fmt"; export function Drawer({ open, onClose, title, children, fr }: { open: boolean; onClose: () => void; title: string; children: React.ReactNode; fr: boolean }) { useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = prev; }; }, [open, onClose]); if (!open) return null; return (
{children}
); } export function KV({ k, v, mono = false }: { k: string; v: React.ReactNode; mono?: boolean }) { return (

{k}

{v}

); } /** Contenu d'un tiroir source pour un prix canonique d'article. */ export function PriceSourceCard({ price, fr, itemName, unit, locationFactor, locationName, lastSync }: { price: ItemPrice | Provenance | null; fr: boolean; itemName: string; unit?: string; locationFactor?: number; locationName?: string; lastSync?: string | null }) { const lang = fr ? "fr" : "en"; if (!price) return

{fr ? "Donnée non disponible." : "Data not available."}

; const isItem = "itemCode" in price; const kind = price.kind; const unitLabel = unit ? (UNIT_LABELS[unit as keyof typeof UNIT_LABELS]?.[lang] ?? unit) : ""; return (

{fr ? "Article" : "Item"}

{itemName}

{kindLabel(kind, fr)}

{isItem ? ( <> = 2 ? "Médiane robuste multi-source (prix régulier préféré, aberrations écartées)" : price.kind === "indexed" ? "Actualisation par indice StatCan" : price.kind === "reference" ? "Prix de référence interne (hypothèse)" : "Observation unique") : price.sourceCount >= 2 ? "Robust multi-source median (regular price preferred, outliers flagged)" : price.kind === "indexed" ? "StatCan index update" : price.kind === "reference" ? "Internal reference price (assumption)" : "Single observation"} /> ) : ( <> )} {locationFactor != null && } {lastSync !== undefined && }
{isItem && price.indexed && (

{fr ? `Prix du ${fmtDate(price.indexed.fromDate, fr)} actualisé au ${fmtDate(price.indexed.toDate, fr)} avec l'indice ${price.indexed.index} (× ${num(price.indexed.factor, lang, 3)}).` : `Price of ${fmtDate(price.indexed.fromDate, fr)} updated to ${fmtDate(price.indexed.toDate, fr)} with the ${price.indexed.index} index (× ${num(price.indexed.factor, lang, 3)}).`}

)} {price.note &&

{price.note}

} {isItem && price.sources.length > 0 && (
{price.sources.map((s, i) => ( ))}
{fr ? "Source" : "Source"}{fr ? "Valeur observée" : "Observed value"}{fr ? "Unité d'origine" : "Original unit"}{fr ? "Conversion" : "Conversion"}{fr ? "Date" : "Date"}
{s.url ? {s.source} ↗ : s.source}{!s.regular && {fr ? "promo" : "sale"}} {s.conversionFactor !== 1 ? `${num(s.price / s.conversionFactor, lang, 2)} $` : `${num(s.price, lang, 2)} $`} {s.sourceUnit} {s.conversionFactor === 1 ? "—" : `× ${s.conversionFactor < 0.01 ? s.conversionFactor.toExponential(2) : num(s.conversionFactor, lang, 4)} → ${num(s.price, lang, 2)} $/${unitLabel}`} {s.observedAt}
)}

{fr ? "Chaque nombre affiché est retraçable : source → observation → conversion → prix canonique → formule → résultat." : "Every displayed number is traceable: source → observation → conversion → canonical price → formula → result."}

); } export { money };