diff --git a/app/thanks/page.tsx b/app/thanks/page.tsx index ab99a2f..1fb89e5 100644 --- a/app/thanks/page.tsx +++ b/app/thanks/page.tsx @@ -113,7 +113,7 @@ export default function ThanksPage() {

- ขอบคุณที่สนับสนุนระบบ Hosting ให้ Buzz Guide ออนไลน์และพร้อมใช้งาน + ขอบคุณที่สนับสนุนระบบ Hosting ที่สามารถให้เว็บออนไลน์และพร้อมใช้งาน

@@ -151,7 +151,7 @@ export default function ThanksPage() {

- ขอบคุณที่เป็นส่วนสำคัญของ Buzz Guide + ขอบคุณที่เป็นส่วนสำคัญของเว็บไซด์นี้

diff --git a/components/public/structured-guide.tsx b/components/public/structured-guide.tsx index cf41530..4a17e49 100644 --- a/components/public/structured-guide.tsx +++ b/components/public/structured-guide.tsx @@ -4,7 +4,6 @@ import { ArrowUpRightIcon, ChevronLeftIcon } from "lucide-react"; import type { ReactNode } from "react"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { Card, CardAction, @@ -28,6 +27,8 @@ import { ScrollableNav } from "@/components/public/scrollable-nav"; import { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search"; import { GuideImageExport } from "@/components/public/guide-image-export"; import { WeaponImageExport } from "@/components/public/weapon-image-export"; +import { WeaponChart } from "@/components/public/weapon-chart"; +import { WeaponDisplayMode, WeaponDisplayModeProvider } from "@/components/public/weapon-display-mode"; import { ArtifactImageExport } from "@/components/public/artifact-image-export"; import { GuideExportBranding } from "@/components/public/guide-export-branding"; import { ConstellationImage } from "@/components/public/constellation-image"; @@ -277,10 +278,6 @@ function GuideOverview({ className="size-full" /> - ))} @@ -438,6 +435,7 @@ function WeaponSectionContent({ ) : null} + group.name || `Condition ${index + 1}`)} @@ -459,6 +457,7 @@ function WeaponSectionContent({ ) : null} {group.note ?

{group.note}

: null} + : null}>
@@ -598,9 +597,11 @@ function WeaponSectionContent({ ))}
+
))}
+
); } diff --git a/components/public/weapon-chart.tsx b/components/public/weapon-chart.tsx new file mode 100644 index 0000000..735ba64 --- /dev/null +++ b/components/public/weapon-chart.tsx @@ -0,0 +1,103 @@ +"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 ( + + + เปรียบเทียบดาเมจอาวุธ + เปอร์เซ็นต์ดาเมจของอาวุธทั้งหมดใน Condition นี้ + + + + + + `${value}%`} /> + { + const weapon = data.find((row) => row.name === payload.value); + if (!weapon) return ; + const size = Math.max(8, Math.min(40, Number(width) / data.length - 4 || 32)); + return ( + + {weapon.name} + + + ); + }} + /> + ( +
+ {chartConfig[name as keyof typeof chartConfig]?.label ?? name} + {value}% +
+ )} /> + } /> + } /> + {hasPersonal ? : null} + {hasOverall ? : null} +
+
+
+
+ ); +} diff --git a/components/public/weapon-display-mode.tsx b/components/public/weapon-display-mode.tsx new file mode 100644 index 0000000..9330023 --- /dev/null +++ b/components/public/weapon-display-mode.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { createContext, useContext, useState, type ReactNode } from "react"; + +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; + +const WeaponModeContext = createContext<{ + mode: string; + setMode: (mode: string) => void; +} | null>(null); + +export function WeaponDisplayModeProvider({ children }: { children: ReactNode }) { + const [mode, setMode] = useState("normal"); + + return ( + + {children} + + ); +} + +export function WeaponDisplayMode({ + children, + chart, + exporting = false, +}: { + children: ReactNode; + chart: ReactNode; + exporting?: boolean; +}) { + const context = useContext(WeaponModeContext); + + if (!context) throw new Error("WeaponDisplayMode requires WeaponDisplayModeProvider"); + const { mode, setMode } = context; + + if (exporting) return children; + + return ( +
+ { + if (values[0]) setMode(values[0]); + }} + > + List + Chart + + {mode === "chart" ? chart : children} +
+ ); +}