diff --git a/src/routes/cost-basis.tsx b/src/routes/cost-basis.tsx index 340d6da..8e898af 100644 --- a/src/routes/cost-basis.tsx +++ b/src/routes/cost-basis.tsx @@ -1,7 +1,10 @@ import { useQuery } from "@tanstack/react-query"; import { createFileRoute, Link } from "@tanstack/react-router"; import { + ArrowDown, ArrowRightLeft, + ArrowUp, + ArrowUpDown, Check, ExternalLink, Loader2, @@ -33,6 +36,8 @@ export const Route = createFileRoute("/cost-basis")({ }); type InputMode = "per-token" | "total"; +type SortKey = "token" | "costBasis" | "price" | "change"; +type SortDirection = "asc" | "desc"; function useDebouncedValue(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); @@ -63,6 +68,10 @@ function CostBasisPage() { const [savedCostBasis, setSavedCostBasis] = useState([]); const [confirmingDelete, setConfirmingDelete] = useState(null); + // Sort state + const [sortKey, setSortKey] = useState("token"); + const [sortDir, setSortDir] = useState("asc"); + // Edit state management const [editingToken, setEditingToken] = useState(null); const [editInputMode, setEditInputMode] = useState("per-token"); @@ -117,6 +126,66 @@ function CostBasisPage() { refetchInterval: 60 * 1000, }); + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortDir((prev) => (prev === "asc" ? "desc" : "asc")); + } else { + setSortKey(key); + setSortDir("asc"); + } + }; + + const sortIcon = (key: SortKey) => + sortKey === key ? ( + sortDir === "asc" ? ( + + ) : ( + + ) + ) : ( + + ); + + const sortedEntries = useMemo(() => { + return [...savedCostBasis].sort((a, b) => { + let comparison = 0; + + switch (sortKey) { + case "token": + comparison = a.tokenName.localeCompare(b.tokenName); + break; + case "costBasis": + comparison = a.costBasisUSD - b.costBasisUSD; + break; + case "price": { + const priceA = tokenPriceMap?.get(a.tokenAddress); + const priceB = tokenPriceMap?.get(b.tokenAddress); + if (priceA === undefined && priceB === undefined) return 0; + if (priceA === undefined) return 1; + if (priceB === undefined) return -1; + comparison = priceA - priceB; + break; + } + case "change": { + const getChange = (entry: StoredCostBasis) => { + const price = tokenPriceMap?.get(entry.tokenAddress); + if (price === undefined || entry.costBasisUSD === 0) return null; + return ((price - entry.costBasisUSD) / entry.costBasisUSD) * 100; + }; + const changeA = getChange(a); + const changeB = getChange(b); + if (changeA === null && changeB === null) return 0; + if (changeA === null) return 1; + if (changeB === null) return -1; + comparison = changeA - changeB; + break; + } + } + + return sortDir === "asc" ? comparison : -comparison; + }); + }, [savedCostBasis, sortKey, sortDir, tokenPriceMap]); + // Focus search input after saving (when selectedToken becomes null and flag is set) useEffect(() => { if (shouldFocusSearch && !selectedToken) { @@ -539,17 +608,45 @@ function CostBasisPage() { - - - - - {savedCostBasis.map((entry) => { + {sortedEntries.map((entry) => { const isEditing = editingToken === entry.tokenAddress; const currentPrice = tokenPriceMap?.get(entry.tokenAddress); const percentChange =
- Token + + - Cost Basis + + - Current Price + + - Change + + Actions @@ -557,7 +654,7 @@ function CostBasisPage() {