71 lines
4.3 KiB
TypeScript
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>;
|
|
}
|