Enhance device management and user experience features

- Updated `package.json` to include the latest version of `@radix-ui/react-separator` and added `moment` for date handling.
- Modified `blockDevice` function in `omada-actions.ts` to include a `blockedBy` parameter, allowing differentiation between admin and parent actions.
- Refactored `payment.ts` to include expiry date handling for devices during payment processing.
- Improved `DevicesTable` and `ClickableRow` components to support admin functionalities and enhance device interaction.
- Updated `BlockDeviceDialog` to accept an `admin` prop, allowing for tailored blocking actions based on user role.
- Enhanced UI components for better consistency and responsiveness across the dashboard.

These changes improve the overall functionality and maintainability of the application, providing a better user experience in device management.
This commit is contained in:
2025-01-01 23:48:56 +05:00
parent bdf3729b0d
commit 745f8d8fad
16 changed files with 378 additions and 213 deletions

View File

@ -13,11 +13,19 @@ import Link from "next/link";
import { auth } from "@/lib/auth";
import { cn } from "@/lib/utils";
import type { Payment, Prisma } from "@prisma/client";
import { Calendar } from "lucide-react";
import { headers } from "next/headers";
import Pagination from "./pagination";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { Separator } from "./ui/separator"
type PaymentWithDevices = Prisma.PaymentGetPayload<{
include: {
devices: true;
};
}>
export async function PaymentsTable({
searchParams,
@ -90,85 +98,143 @@ export async function PaymentsTable({
</div>
) : (
<>
<Table className="overflow-scroll">
<TableCaption>Table of all devices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Details</TableHead>
<TableHead>Duration</TableHead>
<div className="hidden sm:block">
<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>
<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 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 border">
<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>
<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 &quot;{query}
&quot;
</p>
)}
</TableCell>
<TableCell className="font-medium">
{payment.numberOfMonths} Months
</TableCell>
<TableCell>
<span className="font-semibold pr-2">
{payment.amount.toFixed(2)}
</span>
MVR
<TableCell className="text-muted-foreground">
{totalPayments} payments
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>
{query.length > 0 && (
<p className="text-sm text-muted-foreground">
Showing {payments.length} locations for &quot;{query}
&quot;
</p>
)}
</TableCell>
<TableCell className="text-muted-foreground">
{totalPayments} payments
</TableCell>
</TableRow>
</TableFooter>
</Table>
<Pagination totalPages={totalPages} currentPage={page} />
</TableFooter>
</Table>
<Pagination totalPages={totalPages} currentPage={page} />
</div>
<div className="sm:hidden block">
{payments.map((payment) => (
<MobilePaymentDetails key={payment.id} payment={payment} />
))}
</div>
</>
)}
</div>
);
}
function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) {
return (
<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 text-sm">
{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 border">
<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 className="block sm:hidden">
<Separator className="my-2" />
<h3 className="text-sm font-medium">Duration</h3>
<span className="text-sm text-muted-foreground">
{payment.numberOfMonths} Months
</span>
<Separator className="my-2" />
<h3 className="text-sm font-medium">Amount</h3>
<span className="text-sm text-muted-foreground">
{payment.amount.toFixed(2)} MVR
</span>
</div>
</div>
</div>
)
}