diff --git a/components/filter.tsx b/components/filter.tsx
new file mode 100644
index 0000000..f78fd42
--- /dev/null
+++ b/components/filter.tsx
@@ -0,0 +1,73 @@
+"use client";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { cn } from "@/lib/utils";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
+import { useState, useTransition } from "react";
+
+interface FilterOption {
+ value: string;
+ label: string;
+ icon: React.ReactNode;
+}
+
+interface FilterProps {
+ options: FilterOption[];
+ defaultOption: string;
+ queryParamKey: string;
+}
+
+export default function Filter({
+ options,
+ defaultOption,
+ queryParamKey,
+}: FilterProps) {
+ const [selectedOption, setSelectedOption] = useState(defaultOption);
+ const searchParams = useSearchParams();
+ const { replace } = useRouter();
+ const pathname = usePathname();
+ const [isPending, startTransition] = useTransition();
+
+ function handleFilterChange(value: string) {
+ setSelectedOption(value);
+ const params = new URLSearchParams(searchParams.toString());
+
+ params.set(queryParamKey, value);
+ params.set("page", "1");
+
+ startTransition(() => {
+ replace(`${pathname}?${params.toString()}`);
+ });
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/components/pagination.tsx b/components/pagination.tsx
new file mode 100644
index 0000000..bb04780
--- /dev/null
+++ b/components/pagination.tsx
@@ -0,0 +1,109 @@
+"use client";
+import { Button } from "@/components/ui/button";
+import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
+import Link from "next/link";
+import { useRouter, useSearchParams } from "next/navigation";
+import React, { useEffect, useState } from "react";
+
+type PaginationProps = {
+ totalPages: number;
+ currentPage: number;
+ maxVisible?: number;
+};
+
+export default function Pagination({
+ totalPages,
+ currentPage,
+ maxVisible = 4,
+}: PaginationProps) {
+ const searchParams = useSearchParams();
+ const activePage = searchParams.get("page") ?? 1;
+ const router = useRouter();
+
+ const [queryParams, setQueryParams] = useState<{ [key: string]: string }>({});
+
+ useEffect(() => {
+ const params = Object.fromEntries(
+ Array.from(searchParams.entries()).filter(([key]) => key !== "page"),
+ );
+ setQueryParams(params);
+ }, [searchParams]);
+
+ useEffect(() => {
+ if (!searchParams.has("page")) {
+ router.replace(`?page=1${IncludeQueries()}`);
+ }
+ });
+
+ function IncludeQueries() {
+ return Object.entries(queryParams)
+ .map(([key, value]) => `&${key}=${value}`)
+ .join("");
+ }
+
+ const generatePageNumbers = (): (number | string)[] => {
+ const halfVisible = Math.floor(maxVisible / 2);
+ let startPage = Math.max(currentPage - halfVisible, 1);
+ const endPage = Math.min(startPage + maxVisible - 1, totalPages);
+
+ if (endPage - startPage + 1 < maxVisible) {
+ startPage = Math.max(endPage - maxVisible + 1, 1);
+ }
+
+ const pageNumbers: (number | string)[] = [];
+
+ if (startPage > 1) {
+ pageNumbers.push(1);
+ if (startPage > 2) pageNumbers.push("...");
+ }
+
+ for (let i = startPage; i <= endPage; i++) {
+ pageNumbers.push(i);
+ }
+
+ if (endPage < totalPages) {
+ if (endPage < totalPages - 1) pageNumbers.push("...");
+ pageNumbers.push(totalPages);
+ }
+
+ return pageNumbers;
+ };
+
+ const pageNumbers = generatePageNumbers();
+
+ return (
+
+ {currentPage > 1 && (
+
+
+
+ )}
+
+ {pageNumbers.map((page) => (
+
+ {typeof page === "number" ? (
+
+
+
+ ) : (
+ ...
+ )}
+
+ ))}
+
+ {currentPage < totalPages && (
+
+
+
+ )}
+
+ );
+}
diff --git a/components/search.tsx b/components/search.tsx
new file mode 100644
index 0000000..0c798e0
--- /dev/null
+++ b/components/search.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import { Input } from "@/components/ui/input";
+import { cn } from "@/lib/utils";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
+import { useRef, useTransition } from "react";
+import { Button } from "./ui/button";
+import { Loader } from "lucide-react";
+
+export default function Search({ disabled }: { disabled?: boolean }) {
+ const inputRef = useRef(null);
+ const { replace } = useRouter();
+
+ const pathname = usePathname();
+ const [isPending, startTransition] = useTransition();
+ const searchParams = useSearchParams();
+ const searchQuery = searchParams.get("query");
+
+ function handleSearch(term: string) {
+ const params = new URLSearchParams(searchParams.toString());
+
+ if (term) {
+ params.set("query", term);
+ params.set("page", "1");
+ } else {
+ params.delete("query");
+ }
+
+ startTransition(() => {
+ replace(`${pathname}?${params.toString()}`);
+ });
+ }
+
+ return (
+
+ handleSearch(e.target.value)}
+ />
+
+
+ );
+}
diff --git a/components/user-table.tsx b/components/user-table.tsx
new file mode 100644
index 0000000..d8149f3
--- /dev/null
+++ b/components/user-table.tsx
@@ -0,0 +1,204 @@
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import prisma from "@/lib/db";
+import { Badge } from "./ui/badge";
+import Pagination from "./pagination";
+import { UserVerifyDialog } from "./user/user-verify-dialog";
+
+export async function UsersTable({
+ searchParams,
+}: {
+ searchParams: Promise<{
+ query: string;
+ page: number;
+ sortBy: string;
+ status: string;
+ }>;
+}) {
+ const query = (await searchParams)?.query || "";
+ const page = (await searchParams)?.page;
+ const sortBy = (await searchParams)?.sortBy || "asc";
+ const verified = (await searchParams)?.status || "all";
+ const totalUsers = await prisma.user.count({
+ where: {
+ OR: [
+ {
+ name: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ phoneNumber: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ house_name: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ id_card: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ ],
+ verified: verified === "all" ? undefined : verified === "verified",
+ },
+ });
+
+ const totalPages = Math.ceil(totalUsers / 10);
+ const limit = 10;
+ const offset = (Number(page) - 1) * limit || 0;
+
+ const users = await prisma.user.findMany({
+ where: {
+ OR: [
+ {
+ name: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ phoneNumber: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ house_name: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ {
+ id_card: {
+ contains: query || "",
+ mode: "insensitive",
+ },
+ },
+ ],
+ verified: verified === "all" ? undefined : verified === "verified",
+ },
+ include: {
+ island: true,
+ atoll: true,
+ },
+ skip: offset,
+ take: limit,
+ orderBy: {
+ name: `${sortBy}` as "asc" | "desc",
+ },
+ });
+
+ // const users = await prisma.user.findMany({
+ // where: {
+ // role: "USER",
+ // },
+ // include: {
+ // atoll: true,
+ // island: true,
+ // },
+ // });
+ return (
+
+ {users.length === 0 ? (
+
+
No Users yet.
+
+ ) : (
+ <>
+
+ Table of all users.
+
+
+ Name
+ ID Card
+ Atoll
+ Island
+ House Name
+ Status
+ Dob
+ Phone Number
+ Action
+
+
+
+ {users.map((user) => (
+
+ {user.name}
+ {user.id_card}
+ {user.atoll?.name}
+ {user.island?.name}
+ {user.house_name}
+
+
+ {user.verified ? (
+
+ Verified
+
+ ) : (
+
+ Unverified
+
+ )}
+
+
+ {new Date(user.dob ?? "").toLocaleDateString("en-US", {
+ month: "short",
+ day: "2-digit",
+ year: "numeric",
+ })}
+
+
+ {user.phoneNumber}
+
+
+
+
+ ))}
+
+
+
+
+ {query.length > 0 && (
+
+ Showing {users.length} locations for "{query}
+ "
+
+ )}
+
+
+ {totalUsers} users
+
+
+
+
+
+ >
+ )}
+
+ );
+}