Files
buzz-sheet/components/public/weapon-image-export.tsx
T
2026-09-07 22:05:35 +07:00

157 lines
5.4 KiB
TypeScript

"use client";
import Image from "next/image";
import { DownloadIcon } from "lucide-react";
import { GuideExportBranding } from "@/components/public/guide-export-branding";
import { GuideImageExport } from "@/components/public/guide-image-export";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import type { PublicGuide } from "@/lib/guides/queries";
type WeaponGroup = PublicGuide["weaponGroups"][number];
type DamageMetric = "overallPercent" | "personalPercent";
function formattedPercent(value: string | null): string {
if (value === null) return "-%";
const numeric = Number(value);
if (numeric === 0) return "-%";
return `${numeric.toLocaleString(undefined, { maximumFractionDigits: 2 })}%`;
}
function WeaponComparisonImage({
guide,
group,
metric,
conditionLabel,
}: {
guide: PublicGuide;
group: WeaponGroup;
metric: DamageMetric;
conditionLabel: string;
}) {
const values = group.rows.map((row) => Number(row[metric]) || 0);
const maximum = Math.max(100, ...values);
return (
<div className="relative isolate w-300 overflow-hidden bg-[#070b20] px-14 pt-10 pb-12 font-heading text-white">
<GuideExportBranding backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl} />
<header className="relative z-10 flex flex-col items-center px-48 text-center drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
<h2 className="max-w-full text-5xl leading-tight font-bold text-balance">
Weapons {guide.character.name}
</h2>
<p className="mt-1 text-4xl font-bold text-[#f1badd]">
{metric === "overallPercent" ? "Overall Damage" : "Personal Damage"}
</p>
<p className="mt-2 text-2xl font-semibold">{conditionLabel}</p>
</header>
<div className="relative z-10 mt-9 flex flex-col gap-3">
{group.rows.map((row) => {
const numeric = Number(row[metric]) || 0;
const width = row[metric] === null
? 28
: numeric <= 100
? Math.max(28, Math.max(0, numeric) * 0.5)
: Math.min(90, 50 + ((numeric - 100) / Math.max(1, maximum - 100)) * 40);
return (
<div key={row.id} className="grid min-h-22 grid-cols-[76px_1fr] items-center gap-3">
<div className="relative size-18">
<Image
src={row.weapon.imageUrl}
alt=""
fill
loading="eager"
sizes="72px"
className="object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
/>
</div>
<div className="flex min-w-0 items-center">
<div
className="flex min-h-16 shrink-0 items-center rounded-r-xl bg-[#ead1e4] px-5 text-[#15121a]"
style={{ width: `${width}%` }}
>
<span className="truncate text-[28px] font-bold">
{row.weapon.name} R{row.refinement}
</span>
</div>
<div className="ml-3 flex min-w-0 flex-1 flex-col items-start gap-1 text-[#f1badd] drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
<strong className="shrink-0 text-[30px]">{formattedPercent(row[metric])}</strong>
{row.note ? <span className="min-w-0 text-xl leading-tight">{row.note}</span> : null}
</div>
</div>
</div>
);
})}
</div>
{group.note ? (
<p className="relative z-10 mt-8 whitespace-pre-wrap text-center text-2xl font-semibold drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
{group.note}
</p>
) : null}
</div>
);
}
export function WeaponImageExport({
guide,
group,
conditionIndex,
}: {
guide: PublicGuide;
group: WeaponGroup;
conditionIndex: number;
}) {
const conditionLabel = group.name || `Condition ${conditionIndex + 1}`;
const baseName = `${guide.name}-Weapon-${conditionLabel}`;
const metricOptions = ([
["overallPercent", "Overall Damage"],
["personalPercent", "Personal Damage"],
] as const).filter(([metric]) =>
group.rows.some((row) => row[metric] !== null),
);
return (
<Dialog>
<DialogTrigger render={<Button type="button" variant="outline" size="sm" />}>
<DownloadIcon data-icon="inline-start" />
ส่งออกภาพ
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>เลือกประเภทดาเมจ</DialogTitle>
<DialogDescription>
เลือกค่าที่ต้องการใช้สร้างภาพเปรียบเทียบอาวุธ
</DialogDescription>
</DialogHeader>
<div className="grid gap-2 sm:grid-cols-2">
{metricOptions.map(([metric, label]) => (
<GuideImageExport
key={metric}
fileName={`${baseName}-${label}.png`}
width={1200}
captureClassName="p-0"
buttonLabel={label}
>
<WeaponComparisonImage
guide={guide}
group={group}
metric={metric}
conditionLabel={conditionLabel}
/>
</GuideImageExport>
))}
</div>
</DialogContent>
</Dialog>
);
}