feat : server side
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react";
|
||||
import { useMemo, useState, type ReactNode } from "react";
|
||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
import { GuideImageExport } from "@/components/public/guide-image-export";
|
||||
import { ExportText } from "@/components/public/export-language";
|
||||
@@ -19,7 +20,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
||||
import type { PublicGuide } from "@/lib/guides/queries";
|
||||
import type { PublicTeamCardData, PublicTeamConditionGroup } from "@/lib/guides/public-filters";
|
||||
import {
|
||||
searchTeams,
|
||||
sortTeamsByDps,
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
} from "@/lib/guides/team-search";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Team = PublicGuide["teams"][number];
|
||||
type Team = PublicTeamCardData;
|
||||
type TeamArtifact = {
|
||||
key: string;
|
||||
name: string;
|
||||
@@ -379,32 +380,109 @@ function TeamCard({
|
||||
);
|
||||
}
|
||||
|
||||
export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
||||
export function TeamSearch({
|
||||
groups,
|
||||
serverFilters,
|
||||
}: {
|
||||
groups: PublicTeamConditionGroup[];
|
||||
serverFilters?: {
|
||||
conditionIndex: number;
|
||||
query: string;
|
||||
field: TeamSearchTarget;
|
||||
sort: DpsOrder;
|
||||
};
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const searchString = searchParams.toString();
|
||||
const [activeCondition, setActiveCondition] = useState("0");
|
||||
const activeGroup = groups[Number(activeCondition)] ?? groups[0];
|
||||
const activeConditionIndex = serverFilters?.conditionIndex ?? Number(activeCondition);
|
||||
const activeGroup = groups[activeConditionIndex] ?? groups[0];
|
||||
const teams = useMemo(() => activeGroup?.teams ?? [], [activeGroup]);
|
||||
const [query, setQuery] = useState("");
|
||||
const [query, setQuery] = useState(serverFilters?.query ?? "");
|
||||
const [searchTarget, setSearchTarget] = useState<TeamSearchTarget>("all");
|
||||
const [dpsOrder, setDpsOrder] = useState<DpsOrder>(null);
|
||||
const search = useMemo(
|
||||
() => searchTeams(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] : [])),
|
||||
}), query, searchTarget),
|
||||
[query, searchTarget, teams],
|
||||
);
|
||||
const currentQuery = serverFilters?.query ?? "";
|
||||
const currentField = serverFilters?.field ?? searchTarget;
|
||||
const currentSort = serverFilters?.sort ?? dpsOrder;
|
||||
|
||||
const filteredTeams = sortTeamsByDps(search.items, dpsOrder);
|
||||
useEffect(() => {
|
||||
if (!serverFilters) return;
|
||||
const nextQuery = query.trim();
|
||||
if (nextQuery === currentQuery) return;
|
||||
const timeout = window.setTimeout(() => {
|
||||
const nextParams = new URLSearchParams(searchString);
|
||||
if (nextQuery) nextParams.set("q", nextQuery);
|
||||
else nextParams.delete("q");
|
||||
const queryString = nextParams.toString();
|
||||
router.replace(`${pathname}${queryString ? `?${queryString}` : ""}`, { scroll: false });
|
||||
}, 300);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [currentQuery, pathname, query, router, searchString, serverFilters]);
|
||||
|
||||
const localSearch = useMemo(
|
||||
() => serverFilters ? null : searchTeams(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 ?? [])] : [])),
|
||||
}), query, currentField),
|
||||
[currentField, query, serverFilters, teams],
|
||||
);
|
||||
const filteredTeams = serverFilters
|
||||
? teams
|
||||
: sortTeamsByDps(localSearch?.items ?? [], currentSort);
|
||||
|
||||
function updateUrl(name: string, value: string | null) {
|
||||
const nextParams = new URLSearchParams(searchString);
|
||||
const nextQuery = query.trim();
|
||||
if (nextQuery) nextParams.set("q", nextQuery);
|
||||
else nextParams.delete("q");
|
||||
if (value === null) nextParams.delete(name);
|
||||
else nextParams.set(name, value);
|
||||
const queryString = nextParams.toString();
|
||||
router.push(`${pathname}${queryString ? `?${queryString}` : ""}`, { scroll: false });
|
||||
}
|
||||
|
||||
function selectCondition(index: number) {
|
||||
if (!serverFilters) {
|
||||
setActiveCondition(String(index));
|
||||
return;
|
||||
}
|
||||
updateUrl("condition", index === 0 ? null : String(index + 1));
|
||||
}
|
||||
|
||||
function selectField(field: TeamSearchTarget) {
|
||||
if (!serverFilters) {
|
||||
setSearchTarget(field);
|
||||
return;
|
||||
}
|
||||
updateUrl("field", field === "all" ? null : field);
|
||||
}
|
||||
|
||||
function selectSort(order: Exclude<DpsOrder, null>) {
|
||||
if (!serverFilters) {
|
||||
setDpsOrder(order);
|
||||
return;
|
||||
}
|
||||
updateUrl("sort", order);
|
||||
}
|
||||
|
||||
function clearSort() {
|
||||
if (!serverFilters) {
|
||||
setDpsOrder(null);
|
||||
return;
|
||||
}
|
||||
updateUrl("sort", null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-4">
|
||||
|
||||
{groups.length > 1 ? (
|
||||
<ScrollableNav ariaLabel="เลือกเงื่อนไขทีม" className="w-full min-w-0">
|
||||
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[activeCondition]} onValueChange={(values) => {
|
||||
if (values[0]) setActiveCondition(values[0]);
|
||||
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[String(activeConditionIndex)]} onValueChange={(values) => {
|
||||
if (values[0]) selectCondition(Number(values[0]));
|
||||
}}>
|
||||
{groups.map((group, index) => <ToggleGroupItem key={group.id} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">{group.name || `เงื่อนไข ${index + 1}`}</ToggleGroupItem>)}
|
||||
</ToggleGroup>
|
||||
@@ -426,10 +504,10 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
||||
className="max-w-full flex-wrap justify-start"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
value={[searchTarget]}
|
||||
value={[currentField]}
|
||||
onValueChange={(value) => {
|
||||
const target = value[0] as TeamSearchTarget | undefined;
|
||||
if (target) setSearchTarget(target);
|
||||
const field = value[0] as TeamSearchTarget | undefined;
|
||||
if (field) selectField(field);
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value="all">ทั้งหมด</ToggleGroupItem>
|
||||
@@ -443,10 +521,10 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
||||
aria-label="เรียงทีมตาม DPS"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
value={dpsOrder ? [dpsOrder] : []}
|
||||
value={currentSort ? [currentSort] : []}
|
||||
onValueChange={(value) => {
|
||||
const order = value[0] as Exclude<DpsOrder, null> | undefined;
|
||||
if (order) setDpsOrder(order);
|
||||
if (order) selectSort(order);
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value="best">Best DPS</ToggleGroupItem>
|
||||
@@ -455,8 +533,8 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={!dpsOrder}
|
||||
onClick={() => setDpsOrder(null)}
|
||||
disabled={!currentSort}
|
||||
onClick={clearSort}
|
||||
>
|
||||
<XIcon data-icon="inline-start" />
|
||||
Clear
|
||||
|
||||
Reference in New Issue
Block a user