Files
buzz-sheet/components/public/weapon-chart.tsx
T
2026-10-07 17:03:15 +07:00

71 lines
4.3 KiB
TypeScript

"use client";
import { Bar, BarChart, CartesianGrid, LabelList, ReferenceLine, 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: "ดาเมจทั้งทีม", color: "var(--chart-1)" },
personal: { label: "ดาเมจส่วนตัว", color: "var(--chart-2)" },
} satisfies ChartConfig;
function percentage(value: string | null) {
if (!value?.trim()) return null;
const number = Number(value);
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}`,
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 <p className="text-sm text-muted-foreground">ไม่มีข้อมูลเปรียบเทียบสำหรับกราฟนี้</p>;
return <Card className="min-w-0" size="sm">
<CardHeader>
<CardTitle>เปรียบเทียบดาเมจอาวุธ</CardTitle>
<CardDescription>เปอร์เซ็นต์ดาเมจในเงื่อนไขนี้ - เส้น 100% คือค่าฐาน</CardDescription>
</CardHeader>
<CardContent className="min-w-0">
<ChartContainer config={chartConfig} className="aspect-auto min-w-0 w-full"
style={{ height: Math.max(240, data.length * 72 + 80) }}
aria-label="กราฟเปรียบเทียบเปอร์เซ็นต์ดาเมจอาวุธ">
<BarChart accessibilityLayer data={data} layout="vertical" margin={{ right: 42, left: 0 }}>
<CartesianGrid horizontal={false} />
<XAxis type="number" tickLine={false} axisLine={false}
domain={[(minimum: number) => Math.min(0, minimum), (maximum: number) => Math.max(110, maximum)]}
tickFormatter={(value) => `${value}%`} />
<YAxis dataKey="name" type="category" tickLine={false} axisLine={false} width={142} interval={0}
tick={({ x, y, payload }) => <g transform={`translate(${x},${y})`}>
<foreignObject x={-140} y={-32} width={132} height={64}>
<div className="flex h-full items-center justify-end text-right text-xs leading-4 wrap-anywhere text-foreground">{payload.value}</div>
</foreignObject>
</g>} />
<ReferenceLine x={100} stroke="var(--muted-foreground)" strokeDasharray="4 4" />
<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={singleMetric ? 26 : 18} isAnimationActive={false}>
<LabelList position="right" formatter={(value) => typeof value === "number" ? `${value.toLocaleString("th-TH", { maximumFractionDigits: 2 })}%` : ""} className="fill-foreground text-xs" />
</Bar>}
{hasOverall && <Bar dataKey="overall" fill="var(--color-overall)" radius={4} maxBarSize={singleMetric ? 26 : 18} isAnimationActive={false}>
<LabelList position="right" formatter={(value) => typeof value === "number" ? `${value.toLocaleString("th-TH", { maximumFractionDigits: 2 })}%` : ""} className="fill-foreground text-xs" />
</Bar>}
</BarChart>
</ChartContainer>
</CardContent>
</Card>;
}