import { useState, useEffect, useCallback, useRef } from "react"; import { useWatchlist } from "../context/WatchlistContext.jsx"; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from "recharts"; const COLORS = [ "#f0b232", "#58a6ff", "#3fb950", "#f85149", "#bc8cff", "#d2a8ff", "#79c0ff", "#56d364", "#ff7b72", "#ffa657", "#a5d6ff", "#7ee787", ]; function computeLowestSell(orders) { const sells = orders .filter((o) => o.type === "sell" && o.user?.status !== "invisible"); if (sells.length === 0) return null; return Math.min(...sells.map((o) => o.platinum)); } export default function SummaryChart({ priceMin, priceMax }) { const { watchlist } = useWatchlist(); const [catalog, setCatalog] = useState({}); const [ordersCache, setOrdersCache] = useState({}); const [loading, setLoading] = useState(true); const [errors, setErrors] = useState({}); const intervalRef = useRef(null); useEffect(() => { let cancelled = false; async function loadCatalog() { try { const res = await fetch("/api/items"); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (!cancelled) { const map = {}; for (const item of data) map[item.slug] = item; setCatalog(map); } } catch (err) { console.error("Catalog fetch failed:", err); } finally { if (!cancelled) setLoading(false); } } loadCatalog(); return () => { cancelled = true; }; }, []); const fetchOrders = useCallback(async () => { for (const slug of watchlist) { try { const res = await fetch(`/api/orders/${encodeURIComponent(slug)}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setOrdersCache((prev) => ({ ...prev, [slug]: data })); setErrors((prev) => { const next = { ...prev }; delete next[slug]; return next; }); } catch (err) { setErrors((prev) => ({ ...prev, [slug]: err.message })); } } }, [watchlist]); useEffect(() => { if (watchlist.length === 0) return; fetchOrders(); intervalRef.current = setInterval(fetchOrders, 60000); return () => clearInterval(intervalRef.current); }, [watchlist, fetchOrders]); const chartData = watchlist .filter((slug) => catalog[slug]) .map((slug) => { const item = catalog[slug]; const orders = ordersCache[slug] || []; const lowestSell = computeLowestSell(orders); const name = item.i18n?.en?.name || slug; const tags = item.tags || []; return { slug, name, lowestSell, tags }; }) .filter((d) => d.lowestSell != null) .filter((d) => d.lowestSell >= priceMin && d.lowestSell <= priceMax) .sort((a, b) => a.lowestSell - b.lowestSell); // Determine unique categories for coloring const categories = [...new Set(chartData.flatMap((d) => d.tags))].sort(); const colorMap = {}; categories.forEach((cat, i) => { colorMap[cat] = COLORS[i % COLORS.length]; }); if (loading) { return
Loading...
; } if (watchlist.length === 0) { return (
Add items to your watchlist to see the dashboard.
); } if (chartData.length === 0) { return (
No watchlist items with price data. Make sure items have active orders.
); } // Build stacked data: for each item, one bar segment per first tag const stackedData = chartData.map((d) => { const entry = { name: d.name, slug: d.slug }; // Assign to first tag category const cat = d.tags[0] || "other"; entry[cat] = d.lowestSell; entry._cat = cat; return entry; }); // Get the categories actually used in stacked data const usedCats = [...new Set(stackedData.map((d) => d._cat))]; return (

Lowest Sell Price per Watchlisted Item

{usedCats.map((cat) => ( ))}
); }