feat : update mutilple apply
CI / Verify (push) Failing after 45s
CI / Build immutable images and deploy (push) Has been skipped

This commit is contained in:
2026-09-04 17:50:40 +07:00 Unverified
parent 45c1567dff
commit dc96ff0036
6 changed files with 119 additions and 43 deletions
+51 -13
View File
@@ -128,10 +128,10 @@ import {
applyTeamFields,
limitPresets,
parseTeamPresets,
teamApplyFields,
teamPresetCookieKey,
teamPresetStorageKey,
type TeamApplyField,
type TeamApplySelection,
type TeamMemberApplyField,
type TeamPreset,
type TeamToolValue,
} from "@/lib/guides/team-tools";
@@ -159,6 +159,15 @@ type CatalogItem = {
rarity?: number | null;
weaponType?: string | null;
};
const teamMemberApplyOptions = [
["characterKey", "Character"],
["weaponKey", "Weapon"],
["artifactKey", "Artifact 1"],
["secondArtifactKey", "Artifact 2"],
["constellation", "C"],
["refinement", "R"],
["damage", "Damage"],
] as const satisfies readonly (readonly [TeamMemberApplyField, string])[];
type Guide = {
id: string;
characterKey: string;
@@ -2073,7 +2082,7 @@ function TeamEditor({
const [presets, setPresets] = useState<TeamPreset[]>([]);
const [presetsHydrated, setPresetsHydrated] = useState(false);
const [presetsGuideId, setPresetsGuideId] = useState<string | null>(null);
const [applyFields, setApplyFields] = useState<TeamApplyField[]>(["loadout"]);
const [applyFields, setApplyFields] = useState<TeamApplySelection[]>([]);
const [searchQuery, setSearchQuery] = useState("");
const [searchTarget, setSearchTarget] = useState<TeamSearchTarget>("all");
const filteredTeams = useMemo(() => {
@@ -2177,12 +2186,12 @@ function TeamEditor({
return next;
});
};
const toggleApplyField = (field: TeamApplyField, checked: boolean) => {
const toggleApplyField = (field: TeamApplySelection, checked: boolean) => {
setApplyFields((current) => {
const next = new Set(current);
if (checked) next.add(field);
else next.delete(field);
return teamApplyFields.filter((candidate) => next.has(candidate));
return [...next];
});
};
const applyChanges = () => {
@@ -2878,18 +2887,47 @@ function TeamEditor({
/>
</CardFooter>
<Dialog open={applyOpen} onOpenChange={setApplyOpen}>
<DialogContent>
<DialogContent className="max-h-[90svh] overflow-y-auto sm:max-w-3xl">
<DialogHeader>
<DialogTitle>Multiple Apply</DialogTitle>
<DialogDescription>คัดลอกค่าจาก {teams.find((team) => team.clientId === sourceTeamId)?.title || "Source Team"} ไปยัง {selectedTeamIds.size} Team ที่เลือก: {[...selectedTeamIds].map((id) => teams.find((team) => team.clientId === id)?.title || "Untitled Team").join(", ")}</DialogDescription>
</DialogHeader>
<FieldSet>
<FieldLegend>ข้อมูลที่ต้องการคัดลอก</FieldLegend>
<Field orientation="horizontal"><Checkbox checked={applyFields.includes("loadout")} onCheckedChange={(checked) => toggleApplyField("loadout", checked === true)} /><FieldLabel>Loadout: Character, Weapon, Artifact, C, R</FieldLabel></Field>
<Field orientation="horizontal"><Checkbox checked={applyFields.includes("damage")} onCheckedChange={(checked) => toggleApplyField("damage", checked === true)} /><FieldLabel>Damage</FieldLabel></Field>
<Field orientation="horizontal"><Checkbox checked={applyFields.includes("rotation")} onCheckedChange={(checked) => toggleApplyField("rotation", checked === true)} /><FieldLabel>Rotation</FieldLabel></Field>
<Field orientation="horizontal"><Checkbox checked={applyFields.includes("combo")} onCheckedChange={(checked) => toggleApplyField("combo", checked === true)} /><FieldLabel>Combo</FieldLabel></Field>
</FieldSet>
<div className="flex flex-col gap-5">
{teams.find((team) => team.clientId === sourceTeamId)?.members.map((member, memberIndex) => {
const characterName = characters.find((character) => character.key === member.characterKey)?.name || "No character";
return (
<FieldSet key={memberIndex}>
<FieldLegend>{characterName} (char{memberIndex + 1})</FieldLegend>
<div className="grid gap-3 sm:grid-cols-4">
{teamMemberApplyOptions.map(([field, label]) => {
const selection = `member:${memberIndex}:${field}` as TeamApplySelection;
const id = `apply-${memberIndex}-${field}`;
return (
<Field key={field} orientation="horizontal">
<Checkbox id={id} checked={applyFields.includes(selection)} onCheckedChange={(checked) => toggleApplyField(selection, checked === true)} />
<FieldLabel htmlFor={id}>{label}{field === "refinement" ? ` (R${member.refinement})` : ""}</FieldLabel>
</Field>
);
})}
</div>
</FieldSet>
);
})}
<FieldSet>
<FieldLegend>Team fields</FieldLegend>
<div className="grid gap-3 sm:grid-cols-2">
{(["rotation", "combo"] as const).map((field) => {
const id = `apply-${field}`;
return (
<Field key={field} orientation="horizontal">
<Checkbox id={id} checked={applyFields.includes(field)} onCheckedChange={(checked) => toggleApplyField(field, checked === true)} />
<FieldLabel htmlFor={id}>{field === "rotation" ? "Rotation" : "Combo"}</FieldLabel>
</Field>
);
})}
</div>
</FieldSet>
</div>
<DialogFooter><Button type="button" variant="outline" onClick={() => setApplyOpen(false)}>ยกเลิก</Button><Button type="button" disabled={!applyFields.length} onClick={applyChanges}>ยืนยันการคัดลอก</Button></DialogFooter>
</DialogContent>
</Dialog>
+1 -1
View File
@@ -58,7 +58,7 @@ export function ScrollableNav({
<nav
ref={navRef}
aria-label={ariaLabel}
className="flex min-w-0 flex-1 gap-1 overflow-x-auto [scrollbar-width:none]"
className="flex min-w-0 flex-1 gap-1 overflow-x-auto scrollbar-none"
>
{children}
</nav>
+19 -26
View File
@@ -38,28 +38,16 @@ function ArtifactHalf({
sizes: string;
}) {
const content = (
<>
<span className="relative block size-full">
<Image
src={artifact.imageUrl}
alt={linked ? artifact.name : ""}
fill
loading={eager ? "eager" : undefined}
sizes={sizes}
className="object-contain p-1"
/>
</span>
{linked ? (
<Kbd
className={cn(
"absolute h-3 min-w-3 bg-transparent p-0 text-white",
side === "left" ? "bottom-0 left-0" : "top-0 right-0",
)}
>
<ArrowUpRightIcon aria-hidden="true" />
</Kbd>
) : null}
</>
<span className="relative block size-full">
<Image
src={artifact.imageUrl}
alt={linked ? artifact.name : ""}
fill
loading={eager ? "eager" : undefined}
sizes={sizes}
className="object-contain p-1"
/>
</span>
);
const className = cn(
"absolute inset-0 overflow-hidden",
@@ -153,6 +141,11 @@ function TeamArtifactIcon({
aria-hidden="true"
className="pointer-events-none absolute top-1/2 left-1/2 h-px w-[142%] -translate-x-1/2 -translate-y-1/2 rotate-45 bg-white/30"
/>
{linked ? (
<Kbd className="pointer-events-none absolute top-0 right-0 h-3 min-w-3 bg-transparent p-0 text-white">
<ArrowUpRightIcon aria-hidden="true" />
</Kbd>
) : null}
</div>
);
}
@@ -161,7 +154,7 @@ function TeamExportImage({ team }: { team: Team }) {
const total = team.members.reduce((sum, member) => sum + Number(member.damage), 0);
const seconds = Number(team.rotationSeconds);
return (
<div className="flex min-h-[1350px] w-[1080px] flex-col bg-[#05060a] p-[48px_52px] font-heading text-[white]">
<div className="flex min-h-337.5 w-270 flex-col bg-[#05060a] p-[48px_52px] font-heading text-[white]">
<header className="flex flex-col items-center text-center">
<h2 className="text-[64px] leading-tight font-bold">{team.title}</h2>
<p className="text-[32px] text-[#b9b9c2]">ระยะเวลาโรเทชั่น {seconds.toLocaleString(undefined, { maximumFractionDigits: 2 })} วิ</p>
@@ -171,8 +164,8 @@ function TeamExportImage({ team }: { team: Team }) {
const damage = Number(member.damage);
const percent = total > 0 ? damage / total * 100 : 0;
return (
<div key={member.position} className="flex h-[174px] items-center gap-6 rounded-3xl p-3.5" style={{ backgroundColor: elementBackgroundColor(member.character.element) }}>
<div className={cn("relative size-[140px] shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20", rarityGradientClass(isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity))}>
<div key={member.position} className="flex h-43.5 items-center gap-6 rounded-3xl p-3.5" style={{ backgroundColor: elementBackgroundColor(member.character.element) }}>
<div className={cn("relative size-35] shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20", rarityGradientClass(isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity))}>
<Image src={member.character.imageUrl} alt="" fill loading="eager" sizes="140px" className="object-contain" />
<span className="absolute top-1.5 left-1.5 text-xl font-bold">C{member.constellation}</span>
</div>
@@ -188,7 +181,7 @@ function TeamExportImage({ team }: { team: Team }) {
</div>
<div className="flex min-w-0 flex-1 flex-col gap-3.5">
<div className="flex items-center justify-between gap-4"><span className="truncate text-[36px] font-semibold">{member.character.name}</span><span className="text-[36px]">{damage.toLocaleString()}</span></div>
<div className="flex items-center gap-5"><div className="h-11 flex-1 overflow-hidden rounded-xl bg-[#272730]"><div className="h-full rounded-xl bg-gradient-to-r from-[#b7791f] to-[#f0b72f]" style={{ width: `${Math.max(2, Math.min(100, percent))}%` }} /></div><span className="w-28 text-right text-[34px] font-bold">{percent.toFixed(1)}%</span></div>
<div className="flex items-center gap-5"><div className="h-11 flex-1 overflow-hidden rounded-xl bg-[#272730]"><div className="h-full rounded-xl bg-linear-to-r from-[#b7791f] to-[#f0b72f]" style={{ width: `${Math.max(2, Math.min(100, percent))}%` }} /></div><span className="w-28 text-right text-[34px] font-bold">{percent.toFixed(1)}%</span></div>
</div>
</div>
);