"use client" import { useState, useEffect } from "react" import { motion, AnimatePresence } from "framer-motion" import type { AnalysisResult } from "@/app/page" interface SavedAnalysis { id: string summary: string findings: AnalysisResult["findings"] report: AnalysisResult["report"] created_at: string } interface CompareAnalysesProps { a: SavedAnalysis b: SavedAnalysis onClose: () => void } function formatDate(d: string) { if (!d) return "—" return new Date(d).toLocaleDateString("ar-SA", { day: "numeric", month: "short", year: "numeric" }) } const STATUS_CFG = { normal: { label: "طبيعي", cls: "text-success bg-success/10", dot: "bg-success" }, high: { label: "مرتفع", cls: "text-destructive bg-destructive/10", dot: "bg-destructive" }, low: { label: "منخفض", cls: "text-warning bg-warning/10", dot: "bg-warning" }, } as const function getStatusCfg(s: string) { return STATUS_CFG[s as keyof typeof STATUS_CFG] ?? STATUS_CFG.normal } function getTrend(rowA: AnalysisResult["findings"][0] | null, rowB: AnalysisResult["findings"][0] | null) { if (!rowA || !rowB) return null const vA = parseFloat(rowA.value) const vB = parseFloat(rowB.value) if (isNaN(vA) || isNaN(vB)) return null const improved = rowA.status !== "normal" && rowB.status === "normal" const worsened = rowA.status === "normal" && rowB.status !== "normal" const diff = vB - vA const deltaPct = vA !== 0 ? Math.abs((diff / vA) * 100) : 0 if (Math.abs(diff) < 0.001) return { icon: "—", colorCls: "text-muted-foreground", direction: "stable" as const, deltaPct: 0 } return { icon: diff > 0 ? "↑" : "↓", colorCls: improved ? "text-success" : worsened ? "text-destructive" : "text-muted-foreground", direction: diff > 0 ? ("up" as const) : ("down" as const), deltaPct: Math.round(deltaPct), } } function calcPct(value: string, range: string) { const nums = range?.match(/[\d.]+/g) if (!nums || nums.length < 2) return null const lo = parseFloat(nums[0]), hi = parseFloat(nums[1]), val = parseFloat(value) if (isNaN(val) || isNaN(lo) || isNaN(hi) || hi === lo) return null return Math.max(4, Math.min(96, ((val - lo) / (hi - lo)) * 100)) } /* ─── Skeleton pieces ─── */ function Sk({ w = "100%", h = 16, rounded = "rounded-xl" }: { w?: string | number; h?: number; rounded?: string }) { return (
) } function CompareSkeleton() { return ( {/* Header skeleton */}
{/* Stat cards */}
{[0, 1, 2].map(i => (
))}
{/* Column headers */}
{[0, 1].map(i => (
))}
{/* Table rows */}
{[100, 85, 90, 75, 95, 80].map((w, i) => (
{[0, 1].map(j => (
))}
))}
{/* Footer */}
) } /* ─── Real content ─── */ const BACKEND = process.env.NEXT_PUBLIC_BACKEND_URL ?? "http://localhost:8000" function CompareContent({ a, b, onClose, }: { a: SavedAnalysis b: SavedAnalysis onClose: () => void }) { const [groqSummary, setGroqSummary] = useState(null) const [summaryLoading, setSummaryLoading] = useState(true) const dateA = formatDate(a.created_at) const dateB = formatDate(b.created_at) useEffect(() => { setSummaryLoading(true) fetch(`${BACKEND}/api/compare/summary`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ findings_a: a.findings ?? [], findings_b: b.findings ?? [], summary_a: a.summary ?? "", summary_b: b.summary ?? "", date_a: dateA, date_b: dateB, }), }) .then(r => r.ok ? r.json() : Promise.reject()) .then(d => setGroqSummary(d.summary ?? null)) .catch(() => setGroqSummary(null)) .finally(() => setSummaryLoading(false)) }, [a.id, b.id]) const mapA = Object.fromEntries((a.findings ?? []).map(f => [f.name, f])) const mapB = Object.fromEntries((b.findings ?? []).map(f => [f.name, f])) const allNames = [...new Set([...Object.keys(mapA), ...Object.keys(mapB)])] const rows = allNames.map(name => ({ name, a: mapA[name] ?? null, b: mapB[name] ?? null })) const improved = rows.filter(r => r.a?.status !== "normal" && r.b?.status === "normal") const worsened = rows.filter(r => r.a?.status === "normal" && r.b?.status !== "normal") const stable = rows.filter(r => r.a?.status === r.b?.status) const summaryStats = [ { num: improved.length, label: "تحسّن", icon: "↑", bg: "bg-success/8", border: "border-success/20", text: "text-success" }, { num: worsened.length, label: "تراجع", icon: "!", bg: "bg-destructive/8", border: "border-destructive/20", text: "text-destructive" }, { num: stable.length, label: "مستقر", icon: "→", bg: "bg-muted/60", border: "border-border", text: "text-muted-foreground" }, ] const insights = [ ...improved.map(r => ({ type: "success" as const, icon: "↑", text: `تحسّن مستوى ${r.name} من ${r.a?.value ?? "—"} إلى ${r.b?.value ?? "—"} ${r.b?.unit ?? ""}`, })), ...worsened.map(r => ({ type: "warning" as const, icon: "!", text: `تراجع مستوى ${r.name} — يُنصح بمراجعة الطبيب`, })), ] return ( {/* ── Header ── */}

مقارنة التحاليل

{worsened.length > 0 && ( {worsened.length} تحتاج متابعة )} {improved.length > 0 && ( {improved.length} تحسّنت ✓ )}

{dateA} مقابل {dateB}

{/* ── Summary Stats ── */}
{summaryStats.map((s, i) => (

{s.num}

{s.label} {s.icon}

))}
{/* ── Analysis Column Labels ── */}
{[ { label: "التحليل الأول", date: dateA, summary: a.summary, accent: "border-primary/30 bg-primary/5" }, { label: "التحليل الثاني", date: dateB, summary: b.summary, accent: "border-secondary/30 bg-secondary/5" }, ].map((col, i) => (

{col.label}

{col.date}

{col.summary}

))}
{/* ── Comparison Rows ── */}
{rows.map((row, i) => { const cfgA = row.a ? getStatusCfg(row.a.status) : null const cfgB = row.b ? getStatusCfg(row.b.status) : null const trend = getTrend(row.a, row.b) const pctA = row.a ? calcPct(row.a.value, row.a.range ?? "") : null const pctB = row.b ? calcPct(row.b.value, row.b.range ?? "") : null const isWorsened = row.a?.status === "normal" && row.b?.status !== "normal" return ( {/* Test name */}

{row.name}

{(row.a?.unit || row.b?.unit) && (

{row.a?.unit ?? row.b?.unit}

)} {(row.a?.range || row.b?.range) && (

طبيعي: {row.a?.range ?? row.b?.range}

)}
{/* Value A */}
{row.a ? (

{row.a.value}

{cfgA!.label} {pctA !== null && (
)}
) : ( )}
{/* Value B + trend + delta % */}
{row.b ? (

{row.b.value}

{trend && trend.direction !== "stable" && ( {trend.icon} )}
{trend && trend.direction !== "stable" && trend.deltaPct > 0 && (

{trend.deltaPct}%

)} {cfgB!.label} {pctB !== null && (
)}
) : ( )}
) })}
{/* ── Groq AI Summary ── */}

ملخص ذكي مقارَن

{summaryLoading ? (
) : groqSummary ? (

{groqSummary}

) : null}
{/* ── Insights Section ── */} {insights.length > 0 && (

تفاصيل التغيرات

{insights.map((ins, i) => (
{ins.icon}

{ins.text}

))}
)}
{/* ── Footer ── */}

{rows.length} فحص مقارَن

) } /* ─── Main export ─── */ export function CompareAnalyses({ a, b, onClose }: CompareAnalysesProps) { const [isLoading, setIsLoading] = useState(true) useEffect(() => { const t = setTimeout(() => setIsLoading(false), 700) return () => clearTimeout(t) }, []) return ( e.stopPropagation()} className="w-full max-w-3xl max-h-[90vh] flex flex-col rounded-3xl overflow-hidden bg-card" style={{ boxShadow: "0 40px 96px -16px oklch(0 0 0 / 0.32), 0 0 0 1px oklch(0 0 0 / 0.07)" }} > {isLoading ? : } ) }