feat : update

This commit is contained in:
2026-10-07 15:15:53 +07:00 Unverified
parent 22f5f96231
commit 747c922951
3 changed files with 8 additions and 121 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ import {
const destinations = [
{ href: "/", label: "หน้ารวม", icon: null },
{ href: "/commission", label: "ขอคำนวณทีม", icon: "/icon/commission.webp" },
{ href: "/commission", label: "จ้างคำนวณทีม", icon: "/icon/commission.webp" },
{ href: "/stygian", label: "ไกด์ Stygian", icon: "/icon/stygian.webp" },
] as const;
+4 -117
View File
@@ -55,9 +55,7 @@ import {
import {
normalizeTalentPriorities,
talentRelationSymbol,
talentTypeLabel,
} from "@/lib/guides/talents";
import { comparisonDelta, comparisonReferenceNames } from "@/lib/guides/comparison";
import { parseYouTubeUrl } from "@/lib/guides/youtube";
import { normalizeExtraRichText } from "@/lib/rich-text";
import {
@@ -224,7 +222,6 @@ function GuideOverview({
</Badge>
</div>
</div>
<GuideBuildSummary guide={guide} baseHref={baseHref} />
<div className="grid items-start gap-6 lg:grid-cols-2">
<div className="order-1 relative aspect-square w-full overflow-hidden rounded-xl border bg-muted lg:col-start-1 lg:row-start-1">
<Image
@@ -307,75 +304,6 @@ function GuideOverview({
);
}
function GuideBuildSummary({ guide, baseHref }: { guide: PublicGuide; baseHref: string }) {
const weaponGroup = guide.weaponGroups[0];
const summarizeWeapon = (row: PublicGuide["weapons"][number] | undefined) => row ? {
name: row.weapon.name, refinement: row.refinement, note: row.note,
conditionName: weaponGroup?.name, conditionNote: weaponGroup?.note,
} : null;
const artifactGroup = guide.artifactGroups[0];
const artifact = artifactGroup?.tiers.flatMap((tier) => tier.rows)[0];
// Complete admin previews already contain this data; keep their reads unchanged.
const summary = guide.buildSummary ?? {
weapon: summarizeWeapon(weaponGroup?.rows[0]),
fourStarWeapon: summarizeWeapon(weaponGroup?.rows.find((row) => row.weapon.rarity === 4)),
artifact: artifact ? {
name: artifact.first.name, secondKey: artifact.secondArtifactKey, secondName: artifact.second?.name,
note: artifact.note, conditionName: artifactGroup?.name, conditionNote: artifactGroup?.note,
} : null,
profile: guide.artifactProfile,
};
if (!summary || (!summary.weapon && !summary.artifact && !summary.profile)) return null;
const profile = summary.profile;
const recommended = profile?.recommendedConditions[0] ?? profile;
const target = profile?.targetConditions[0];
const targets = target?.rows ?? profile?.targets ?? [];
const talents = normalizeTalentPriorities(profile?.talents ?? []);
return <Card>
<CardHeader>
<CardTitle>สรุปคำแนะนำในไกด์</CardTitle>
<CardDescription>สรุปจากเงื่อนไขแรกของแต่ละหัวข้อ เปิดรายละเอียดเพื่อเลือกเงื่อนไขที่ตรงกับทีมของคุณ</CardDescription>
</CardHeader>
<CardContent className="grid gap-5 sm:grid-cols-2">
{summary.weapon && <div className="flex flex-col gap-2">
<h2 className="font-semibold">อาวุธ</h2>
<p>{summary.weapon.name} R{summary.weapon.refinement}</p>
{summary.fourStarWeapon && <p className="text-sm text-muted-foreground">ตัวเลือก 4 ดาวในไกด์: {summary.fourStarWeapon.name} R{summary.fourStarWeapon.refinement}</p>}
{summary.weapon.conditionName && <Badge variant="outline">{summary.weapon.conditionName}</Badge>}
{summary.weapon.conditionNote && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{summary.weapon.conditionNote}</p>}
{summary.weapon.note && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{summary.weapon.note}</p>}
<Link href={`${baseHref}/weapon`} prefetch={false} className="text-sm text-primary underline underline-offset-4">เปรียบเทียบอาวุธทั้งหมด</Link>
</div>}
{summary.artifact && <div className="flex flex-col gap-2">
<h2 className="font-semibold">อาร์ติแฟกต์</h2>
<p>{summary.artifact.secondKey ? `2 ${summary.artifact.name} + 2 ${summary.artifact.secondName ?? "ชุดที่สองตามไกด์"}` : `4 ${summary.artifact.name}`}</p>
{summary.artifact.conditionName && <Badge variant="outline">{summary.artifact.conditionName}</Badge>}
{summary.artifact.conditionNote && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{summary.artifact.conditionNote}</p>}
{summary.artifact.note && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{summary.artifact.note}</p>}
</div>}
{recommended && [recommended.sands, recommended.goblet, recommended.circlet].some((values) => values.length) && <div className="flex flex-col gap-2">
<h2 className="font-semibold">ออฟหลัก</h2>
{profile?.recommendedConditions[0]?.name && <Badge variant="outline">{profile.recommendedConditions[0].name}</Badge>}
{profile?.recommendedConditions[0]?.note && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{profile.recommendedConditions[0].note}</p>}
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1">
{([['นาฬิกา', recommended.sands], ['ถ้วย', recommended.goblet], ['หัว', recommended.circlet]] as const).map(([label, values]) =>
values.length ? <div key={label} className="contents"><dt className="text-muted-foreground">{label}</dt><dd>{values.map((value) => statLabel(value)).join(" / ")}</dd></div> : null)}
</dl>
</div>}
{(targets.length > 0 || talents.length > 0) && <div className="flex flex-col gap-2">
{targets.length > 0 && <>
<h2 className="font-semibold">ค่าสถานะเป้าหมาย</h2>
{target?.name && <Badge variant="outline">{target.name}</Badge>}
{target?.note && <p className="whitespace-pre-wrap text-sm text-muted-foreground">{target.note}</p>}
<dl className="grid grid-cols-[1fr_auto] gap-x-3 gap-y-1">{targets.map((row, index) => <div key={index} className="contents"><dt className="text-muted-foreground">{statLabel(row.stat)}</dt><dd>{row.value}</dd></div>)}</dl>
</>}
{talents.length > 0 && <><h2 className="font-semibold">ลำดับพรสวรรค์</h2><p>{talents.map((talent, index) => <span key={talent.type}>{talentTypeLabel(talent.type)}{talent.level ? ` Lv.${talent.level}` : ""}{index < talents.length - 1 ? ` ${talentRelationSymbol(talent.relationToNext)} ` : ""}</span>)}</p></>}
<Link href={`${baseHref}/artifact`} prefetch={false} className="text-sm text-primary underline underline-offset-4">ดูค่าสถานะและเงื่อนไขทั้งหมด</Link>
</div>}
</CardContent>
</Card>;
}
function GuideNavigation({
guide,
sections,
@@ -501,9 +429,6 @@ function WeaponSectionContent({
) : null}
<header className="relative z-10 flex items-center justify-center text-center">
<div className="flex flex-col items-center gap-3">
<p className={cn("text-sm font-medium text-primary", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
อาวุธที่แนะนำ
</p>
<div className="flex items-center justify-center gap-3">
<Image
src="/icon/nav/Weapon.png"
@@ -518,7 +443,7 @@ function WeaponSectionContent({
)}
/>
<h1 className={cn("whitespace-nowrap font-heading text-4xl font-semibold", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
{exportLabel(exporting, "อาวุธ", "Weapons")}
อาวุธ
</h1>
</div>
{note ? (
@@ -551,41 +476,11 @@ function WeaponSectionContent({
) : null}
</div>
{group.note ? <p className="whitespace-pre-wrap text-muted-foreground">{group.note}</p> : null}
{!exporting && <Alert>
<AlertTitle>เปอร์เซ็นต์นี้หมายถึงอะไร?</AlertTitle>
<AlertDescription>
<p>100% คือค่าฐานของเงื่อนไขนี้ เช่น 109% คือดาเมจมากกว่าค่าฐาน 9% เปรียบเทียบภายในทีมและเงื่อนไขเดียวกัน</p>
{([['overallPercent', 'ดาเมจทั้งทีม'], ['personalPercent', 'ดาเมจส่วนตัว']] as const).map(([key, label]) => {
if (!group.rows.some((row) => Number(row[key]) > 0)) return null;
const references = comparisonReferenceNames(group.rows, key);
return <p key={key}>{label} · แถวที่แสดง 100%: {references.length ? references.join(", ") : "ผู้เขียนยังไม่ได้ระบุแถว 100%"}</p>;
})}
</AlertDescription>
</Alert>}
<WeaponDisplayMode exporting={exporting} rows={group.rows}>
<div
className={cn("grid gap-3", useCompactExportGrid && "grid-cols-2")}
>
{group.rows.map((row, index) => (!exporting ? (
<Card key={row.id} size="sm">
<CardContent className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
<a href={lunarisItemUrl("weapon", row.weapon.key)} target="_blank" rel="noreferrer"
aria-label={`เปิดข้อมูลอาวุธ ${row.weapon.name} บน Lunaris`}
className="flex min-w-0 items-center gap-3 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring">
<span className={cn("relative size-10 shrink-0 rounded-md sm:size-14", rarityGradientClass(row.weapon.rarity))}>
<Image src={row.weapon.imageUrl} alt="" fill sizes="(min-width: 640px) 56px, 40px" className="object-contain p-1" />
</span>
<span className="min-w-0 font-semibold">{index + 1}. {row.weapon.name} R{row.refinement}</span>
</a>
<div className="flex flex-col gap-2 text-right sm:flex-row sm:gap-5">
{([['overallPercent', 'ทั้งทีม'], ['personalPercent', 'ส่วนตัว']] as const).map(([key, label]) => comparisonDelta(row[key]) !== null ?
<div key={key}><p className="text-lg font-semibold text-primary sm:text-2xl">{metric(row[key])}</p><p className="text-xs text-muted-foreground">{label}</p><p className="text-xs text-muted-foreground">{comparisonDelta(row[key])}</p></div> : null)}
{comparisonDelta(row.overallPercent) === null && comparisonDelta(row.personalPercent) === null && <span className="text-xs text-muted-foreground">ยังไม่มีผลคำนวณ</span>}
</div>
{row.note && <p className="col-span-2 whitespace-pre-wrap text-sm text-muted-foreground">{row.note}</p>}
</CardContent>
</Card>
) : (
{group.rows.map((row, index) => (
<Card key={row.id} size="sm" className="bg-white py-0">
<CardContent
className={cn(
@@ -719,7 +614,7 @@ function WeaponSectionContent({
</div>
</CardContent>
</Card>
)))}
))}
</div>
</WeaponDisplayMode>
</div>
@@ -834,9 +729,6 @@ function ArtifactSectionContent({
) : null}
<header className={cn("relative z-10 flex items-center justify-center text-center", exporting && kanitSemiBold.className)}>
<div className="flex flex-col items-center gap-3">
<p className={cn("text-sm font-medium text-primary", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
อาร์ติแฟกต์และสแตทที่แนะนำ
</p>
<div className="flex items-center justify-center gap-3">
<Image
src="/icon/nav/Artifact.webp"
@@ -870,7 +762,7 @@ function ArtifactSectionContent({
{recommendedCards}
</ConditionSwitcher>
)}
<div data-artifact-export-section="tierlist" className={exporting ? "order-first" : undefined}>
<div data-artifact-export-section="tierlist" className="order-first">
{(() => {
const tierCards = guide.artifactGroups.map((condition, conditionIndex) => (
<div key={condition.id} data-tier-condition={conditionIndex} className="artifact-export-tierlist flex flex-col gap-5">
@@ -1275,9 +1167,6 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
return (
<section className="flex flex-col gap-6">
<header className="flex flex-col items-center gap-3 text-center">
<p className="text-sm font-medium text-primary">
กลุ่มดาวที่แนะนำ
</p>
<div className="flex items-center justify-center gap-3">
<Image
src="/icon/nav/Constellations.webp"
@@ -1364,7 +1253,6 @@ function TeamSection({ guide }: { guide: PublicGuide }) {
return (
<section className="flex flex-col gap-6">
<header className="text-center">
<p className="text-sm font-medium text-primary">คำนวณความเสียหาย</p>
<h1 className="font-heading text-3xl font-semibold sm:text-4xl">คำนวณทีม</h1>
</header>
<TeamSearch groups={guide.teamGroups} />
@@ -1415,7 +1303,6 @@ function ExtraSectionContent({
/>
) : null}
<header className="relative z-10 flex flex-col items-center gap-3 text-center">
<p className="text-sm font-medium text-primary">คู่มือเสริม</p>
<h1 className="font-heading text-4xl font-semibold">{extra.title}</h1>
{action}
</header>
+3 -3
View File
@@ -46,15 +46,15 @@ export function WeaponDisplayMode({
return (
<div className="flex min-w-0 flex-col gap-3">
<ToggleGroup
aria-label="รูปแบบการแสดงอาวุธ"
aria-label="Weapon display mode"
variant="outline"
value={[mode]}
onValueChange={(values) => {
if (values[0]) setMode(values[0]);
}}
>
<ToggleGroupItem value="normal">รายการ</ToggleGroupItem>
<ToggleGroupItem value="chart">กราฟ</ToggleGroupItem>
<ToggleGroupItem value="normal">List</ToggleGroupItem>
<ToggleGroupItem value="chart">Chart</ToggleGroupItem>
</ToggleGroup>
{mode === "chart" ? <LazyWeaponChart rows={rows} /> : children}
</div>