- Cache read-heavy public API routes via redisCache (leaderboard, values, shop, articles, photos, guilds, teams, staff, users, home, radio, badges) - Add single-flight and bounded-memory cache layer with unit tests - Parallelize independent DB queries on search, rares, shop, staff, polls and profile pages - Push radio points leaderboard aggregation to SQL with a LIMIT - Split studio-client and import-furni-client into focused modules - Clean up next.config.ts
90 lines
1.7 KiB
TypeScript
90 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactNode } from "react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import type { Meta } from "./import-furni-types";
|
|
|
|
export function Pagination({
|
|
meta,
|
|
loading,
|
|
onPageChange,
|
|
}: {
|
|
meta: Meta | null;
|
|
loading: boolean;
|
|
onPageChange: (page: number) => void;
|
|
}) {
|
|
if (!meta || meta.lastPage <= 1) return null;
|
|
const nodes: ReactNode[] = [];
|
|
if (meta.currentPage > 1) {
|
|
nodes.push(
|
|
<Button
|
|
key="prev"
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onPageChange(meta.currentPage - 1)}
|
|
disabled={loading}
|
|
className="text-xs"
|
|
>
|
|
← Prev
|
|
</Button>,
|
|
);
|
|
}
|
|
for (let p = 1; p <= meta.lastPage; p++) {
|
|
if (p === meta.currentPage) {
|
|
nodes.push(
|
|
<Button
|
|
key={p}
|
|
variant="default"
|
|
size="sm"
|
|
className="text-xs min-w-[32px]"
|
|
>
|
|
{p}
|
|
</Button>,
|
|
);
|
|
} else if (
|
|
Math.abs(p - meta.currentPage) <= 2 ||
|
|
p === 1 ||
|
|
p === meta.lastPage
|
|
) {
|
|
nodes.push(
|
|
<Button
|
|
key={p}
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onPageChange(p)}
|
|
disabled={loading}
|
|
className="text-xs min-w-[32px]"
|
|
>
|
|
{p}
|
|
</Button>,
|
|
);
|
|
} else if (Math.abs(p - meta.currentPage) === 3) {
|
|
nodes.push(
|
|
<span key={`dots-${p}`} className="text-muted-foreground text-xs px-1">
|
|
…
|
|
</span>,
|
|
);
|
|
}
|
|
}
|
|
if (meta.currentPage < meta.lastPage) {
|
|
nodes.push(
|
|
<Button
|
|
key="next"
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onPageChange(meta.currentPage + 1)}
|
|
disabled={loading}
|
|
className="text-xs"
|
|
>
|
|
Next →
|
|
</Button>,
|
|
);
|
|
}
|
|
return (
|
|
<div className="flex items-center justify-center gap-1 mt-4 flex-wrap">
|
|
{nodes}
|
|
</div>
|
|
);
|
|
}
|