feat(guides) : add selectable target and team conditions
CI / Build immutable images and deploy (push) Has been cancelled
CI / Verify (push) Has been cancelled

This commit is contained in:
2026-09-06 17:40:06 +07:00 Unverified
parent 6dc46ca2a7
commit fcf6a2c187
13 changed files with 4207 additions and 63 deletions
+23
View File
@@ -0,0 +1,23 @@
"use client";
import { Children, type ReactNode, useState } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export function ConditionSwitcher({ children, label = "เลือก Condition" }: { children: ReactNode; label?: string }) {
const items = Children.toArray(children);
const [active, setActive] = useState("0");
if (items.length <= 1) return items[0] ?? null;
return (
<div className="flex flex-col gap-4">
<ToggleGroup aria-label={label} variant="outline" value={[active]} onValueChange={(values) => {
if (values[0]) setActive(values[0]);
}}>
{items.map((_, index) => <ToggleGroupItem key={index} value={String(index)}>Condition {index + 1}</ToggleGroupItem>)}
</ToggleGroup>
{items[Number(active)]}
</div>
);
}
+16 -5
View File
@@ -29,6 +29,7 @@ import { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search";
import { GuideImageExport } from "@/components/public/guide-image-export";
import { GuideExportBranding } from "@/components/public/guide-export-branding";
import { ConstellationImage } from "@/components/public/constellation-image";
import { ConditionSwitcher } from "@/components/public/condition-switcher";
import { MaterialPoster } from "@/components/public/material-poster";
import { RichTextContent } from "@/components/rich-text-content";
import { LiveRefresh } from "@/components/live-refresh";
@@ -62,7 +63,10 @@ function hasSection(guide: PublicGuide, slug: string): boolean {
guide.artifactProfile &&
(guide.artifactProfile.sands.length ||
guide.artifactProfile.goblet.length ||
guide.artifactProfile.circlet.length),
guide.artifactProfile.circlet.length ||
guide.artifactProfile.substats.length ||
guide.artifactProfile.targetConditions.some((group) => group.rows.length) ||
guide.artifactProfile.talents.length),
)
);
if (slug === "constellations") return guide.constellationGroups.some((group) => group.rows.length > 0);
@@ -432,6 +436,7 @@ function WeaponSectionContent({
) : null}
</div>
</header>
<ConditionSwitcher label="เลือกเงื่อนไขอาวุธ">
{weaponGroups.map((group, groupIndex, groups) => (
<div key={group.id} className="relative z-10 flex flex-col gap-3">
<div className="flex items-center justify-between gap-3">
@@ -596,6 +601,7 @@ function WeaponSectionContent({
</div>
</div>
))}
</ConditionSwitcher>
</section>
);
}
@@ -778,11 +784,14 @@ function ArtifactSectionContent({
<CardHeader>
</CardHeader>
<CardContent className="flex flex-col gap-5">
{profile.targets.length ? (
<div>
{profile.targetConditions.some((group) => group.rows.length) ? (
<ConditionSwitcher label="เลือกเงื่อนไขค่าสถานะเป้าหมาย">
{profile.targetConditions.filter((group) => group.rows.length).map((group, groupIndex) => (
<div key={groupIndex}>
<p className="mb-2 text-sm font-medium">ค่าสถานะต่ำๆที่ควรจะปั้นไว้</p>
{group.note ? <p className="mb-2 whitespace-pre-wrap text-sm text-muted-foreground">{group.note}</p> : null}
<div className="flex flex-col gap-1">
{profile.targets.map((target, index) => (
{group.rows.map((target, index) => (
<div
key={`${target.stat}-${target.value}`}
className={cn(
@@ -812,6 +821,8 @@ function ArtifactSectionContent({
))}
</div>
</div>
))}
</ConditionSwitcher>
) : null}
{profile.talents.length ? (
<div>
@@ -1137,7 +1148,7 @@ function TeamSection({ guide }: { guide: PublicGuide }) {
<p className="text-sm font-medium text-primary">คำนวณความเสียหาย</p>
<h1 className="font-heading text-4xl font-semibold">คำนวณทีม</h1>
</header>
<TeamSearch teams={guide.teams} />
<TeamSearch groups={guide.teamGroups} />
</section>
);
}
+12 -1
View File
@@ -360,7 +360,10 @@ function TeamCard({
);
}
export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
const [activeCondition, setActiveCondition] = useState("0");
const activeGroup = groups[Number(activeCondition)] ?? groups[0];
const teams = useMemo(() => activeGroup?.teams ?? [], [activeGroup]);
const [query, setQuery] = useState("");
const [searchTarget, setSearchTarget] = useState<TeamSearchTarget>("all");
const [dpsOrder, setDpsOrder] = useState<DpsOrder>(null);
@@ -376,6 +379,14 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
return (
<div className="flex flex-col gap-4">
{groups.length > 1 ? (
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[activeCondition]} onValueChange={(values) => {
if (values[0]) setActiveCondition(values[0]);
}}>
{groups.map((group, index) => <ToggleGroupItem key={group.id} value={String(index)}>Condition {index + 1}</ToggleGroupItem>)}
</ToggleGroup>
) : null}
{activeGroup?.note ? <p className="whitespace-pre-wrap text-muted-foreground">{activeGroup.note}</p> : null}
<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