feat : server side
CI / Verify (push) Successful in 1m33s
CI / Build immutable images and deploy (push) Successful in 2m16s

This commit is contained in:
2026-09-23 21:45:46 +07:00 Unverified
parent bd84d49108
commit e0b25500bc
10 changed files with 1126 additions and 611 deletions
+143
View File
@@ -0,0 +1,143 @@
import type { PublicGuide } from "@/lib/guides/queries";
import {
searchTeams,
sortTeamsByDps,
type DpsOrder,
type TeamSearchTarget,
} from "@/lib/guides/team-search";
export type GuideSearchParams = Record<string, string | string[] | undefined>;
export interface PublicGuideFilters {
conditionIndex: number;
recommendedIndex: number;
setsIndex: number;
targetsIndex: number;
query: string;
field: TeamSearchTarget;
sort: DpsOrder;
recommendedLabels: string[];
setsLabels: string[];
targetsLabels: string[];
}
type GuideTeam = PublicGuide["teams"][number];
type GuideMember = GuideTeam["members"][number];
type GuideItem = NonNullable<GuideMember["weapon"]>;
export type PublicTeamCardData = Pick<GuideTeam, "id" | "title" | "rotationSeconds" | "combo"> & {
members: Array<Pick<GuideMember, "position" | "damage" | "constellation" | "refinement"> & {
character: Pick<GuideMember["character"], "key" | "name" | "imageUrl" | "element" | "rarity"> & { aliases?: string[] };
weapon: (Pick<GuideItem, "key" | "name" | "imageUrl" | "rarity"> & { aliases?: string[] }) | null;
artifact: (Pick<GuideItem, "key" | "name" | "imageUrl" | "rarity"> & { aliases?: string[] }) | null;
secondArtifact: (Pick<GuideItem, "key" | "name" | "imageUrl" | "rarity"> & { aliases?: string[] }) | null;
}>;
};
export type PublicTeamConditionGroup = Pick<PublicGuide["teamGroups"][number], "id" | "name" | "note"> & {
teams: PublicTeamCardData[];
};
export function firstSearchValue(value: string | string[] | undefined): string | undefined {
return Array.isArray(value) ? value[0] : value;
}
export function resolveOneBasedIndex(value: string | undefined, length: number): number {
if (length <= 0 || !value || !/^\d+$/u.test(value)) return 0;
const index = Number(value) - 1;
return Number.isSafeInteger(index) && index >= 0 && index < length ? index : 0;
}
export function resolvePublicGuideFilters(
guide: PublicGuide,
section: string,
params: GuideSearchParams,
): PublicGuideFilters {
const profile = guide.artifactProfile;
const recommended = profile?.recommendedConditions ?? [];
const sets = guide.artifactGroups;
const targets = profile?.targetConditions.filter((condition) => condition.rows.length) ?? [];
const fieldValue = firstSearchValue(params.field);
const sortValue = firstSearchValue(params.sort);
return {
conditionIndex: resolveOneBasedIndex(
firstSearchValue(params.condition),
section === "weapon"
? guide.weaponGroups.filter((group) => group.rows.length > 0).length
: section === "constellations"
? guide.constellationGroups.filter((group) => group.rows.length > 0).length
: guide.teamGroups.length,
),
recommendedIndex: resolveOneBasedIndex(firstSearchValue(params.recommended), recommended.length),
setsIndex: resolveOneBasedIndex(firstSearchValue(params.sets), sets.length),
targetsIndex: resolveOneBasedIndex(firstSearchValue(params.targets), targets.length),
query: firstSearchValue(params.q)?.trim() ?? "",
field: fieldValue === "team" || fieldValue === "character" || fieldValue === "weapon" || fieldValue === "artifact"
? fieldValue
: "all",
sort: sortValue === "best" || sortValue === "worst" ? sortValue : null,
recommendedLabels: recommended.map((condition, index) => condition.name || `เงื่อนไข ${index + 1}`),
setsLabels: sets.map((condition, index) => condition.name || `เงื่อนไข ${index + 1}`),
targetsLabels: targets.map((condition, index) => condition.name || `เงื่อนไข ${index + 1}`),
};
}
export function filterPublicTeamGroups(
guide: PublicGuide,
filters: PublicGuideFilters,
): PublicTeamConditionGroup[] {
const activeGroup = guide.teamGroups[filters.conditionIndex] ?? guide.teamGroups[0];
if (!activeGroup) return [];
const matches = searchTeams(activeGroup.teams, (team) => ({
team: [team.title],
character: team.members.flatMap((member) => [member.character.name, ...member.character.aliases]),
weapon: team.members.flatMap((member) => member.weapon ? [member.weapon.name, ...member.weapon.aliases] : []),
artifact: team.members.flatMap((member) => [member.artifact, member.secondArtifact].flatMap((artifact) => artifact ? [artifact.name, ...artifact.aliases] : [])),
}), filters.query, filters.field);
const teams = sortTeamsByDps(matches.items, filters.sort).map((team) => ({
id: team.id,
title: team.title,
rotationSeconds: team.rotationSeconds,
combo: team.combo,
members: team.members.map((member) => ({
position: member.position,
damage: member.damage,
constellation: member.constellation,
refinement: member.refinement,
character: {
key: member.character.key,
name: member.character.name,
imageUrl: member.character.imageUrl,
element: member.character.element,
rarity: member.character.rarity,
},
weapon: member.weapon ? {
key: member.weapon.key,
name: member.weapon.name,
imageUrl: member.weapon.imageUrl,
rarity: member.weapon.rarity,
} : null,
artifact: member.artifact ? {
key: member.artifact.key,
name: member.artifact.name,
imageUrl: member.artifact.imageUrl,
rarity: member.artifact.rarity,
} : null,
secondArtifact: member.secondArtifact ? {
key: member.secondArtifact.key,
name: member.secondArtifact.name,
imageUrl: member.secondArtifact.imageUrl,
rarity: member.secondArtifact.rarity,
} : null,
})),
}));
return guide.teamGroups.map((group, index) => ({
id: group.id,
name: group.name,
note: index === filters.conditionIndex ? group.note : null,
teams: index === filters.conditionIndex ? teams : [],
}));
}