feat : update admin ui
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import Image from "next/image";
|
||||
import { CheckIcon, XIcon } from "lucide-react";
|
||||
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { LunarisText } from "@/components/public/lunaris-text";
|
||||
import { parseLunarisText } from "@/lib/catalog/lunaris";
|
||||
import {
|
||||
StygianBossCard,
|
||||
StygianBossTab,
|
||||
} from "@/components/stygian/stygian-boss-display";
|
||||
import { elementIconFilter } from "@/lib/catalog/element";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import type { getActiveCatalog } from "@/lib/guides/queries";
|
||||
@@ -17,24 +16,6 @@ type Schedule = NonNullable<Awaited<ReturnType<typeof getStygianSchedule>>>;
|
||||
type Catalog = NonNullable<Awaited<ReturnType<typeof getActiveCatalog>>>;
|
||||
type Item = Schedule["recommendations"][number]["items"][number];
|
||||
|
||||
function RecommendedMechanic({ mechanic, type }: { mechanic: string; type: string }) {
|
||||
const parts = mechanic.split(/(\{SPRITE_PRESET#\d+\})/gu);
|
||||
return (
|
||||
<span className="inline-flex flex-wrap items-center gap-1">
|
||||
{parts.map((part, index) => {
|
||||
const preset = /^\{SPRITE_PRESET#(\d+)\}$/u.exec(part)?.[1];
|
||||
if (preset) return <Image key={`${part}-${index}`} src={`https://api.lunaris.moe/data/assets/icons/${preset}.png`} alt="" width={20} height={20} className="size-5 shrink-0 object-contain" />;
|
||||
return part ? <LunarisText key={`${part}-${index}`} nodes={parseLunarisText(part)} fallback={part} /> : null;
|
||||
})}
|
||||
{type === "Disadvantage"
|
||||
? <XIcon className="size-4 text-red-500" aria-hidden="true" />
|
||||
: type === "Advantage"
|
||||
? <CheckIcon className="size-4 text-emerald-500" aria-hidden="true" />
|
||||
: null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function recommendation(item: Item, catalog: Catalog) {
|
||||
if (item.kind === "element" && item.element) {
|
||||
const source = catalog.characters.find((character) => character.element === item.element && character.elementIconUrl);
|
||||
@@ -98,45 +79,21 @@ function BossCard({ schedule, level, position, catalog }: { schedule: Schedule;
|
||||
const teams = groups.filter((group) => group.kind === "team");
|
||||
const units = groups.filter((group) => group.kind === "unit").flatMap((group) => group.items);
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col gap-6">
|
||||
<header className="text-center">
|
||||
<h2 className="font-heading text-lg font-semibold">{boss.name}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">Level {level} · HP {Math.round(boss.hp).toLocaleString("en-US")}</p>
|
||||
</header>
|
||||
<div className="flex min-w-0 flex-col gap-5">
|
||||
<Accordion>
|
||||
<AccordionItem value="overview">
|
||||
<AccordionTrigger>Boss overview</AccordionTrigger>
|
||||
<AccordionContent className="whitespace-pre-line text-muted-foreground">
|
||||
<LunarisText nodes={parseLunarisText(boss.description)} fallback={boss.description} />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{boss.mechanics.map((mechanic, index) => (
|
||||
<AccordionItem key={`${mechanic.name}-${index}`} value={`mechanic-${index}`}>
|
||||
<AccordionTrigger>{mechanic.name}</AccordionTrigger>
|
||||
<AccordionContent className="text-muted-foreground">
|
||||
<LunarisText nodes={parseLunarisText(mechanic.description)} fallback={mechanic.description} />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
<section className="flex flex-col gap-3" aria-label={`Recommended teams for ${boss.name}`}>
|
||||
<h3 className="font-heading text-lg font-semibold">Recommended team</h3>
|
||||
{teams.length ? teams.map((team) => (
|
||||
<div key={team.id} className="grid w-full grid-cols-4 items-start gap-1 rounded-lg border bg-muted/20 p-2 sm:flex sm:w-fit sm:flex-wrap sm:gap-3">
|
||||
{team.items.slice(0, 4).map((item) => <RecommendationIcon key={`${item.groupId}-${item.position}`} item={item} catalog={catalog} teamLayout />)}
|
||||
{Array.from({ length: Math.max(0, 4 - team.items.length) }, (_, index) => <div key={`empty-${index}`} className="aspect-square w-full rounded-lg border border-dashed bg-muted/30 sm:w-[116px]" aria-hidden="true" />)}
|
||||
</div>
|
||||
)) : <p className="text-sm text-muted-foreground">No team recommendations yet.</p>}
|
||||
</section>
|
||||
<section className="flex flex-col gap-3" aria-label={`Recommended units for ${boss.name}`}>
|
||||
<h3 className="font-heading text-lg font-semibold">Recommended unit</h3>
|
||||
{units.length ? <div className="grid grid-cols-[repeat(auto-fill,minmax(5rem,6.25rem))] gap-2">{units.map((item) => <RecommendationIcon key={`${item.groupId}-${item.position}`} item={item} catalog={catalog} />)}</div> : <p className="text-sm text-muted-foreground">No unit recommendations yet.</p>}
|
||||
</section>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<StygianBossCard boss={boss} level={level}>
|
||||
<section className="flex flex-col gap-3" aria-label={`Recommended teams for ${boss.name}`}>
|
||||
<h3 className="font-heading text-lg font-semibold">Recommended team</h3>
|
||||
{teams.length ? teams.map((team) => (
|
||||
<div key={team.id} className="grid w-full grid-cols-4 items-start gap-1 rounded-lg border bg-muted/20 p-2 sm:flex sm:w-fit sm:flex-wrap sm:gap-3">
|
||||
{team.items.slice(0, 4).map((item) => <RecommendationIcon key={`${item.groupId}-${item.position}`} item={item} catalog={catalog} teamLayout />)}
|
||||
{Array.from({ length: Math.max(0, 4 - team.items.length) }, (_, index) => <div key={`empty-${index}`} className="aspect-square w-full rounded-lg border border-dashed bg-muted/30 sm:w-[116px]" aria-hidden="true" />)}
|
||||
</div>
|
||||
)) : <p className="text-sm text-muted-foreground">No team recommendations yet.</p>}
|
||||
</section>
|
||||
<section className="flex flex-col gap-3" aria-label={`Recommended units for ${boss.name}`}>
|
||||
<h3 className="font-heading text-lg font-semibold">Recommended unit</h3>
|
||||
{units.length ? <div className="grid grid-cols-[repeat(auto-fill,minmax(5rem,6.25rem))] gap-2">{units.map((item) => <RecommendationIcon key={`${item.groupId}-${item.position}`} item={item} catalog={catalog} />)}</div> : <p className="text-sm text-muted-foreground">No unit recommendations yet.</p>}
|
||||
</section>
|
||||
</StygianBossCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,11 +113,7 @@ export function StygianGuide({ schedule, catalog }: { schedule: Schedule; catalo
|
||||
const label = boss?.name ?? `Boss ${position + 1}`;
|
||||
const positionLabel = ["บน", "กลาง", "ล่าง"][position];
|
||||
return <TabsTrigger key={position} value={String(position)} aria-label={`${positionLabel}: ${label}`} title={label} className="h-full min-w-0 flex-col justify-start overflow-hidden rounded-none px-2 py-3">
|
||||
{boss ? <Image src={boss.imageUrl} alt="" width={256} height={256} sizes="256px" className="size-64 shrink-0 object-contain" priority={level === 110} /> : <span>{position + 1}</span>}
|
||||
<span className="text-base font-semibold">{positionLabel}</span>
|
||||
{boss?.recommendedMechanics.some(Boolean) ? <span className="mt-2 flex flex-col items-center gap-1 text-xs font-normal whitespace-normal text-muted-foreground">
|
||||
{boss.recommendedMechanics.map((mechanic, index) => mechanic ? <RecommendedMechanic key={`${mechanic}-${index}`} mechanic={mechanic} type={boss.recommendedMechanicTypes[index] ?? (index === 0 ? "Advantage" : index === 1 ? "Disadvantage" : "None")} /> : null)}
|
||||
</span> : null}
|
||||
<StygianBossTab boss={boss} position={position} priority={level === 110} />
|
||||
</TabsTrigger>;
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
Reference in New Issue
Block a user