diff --git a/README.md b/README.md index 5d7f9a0..cf12364 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is a web portal for SAR Link customers. ### Parental Control - [x] Fix block device feature - - [ ] Add all the filters for parental control table (mobile responsive) + - [x] Add all the filters for parental control table (mobile responsive) - [ ] Disable blocking if payment is pending or omit from the table if device payment is pending ### Agreements @@ -30,7 +30,7 @@ This is a web portal for SAR Link customers. ### Users - [x] Show users table - [ ] handle verify api no response case -- [ ] Add all relavant filters for users table +- [x] Add all relavant filters for users table - [x] Verify or reject users with a custom message - [ ] Add functionality to send custom sms to users in user:id page diff --git a/app/(dashboard)/user-devices/page.tsx b/app/(dashboard)/user-devices/page.tsx index a214eb6..95f3794 100644 --- a/app/(dashboard)/user-devices/page.tsx +++ b/app/(dashboard)/user-devices/page.tsx @@ -60,7 +60,7 @@ export default async function UserDevices({ /> - + ); diff --git a/app/(dashboard)/user-topups/page.tsx b/app/(dashboard)/user-topups/page.tsx new file mode 100644 index 0000000..cff10fb --- /dev/null +++ b/app/(dashboard)/user-topups/page.tsx @@ -0,0 +1,86 @@ +import { Suspense } from "react"; +import { AdminTopupsTable } from "@/components/admin/admin-topup-table"; +import DynamicFilter from "@/components/generic-filter"; + +export default async function UserTopups({ + searchParams, +}: { + searchParams: Promise<{ + [key: string]: string; + }>; +}) { + const query = (await searchParams)?.query || ""; + // const session = await getServerSession(authOptions); + return ( +
+
+

User Topups

+
+ + + + +
+ ); +} diff --git a/components/admin/admin-devices-table.tsx b/components/admin/admin-devices-table.tsx index 398c2cb..e8e4fab 100644 --- a/components/admin/admin-devices-table.tsx +++ b/components/admin/admin-devices-table.tsx @@ -18,21 +18,18 @@ import { getDevices } from "@/queries/devices"; import { tryCatch } from "@/utils/tryCatch"; import BlockDeviceDialog from "../block-device-dialog"; import ClientErrorMessage from "../client-error-message"; -import DeviceCard from "../device-card"; import Pagination from "../pagination"; export async function AdminDevicesTable({ searchParams, - parentalControl, }: { searchParams: Promise<{ [key: string]: unknown; }>; - parentalControl?: boolean; }) { const resolvedParams = await searchParams; const session = await getServerSession(authOptions); - const isAdmin = session?.user?.is_superuser; + const isAdmin = session?.user?.is_admin; const page = Number.parseInt(resolvedParams.page as string) || 1; const limit = 10; @@ -67,7 +64,7 @@ export async function AdminDevicesTable({ ) : ( <> -
+
Table of all devices. @@ -167,15 +164,7 @@ export async function AdminDevicesTable({
-
- {data?.map((device) => ( - - ))} -
+ ; +}) { + const resolvedParams = await searchParams; + const page = Number.parseInt(resolvedParams.page as string) || 1; + const limit = 10; + const offset = (page - 1) * limit; + // Build params object + const apiParams: Record = {}; + for (const [key, value] of Object.entries(resolvedParams)) { + if (value !== undefined && value !== "") { + apiParams[key] = typeof value === "number" ? value : String(value); + } + } + apiParams.limit = limit; + apiParams.offset = offset; + const [error, topups] = await tryCatch(getTopups(apiParams,)); + + if (error) { + if (error.message.includes("Unauthorized")) { + redirect("/auth/signin"); + } else { + return
{JSON.stringify(error, null, 2)}
; + } + } + const { data, meta } = topups; + return ( +
+ {data?.length === 0 ? ( +
+

No topups yet.

+
+ ) : ( + <> +
+ + Table of all topups. + + + User + Status + Amount + Action + + + + {topups?.data?.map((topup) => ( + + + +
+ {topup?.user?.name} + {topup?.user?.id_card} +
+
+ + + {topup.paid ? ( + + {topup.status} + + ) : topup.is_expired ? ( + Expired + ) : ( + {topup.status} + )} + + + + + {topup.amount.toFixed(2)} + + MVR + + +
+
+ + + +
+
+
+
+ ))} +
+ + + + {meta?.total === 1 ? ( +

Total {meta?.total} topup.

+ ) : ( +

Total {meta?.total} topups.

+ )} +
+
+
+
+
+
+ {data.map((topup) => ( + + ))} +
+ + + )} +
+ ); +} + +function MobileTopupDetails({ topup }: { topup: Topup }) { + return ( +
+
+ + + {new Date(topup.created_at).toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + })} + +
+
+ {topup?.user?.name} + {topup?.user?.id_card} +
+
+ + + +
+ +
+
+

Amount

+ + {topup.amount.toFixed(2)} MVR + +
+ + {topup.paid ? ( + + {topup.status} + + ) : topup.is_expired ? ( + Expired + ) : ( + {topup.status} + )} + +
+
+ ); +} diff --git a/components/admin/user-payments-table.tsx b/components/admin/user-payments-table.tsx index 32d2c9c..59483ff 100644 --- a/components/admin/user-payments-table.tsx +++ b/components/admin/user-payments-table.tsx @@ -95,13 +95,13 @@ export async function UsersPaymentsTable({ {/* {payment.user.id_card} */}
- {payment.devices[0]?.user?.name} + {payment?.user?.name} - {payment.devices[0]?.user?.id_card} + {payment?.user?.id_card}
{" "}
- {payment.amount} + {payment.amount} MVR {payment.number_of_months} Months @@ -144,7 +144,7 @@ export async function UsersPaymentsTable({ - + diff --git a/components/block-device-dialog.tsx b/components/block-device-dialog.tsx index 55d1f89..9d671a9 100644 --- a/components/block-device-dialog.tsx +++ b/components/block-device-dialog.tsx @@ -139,7 +139,7 @@ export default function BlockDeviceDialog({ id="reason_for_blocking" defaultValue={(state?.payload?.get("reason_for_blocking") || "") as string} className={cn( - "col-span-5", + "col-span-5 mt-2", (state.fieldErrors?.reason_for_blocking) && "ring-2 ring-red-500", )} /> diff --git a/components/device-card.tsx b/components/device-card.tsx index 1f3fb72..72c5393 100644 --- a/components/device-card.tsx +++ b/components/device-card.tsx @@ -12,12 +12,13 @@ import { Badge } from "./ui/badge"; export default function DeviceCard({ device, parentalControl, -}: { device: Device; parentalControl?: boolean }) { +}: { device: Device; parentalControl?: boolean, isAdmin?: boolean }) { const [devices, setDeviceCart] = useAtom(deviceCartAtom); const isChecked = devices.some((d) => d.id === device.id); return ( + // biome-ignore lint/a11y/noStaticElementInteractions:
{ }} onClick={() => { @@ -58,6 +59,7 @@ export default function DeviceCard({
+ {device.is_active ? (
Active until{" "} @@ -77,7 +79,7 @@ export default function DeviceCard({ )} {device.has_a_pending_payment && ( - + Payment Pending{" "} diff --git a/components/payments-table.tsx b/components/payments-table.tsx index 5dcab40..2abf0e3 100644 --- a/components/payments-table.tsx +++ b/components/payments-table.tsx @@ -189,7 +189,7 @@ export async function PaymentsTable({ ); } -function MobilePaymentDetails({ payment }: { payment: Payment }) { +export function MobilePaymentDetails({ payment, isAdmin = false }: { payment: Payment, isAdmin?: boolean }) { return (
{payment.status} )} + {isAdmin && ( +
+ {payment?.user?.name} + {payment?.user?.id_card} +
+ )}
diff --git a/components/ui/app-sidebar.tsx b/components/ui/app-sidebar.tsx index 5dc6431..2d43736 100644 --- a/components/ui/app-sidebar.tsx +++ b/components/ui/app-sidebar.tsx @@ -122,6 +122,12 @@ export async function AppSidebar({ icon: , perm_identifier: "payment", }, + { + title: "User Topups", + link: "/user-topups", + icon: , + perm_identifier: "topup", + }, { title: "Price Calculator", link: "/price-calculator", diff --git a/lib/backend-types.ts b/lib/backend-types.ts index 1cdfcd5..9a43cfd 100644 --- a/lib/backend-types.ts +++ b/lib/backend-types.ts @@ -87,7 +87,9 @@ export interface Payment { updated_at: string; status: "CANCELLED" | "PENDING" | "PAID"; mib_reference: string | null; - user: number; + user: Pick & { + name: string; + }; } export interface NewPayment {