style: update better visibility pagination

This commit is contained in:
Mahen 2026-02-22 09:03:54 +07:00
parent 67f2fccd33
commit 8204b15b4b
6 changed files with 114 additions and 16 deletions

View File

@ -20,7 +20,7 @@ export function BrandFilter() {
<button <button
onClick={() => handleSelect(null)} onClick={() => handleSelect(null)}
className={cn( className={cn(
"rounded-lg border px-4 py-2 text-sm font-medium transition-all", "rounded-lg border px-4 py-2 text-sm font-medium transition-all cursor-pointer",
selectedBrand === null selectedBrand === null
? "border-primary bg-primary text-primary-foreground" ? "border-primary bg-primary text-primary-foreground"
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground", : "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",
@ -40,7 +40,7 @@ export function BrandFilter() {
key={brand.name} key={brand.name}
onClick={() => handleSelect(brand.name)} onClick={() => handleSelect(brand.name)}
className={cn( className={cn(
"rounded-lg border px-4 py-2 text-sm font-medium transition-all", "rounded-lg border px-4 py-2 text-sm font-medium transition-all cursor-pointer",
isActive isActive
? "border-primary bg-primary text-primary-foreground" ? "border-primary bg-primary text-primary-foreground"
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground", : "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",

View File

@ -9,24 +9,20 @@ import {
TableRow, TableRow,
} from "../../components/ui/table"; } from "../../components/ui/table";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
import { EllipsisVertical, Inbox, Loader2, Pencil, Trash } from "lucide-react"; import { Inbox, Loader2 } from "lucide-react";
import getSentimentBadge from "./SentimentBadge"; import getSentimentBadge from "./SentimentBadge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { useReviewTable } from "@/src/hooks/useReviewTable"; import { useReviewTable } from "@/src/hooks/useReviewTable";
import { import {
Pagination, Pagination,
PaginationContent, PaginationContent,
PaginationEllipsis,
PaginationItem, PaginationItem,
PaginationLink, PaginationLink,
PaginationNext, PaginationNext,
PaginationPrevious, PaginationPrevious,
} from "../ui/pagination"; } from "../ui/pagination";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { getVisiblePages } from "@/src/utils/datas";
export function ReviewTable() { export function ReviewTable() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -36,6 +32,7 @@ export function ReviewTable() {
selectedBrand, selectedBrand,
); );
const { currentPage, totalPages, nextPage, prevPage, goToPage } = pagination; const { currentPage, totalPages, nextPage, prevPage, goToPage } = pagination;
const visiblePage = getVisiblePages({ totalPages, currentPage });
if (isLoading) { if (isLoading) {
return ( return (
@ -171,7 +168,7 @@ export function ReviewTable() {
</TableBody> </TableBody>
</Table> </Table>
{totalPages > 1 && ( {/* {totalPages > 1 && (
<div className="border-t bg-muted/20 px-6 py-4"> <div className="border-t bg-muted/20 px-6 py-4">
<Pagination className="justify-center sm:justify-end"> <Pagination className="justify-center sm:justify-end">
<PaginationContent> <PaginationContent>
@ -222,6 +219,63 @@ export function ReviewTable() {
</PaginationContent> </PaginationContent>
</Pagination> </Pagination>
</div> </div>
)} */}
{totalPages > 1 && (
<div className="border-t bg-muted/20 px-6 py-4">
<Pagination className="justify-center sm:justify-end">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
prevPage();
}}
className={
currentPage === 1
? "pointer-events-none opacity-50"
: "cursor-pointer hover:bg-[#F8FBFF] hover:text-primary"
}
/>
</PaginationItem>
{visiblePage.map((page, index) => (
<PaginationItem key={index}>
{page === "..." ? (
<PaginationEllipsis />
) : (
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
goToPage(page as number);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
nextPage();
}}
className={
currentPage === totalPages
? "pointer-events-none opacity-50"
: "cursor-pointer hover:bg-primary hover:text-card"
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)} )}
</div> </div>
); );

View File

@ -19,6 +19,7 @@ const buttonVariants = cva(
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
primary: "bg-primary text-card"
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: "h-9 px-4 py-2 has-[>svg]:px-3",

View File

@ -55,7 +55,7 @@ function PaginationLink({
data-active={isActive} data-active={isActive}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: isActive ? "outline" : "ghost", variant: isActive ? "primary" : "ghost",
size, size,
}), }),
className, className,

View File

@ -285,3 +285,8 @@ export type AnalysisData = {
}; };
export type BodyData = (req: Request, body: any) => Promise<NextResponse>; export type BodyData = (req: Request, body: any) => Promise<NextResponse>;
export interface VisiblePageProps {
totalPages: number;
currentPage: number;
}

View File

@ -1,4 +1,10 @@
import { ScrapeResult, WordCloudConfig, WordItem } from "../types"; import { useReviewTable } from "../hooks/useReviewTable";
import {
ScrapeResult,
VisiblePageProps,
WordCloudConfig,
WordItem,
} from "../types";
import { Brand } from "@prisma/client"; import { Brand } from "@prisma/client";
export const setWordCloud = ({ maxValue, minValue }: WordCloudConfig) => { export const setWordCloud = ({ maxValue, minValue }: WordCloudConfig) => {
@ -24,7 +30,7 @@ export const setWordCloud = ({ maxValue, minValue }: WordCloudConfig) => {
return { getSize, getColor }; return { getSize, getColor };
}; };
export function getFallbackData(url: string): ScrapeResult { export const getFallbackData = (url: string): ScrapeResult => {
return { return {
name: "Produk (Data Sampel)", name: "Produk (Data Sampel)",
url: url, url: url,
@ -39,7 +45,7 @@ export function getFallbackData(url: string): ScrapeResult {
"Pengiriman cepat dan packing kayu sangat aman.", "Pengiriman cepat dan packing kayu sangat aman.",
], ],
}; };
} };
export const formatRupiah = (value: number | string) => { export const formatRupiah = (value: number | string) => {
if (!value) return "Rp 0"; if (!value) return "Rp 0";
@ -63,10 +69,42 @@ export const brandFormat = ({
return { brands }; return { brands };
}; };
export function toTitleCase(str: string) { export const toTitleCase = (str: string) => {
return str return str
.toLowerCase() .toLowerCase()
.split(/[\s-_]+/) .split(/[\s-_]+/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" "); .join(" ");
} };
export const getVisiblePages = (data: VisiblePageProps) => {
if (data.totalPages <= 6) {
return Array.from({ length: data.totalPages }, (_, i) => i + 1);
}
if (data.currentPage <= 3) {
return [1, 2, 3, 4, 5, "...", data.totalPages];
}
if (data.currentPage >= data.totalPages - 2) {
return [
1,
"...",
data.totalPages - 4,
data.totalPages - 3,
data.totalPages - 2,
data.totalPages - 1,
data.totalPages,
];
}
return [
1,
"...",
data.currentPage - 1,
data.currentPage,
data.currentPage + 1,
"...",
data.totalPages,
];
};