Files
buzz-sheet/components/public/weapon-chart.tsx
T
gunshiz c48b1a962d
CI / Verify (push) Successful in 1m40s
CI / Build immutable images and deploy (push) Successful in 4m46s
feat ; adding chart viewing type to weapon
2026-09-13 17:12:23 +07:00

104 lines
4.3 KiB
TypeScript

"use client";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart";
import type { PublicGuide } from "@/lib/guides/queries";
const chartConfig = {
overall: { label: "Overall · ดาเมจทั้งทีม", color: "var(--chart-1)" },
personal: { label: "Personal · ดาเมจส่วนตัว", color: "var(--chart-2)" },
} satisfies ChartConfig;
function percentage(value: string | null) {
if (!value?.trim()) return null;
const number = Number(value);
// Zero is displayed as an unavailable metric in the weapon cards.
return Number.isFinite(number) && number !== 0 ? number : null;
}
export function WeaponChart({ rows }: { rows: PublicGuide["weaponGroups"][number]["rows"] }) {
const data = rows.map((row, index) => ({
name: `${index + 1}. ${row.weapon.name} R${row.refinement}`,
imageUrl: row.weapon.imageUrl,
overall: percentage(row.overallPercent),
personal: percentage(row.personalPercent),
}));
const hasOverall = data.some((row) => row.overall !== null);
const hasPersonal = data.some((row) => row.personal !== null);
const singleMetric = !hasOverall || !hasPersonal;
if (!hasOverall && !hasPersonal) return null;
return (
<Card className="min-w-0" size={singleMetric ? "sm" : "default"}>
<CardHeader>
<CardTitle>เปรียบเทียบดาเมจอาวุธ</CardTitle>
<CardDescription>เปอร์เซ็นต์ดาเมจของอาวุธทั้งหมดใน Condition นี้</CardDescription>
</CardHeader>
<CardContent className="min-w-0">
<ChartContainer
config={chartConfig}
className="min-w-0 aspect-auto w-full"
style={{
height: singleMetric ? 300 : 400,
}}
aria-label="กราฟเปรียบเทียบเปอร์เซ็นต์ดาเมจอาวุธ"
>
<BarChart accessibilityLayer data={data} margin={{ right: 16 }}>
<CartesianGrid vertical={false} />
<YAxis type="number" tickLine={false} axisLine={false} tickFormatter={(value) => `${value}%`} />
<XAxis
dataKey="name"
type="category"
height={60}
tickLine={false}
axisLine={false}
interval={0}
tick={({ x, y, payload, width }) => {
const weapon = data.find((row) => row.name === payload.value);
if (!weapon) return <g />;
const size = Math.max(8, Math.min(40, Number(width) / data.length - 4 || 32));
return (
<g transform={`translate(${x},${y})`}>
<title>{weapon.name}</title>
<image
href={weapon.imageUrl}
x={-size / 2}
y={4}
width={size}
height={size}
preserveAspectRatio="xMidYMid meet"
role="img"
aria-label={weapon.name}
/>
</g>
);
}}
/>
<ChartTooltip content={
<ChartTooltipContent formatter={(value, name) => (
<div className="flex flex-1 items-center justify-between gap-4">
<span className="text-muted-foreground">{chartConfig[name as keyof typeof chartConfig]?.label ?? name}</span>
<span className="font-mono font-medium">{value}%</span>
</div>
)} />
} />
<ChartLegend content={<ChartLegendContent className="flex-wrap" />} />
{hasPersonal ? <Bar dataKey="personal" fill="var(--color-personal)" radius={4} maxBarSize={32} /> : null}
{hasOverall ? <Bar dataKey="overall" fill="var(--color-overall)" radius={4} maxBarSize={32} /> : null}
</BarChart>
</ChartContainer>
</CardContent>
</Card>
);
}