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
+
+
+
+
+
+
+ View Details
+
+
+
+
+
+
+ ))}
+
+
+
+
+ {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}
+
+
+
+
+ View Details
+
+
+
+
+
+
+
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({
-
+
Details
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 {