Files
buzz-sheet/lib/guides/team-search.ts
T
gunshiz 9896e8a4ad
CI / Verify (push) Successful in 1m35s
CI / Build immutable images and deploy (push) Successful in 5m27s
feat(guides) : add safer publishing and discovery
2026-09-19 13:32:57 +07:00

80 lines
2.2 KiB
TypeScript

import { normalizeSearch, searchItems } from "../search";
export type TeamSearchTarget =
| "all"
| "team"
| "character"
| "weapon"
| "artifact";
export interface TeamSearchValues {
team: string[];
character: string[];
weapon: string[];
artifact: string[];
}
export interface DpsTeam {
rotationSeconds: string | number;
members: Array<{ damage: string | number }>;
}
export type DpsOrder = "best" | "worst" | null;
function teamDps(team: DpsTeam): number {
const seconds = Number(team.rotationSeconds);
if (!(seconds > 0)) return 0;
return team.members.reduce((sum, member) => sum + Number(member.damage), 0) / seconds;
}
export function sortTeamsByDps<Team extends DpsTeam>(
teams: readonly Team[],
order: DpsOrder,
): Team[] {
if (!order) return [...teams];
return teams
.map((team, index) => ({ team, index }))
.sort((left, right) => {
const difference = order === "best"
? teamDps(right.team) - teamDps(left.team)
: teamDps(left.team) - teamDps(right.team);
return difference || left.index - right.index;
})
.map(({ team }) => team);
}
export function matchesTeamSearch(
values: TeamSearchValues,
query: string,
target: TeamSearchTarget,
): boolean {
const normalizedQuery = normalizeSearch(query);
if (!normalizedQuery) return true;
const candidates = target === "all"
? Object.values(values).flat()
: values[target];
return candidates.some((value) =>
normalizeSearch(value).includes(normalizedQuery),
);
}
export function searchTeams<T>(
teams: readonly T[],
values: (team: T) => TeamSearchValues,
query: string,
target: TeamSearchTarget,
) {
const candidates = (team: T) => {
const fields = values(team);
return target === "all" ? Object.values(fields).flat() : fields[target];
};
// Separate candidates retain the existing rule that a direct match must
// occur within one name, rather than spanning two members or categories.
const direct = teams.filter((team) => matchesTeamSearch(values(team), query, target));
if (direct.length || !query.trim()) return { items: direct, fuzzy: false };
return searchItems(teams, query, () => "", candidates);
}