Merge pull request #7 from mitchell131/transactions-page
Transactions page
This commit is contained in:
commit
7dd4323ebe
191
payment-iq/app/api/transactions/deposit/mockData.ts
Normal file
191
payment-iq/app/api/transactions/deposit/mockData.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { GridColDef } from "@mui/x-data-grid";
|
||||
|
||||
|
||||
export const depositTransactionDummyData = [
|
||||
{
|
||||
id: 1,
|
||||
userId: 17,
|
||||
merchandId: 100987998,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Completed",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userId: 17,
|
||||
merchandId: 100987998,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Completed",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
userId: 17,
|
||||
merchandId: 100987997,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Complete",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
userId: 19,
|
||||
merchandId: 100987997,
|
||||
transactionId: 1049136973,
|
||||
depositMethod: "Card",
|
||||
status: "Completed",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
userId: 19,
|
||||
merchandId: 100987998,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Completed",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
userId: 27,
|
||||
merchandId: 100987997,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Pending",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
userId: 175,
|
||||
merchandId: 100987938,
|
||||
transactionId: 1049136973,
|
||||
depositMethod: "Card",
|
||||
status: "Pending",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
userId: 172,
|
||||
merchandId: 100987938,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Card",
|
||||
status: "Pending",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-12 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
userId: 174,
|
||||
merchandId: 100987938,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Bank Transfer",
|
||||
status: "Inprogress",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
userId: 7,
|
||||
merchandId: 100987998,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Bank Transfer",
|
||||
status: "Inprogress",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
userId: 1,
|
||||
merchandId: 100987998,
|
||||
transactionId: 1049131973,
|
||||
depositMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
currency: "EUR",
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
];
|
||||
|
||||
export const depositTransactionsColumns: GridColDef[] = [
|
||||
{ field: "userId", headerName: "User ID", width: 130 },
|
||||
{ field: "merchandId", headerName: "Merchant ID", width: 130 },
|
||||
{ field: "transactionId", headerName: "Transaction ID", width: 130 },
|
||||
{ field: "depositMethod", headerName: "Deposit Method", width: 130 },
|
||||
{ field: "status", headerName: "Status", width: 130 },
|
||||
{ field: "amount", headerName: "Amount", width: 130 },
|
||||
{ field: "currency", headerName: "Currency", width: 130 },
|
||||
{ field: "dateTime", headerName: "Date / Time", width: 130 },
|
||||
{ field: "errorInfo", headerName: "Error Info", width: 130 },
|
||||
{ field: "fraudScore", headerName: "Fraud Score", width: 130 },
|
||||
]
|
||||
|
||||
|
||||
export const depositTransactionsSearchLabels = [
|
||||
{ label: "User", field: "userId", type: "text" },
|
||||
{ label: "Transaction ID", field: "transactionId", type: "text" },
|
||||
{
|
||||
label: "Transaction Reference ID",
|
||||
field: "transactionReferenceId",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
label: "Currency",
|
||||
field: "currency",
|
||||
type: "select",
|
||||
options: ["USD", "EUR", "GBP"]
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
field: "status",
|
||||
type: "select",
|
||||
options: ["Pending","Inprogress", "Completed", "Failed"]
|
||||
},
|
||||
{
|
||||
label: "Payment Method",
|
||||
field: "depositMethod",
|
||||
type: "select",
|
||||
options: ["Card", "Bank Transfer"]
|
||||
},
|
||||
{ label: "Date / Time", field: "dateTime", type: "date" },
|
||||
]
|
||||
57
payment-iq/app/api/transactions/deposit/route.ts
Normal file
57
payment-iq/app/api/transactions/deposit/route.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { depositTransactionDummyData, depositTransactionsColumns, depositTransactionsSearchLabels } from "./mockData";
|
||||
import { formatToDateTimeString } from "@/app/utils/formatDate";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const status = searchParams.get("status");
|
||||
const userId = searchParams.get("userId");
|
||||
const depositMethod = searchParams.get("depositMethod");
|
||||
const merchandId = searchParams.get("merchandId");
|
||||
const transactionId = searchParams.get("transactionId");
|
||||
const dateTime = searchParams.get("dateTime");
|
||||
|
||||
|
||||
let filteredTransactions = [...depositTransactionDummyData];
|
||||
|
||||
if (userId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.userId.toString() === userId
|
||||
);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.status.toLowerCase() === status.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (depositMethod) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.depositMethod.toLowerCase() === depositMethod.toLowerCase()
|
||||
);
|
||||
}
|
||||
if (merchandId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.merchandId.toString() === merchandId
|
||||
);
|
||||
}
|
||||
if (transactionId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.transactionId.toString() === transactionId
|
||||
);
|
||||
}
|
||||
|
||||
if (dateTime) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.dateTime.split(" ")[0] === formatToDateTimeString(dateTime).split(" ")[0]
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
filteredTransactions: filteredTransactions,
|
||||
transactionsSearchLabels: depositTransactionsSearchLabels,
|
||||
transactionsColumns: depositTransactionsColumns
|
||||
});
|
||||
}
|
||||
176
payment-iq/app/api/transactions/withdrawal/mockData.ts
Normal file
176
payment-iq/app/api/transactions/withdrawal/mockData.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import { GridColDef } from "@mui/x-data-grid";
|
||||
|
||||
export const withdrawalTransactionDummyData = [
|
||||
{
|
||||
id: 1,
|
||||
userId: 17,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userId: 17,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
userId: 17,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Complete",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
userId: 19,
|
||||
transactionId: 1049136973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Completed",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-18 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
userId: 19,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
userId: 27,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
userId: 1,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
userId: 172,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Card",
|
||||
status: "Pending",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-12 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
userId: 174,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Inprogress",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
userId: 1,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
userId: 1,
|
||||
transactionId: 1049131973,
|
||||
withdrawalMethod: "Bank Transfer",
|
||||
status: "Error",
|
||||
amount: 4000,
|
||||
dateTime: "2025-06-17 10:10:30",
|
||||
errorInfo: "-",
|
||||
fraudScore: "frad score 1234",
|
||||
manualCorrectionFlag: "-",
|
||||
informationWhoApproved: "-",
|
||||
},
|
||||
];
|
||||
|
||||
export const withdrawalTransactionsColumns: GridColDef[] = [
|
||||
{ field: "userId", headerName: "User ID", width: 130 },
|
||||
{ field: "transactionId", headerName: "Transaction ID", width: 130 },
|
||||
{ field: "withdrawalMethod", headerName: "Withdrawal Method", width: 130 },
|
||||
{ field: "status", headerName: "Status", width: 130 },
|
||||
{ field: "amount", headerName: "Amount", width: 130 },
|
||||
{ field: "dateTime", headerName: "Date / Time", width: 130 },
|
||||
{ field: "errorInfo", headerName: "Error Info", width: 130 },
|
||||
{ field: "fraudScore", headerName: "Fraud Score", width: 130 },
|
||||
{
|
||||
field: "manualCorrectionFlag",
|
||||
headerName: "Manual Correction Flag",
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
field: "informationWhoApproved",
|
||||
headerName: "Information who approved",
|
||||
width: 130,
|
||||
},
|
||||
];
|
||||
|
||||
export const withdrawalTransactionsSearchLabels = [
|
||||
{
|
||||
label: "Status",
|
||||
field: "status",
|
||||
type: "select",
|
||||
options: ["Pending", "Inprogress", "Completed", "Failed"],
|
||||
},
|
||||
{
|
||||
label: "Payment Method",
|
||||
field: "depositMethod",
|
||||
type: "select",
|
||||
options: ["Card", "Bank Transfer"],
|
||||
},
|
||||
{ label: "Date / Time", field: "dateTime", type: "date" },
|
||||
];
|
||||
45
payment-iq/app/api/transactions/withdrawal/route.ts
Normal file
45
payment-iq/app/api/transactions/withdrawal/route.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { withdrawalTransactionDummyData, withdrawalTransactionsColumns, withdrawalTransactionsSearchLabels } from "./mockData"
|
||||
import { formatToDateTimeString } from "@/app/utils/formatDate";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const userId = searchParams.get("userId");
|
||||
const status = searchParams.get("status");
|
||||
const dateTime = searchParams.get("dateTime");
|
||||
const withdrawalMethod = searchParams.get("withdrawalMethod");
|
||||
|
||||
|
||||
let filteredTransactions = [...withdrawalTransactionDummyData];
|
||||
|
||||
if (userId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.userId.toString() === userId
|
||||
);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.status.toLowerCase() === status.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (withdrawalMethod) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.withdrawalMethod.toLowerCase() === withdrawalMethod.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (dateTime) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
(tx) => tx.dateTime.split(" ")[0] === formatToDateTimeString(dateTime).split(" ")[0]
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
filteredTransactions: filteredTransactions,
|
||||
transactionsColumns: withdrawalTransactionsColumns,
|
||||
transactionsSearchLabels: withdrawalTransactionsSearchLabels
|
||||
});
|
||||
}
|
||||
@ -1,31 +1,57 @@
|
||||
// components/SearchFilters.js
|
||||
import React from 'react';
|
||||
import { Box, Chip, Typography, Button } from '@mui/material';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
const SearchFilters = ({ filters, onDeleteFilter, onClearAll }) => {
|
||||
const renderChip = (label, value, key) => (
|
||||
interface SearchFiltersProps {
|
||||
filters: Record<string, string>;
|
||||
}
|
||||
|
||||
const SearchFilters = ({ filters }: SearchFiltersProps) => {
|
||||
const router = useRouter();
|
||||
const filterLabels: Record<string, string> = {
|
||||
userId: "User",
|
||||
state: "State",
|
||||
startDate: "Start Date",
|
||||
// Add others here
|
||||
};
|
||||
const searchParams = useSearchParams()
|
||||
const handleDeleteFilter = (key: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.delete(key);
|
||||
router.push(`?${params.toString()}`);
|
||||
};
|
||||
|
||||
|
||||
const onClearAll = () => {
|
||||
router.push("?");
|
||||
}
|
||||
|
||||
const renderChip = (label: string, value: string, key: string) => (
|
||||
<Chip
|
||||
key={key}
|
||||
label={
|
||||
<Typography variant="body2" sx={{ fontWeight: key === 'state' ? 'bold' : 'normal' }}>
|
||||
{label} {value}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: key === "state" ? "bold" : "normal" }}
|
||||
>
|
||||
{label}: {value}
|
||||
</Typography>
|
||||
}
|
||||
onDelete={() => onDeleteFilter(key)}
|
||||
onDelete={() => handleDeleteFilter(key)}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center" flexWrap="wrap" sx={{ p: 2 }}>
|
||||
{filters.user && renderChip('User', filters.user, 'user')}
|
||||
{filters.state && renderChip('State', filters.state, 'state')}
|
||||
{filters.startDate && renderChip('Start Date', filters.startDate, 'startDate')}
|
||||
{Object.entries(filters).map(([key, value]) =>
|
||||
value ? renderChip(filterLabels[key] ?? key, value, key) : null
|
||||
)}
|
||||
|
||||
{Object.values(filters).some(Boolean) && (
|
||||
<Button
|
||||
onClick={onClearAll}
|
||||
sx={{ ml: 1, textDecoration: 'underline', color: 'black' }}
|
||||
sx={{ ml: 1, textDecoration: "underline", color: "black" }}
|
||||
>
|
||||
Clear All
|
||||
</Button>
|
||||
@ -34,4 +60,5 @@ const SearchFilters = ({ filters, onDeleteFilter, onClearAll }) => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SearchFilters;
|
||||
|
||||
23
payment-iq/app/dashboard/transactions/deposits/page.tsx
Normal file
23
payment-iq/app/dashboard/transactions/deposits/page.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import TransactionsTable from "@/app/features/Pages/Transactions/TransactionsTable";
|
||||
import { getTransactions } from "@/app/services/transactions";
|
||||
|
||||
export default async function DepositTransactionPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
// Await searchParams before processing
|
||||
const params = await searchParams;
|
||||
// Create a safe query string by filtering only string values
|
||||
const safeParams: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (typeof value === "string") {
|
||||
safeParams[key] = value;
|
||||
}
|
||||
}
|
||||
const query = new URLSearchParams(safeParams).toString();
|
||||
const transactionType = 'deposit';
|
||||
const data = await getTransactions({ transactionType, query });
|
||||
|
||||
return <TransactionsTable res={data}/>;
|
||||
}
|
||||
23
payment-iq/app/dashboard/transactions/history/page.tsx
Normal file
23
payment-iq/app/dashboard/transactions/history/page.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import TransactionsTable from "@/app/features/Pages/Transactions/TransactionsTable";
|
||||
import { getTransactions } from "@/app/services/transactions";
|
||||
|
||||
export default async function DepositTransactionPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
// Await searchParams before processing
|
||||
const params = await searchParams;
|
||||
// Create a safe query string by filtering only string values
|
||||
const safeParams: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (typeof value === "string") {
|
||||
safeParams[key] = value;
|
||||
}
|
||||
}
|
||||
const query = new URLSearchParams(safeParams).toString();
|
||||
const transactionType = 'deposit';
|
||||
const data = await getTransactions({ transactionType, query });
|
||||
|
||||
return <TransactionsTable res={data}/>;
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
// This ensures this component is rendered only on the client side
|
||||
|
||||
import TransactionTable from "@/app/features/Pages/Transactions/Transactions";
|
||||
|
||||
|
||||
export default function TransactionPage() {
|
||||
return (
|
||||
<div style={{ width: "70%" }}>
|
||||
{/* This page will now be rendered on the client-side */}
|
||||
<TransactionTable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
payment-iq/app/dashboard/transactions/withdrawals/page.tsx
Normal file
23
payment-iq/app/dashboard/transactions/withdrawals/page.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import TransactionsTable from "@/app/features/Pages/Transactions/TransactionsTable";
|
||||
import { getTransactions } from "@/app/services/transactions";
|
||||
|
||||
export default async function DepositTransactionPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
// Await searchParams before processing
|
||||
const params = await searchParams;
|
||||
// Create a safe query string by filtering only string values
|
||||
const safeParams: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (typeof value === "string") {
|
||||
safeParams[key] = value;
|
||||
}
|
||||
}
|
||||
const query = new URLSearchParams(safeParams).toString();
|
||||
const transactionType = 'withdrawal';
|
||||
const data = await getTransactions({ transactionType, query });
|
||||
|
||||
return <TransactionsTable res={data}/>;
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
@ -9,168 +8,70 @@ import {
|
||||
Select,
|
||||
Typography,
|
||||
Stack,
|
||||
debounce,
|
||||
} from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
|
||||
const currencies = ["USD", "EUR", "GBP"];
|
||||
const states = ["Pending", "Completed", "Failed"];
|
||||
const transactionTypes = ["Credit", "Debit"];
|
||||
const paymentMethods = ["Card", "Bank Transfer"];
|
||||
|
||||
export default function AdvancedSearch({ setForm, form, resetForm }) {
|
||||
interface ILabel {
|
||||
label: string;
|
||||
field: string;
|
||||
type: string;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formValues, setFormValues] = useState<Record<string, string>>({});
|
||||
|
||||
const handleChange = (field: string, value: any) => {
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
useEffect(() => {
|
||||
const initialParams = Object.fromEntries(searchParams.entries());
|
||||
setFormValues(initialParams);
|
||||
}, [searchParams]);
|
||||
|
||||
|
||||
const updateURL = useMemo(
|
||||
() =>
|
||||
debounce((newValues: Record<string, string>) => {
|
||||
const updatedParams = new URLSearchParams();
|
||||
Object.entries(newValues).forEach(([key, value]) => {
|
||||
if (value) updatedParams.set(key, value);
|
||||
});
|
||||
router.push(`?${updatedParams.toString()}`);
|
||||
}, 500),
|
||||
[router]
|
||||
);
|
||||
|
||||
const handleFieldChange = (field: string, value: string) => {
|
||||
const updatedValues = { ...formValues, [field]: value };
|
||||
setFormValues(updatedValues);
|
||||
updateURL(updatedValues);
|
||||
};
|
||||
|
||||
const toggleDrawer =
|
||||
(open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (
|
||||
event.type === "keydown" &&
|
||||
((event as React.KeyboardEvent).key === "Tab" ||
|
||||
(event as React.KeyboardEvent).key === "Shift")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const resetForm = () => {
|
||||
setFormValues({});
|
||||
router.push("?");
|
||||
};
|
||||
|
||||
setOpen(open);
|
||||
};
|
||||
const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (
|
||||
event.type === "keydown" &&
|
||||
((event as React.KeyboardEvent).key === "Tab" ||
|
||||
(event as React.KeyboardEvent).key === "Shift")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setOpen(open);
|
||||
};
|
||||
|
||||
const list = () => (
|
||||
<Box sx={{ width: 400 }} role="presentation">
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<Box p={2}>
|
||||
<Box sx={{ display: "flex", gap: "60px" }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Search
|
||||
</Typography>
|
||||
{/* Buttons */}
|
||||
<Box display="flex" justifyContent="flex-end" gap={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<SearchIcon />}
|
||||
onClick={() => console.log("Apply Filter", form)}
|
||||
sx={{ "& .span": { margin: "0px", padding: "0px" } }}
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon sx={{ margin: "0px" }} />}
|
||||
onClick={resetForm}
|
||||
sx={{ "& span": { margin: "0px", padding: "0px" } }}
|
||||
></Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Stack spacing={2}>
|
||||
{[
|
||||
{ label: "Keyword", field: "keyword", type: "text" },
|
||||
{ label: "Transaction ID", field: "transactionID", type: "text" },
|
||||
{
|
||||
label: "Transaction Reference ID",
|
||||
field: "transactionReferenceId",
|
||||
type: "text",
|
||||
},
|
||||
{ label: "User", field: "user", type: "text" },
|
||||
{
|
||||
label: "Currency",
|
||||
field: "currency",
|
||||
type: "select",
|
||||
options: currencies,
|
||||
},
|
||||
{
|
||||
label: "State",
|
||||
field: "state",
|
||||
type: "select",
|
||||
options: states,
|
||||
},
|
||||
{
|
||||
label: "Status Description",
|
||||
field: "statusDescription",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
label: "Transaction Type",
|
||||
field: "transactionType",
|
||||
type: "select",
|
||||
options: transactionTypes,
|
||||
},
|
||||
{
|
||||
label: "Payment Method",
|
||||
field: "paymentMethod",
|
||||
type: "select",
|
||||
options: paymentMethods,
|
||||
},
|
||||
{ label: "PSPs", field: "psps", type: "text" },
|
||||
{ label: "Initial PSPs", field: "initialPsps", type: "text" },
|
||||
{ label: "Merchants", field: "merchants", type: "text" },
|
||||
{ label: "Start Date", field: "startDate", type: "date" },
|
||||
{ label: "End Date", field: "endDate", type: "date" },
|
||||
{
|
||||
label: "Last Updated From",
|
||||
field: "lastUpdatedFrom",
|
||||
type: "date",
|
||||
},
|
||||
{
|
||||
label: "Last Updated To",
|
||||
field: "lastUpdatedTo",
|
||||
type: "date",
|
||||
},
|
||||
{ label: "Min Amount", field: "minAmount", type: "text" },
|
||||
{ label: "Max Amount", field: "maxAmount", type: "text" },
|
||||
{ label: "Channel", field: "channel", type: "text" },
|
||||
].map(({ label, field, type, options }) => (
|
||||
<Box key={field}>
|
||||
<Typography variant="body2" fontWeight={600} mb={0.5}>
|
||||
{label}
|
||||
</Typography>
|
||||
{type === "text" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
value={form[field]}
|
||||
onChange={(e) => handleChange(field, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
{type === "select" && (
|
||||
<FormControl fullWidth size="small">
|
||||
<Select
|
||||
value={form[field]}
|
||||
onChange={(e) => handleChange(field, e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>{label}</em>
|
||||
</MenuItem>
|
||||
{options.map((option) => (
|
||||
<MenuItem value={option} key={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{type === "date" && (
|
||||
<DatePicker
|
||||
value={form[field]}
|
||||
onChange={(newValue) => handleChange(field, newValue)}
|
||||
renderInput={(params) => (
|
||||
<TextField fullWidth size="small" {...params} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box sx={{ width: '185px' }}>
|
||||
<Button
|
||||
@ -199,10 +100,87 @@ export default function AdvancedSearch({ setForm, form, resetForm }) {
|
||||
>
|
||||
Advanced Search
|
||||
</Button>
|
||||
{/* <Button onClick={toggleDrawer(true)}>Open Right Drawer</Button> */}
|
||||
|
||||
<Drawer anchor="right" open={open} onClose={toggleDrawer(false)}>
|
||||
{list()}
|
||||
<Box sx={{ width: 400 }} role="presentation">
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<Box p={2}>
|
||||
<Box sx={{ display: "flex", gap: "60px" }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Search
|
||||
</Typography>
|
||||
<Box display="flex" justifyContent="flex-end" gap={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<SearchIcon />}
|
||||
onClick={() => console.log("Params:", formValues)}
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={resetForm}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={2}>
|
||||
{labels?.map(({ label, field, type, options }) => (
|
||||
<Box key={field}>
|
||||
<Typography variant="body2" fontWeight={600} mb={0.5}>
|
||||
{label}
|
||||
</Typography>
|
||||
|
||||
{type === "text" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
value={formValues[field] || ""}
|
||||
onChange={(e) => handleFieldChange(field, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "select" && (
|
||||
<FormControl fullWidth size="small">
|
||||
<Select
|
||||
value={formValues[field] || ""}
|
||||
onChange={(e) => handleFieldChange(field, e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>{label}</em>
|
||||
</MenuItem>
|
||||
{options?.map((option) => (
|
||||
<MenuItem value={option} key={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
{type === "date" && (
|
||||
<DatePicker
|
||||
value={formValues[field] ? new Date(formValues[field]) : null}
|
||||
onChange={(newValue) =>
|
||||
handleFieldChange(
|
||||
field,
|
||||
newValue?.toISOString() || ""
|
||||
)
|
||||
}
|
||||
slotProps={{
|
||||
textField: { fullWidth: true, size: "small" }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
</Drawer>
|
||||
</Box>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@ -1,226 +0,0 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Stack,
|
||||
Paper,
|
||||
styled,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
||||
import * as XLSX from "xlsx";
|
||||
import { saveAs } from "file-saver";
|
||||
import { DataGrid } from "@mui/x-data-grid";
|
||||
import { columns } from "./constants";
|
||||
// import { rows } from "./mockData";
|
||||
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch";
|
||||
import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
||||
|
||||
const paginationModel = { page: 0, pageSize: 50 };
|
||||
|
||||
export default function TransactionTable() {
|
||||
const [form, setForm] = useState({
|
||||
keyword: "",
|
||||
transactionID: "",
|
||||
transactionReferenceId: "",
|
||||
user: "",
|
||||
currency: "",
|
||||
state: "",
|
||||
statusDescription: "",
|
||||
transactionType: "",
|
||||
paymentMethod: "",
|
||||
psps: "",
|
||||
initialPsps: "",
|
||||
merchants: "",
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
lastUpdatedFrom: null,
|
||||
lastUpdatedTo: null,
|
||||
minAmount: "",
|
||||
maxAmount: "",
|
||||
channel: "",
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
setForm({
|
||||
keyword: "",
|
||||
transactionID: "",
|
||||
transactionReferenceId: "",
|
||||
user: "",
|
||||
currency: "",
|
||||
state: "",
|
||||
statusDescription: "",
|
||||
transactionType: "",
|
||||
paymentMethod: "",
|
||||
psps: "",
|
||||
initialPsps: "",
|
||||
merchants: "",
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
lastUpdatedFrom: null,
|
||||
lastUpdatedTo: null,
|
||||
minAmount: "",
|
||||
maxAmount: "",
|
||||
channel: "",
|
||||
});
|
||||
};
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
||||
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
||||
|
||||
const handleExport = () => {
|
||||
const exportRows = onlyCurrentTable
|
||||
? transactions.slice(0, 5)
|
||||
: transactions;
|
||||
const exportData = [
|
||||
columns.map((col) => col.headerName),
|
||||
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
|
||||
];
|
||||
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(exportData);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Transactions");
|
||||
|
||||
if (fileType === "csv") {
|
||||
const csv = XLSX.utils.sheet_to_csv(worksheet);
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
saveAs(blob, "transactions.csv");
|
||||
} else {
|
||||
XLSX.writeFile(workbook, `transactions.${fileType}`, {
|
||||
bookType: fileType,
|
||||
});
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const [transactions, setTransactions] = useState<any[]>([]);
|
||||
|
||||
const fetchTransactions = async () => {
|
||||
try {
|
||||
const query = new URLSearchParams(form as any).toString();
|
||||
const res = await fetch(`/api/transactions?${query}`);
|
||||
const data = await res.json();
|
||||
setTransactions(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching transactions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTransactions();
|
||||
}, [form]);
|
||||
|
||||
const handleDeleteFilter = (key) => {
|
||||
setForm((prev) => ({ ...prev, [key]: "" }));
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
resetForm();
|
||||
fetchTransactions();
|
||||
};
|
||||
|
||||
const handleClickField = (field: string, value: any) => {
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledPaper>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p={2}
|
||||
>
|
||||
<TextField
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
// value={'searchQuery'}
|
||||
onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)}
|
||||
sx={{ width: 300 }}
|
||||
/>
|
||||
<AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} />
|
||||
<SearchFilters
|
||||
filters={form}
|
||||
onDeleteFilter={handleDeleteFilter}
|
||||
onClearAll={handleClearAll}
|
||||
/>
|
||||
{/* <RightTemporaryDrawer /> */}
|
||||
{/* <SearchFilterForm /> */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<FileUploadIcon />}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<DataGrid
|
||||
rows={transactions}
|
||||
columns={columns}
|
||||
initialState={{ pagination: { paginationModel } }}
|
||||
pageSizeOptions={[50, 100]}
|
||||
sx={{ border: 0, cursor: "pointer" }}
|
||||
onCellClick={(params, event) => {
|
||||
// Check if the click is on a specific column
|
||||
// Do something when this specific column is clicked
|
||||
handleClickField(params.field, params.value);
|
||||
console.log("Clicked cell value:", params.value); // The cell's value
|
||||
console.log("Column field:", params.field); // The column's field name (from your columns definition)
|
||||
console.log("Column header:", params.colDef.headerName); // The column's display name // Your custom logic here
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Export Dialog */}
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<DialogTitle>Export Transactions</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||
<Select
|
||||
value={fileType}
|
||||
onChange={(e) =>
|
||||
setFileType(e.target.value as "csv" | "xls" | "xlsx")
|
||||
}
|
||||
>
|
||||
<MenuItem value="csv">CSV</MenuItem>
|
||||
<MenuItem value="xls">XLS</MenuItem>
|
||||
<MenuItem value="xlsx">XLSX</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={onlyCurrentTable}
|
||||
onChange={(e) => setOnlyCurrentTable(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Only export the results in the current table"
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleExport}>
|
||||
Export
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</StyledPaper>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledPaper = styled(Paper)(() => ({
|
||||
height: "90vh",
|
||||
}));
|
||||
130
payment-iq/app/features/Pages/transactions/TransactionsTable.tsx
Normal file
130
payment-iq/app/features/Pages/transactions/TransactionsTable.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Stack,
|
||||
Paper,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
||||
import { DataGrid } from "@mui/x-data-grid";
|
||||
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch";
|
||||
import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
||||
import { exportData } from "@/app/utils/exportData";
|
||||
import { ITransaction } from "./types";
|
||||
|
||||
|
||||
const paginationModel = { page: 0, pageSize: 50 };
|
||||
|
||||
interface IDepositProps {
|
||||
res: ITransaction
|
||||
}
|
||||
|
||||
const TransactionsTable = ({ res }: IDepositProps) => {
|
||||
const {filteredTransactions, transactionsColumns, transactionsSearchLabels} = res;
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
||||
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
||||
|
||||
const filters = Object.fromEntries(searchParams.entries());
|
||||
|
||||
|
||||
const handleClickField = (field: string, value: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.set(field, value)
|
||||
router.push(`?${params.toString()}`)
|
||||
router.refresh()
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p={2}
|
||||
>
|
||||
<TextField
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)}
|
||||
sx={{ width: 300 }}
|
||||
/>
|
||||
<AdvancedSearch labels={transactionsSearchLabels} />
|
||||
<SearchFilters filters={filters} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<FileUploadIcon />}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<DataGrid
|
||||
rows={filteredTransactions}
|
||||
columns={transactionsColumns}
|
||||
initialState={{ pagination: { paginationModel } }}
|
||||
pageSizeOptions={[50, 100]}
|
||||
sx={{ border: 0, cursor: 'pointer' }}
|
||||
|
||||
onCellClick={(params) => {
|
||||
handleClickField(params.field, params.value as string)
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Export Dialog */}
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<DialogTitle>Export Transactions</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||
<Select
|
||||
value={fileType}
|
||||
onChange={(e) =>
|
||||
setFileType(e.target.value as "csv" | "xls" | "xlsx")
|
||||
}
|
||||
>
|
||||
<MenuItem value="csv">CSV</MenuItem>
|
||||
<MenuItem value="xls">XLS</MenuItem>
|
||||
<MenuItem value="xlsx">XLSX</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={onlyCurrentTable}
|
||||
onChange={(e) => setOnlyCurrentTable(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Only export the results in the current table"
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={() => exportData(filteredTransactions as unknown as ITransaction[], transactionsColumns, fileType, onlyCurrentTable, setOpen)}>
|
||||
Export
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default TransactionsTable
|
||||
@ -119,3 +119,50 @@ export const columns: GridColDef[] = [
|
||||
// { field: 'originTransactionId', headerName: 'Origin Transaction ID', type: 'number', width: 90 },
|
||||
// { field: 'transactionReferenceId', headerName: 'Transaction Reference ID', type: 'number', width: 90 },
|
||||
];
|
||||
|
||||
export const depositTransactionsColumns = [
|
||||
{ field: "userId", headerName: "User ID", width: 130 },
|
||||
{ field: "merchandId", headerName: "Merchant ID", width: 130 },
|
||||
{ field: "transactionId", headerName: "Transaction ID", width: 130 },
|
||||
{ field: "depositMethod", headerName: "Deposit Method", width: 130 },
|
||||
{ field: "status", headerName: "Status", width: 130 },
|
||||
{ field: "amount", headerName: "Amount", width: 130 },
|
||||
{ field: "currency", headerName: "Currency", width: 130 },
|
||||
{ field: "dateTime", headerName: "Date / Time", width: 130 },
|
||||
{ field: "errorInfo", headerName: "Error Info", width: 130 },
|
||||
{ field: "fraudScore", headerName: "Fraud Score", width: 130 },
|
||||
]
|
||||
|
||||
|
||||
export const currencies = ["USD", "EUR", "GBP"];
|
||||
export const states = ["Pending","Inprogress", "Completed", "Failed"];
|
||||
export const depositMethod = ["Card", "Bank Transfer"];
|
||||
|
||||
export const Labels = [
|
||||
{ label: "User", field: "userId", type: "text" },
|
||||
{ label: "Transaction ID", field: "transactionId", type: "text" },
|
||||
{
|
||||
label: "Transaction Reference ID",
|
||||
field: "transactionReferenceId",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
label: "Currency",
|
||||
field: "currency",
|
||||
type: "select",
|
||||
options: currencies,
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
field: "status",
|
||||
type: "select",
|
||||
options: states,
|
||||
},
|
||||
{
|
||||
label: "Payment Method",
|
||||
field: "depositMethod",
|
||||
type: "select",
|
||||
options: depositMethod,
|
||||
},
|
||||
{ label: "Date / Time", field: "dateTime", type: "date" },
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
32
payment-iq/app/features/Pages/transactions/types.ts
Normal file
32
payment-iq/app/features/Pages/transactions/types.ts
Normal file
@ -0,0 +1,32 @@
|
||||
interface IDepositTransactionsColumns {
|
||||
field: string;
|
||||
headerName: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface IFilteredTransactions {
|
||||
id: number;
|
||||
userId: number;
|
||||
merchandId: number;
|
||||
transactionId: number;
|
||||
depositMethod: string;
|
||||
status: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
dateTime: string;
|
||||
errorInfo: string;
|
||||
fraudScore: string;
|
||||
}
|
||||
|
||||
interface IDepositTransactionsSearchLabels {
|
||||
label: string;
|
||||
field: string;
|
||||
type: string;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
export interface ITransaction {
|
||||
filteredTransactions: IFilteredTransactions[];
|
||||
transactionsColumns: IDepositTransactionsColumns[];
|
||||
transactionsSearchLabels: IDepositTransactionsSearchLabels[];
|
||||
}
|
||||
@ -10,14 +10,36 @@ import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings";
|
||||
import InsightsIcon from "@mui/icons-material/Insights";
|
||||
import ListAltIcon from "@mui/icons-material/ListAlt";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
|
||||
import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces";
|
||||
|
||||
export const PAGE_LINKS: ISidebarLink[] = [
|
||||
{ title: "Home", path: "/dashboard", icon: HomeIcon },
|
||||
|
||||
{
|
||||
title: "Transaction",
|
||||
path: "/dashboard/transactions",
|
||||
icon: AccountBalanceWalletIcon,
|
||||
icon: AccountBalanceWalletIcon, children: [
|
||||
{
|
||||
title: "Deposits",
|
||||
path: "/dashboard/transactions/deposits",
|
||||
icon: ArrowDownwardIcon,
|
||||
},
|
||||
{
|
||||
title: "Withdrawals",
|
||||
path: "/dashboard/transactions/withdrawals",
|
||||
icon: ArrowUpwardIcon,
|
||||
},
|
||||
{
|
||||
title: "Transaction History",
|
||||
path: "/dashboard/transactions/history",
|
||||
icon: HistoryIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ title: "Approve", path: "/dashboard/approve", icon: CheckCircleIcon },
|
||||
{ title: "Investigate", path: "/dashboard/investigate", icon: SearchIcon },
|
||||
|
||||
13
payment-iq/app/services/transactions.ts
Normal file
13
payment-iq/app/services/transactions.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export async function getTransactions({ transactionType, query }: { transactionType: string, query: string }) {
|
||||
const res = await fetch(`http://localhost:3000/api/transactions/${transactionType}?${query}`, {
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
// Handle error from the API
|
||||
const errorData = await res.json().catch(() => ({ message: 'Unknown error' }));
|
||||
throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
37
payment-iq/app/utils/exportData.ts
Normal file
37
payment-iq/app/utils/exportData.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import * as XLSX from "xlsx";
|
||||
import { GridColDef } from "@mui/x-data-grid";
|
||||
export type FileType = "csv" | "xls" | "xlsx";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import type { ITransaction } from "../features/Pages/Transactions/types";
|
||||
|
||||
|
||||
export const exportData = (
|
||||
transactions: ITransaction[],
|
||||
columns: GridColDef[],
|
||||
fileType: FileType = "csv",
|
||||
onlyCurrentTable = false,
|
||||
setOpen: (open: boolean) => void
|
||||
) => {
|
||||
const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions;
|
||||
const exportData = [
|
||||
columns.map((col) => col.headerName),
|
||||
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
|
||||
];
|
||||
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(exportData);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Transactions");
|
||||
|
||||
if (fileType === "csv") {
|
||||
const csv = XLSX.utils.sheet_to_csv(worksheet);
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
saveAs(blob, "transactions.csv");
|
||||
} else {
|
||||
XLSX.writeFile(workbook, `transactions.${fileType}`, {
|
||||
bookType: fileType,
|
||||
});
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
13
payment-iq/app/utils/formatDate.ts
Normal file
13
payment-iq/app/utils/formatDate.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const formatToDateTimeString = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // months are 0-indexed
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user