import { useEffect, useMemo, useState } from 'react'; import { Link, Navigate } from 'react-router-dom'; import { api, formatCredits } from '../api'; import { useAuth } from '../AuthContext'; import ItemTile from '../components/ItemTile'; import { onInventoryChanged } from '../inventoryEvents'; import { loadInventorySort, saveInventorySort } from '../userPrefs'; const PAGE_SIZE = 50; export default function Dashboard() { const { user, loading, patchUser } = useAuth(); const [inventory, setInventory] = useState([]); const [error, setError] = useState(''); const [sort, setSort] = useState('value'); const [page, setPage] = useState(1); const [selected, setSelected] = useState([]); const [busy, setBusy] = useState(false); const [maxValue, setMaxValue] = useState('10'); const [autoSellEnabled, setAutoSellEnabled] = useState(false); const [settingsBusy, setSettingsBusy] = useState(false); useEffect(() => { if (!user?.id) return; const saved = loadInventorySort(user.id); if (saved) setSort(saved); }, [user?.id]); useEffect(() => { if (!user) return; setAutoSellEnabled(Boolean(user.autoSellEnabled)); if (user.autoSellThresholdCents != null) { setMaxValue((user.autoSellThresholdCents / 100).toFixed(2).replace(/\.?0+$/, '') || '0'); } }, [user?.id, user?.autoSellEnabled, user?.autoSellThresholdCents]); const load = async () => { const inv = await api.inventory(); setInventory(inv.inventory); }; useEffect(() => { if (!user || user.role === 'admin') return; let cancelled = false; (async () => { try { await load(); } catch (err) { if (!cancelled) setError(err.message); } })(); return () => { cancelled = true; }; }, [user]); useEffect(() => { if (!user || user.role === 'admin') return undefined; return onInventoryChanged(() => { load().catch(() => {}); }); }, [user]); const sorted = useMemo(() => { const list = [...inventory]; if (sort === 'value') { list.sort( (a, b) => (b.valueCents ?? b.item?.valueCents ?? b.item?.marketValue) - (a.valueCents ?? a.item?.valueCents ?? a.item?.marketValue) ); } else { list.sort((a, b) => new Date(b.obtainedAt) - new Date(a.obtainedAt)); } return list; }, [inventory, sort]); const totalPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE)); const currentPage = Math.min(page, totalPages); const pageItems = useMemo(() => { const start = (currentPage - 1) * PAGE_SIZE; return sorted.slice(start, start + PAGE_SIZE); }, [sorted, currentPage]); useEffect(() => { setPage(1); }, [sort]); useEffect(() => { if (page > totalPages) setPage(totalPages); }, [page, totalPages]); const selectedValue = useMemo( () => inventory .filter((inv) => selected.includes(inv.id)) .reduce((sum, inv) => sum + (inv.valueCents ?? inv.item?.valueCents ?? 0), 0), [inventory, selected] ); const thresholdCents = useMemo(() => { const credits = Number(maxValue); if (!Number.isFinite(credits) || credits < 0) return null; return Math.round(credits * 100); }, [maxValue]); const belowThreshold = useMemo(() => { if (thresholdCents == null) return []; return inventory.filter( (inv) => !inv.favorite && (inv.valueCents ?? inv.item?.valueCents ?? 0) < thresholdCents ); }, [inventory, thresholdCents]); const selectedItems = useMemo( () => inventory.filter((inv) => selected.includes(inv.id)), [inventory, selected] ); const selectedHasFavorite = selectedItems.some((inv) => inv.favorite); const selectedAllFavorite = selectedItems.length > 0 && selectedItems.every((inv) => inv.favorite); const selectedCanSell = selectedItems.some((inv) => !inv.favorite); const belowValue = useMemo( () => belowThreshold.reduce( (sum, inv) => sum + (inv.valueCents ?? inv.item?.valueCents ?? 0), 0 ), [belowThreshold] ); const toggle = (id) => { setSelected((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id] ); }; const sellIds = async (ids) => { if (!ids.length) return; setBusy(true); setError(''); try { const data = await api.sellInventory(ids); patchUser({ balance: data.user.balance }); setSelected((prev) => prev.filter((id) => !ids.includes(id))); await load(); } catch (err) { setError(err.message); } finally { setBusy(false); } }; const sell = async () => { const ids = selectedItems.filter((inv) => !inv.favorite).map((inv) => inv.id); if (!ids.length) return; await sellIds(ids); }; const setFavorite = async (favorite) => { if (!selected.length) return; setBusy(true); setError(''); try { await api.setInventoryFavorite(selected, favorite); await load(); } catch (err) { setError(err.message); } finally { setBusy(false); } }; const sellBelow = async () => { const ids = belowThreshold.map((inv) => inv.id); if (!ids.length || thresholdCents == null) return; const favSkipped = inventory.filter( (inv) => inv.favorite && (inv.valueCents ?? inv.item?.valueCents ?? 0) < thresholdCents ).length; const ok = window.confirm( `Sell ${ids.length} item${ids.length === 1 ? '' : 's'} below ${formatCredits(thresholdCents)} cr for ${formatCredits(belowValue)} cr?` + (favSkipped ? ` (${favSkipped} favorite item${favSkipped === 1 ? '' : 's'} excluded)` : '') ); if (!ok) return; await sellIds(ids); }; const saveAutoSell = async (enabled, thresholdCredits) => { setSettingsBusy(true); setError(''); try { const data = await api.updateAutoSell({ enabled, thresholdCredits }); patchUser(data.user); } catch (err) { setError(err.message); } finally { setSettingsBusy(false); } }; const onAutoSellToggle = async (enabled) => { setAutoSellEnabled(enabled); const credits = Number(maxValue); if (!Number.isFinite(credits) || credits < 0) return; await saveAutoSell(enabled, credits); }; const onThresholdBlur = async () => { const credits = Number(maxValue); if (!Number.isFinite(credits) || credits < 0) return; const savedCents = user?.autoSellThresholdCents ?? 1000; if (Math.round(credits * 100) === savedCents && autoSellEnabled === Boolean(user?.autoSellEnabled)) { return; } await saveAutoSell(autoSellEnabled, credits); }; if (loading) return

Loading…

; if (!user) return ; if (user.role === 'admin') return ; return (

Inventory

Select items to sell at market value.

{inventory.length > 0 && ( {inventory.length} item{inventory.length === 1 ? '' : 's'} )}
{selected.length > 0 && ( <> {selected.length} selected · {formatCredits(selectedValue)} cr {selectedHasFavorite ? ' · includes favorites' : ''} {!selectedAllFavorite && ( )} {selectedItems.some((inv) => inv.favorite) && ( )} )}
{inventory.length > 0 && ( <> {belowThreshold.length} item{belowThreshold.length === 1 ? '' : 's'} ·{' '} {formatCredits(belowValue)} cr )} {inventory.length === 0 && ( )}
at{' '} {formatCredits( Number.isFinite(Number(maxValue)) && Number(maxValue) >= 0 ? Math.round(Number(maxValue) * 100) : 0 )}{' '} cr
{error &&
{error}
} {inventory.length === 0 ? (
Empty for now —{' '} open a case .
) : ( <>
{pageItems.map((inv) => ( toggle(inv.id)} /> ))}
{totalPages > 1 && (
Page {currentPage} / {totalPages}
)} )}
); }