224 lines
10 KiB
TypeScript
224 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { DownloadIcon, SearchIcon } from "lucide-react";
|
|
import { useMemo, useState } from "react";
|
|
|
|
import { buttonVariants } from "@/components/ui/button";
|
|
import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Kbd } from "@/components/ui/kbd";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { elementBackgroundColor } from "@/lib/catalog/element";
|
|
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
|
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
|
import type { PublicGuide } from "@/lib/guides/queries";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
|
const [query, setQuery] = useState("");
|
|
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
const filteredTeams = useMemo(
|
|
() => teams.filter((team) => {
|
|
if (!normalizedQuery) return true;
|
|
const searchableNames = [
|
|
team.title,
|
|
...team.members.flatMap((member) => [
|
|
member.character.name,
|
|
member.weapon?.name,
|
|
member.artifact?.name,
|
|
member.secondArtifact?.name,
|
|
]),
|
|
].filter((name): name is string => Boolean(name));
|
|
return searchableNames.some((name) => name.toLocaleLowerCase().includes(normalizedQuery));
|
|
}),
|
|
[normalizedQuery, teams],
|
|
);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="relative">
|
|
<SearchIcon aria-hidden="true" className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
<Input
|
|
aria-label="ค้นหาทีม ตัวละคร อาวุธ หรืออาร์ติแฟกต์"
|
|
className="pl-9"
|
|
placeholder="ค้นหาทีม ตัวละคร อาวุธ หรืออาร์ติแฟกต์"
|
|
value={query}
|
|
onValueChange={setQuery}
|
|
/>
|
|
</div>
|
|
{filteredTeams.length ? (
|
|
<div className="grid gap-5 xl:grid-cols-2">
|
|
{filteredTeams.map((team) => {
|
|
const total = team.members.reduce(
|
|
(sum, member) => sum + Number(member.damage),
|
|
0,
|
|
);
|
|
const seconds = Number(team.rotationSeconds);
|
|
return (
|
|
<Card key={team.id}>
|
|
<CardHeader>
|
|
<CardTitle>{team.title}</CardTitle>
|
|
<CardDescription>โรเทชั่น {seconds} วิ</CardDescription>
|
|
<CardAction>
|
|
<a
|
|
href={`/api/teams/${team.id}/image`}
|
|
download
|
|
className={buttonVariants({
|
|
variant: "outline",
|
|
size: "sm",
|
|
})}
|
|
>
|
|
<DownloadIcon data-icon="inline-start" />
|
|
ส่งออกภาพ
|
|
</a>
|
|
</CardAction>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
<div className="grid grid-cols-4 gap-1 sm:gap-3">
|
|
{team.members.map((member) => (
|
|
<div
|
|
key={member.position}
|
|
className="min-w-0 rounded-lg p-1 text-center sm:p-2"
|
|
style={{ backgroundColor: elementBackgroundColor(member.character.element) }}
|
|
>
|
|
<div className="flex flex-col gap-2 sm:grid sm:grid-cols-3">
|
|
<div
|
|
className={cn(
|
|
"relative aspect-square min-h-0 overflow-hidden rounded-lg bg-muted sm:col-span-2 sm:row-span-2",
|
|
rarityGradientClass(isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity),
|
|
)}
|
|
>
|
|
<Image
|
|
src={member.character.imageUrl}
|
|
alt={member.character.name}
|
|
fill
|
|
className="object-contain"
|
|
/>
|
|
<Kbd className="absolute top-0 left-0 bg-transparent text-white">
|
|
C{member.constellation}
|
|
</Kbd>
|
|
</div>
|
|
<div className="grid min-w-0 grid-cols-2 gap-2 sm:col-span-1 sm:row-span-2 sm:flex sm:flex-col">
|
|
{member.weapon ? (
|
|
<a
|
|
href={`https://lunaris.moe/weapon/${encodeURIComponent(member.weapon.key)}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
aria-label={`เปิดข้อมูลอาวุธ ${member.weapon.name} บน Lunaris`}
|
|
className={cn(
|
|
"relative aspect-square overflow-hidden rounded-md bg-muted outline-none",
|
|
rarityGradientClass(member.weapon.rarity),
|
|
)}
|
|
>
|
|
<Image
|
|
src={member.weapon.imageUrl}
|
|
alt={member.weapon.name}
|
|
fill
|
|
className="object-contain p-1"
|
|
/>
|
|
<Kbd className="absolute top-0 left-0 h-4 min-w-4 whitespace-nowrap bg-transparent px-0.5 text-[9px] text-white">
|
|
R{member.refinement}
|
|
</Kbd>
|
|
</a>
|
|
) : (
|
|
<div className="aspect-square rounded-md border bg-muted" />
|
|
)}
|
|
{member.artifact ? (
|
|
<Link
|
|
href={`https://lunaris.moe/artifact/${encodeURIComponent(member.artifact.key)}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
aria-label={`เปิดข้อมูลอาร์ติแฟกต์ ${member.artifact.name} บน Lunaris`}
|
|
className={cn(
|
|
"relative aspect-square overflow-hidden rounded-md bg-muted",
|
|
rarityGradientClass(member.artifact.rarity),
|
|
)}
|
|
>
|
|
<Image
|
|
src={member.artifact.imageUrl}
|
|
alt={member.artifact.name}
|
|
fill
|
|
className="object-contain p-1"
|
|
/>
|
|
</Link>
|
|
) : (
|
|
<div className="aspect-square rounded-md border bg-muted" />
|
|
)}
|
|
{member.secondArtifact ? (
|
|
<Link
|
|
href={`https://lunaris.moe/artifact/${encodeURIComponent(member.secondArtifact.key)}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
aria-label={`เปิดข้อมูลอาร์ติแฟกต์ ${member.secondArtifact.name} บน Lunaris`}
|
|
className={cn(
|
|
"relative aspect-square overflow-hidden rounded-md bg-muted",
|
|
rarityGradientClass(member.secondArtifact.rarity),
|
|
)}
|
|
>
|
|
<Image
|
|
src={member.secondArtifact.imageUrl}
|
|
alt={member.secondArtifact.name}
|
|
fill
|
|
className="object-contain p-1"
|
|
/>
|
|
</Link>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 truncate text-sm font-medium">
|
|
{member.character.name}
|
|
</p>
|
|
<p className="text-sm font-semibold">
|
|
{Number(member.damage).toLocaleString(undefined, {
|
|
maximumFractionDigits: 2,
|
|
})}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{total
|
|
? ((Number(member.damage) / total) * 100).toFixed(2)
|
|
: "0.00"}
|
|
%
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<Separator />
|
|
<div className="flex items-end justify-between">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">
|
|
ดาเมจรวม
|
|
</p>
|
|
<p className="font-semibold">{total.toLocaleString()}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-sm text-muted-foreground">DPS</p>
|
|
<p className="text-3xl font-semibold text-primary">
|
|
{seconds > 0
|
|
? (total / seconds).toLocaleString(undefined, {
|
|
maximumFractionDigits: 0,
|
|
})
|
|
: "0"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<p className="rounded-lg bg-muted p-3 text-sm leading-6">
|
|
{team.combo}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<Card>
|
|
<CardContent className="py-8 text-center text-sm text-muted-foreground">
|
|
ไม่พบทีมที่ตรงกับคำค้นหา
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|