Files
buzz-sheet/components/public/weapon-chart.tsx
T
gunshiz bec24ad00b
CI / Verify (push) Successful in 2m34s
CI / Build immutable images and deploy (push) Successful in 4m1s
feat : dd up up
2026-10-07 18:15:27 +07:00

83 lines
4.8 KiB
TypeScript

"use client";
import { Bar, BarChart, CartesianGrid, LabelList, ReferenceLine, XAxis, YAxis } from "recharts";
import Image from "@/components/ui/resilient-image";
import { Badge } from "@/components/ui/badge";
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}`,
weapon: row.weapon,
refinement: 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: 420 }}
aria-label="กราฟเปรียบเทียบเปอร์เซ็นต์ดาเมจอาวุธ">
<BarChart accessibilityLayer data={data} layout="horizontal" barGap={4} margin={{ top: 28, right: 12, left: 0 }}>
<CartesianGrid vertical={false} />
<YAxis type="number" tickLine={false} axisLine={false}
domain={[(minimum: number) => Math.min(0, minimum), (maximum: number) => Math.max(110, maximum)]}
tickFormatter={(value) => `${value}%`} />
<XAxis dataKey="name" type="category" tickLine={false} axisLine={false} height={96} interval={0}
tick={({ x, y, width, payload }) => {
const row = data.find((item) => item.name === payload.value);
if (!row) return <g />;
const scale = Math.min(1, Math.max(0, Number(width) / data.length - 4) / 56);
return <g transform={`translate(${x},${Number(y) + 8}) scale(${scale})`}>
<foreignObject x={-28} y={0} width={56} height={80}>
<div className="flex flex-col items-center gap-1" title={row.name}>
<Image src={row.weapon.imageUrl} alt={row.weapon.name} width={56} height={56} className="size-14 object-contain" />
<Badge variant="secondary">R{row.refinement}</Badge>
</div>
</foreignObject>
</g>;
}} />
<ReferenceLine y={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 ? 48 : 40} isAnimationActive={false}>
<LabelList position="top" 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 ? 48 : 40} isAnimationActive={false}>
<LabelList position="top" formatter={(value) => typeof value === "number" ? `${value.toLocaleString("th-TH", { maximumFractionDigits: 2 })}%` : ""} className="fill-foreground text-xs" />
</Bar>}
</BarChart>
</ChartContainer>
</CardContent>
</Card>;
}