feat: donut allocation chart + History/Allocation tabs on Crypto tab

Merges the separate history chart and allocation cards into one
CryptoOverviewCard with History/Allocation tab pills, matching CoinMarketCap's
layout — switching views instead of stacking two cards keeps the tab's
footprint on the page instead of doubling it. The total value, eye toggle,
and change-over-range line stay in the shared header regardless of which
tab is active.

Allocation is now a donut (Recharts Pie, innerRadius 68%, paddingAngle gap
between segments per the dataviz skill's mark spec) instead of a plain bar.
Unlike CoinMarketCap's own version, the list next to it shows both percent
and the $ amount per coin, per explicit request — CMC's reference only
shows percent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
This commit is contained in:
ZProger
2026-08-30 04:47:13 +03:00
parent 5f34d0a612
commit 7a23ed1ff8
7 changed files with 338 additions and 250 deletions

View File

@@ -3,9 +3,8 @@ import { Plus, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/Button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
import { CryptoAddModal } from "@/components/crypto/CryptoAddModal";
import { CryptoAllocationCard } from "@/components/crypto/CryptoAllocationCard";
import { CryptoHistoryChart } from "@/components/crypto/CryptoHistoryChart";
import { CryptoHoldingsTable } from "@/components/crypto/CryptoHoldingsTable";
import { CryptoOverviewCard } from "@/components/crypto/CryptoOverviewCard";
import { CryptoStatsRow } from "@/components/crypto/CryptoStatsRow";
import { CryptoTransactionHistoryModal } from "@/components/crypto/CryptoTransactionHistoryModal";
import { CryptoTransactionModal } from "@/components/crypto/CryptoTransactionModal";
@@ -36,6 +35,7 @@ export function CryptoPage() {
const [historyHolding, setHistoryHolding] = useState<CryptoHolding | null>(null);
const holdings = data?.holdings ?? [];
const totalValue = holdings.reduce((sum, holding) => sum + (holding.value !== null ? Number(holding.value) : 0), 0);
function handleDelete(holding: CryptoHolding) {
if (window.confirm(t("crypto.confirmDelete", { name: holding.name }))) {
@@ -45,19 +45,20 @@ export function CryptoPage() {
return (
<div className="space-y-5">
<CryptoHistoryChart
<CryptoOverviewCard
totalValue={totalValue}
history={history}
isLoading={isHistoryLoading}
isHistoryLoading={isHistoryLoading}
range={range}
onRangeChange={setRange}
holdings={holdings}
isHoldingsLoading={isLoading}
hidden={hidden}
onToggleHidden={() => setHidden((prev) => !prev)}
/>
<CryptoStatsRow holdings={holdings} isLoading={isLoading} hidden={hidden} />
<CryptoAllocationCard holdings={holdings} isLoading={isLoading} hidden={hidden} />
<Card>
<CardHeader className="flex-col items-stretch gap-3 sm:flex-row sm:items-center">
<div>