147 lines
4.6 KiB
TypeScript
147 lines
4.6 KiB
TypeScript
"use client";
|
|
|
|
import { Button, Table } from "@heroui/react";
|
|
import { buttonVariants } from "@heroui/styles";
|
|
import Link from "next/link";
|
|
import useSWRInfinite from "swr/infinite";
|
|
import { TimeAgo } from "@/components/time-ago";
|
|
import { formatElo } from "@/lib/format";
|
|
import type { MatchEntry } from "@/lib/stats";
|
|
|
|
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
|
|
|
function MatchRows({ matches }: { matches: MatchEntry[] }) {
|
|
return (
|
|
<>
|
|
{matches.map((match) => (
|
|
<Table.Row key={match.id}>
|
|
<Table.Cell className="text-center">
|
|
<Link
|
|
href={`/assets/${match.leftId}`}
|
|
className="inline-flex justify-center"
|
|
>
|
|
<img
|
|
src={`/img/${match.leftId}`}
|
|
alt=""
|
|
className="h-8 rounded object-contain"
|
|
style={{ aspectRatio: match.leftAspectRatio }}
|
|
/>
|
|
</Link>
|
|
</Table.Cell>
|
|
<Table.Cell
|
|
className={`text-center text-xs tabular-nums font-semibold ${match.winningSide === "left" ? "text-success" : "text-danger"}`}
|
|
>
|
|
{formatElo(match.leftEloAfter)}
|
|
</Table.Cell>
|
|
<Table.Cell className="text-center tabular-nums text-sm text-muted">
|
|
±{match.eloDelta.toFixed(1)}
|
|
</Table.Cell>
|
|
<Table.Cell
|
|
className={`text-center text-xs tabular-nums font-semibold ${match.winningSide === "right" ? "text-success" : "text-danger"}`}
|
|
>
|
|
{formatElo(match.rightEloAfter)}
|
|
</Table.Cell>
|
|
<Table.Cell className="text-center">
|
|
<Link
|
|
href={`/assets/${match.rightId}`}
|
|
className="inline-flex justify-center"
|
|
>
|
|
<img
|
|
src={`/img/${match.rightId}`}
|
|
alt=""
|
|
className="h-8 rounded object-contain"
|
|
style={{ aspectRatio: match.rightAspectRatio }}
|
|
/>
|
|
</Link>
|
|
</Table.Cell>
|
|
<Table.Cell>
|
|
<TimeAgo date={match.createdAt} />
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function MatchesTableHeader() {
|
|
return (
|
|
<Table.Header>
|
|
<Table.Column isRowHeader className="text-center">
|
|
Left
|
|
</Table.Column>
|
|
<Table.Column className="text-center tabular-nums">ELO</Table.Column>
|
|
<Table.Column className="text-center">±</Table.Column>
|
|
<Table.Column className="text-center tabular-nums">ELO</Table.Column>
|
|
<Table.Column className="text-center">Right</Table.Column>
|
|
<Table.Column>When</Table.Column>
|
|
</Table.Header>
|
|
);
|
|
}
|
|
|
|
export function MatchesPreview({ data }: { data: MatchEntry[] }) {
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<h3 className="text-sm font-semibold text-muted uppercase tracking-widest">
|
|
Recent Matches
|
|
</h3>
|
|
<Table>
|
|
<Table.ScrollContainer>
|
|
<Table.Content aria-label="Recent matches">
|
|
<MatchesTableHeader />
|
|
<Table.Body>
|
|
<MatchRows matches={data} />
|
|
</Table.Body>
|
|
</Table.Content>
|
|
</Table.ScrollContainer>
|
|
</Table>
|
|
<div className="flex justify-center">
|
|
<Link
|
|
href="/stats/matches"
|
|
className={buttonVariants({ variant: "secondary" })}
|
|
>
|
|
See more
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function MatchesTable({ initialData }: { initialData: MatchEntry[] }) {
|
|
const { data, size, setSize, isValidating } = useSWRInfinite(
|
|
(index) => `/api/stats/matches?page=${index}`,
|
|
fetcher,
|
|
{ fallbackData: [initialData], revalidateFirstPage: false },
|
|
);
|
|
|
|
const allMatches: MatchEntry[] = data ? data.flat() : [];
|
|
const hasMore = data ? data[data.length - 1]?.length === 20 : false;
|
|
const isLoadingMore =
|
|
isValidating && data && typeof data[size - 1] === "undefined";
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<Table>
|
|
<Table.ScrollContainer>
|
|
<Table.Content aria-label="Recent matches">
|
|
<MatchesTableHeader />
|
|
<Table.Body>
|
|
<MatchRows matches={allMatches} />
|
|
</Table.Body>
|
|
</Table.Content>
|
|
</Table.ScrollContainer>
|
|
</Table>
|
|
{hasMore && (
|
|
<div className="flex justify-center">
|
|
<Button
|
|
variant="secondary"
|
|
onPress={() => setSize(size + 1)}
|
|
isDisabled={isLoadingMore}
|
|
>
|
|
{isLoadingMore ? "Loading..." : "Load more"}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|