Files
buzz-sheet/components/commission/request-summary.tsx
T

134 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ArrowUpRightIcon } from "lucide-react";
import type { ReactNode } from "react";
import { Badge } from "@/components/ui/badge";
import { CatalogItemImage, type CatalogItemKind } from "@/components/catalog-item-image";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Kbd } from "@/components/ui/kbd";
import { elementBackgroundColor } from "@/lib/catalog/element";
import { hasCustomStats, type CommissionRequest } from "@/lib/commission/request";
type Item = { key: string; name: string; imageUrl: string; rarity?: number | null; element?: string | null };
type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
function ItemImage({ item, size = "small", kind = "weapon" }: { item: Item; size?: "small" | "large"; kind?: CatalogItemKind }) {
return <CatalogItemImage item={item} kind={kind} alt={item.name} title={item.name} className="aspect-square min-w-0 rounded-lg" sizes={size === "large" ? "160px" : "64px"} />;
}
function LunarisItemLink({ item, kind, children }: {
item: Item; kind: "weapon" | "artifact"; children?: ReactNode;
}) {
return <a href={`https://lunaris.moe/${kind}/${encodeURIComponent(item.key)}`}
target="_blank" rel="noreferrer"
aria-label={`เปิดข้อมูล${kind === "weapon" ? "อาวุธ" : "อาร์ติแฟกต์"} ${item.name} บน Lunaris`}
className="block min-w-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring">
<div className="relative"><ItemImage item={item} kind={kind} />
<Kbd className="absolute top-0 right-0 h-3 min-w-3 bg-transparent! p-0 text-white">
<ArrowUpRightIcon aria-hidden="true" />
</Kbd>
</div>
{children}
</a>;
}
function CommissionTeamCard({ team, index, catalog }: { team: CommissionRequest["teams"][number]; index: number; catalog: Catalog }) {
const characters = new Map(catalog.characters.map((item) => [item.key, item]));
const weapons = new Map(catalog.weapons.map((item) => [item.key, item]));
const artifacts = new Map(catalog.artifacts.map((item) => [item.key, item]));
return <div className="@container"><Card size="sm">
<CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle>ทีม {index + 1}</CardTitle><Badge variant="secondary">฿100</Badge>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-2 @min-[560px]:grid-cols-4">
{team.members.map((member, memberIndex) => {
const character = characters.get(member.characterKey);
const weapon = weapons.get(member.weaponKey);
const artifact = artifacts.get(member.artifactKey);
return <div key={memberIndex} className="min-w-0 rounded-lg p-1 lg:p-1.5" style={{ backgroundColor: elementBackgroundColor(character?.element) }}>
<div className="grid grid-cols-1 gap-1 lg:grid-cols-[2fr_1fr]">
{character ? <ItemImage item={character} size="large" kind="character" /> : <div className="aspect-square rounded-lg bg-muted" />}
<div className="grid min-w-0 grid-cols-2 gap-0.5 lg:grid-cols-1 lg:grid-rows-2 lg:gap-1">
{weapon ? <LunarisItemLink item={weapon} kind="weapon" /> : <div title={member.weaponKey} className="aspect-square rounded-lg bg-muted" />}
{artifact ? <LunarisItemLink item={artifact} kind="artifact" /> : <div title={member.artifactKey} className="aspect-square rounded-lg bg-muted" />}
</div>
</div>
<div className="mt-1 flex flex-wrap items-center justify-center gap-0.5 lg:flex-nowrap lg:gap-1">
<Badge variant="secondary" className="h-4 px-0.5 text-[9px] lg:h-auto lg:px-2 lg:text-xs">C{member.constellation ?? "0"}</Badge>
<Badge variant="secondary" className="h-4 px-0.5 text-[9px] lg:h-auto lg:px-2 lg:text-xs">R{member.refinement ?? "1"}</Badge>
</div>
<p className="mt-1 truncate text-center text-[10px] font-medium lg:text-xs" title={character?.name}>{character?.name ?? member.characterKey}</p>
{member.customStats !== undefined && <div className="mt-2 flex flex-col gap-1">
<Badge variant="secondary">Custom stats</Badge>
<p className="whitespace-pre-wrap wrap-break-word text-xs">{member.customStats}</p>
</div>}
</div>;
})}
</CardContent>
</Card></div>;
}
export function CommissionRequestSummary({ request, catalog, amountBaht }: {
request: CommissionRequest; catalog: Catalog; amountBaht: number;
}) {
const characters = new Map(catalog.characters.map((item) => [item.key, item]));
const weapons = new Map(catalog.weapons.map((item) => [item.key, item]));
const comparisonTotal = request.weapons.reduce((sum, set) => sum + set.weaponKeys.length * 20, 0) +
request.constellations.reduce((sum, set) => sum + set.levels.length * 20, 0);
return <div className="@container flex flex-col gap-4">
{hasCustomStats(request) && <Card size="sm">
<CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle>กำหนดค่าสเตตัสเอง (Custom stats)</CardTitle><Badge variant="secondary">ราคาทั้งหมด ×2</Badge>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">เพิ่มจากยอดรวมปกติ ฿{amountBaht / 2} รวมทั้งหมด ฿{amountBaht}</p>
</CardContent>
</Card>}
{request.teams.length === 0 && amountBaht > comparisonTotal ?
<Card size="sm"><CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle>ค่าบริการพื้นฐาน</CardTitle><Badge variant="secondary">฿100</Badge>
</CardHeader></Card> : null}
{request.teams.map((team, index) => <CommissionTeamCard key={`team-${index}`} team={team} index={index} catalog={catalog} />)}
{request.weapons.map((set, index) => {
const character = characters.get(set.characterKey);
return <Card key={`weapon-${index}`} size="sm">
<CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle>เปรียบเทียบอาวุธ {index + 1}</CardTitle><Badge variant="secondary">฿{set.weaponKeys.length * 20}</Badge>
</CardHeader>
<CardContent className="grid grid-cols-[minmax(0,1fr)_64px] items-start gap-3">
<div className="flex min-w-0 flex-wrap gap-2">
{set.weaponKeys.map((key) => {
const weapon = weapons.get(key);
return weapon ? <div key={key} className="w-16 shrink-0"><LunarisItemLink item={weapon} kind="weapon">
<div className="mt-1 flex justify-center"><Badge variant="secondary">R{set.refinements?.[key] ?? "1"}</Badge></div>
<p className="mt-1 truncate text-center text-xs" title={weapon.name}>{weapon.name}</p>
</LunarisItemLink></div>
: <p key={key} className="w-16 wrap-break-word text-xs">{key}</p>;
})}
</div>
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" kind="character" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
{set.customStats !== undefined && <div className="col-span-2 flex flex-col gap-1">
<Badge variant="secondary">Custom stats ของ {character?.name ?? set.characterKey}</Badge>
<p className="whitespace-pre-wrap wrap-break-word text-sm">{set.customStats}</p>
</div>}
</CardContent>
</Card>;
})}
{request.constellations.map((set, index) => {
const character = characters.get(set.characterKey);
return <Card key={`constellation-${index}`} size="sm">
<CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle>เปรียบเทียบกลุ่มดาว {index + 1}</CardTitle><Badge variant="secondary">฿{set.levels.length * 20}</Badge>
</CardHeader>
<CardContent className="grid grid-cols-[minmax(0,1fr)_64px] items-center gap-3">
<div className="flex min-w-0 flex-wrap gap-2">{set.levels.map((level) => <Badge key={level} variant="outline">C{level}</Badge>)}</div>
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" kind="character" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
{set.customStats !== undefined && <div className="col-span-2 flex flex-col gap-1">
<Badge variant="secondary">Custom stats ของ {character?.name ?? set.characterKey}</Badge>
<p className="whitespace-pre-wrap wrap-break-word text-sm">{set.customStats}</p>
</div>}
</CardContent>
</Card>;
})}
</div>;
}