WarTracker / client /src /components /ItemDetail.jsx
skander101's picture
initial commit: vibes only, probably bloated
6512187
Raw
History Blame Contribute Delete
6.94 kB
import { useState, useEffect } from "react";
const STATIC_ASSETS = "https://warframe.market/static/assets";
function median(arr) {
if (arr.length === 0) return null;
const sorted = [...arr].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 !== 0
? sorted[mid]
: (sorted[mid - 1] + sorted[mid]) / 2;
}
export default function ItemDetail({ slug, onBack }) {
const [orders, setOrders] = useState(null);
const [item, setItem] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function load() {
setLoading(true);
setError(null);
try {
const [ordersRes, catalogRes] = await Promise.all([
fetch(`/api/orders/${encodeURIComponent(slug)}`),
fetch("/api/items"),
]);
if (!ordersRes.ok) throw new Error(`Orders HTTP ${ordersRes.status}`);
const ordersData = await ordersRes.json();
if (!cancelled) setOrders(ordersData);
if (catalogRes.ok) {
const catalogData = await catalogRes.json();
const found = catalogData.find((i) => i.slug === slug);
if (!cancelled) setItem(found);
}
} catch (err) {
if (!cancelled) setError(err.message);
} finally {
if (!cancelled) setLoading(false);
}
}
load();
return () => { cancelled = true; };
}, [slug]);
if (loading) {
return <div className="loading"><div className="spinner" /> Loading orders...</div>;
}
if (error) {
return (
<div>
<button className="back-btn" onClick={onBack}>&larr; Back</button>
<div className="error-box">
<p>Failed to load orders: {error}</p>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
</div>
);
}
const name = item?.i18n?.en?.name || slug;
const thumb = item?.i18n?.en?.thumb;
const onlineFilter = (o) =>
o.user?.status === "ingame" || o.user?.status === "online";
const sells = orders
.filter((o) => o.type === "sell" && onlineFilter(o))
.sort((a, b) => a.platinum - b.platinum)
.slice(0, 5);
const buys = orders
.filter((o) => o.type === "buy" && onlineFilter(o))
.sort((a, b) => b.platinum - a.platinum)
.slice(0, 5);
const allSells = orders.filter((o) => o.type === "sell" && onlineFilter(o));
const sellPrices = allSells.map((o) => o.platinum);
const stats = {
median: median(sellPrices),
min: sellPrices.length > 0 ? Math.min(...sellPrices) : null,
max: sellPrices.length > 0 ? Math.max(...sellPrices) : null,
count: allSells.length,
};
return (
<div>
<button className="back-btn" onClick={onBack}>&larr; Back to watchlist</button>
<div className="detail-panel">
<div className="detail-header">
{thumb && (
<img src={`${STATIC_ASSETS}/${thumb}`} alt={name} />
)}
<div>
<h2>{name}</h2>
<span style={{ color: "var(--text-muted)", fontSize: "0.85rem" }}>
/v2/orders/item/{slug}
</span>
</div>
</div>
<div className="stats-grid">
<div className="stat-card">
<div className="stat-label">Median Sell</div>
<div className="stat-value price-sell">
{stats.median != null ? `${stats.median}p` : "\u2014"}
</div>
</div>
<div className="stat-card">
<div className="stat-label">Min Sell</div>
<div className="stat-value price-sell">
{stats.min != null ? `${stats.min}p` : "\u2014"}
</div>
</div>
<div className="stat-card">
<div className="stat-label">Max Sell</div>
<div className="stat-value price-sell">
{stats.max != null ? `${stats.max}p` : "\u2014"}
</div>
</div>
<div className="stat-card">
<div className="stat-label">Active Sells</div>
<div className="stat-value">{stats.count}</div>
</div>
</div>
{sells.length === 0 && buys.length === 0 ? (
<div className="empty-msg">No active orders from online/ingame users.</div>
) : (
<>
{sells.length > 0 && (
<div className="orders-section">
<h3 className="price-sell">Top Sell Orders (low to high)</h3>
<table>
<thead>
<tr>
<th>Price</th>
<th>Qty</th>
<th>User</th>
<th>Rep</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{sells.map((o) => (
<tr key={o.id}>
<td className="price-sell">{o.platinum}p</td>
<td>{o.quantity}</td>
<td>{o.user?.ingameName || "?"}</td>
<td>{o.user?.reputation ?? "?"}</td>
<td>
<span style={{
color: o.user?.status === "ingame" ? "var(--green)" : "var(--blue)",
}}>
{o.user?.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{buys.length > 0 && (
<div className="orders-section" style={{ marginTop: 20 }}>
<h3 className="price-buy">Top Buy Orders (high to low)</h3>
<table>
<thead>
<tr>
<th>Price</th>
<th>Qty</th>
<th>User</th>
<th>Rep</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{buys.map((o) => (
<tr key={o.id}>
<td className="price-buy">{o.platinum}p</td>
<td>{o.quantity}</td>
<td>{o.user?.ingameName || "?"}</td>
<td>{o.user?.reputation ?? "?"}</td>
<td>
<span style={{
color: o.user?.status === "ingame" ? "var(--green)" : "var(--blue)",
}}>
{o.user?.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
)}
</div>
</div>
);
}