Add pagination to pelanggan list
parent
cb9b4b8730
commit
eae4857c9d
@ -0,0 +1,110 @@
|
|||||||
|
import { PencilIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Pagination } from "@/components/pagination";
|
||||||
|
import { listPelanggan } from "@/data/pelanggan";
|
||||||
|
import { HapusPelangganButton } from "./hapus-pelanggan-button";
|
||||||
|
import { PelangganForm } from "./pelanggan-form";
|
||||||
|
|
||||||
|
export async function DaftarPelanggan({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{ search?: string; page?: string }>;
|
||||||
|
}) {
|
||||||
|
const { search, page: pageParam } = await searchParams;
|
||||||
|
const parsed = Number.parseInt(pageParam ?? "1", 10);
|
||||||
|
// Batas atas mencegah offset melebihi BIGINT MariaDB (query error).
|
||||||
|
const page = Number.isSafeInteger(parsed) && parsed >= 1 ? parsed : 1;
|
||||||
|
const { rows, hasNext } = await listPelanggan(search, page);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mt-4 overflow-hidden rounded-lg border">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full min-w-[720px] text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b bg-muted/50 text-left text-xs tracking-wide text-muted-foreground uppercase">
|
||||||
|
<th className="px-3 py-2.5 font-medium">Kode</th>
|
||||||
|
<th className="px-3 py-2.5 font-medium">Nama</th>
|
||||||
|
<th className="px-3 py-2.5 font-medium">Alias</th>
|
||||||
|
<th className="px-3 py-2.5 font-medium">Alamat</th>
|
||||||
|
<th className="px-3 py-2.5 font-medium">Ponsel</th>
|
||||||
|
<th className="px-3 py-2.5 text-right font-medium">Bon</th>
|
||||||
|
<th className="px-3 py-2.5 text-right font-medium">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
{rows.map((p) => (
|
||||||
|
<tr
|
||||||
|
key={p.kdCustomer}
|
||||||
|
className="transition-colors hover:bg-muted/40"
|
||||||
|
>
|
||||||
|
<td className="px-3 py-2 font-mono text-xs whitespace-nowrap text-muted-foreground">
|
||||||
|
{p.kdCustomer}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-2 font-medium">{p.nama}</td>
|
||||||
|
<td className="px-3 py-2 whitespace-nowrap text-muted-foreground">
|
||||||
|
{p.alias}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-2 text-muted-foreground">
|
||||||
|
{p.alamat}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-2 whitespace-nowrap text-muted-foreground">
|
||||||
|
{p.ponsel ?? "-"}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-2 text-right tabular-nums">
|
||||||
|
{p.nilaiBon !== null && p.nilaiBon > 0 ? p.nilaiBon : "-"}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-2">
|
||||||
|
<div className="flex justify-end gap-1">
|
||||||
|
<PelangganForm
|
||||||
|
pelanggan={p}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
title="Edit pelanggan"
|
||||||
|
>
|
||||||
|
<PencilIcon />
|
||||||
|
<span className="sr-only">Edit</span>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<HapusPelangganButton
|
||||||
|
kdCustomer={p.kdCustomer}
|
||||||
|
nama={p.nama}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{rows.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colSpan={7}
|
||||||
|
className="px-3 py-10 text-center text-muted-foreground"
|
||||||
|
>
|
||||||
|
{page > 1 ? "Halaman ini kosong" : "Tidak ada pelanggan"}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pagination
|
||||||
|
page={page}
|
||||||
|
hasNext={hasNext}
|
||||||
|
hrefPage={(p) => halamanHref(p, search)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function halamanHref(page: number, search?: string): string {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (search) params.set("search", search);
|
||||||
|
if (page > 1) params.set("page", String(page));
|
||||||
|
const query = params.toString();
|
||||||
|
return query ? `/master/pelanggan?${query}` : "/master/pelanggan";
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { cn } from "cn";
|
||||||
|
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
|
type PaginationProps = {
|
||||||
|
page: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hrefPage: (page: number) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Pagination({ page, hasNext, hrefPage }: PaginationProps) {
|
||||||
|
return (
|
||||||
|
<div className="mt-4 flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Halaman {page}</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<TombolHalaman href={page > 1 ? hrefPage(page - 1) : null}>
|
||||||
|
Sebelumnya
|
||||||
|
</TombolHalaman>
|
||||||
|
<TombolHalaman href={hasNext ? hrefPage(page + 1) : null}>
|
||||||
|
Berikutnya
|
||||||
|
</TombolHalaman>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TombolHalaman({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
href: string | null;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
const className = buttonVariants({ variant: "outline", size: "sm" });
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
return (
|
||||||
|
<span className={cn(className, "pointer-events-none opacity-50")}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={href} className={className}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue