mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 17:42:00 +00:00
- Added a new `bun.lockb` file for dependency management. - Updated `next.config.ts` to set output to "standalone" for better deployment options. - Removed `package-lock.json` to streamline package management. - Modified `package.json` to update dependencies, including `@prisma/client` and `sonner`, and adjusted build scripts for improved functionality. - Enhanced Tailwind CSS configuration to include new animations and color schemes. - Refactored various dashboard components to improve UI consistency, including adding a new `My Wallet` page and updating existing pages to use a unified styling approach. - Introduced a new `BlockDeviceDialog` component for managing device blocking with user-defined reasons. - Improved logging and error handling in payment verification and device management functions. These changes enhance the overall functionality, maintainability, and user experience of the application.
175 lines
5.5 KiB
TypeScript
175 lines
5.5 KiB
TypeScript
import {
|
|
Table,
|
|
TableBody,
|
|
TableCaption,
|
|
TableCell,
|
|
TableFooter,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import prisma from "@/lib/db";
|
|
import Link from "next/link";
|
|
|
|
import { auth } from "@/lib/auth";
|
|
import { cn } from "@/lib/utils";
|
|
import { Calendar } from "lucide-react";
|
|
import { headers } from "next/headers";
|
|
import Pagination from "./pagination";
|
|
import { Badge } from "./ui/badge";
|
|
import { Button } from "./ui/button";
|
|
|
|
export async function PaymentsTable({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{
|
|
query: string;
|
|
page: number;
|
|
sortBy: string;
|
|
}>;
|
|
}) {
|
|
const session = await auth.api.getSession({
|
|
headers: await headers()
|
|
})
|
|
const query = (await searchParams)?.query || "";
|
|
const page = (await searchParams)?.page;
|
|
const totalPayments = await prisma.payment.count({
|
|
where: {
|
|
userId: session?.session.userId,
|
|
OR: [
|
|
{
|
|
devices: {
|
|
every: {
|
|
name: {
|
|
contains: query || "",
|
|
mode: "insensitive",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const totalPages = Math.ceil(totalPayments / 10);
|
|
const limit = 10;
|
|
const offset = (Number(page) - 1) * limit || 0;
|
|
|
|
const payments = await prisma.payment.findMany({
|
|
where: {
|
|
userId: session?.session.userId,
|
|
OR: [
|
|
{
|
|
devices: {
|
|
every: {
|
|
name: {
|
|
contains: query || "",
|
|
mode: "insensitive",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
include: {
|
|
devices: true
|
|
},
|
|
|
|
skip: offset,
|
|
take: limit,
|
|
orderBy: {
|
|
createdAt: "desc",
|
|
},
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
{payments.length === 0 ? (
|
|
<div className="h-[calc(100svh-400px)] flex flex-col items-center justify-center my-4">
|
|
<h3>No Payments yet.</h3>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<Table className="overflow-scroll">
|
|
<TableCaption>Table of all devices.</TableCaption>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Details</TableHead>
|
|
<TableHead>Duration</TableHead>
|
|
|
|
<TableHead>Amount</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody className="overflow-scroll">
|
|
{payments.map((payment) => (
|
|
<TableRow key={payment.id}>
|
|
<TableCell>
|
|
<div className={cn("flex flex-col items-start border rounded p-2", payment?.paid ? "bg-green-500/10 border-dashed border-green=500" : "bg-yellow-500/10 border-dashed border-yellow-500 dark:border-yellow-500/50")}>
|
|
<div className="flex items-center gap-2">
|
|
<Calendar size={16} opacity={0.5} />
|
|
<span className="text-muted-foreground">
|
|
{new Date(payment.createdAt).toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "2-digit",
|
|
year: "numeric",
|
|
})}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 mt-2">
|
|
<Link className="font-medium hover:underline" href={`/payments/${payment.id}`}>
|
|
<Button size={"sm"} variant="outline">
|
|
View Details
|
|
</Button>
|
|
</Link>
|
|
<Badge className={cn(payment?.paid ? "text-green-500 bg-green-500/20" : "text-yellow-500 bg-yellow-500/20")} variant={payment.paid ? "outline" : "secondary"}>
|
|
{payment.paid ? "Paid" : "Unpaid"}
|
|
</Badge>
|
|
</div>
|
|
<div className="bg-white dark:bg-black p-2 rounded mt-2 w-full">
|
|
<h3 className="text-sm font-medium">Devices</h3>
|
|
<ol className="list-disc list-inside text-sm">
|
|
{payment.devices.map((device) => (
|
|
<li key={device.id} className="text-sm text-muted-foreground">
|
|
{device.name}
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="font-medium">
|
|
{payment.numberOfMonths} Months
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="font-semibold pr-2">
|
|
{payment.amount.toFixed(2)}
|
|
</span>
|
|
MVR
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
<TableFooter>
|
|
<TableRow>
|
|
<TableCell colSpan={2}>
|
|
{query.length > 0 && (
|
|
<p className="text-sm text-muted-foreground">
|
|
Showing {payments.length} locations for "{query}
|
|
"
|
|
</p>
|
|
)}
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground">
|
|
{totalPayments} payments
|
|
</TableCell>
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
<Pagination totalPages={totalPages} currentPage={page} />
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|