make transactions dynamic
This commit is contained in:
parent
1bc9d28d7f
commit
69c572d70c
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" },
|
||||||
|
]
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { depositTransactionDummyData } from "@/app/features/Pages/transactions/mockData";
|
|
||||||
import { formatToDateTimeString } from "@/app/utils/formatDate";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { depositTransactionDummyData, depositTransactionsColumns, depositTransactionsSearchLabels } from "./mockData";
|
||||||
|
import { formatToDateTimeString } from "@/app/utils/formatDate";
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
@ -49,5 +49,9 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(filteredTransactions);
|
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 React from 'react';
|
||||||
import { Box, Chip, Typography, Button } from '@mui/material';
|
import { Box, Chip, Typography, Button } from '@mui/material';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
const SearchFilters = ({ filters, onDeleteFilter, onClearAll }) => {
|
interface SearchFiltersProps {
|
||||||
const renderChip = (label, value, key) => (
|
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
|
<Chip
|
||||||
key={key}
|
key={key}
|
||||||
label={
|
label={
|
||||||
<Typography variant="body2" sx={{ fontWeight: key === 'state' ? 'bold' : 'normal' }}>
|
<Typography
|
||||||
{label} {value}
|
variant="body2"
|
||||||
|
sx={{ fontWeight: key === "state" ? "bold" : "normal" }}
|
||||||
|
>
|
||||||
|
{label}: {value}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
onDelete={() => onDeleteFilter(key)}
|
onDelete={() => handleDeleteFilter(key)}
|
||||||
sx={{ mr: 1, mb: 1 }}
|
sx={{ mr: 1, mb: 1 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex" alignItems="center" flexWrap="wrap" sx={{ p: 2 }}>
|
<Box display="flex" alignItems="center" flexWrap="wrap" sx={{ p: 2 }}>
|
||||||
{filters.user && renderChip('User', filters.user, 'user')}
|
{Object.entries(filters).map(([key, value]) =>
|
||||||
{filters.state && renderChip('State', filters.state, 'state')}
|
value ? renderChip(filterLabels[key] ?? key, value, key) : null
|
||||||
{filters.startDate && renderChip('Start Date', filters.startDate, 'startDate')}
|
)}
|
||||||
|
|
||||||
{Object.values(filters).some(Boolean) && (
|
{Object.values(filters).some(Boolean) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onClearAll}
|
onClick={onClearAll}
|
||||||
sx={{ ml: 1, textDecoration: 'underline', color: 'black' }}
|
sx={{ ml: 1, textDecoration: "underline", color: "black" }}
|
||||||
>
|
>
|
||||||
Clear All
|
Clear All
|
||||||
</Button>
|
</Button>
|
||||||
@ -34,4 +60,5 @@ const SearchFilters = ({ filters, onDeleteFilter, onClearAll }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default SearchFilters;
|
export default SearchFilters;
|
||||||
|
|||||||
4
payment-iq/app/dashboard/transactions/constants.ts
Normal file
4
payment-iq/app/dashboard/transactions/constants.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum TransactionType {
|
||||||
|
Deposit = "deposit",
|
||||||
|
Withdraw = "withdraw"
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import DepositsTransactionsTable from "@/app/features/Pages/transactions/DepositTransactionsTable";
|
import TransactionsTable from "@/app/features/Pages/Transactions/TransactionsTable";
|
||||||
import { getTransactions } from "@/app/services/transactions";
|
import { getTransactions } from "@/app/services/transactions";
|
||||||
|
|
||||||
export default async function DepositTransactionPage({
|
export default async function DepositTransactionPage({
|
||||||
@ -16,7 +16,8 @@ export default async function DepositTransactionPage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const query = new URLSearchParams(safeParams).toString();
|
const query = new URLSearchParams(safeParams).toString();
|
||||||
const transactions = await getTransactions({ query });
|
const transactionType = 'deposit';
|
||||||
|
const data = await getTransactions({ transactionType, query });
|
||||||
|
|
||||||
return <DepositsTransactionsTable data={transactions} />;
|
return <TransactionsTable res={data}/>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
// This ensures this component is rendered only on the client side
|
import TransactionsTable from "@/app/features/Pages/Transactions/TransactionsTable";
|
||||||
|
import { getTransactions } from "@/app/services/transactions";
|
||||||
|
|
||||||
import TransactionTable from "@/app/features/Pages/Transactions/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}/>;
|
||||||
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 {
|
import {
|
||||||
Box,
|
Box,
|
||||||
TextField,
|
TextField,
|
||||||
@ -9,27 +8,60 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
|
debounce,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
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 [open, setOpen] = useState(false);
|
||||||
|
const [formValues, setFormValues] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
const handleChange = (field: string, value: any) => {
|
useEffect(() => {
|
||||||
setForm((prev) => ({ ...prev, [field]: value }));
|
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 =
|
const resetForm = () => {
|
||||||
(open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
setFormValues({});
|
||||||
|
router.push("?");
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||||
if (
|
if (
|
||||||
event.type === "keydown" &&
|
event.type === "keydown" &&
|
||||||
((event as React.KeyboardEvent).key === "Tab" ||
|
((event as React.KeyboardEvent).key === "Tab" ||
|
||||||
@ -37,140 +69,9 @@ export default function AdvancedSearch({ setForm, form, resetForm }) {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpen(open);
|
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 (
|
return (
|
||||||
<Box sx={{ width: '185px' }}>
|
<Box sx={{ width: '185px' }}>
|
||||||
<Button
|
<Button
|
||||||
@ -199,10 +100,87 @@ export default function AdvancedSearch({ setForm, form, resetForm }) {
|
|||||||
>
|
>
|
||||||
Advanced Search
|
Advanced Search
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button onClick={toggleDrawer(true)}>Open Right Drawer</Button> */}
|
|
||||||
<Drawer anchor="right" open={open} onClose={toggleDrawer(false)}>
|
<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>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
TextField,
|
|
||||||
MenuItem,
|
|
||||||
Button,
|
|
||||||
Drawer,
|
|
||||||
FormControl,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
Stack,
|
|
||||||
} 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";
|
|
||||||
|
|
||||||
|
|
||||||
interface IForm {
|
|
||||||
userId: string;
|
|
||||||
transactionId: string;
|
|
||||||
transactionReferenceId: string;
|
|
||||||
currency: string;
|
|
||||||
state: string;
|
|
||||||
depositMethod: string;
|
|
||||||
dateTime: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILabel {
|
|
||||||
label: string;
|
|
||||||
field: string;
|
|
||||||
type: string;
|
|
||||||
options?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAdvancedSearch {
|
|
||||||
setForm: () => void;
|
|
||||||
form: IForm[];
|
|
||||||
resetForm: () => void;
|
|
||||||
labels: ILabel[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AdvancedSearch1({ setForm, form, resetForm, labels }: IAdvancedSearch) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleChange = (field: string, value: any) => {
|
|
||||||
setForm((prev) => ({ ...prev, [field]: value }));
|
|
||||||
};
|
|
||||||
|
|
||||||
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}>
|
|
||||||
{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={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
|
|
||||||
sx={{
|
|
||||||
borderRadius: "8px",
|
|
||||||
textTransform: "none",
|
|
||||||
backgroundColor: "#f5f5f5",
|
|
||||||
color: "#555",
|
|
||||||
padding: "6px 12px",
|
|
||||||
boxShadow: "inset 0 0 0 1px #ddd",
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: "16px",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
"& .MuiButton-startIcon": {
|
|
||||||
borderRadius: "4px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "#e0e0e0",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
startIcon={<SearchIcon />}
|
|
||||||
onClick={toggleDrawer(true)}
|
|
||||||
>
|
|
||||||
Advanced Search
|
|
||||||
</Button>
|
|
||||||
{/* <Button onClick={toggleDrawer(true)}>Open Right Drawer</Button> */}
|
|
||||||
<Drawer anchor="right" open={open} onClose={toggleDrawer(false)}>
|
|
||||||
{list()}
|
|
||||||
</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",
|
|
||||||
}));
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -13,102 +14,45 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
Stack,
|
Stack,
|
||||||
Paper,
|
Paper,
|
||||||
styled,
|
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
||||||
import { DataGrid } from "@mui/x-data-grid";
|
import { DataGrid } from "@mui/x-data-grid";
|
||||||
import { depositTransactionsColumns, Labels } from "./constants";
|
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch";
|
||||||
import AdvancedSearch1 from "../../AdvancedSearch/AdvancedSearch1";
|
|
||||||
import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
||||||
import { exportData } from "@/app/utils/exportData";
|
import { exportData } from "@/app/utils/exportData";
|
||||||
import { ITransaction } from "./types";
|
import { ITransaction } from "./types";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
|
|
||||||
const paginationModel = { page: 0, pageSize: 50 };
|
const paginationModel = { page: 0, pageSize: 50 };
|
||||||
|
|
||||||
interface DepositProps {
|
interface IDepositProps {
|
||||||
data: any
|
res: ITransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
const DepositsTransactionsTable: React.FC<DepositProps> = ({ data }) => {
|
const TransactionsTable = ({ res }: IDepositProps) => {
|
||||||
|
const {filteredTransactions, transactionsColumns, transactionsSearchLabels} = res;
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
// const [form, setForm] = useState({
|
|
||||||
// userId: "",
|
|
||||||
// transactionId: "",
|
|
||||||
// transactionReferenceId: "",
|
|
||||||
// currency: "",
|
|
||||||
// state: "",
|
|
||||||
// depositMethod: "",
|
|
||||||
// dateTime: "",
|
|
||||||
// });
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
||||||
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
||||||
const [transactions, setTransactions] = useState<ITransaction[]>([]);
|
|
||||||
|
|
||||||
|
const filters = Object.fromEntries(searchParams.entries());
|
||||||
// console.log(777, form)
|
|
||||||
|
|
||||||
|
|
||||||
// const fetchTransactions = useCallback(async () => {
|
|
||||||
// try {
|
|
||||||
// const stringForm: Record<string, string> = Object.fromEntries(
|
|
||||||
// Object.entries(form).map(([key, value]) => [key, value === null ? "" : String(value)])
|
|
||||||
// );
|
|
||||||
// const query = new URLSearchParams(stringForm).toString();
|
|
||||||
// const res = await fetch(`/api/transactions/deposit?${query}`);
|
|
||||||
// const data = await res.json();
|
|
||||||
// setTransactions(data);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Error fetching transactions:', error);
|
|
||||||
// }
|
|
||||||
// }, [form]);
|
|
||||||
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
// setForm({
|
|
||||||
// userId: "",
|
|
||||||
// transactionId: "",
|
|
||||||
// transactionReferenceId: "",
|
|
||||||
// currency: "",
|
|
||||||
// state: "",
|
|
||||||
// depositMethod: "",
|
|
||||||
// dateTime: "",
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// fetchTransactions();
|
|
||||||
// }, [form, fetchTransactions]);
|
|
||||||
|
|
||||||
const handleDeleteFilter = (key: string) => {
|
|
||||||
// setForm((prev) => ({ ...prev, [key]: '' }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearAll = () => {
|
|
||||||
// resetForm()
|
|
||||||
// fetchTransactions()
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleClickField = (field: string, value: string) => {
|
const handleClickField = (field: string, value: string) => {
|
||||||
// setForm((prev) => ({ ...prev, [field]: value }));
|
|
||||||
|
|
||||||
const params = new URLSearchParams(searchParams.toString())
|
const params = new URLSearchParams(searchParams.toString())
|
||||||
params.set(field, value)
|
params.set(field, value)
|
||||||
router.push(`?${params.toString()}`)
|
router.push(`?${params.toString()}`)
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledPaper>
|
<Paper>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@ -122,8 +66,8 @@ const DepositsTransactionsTable: React.FC<DepositProps> = ({ data }) => {
|
|||||||
onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)}
|
onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)}
|
||||||
sx={{ width: 300 }}
|
sx={{ width: 300 }}
|
||||||
/>
|
/>
|
||||||
{/* <AdvancedSearch1 form={form} resetForm={resetForm} setForm={setForm} labels={Labels} /> */}
|
<AdvancedSearch labels={transactionsSearchLabels} />
|
||||||
{/* <SearchFilters filters={form} onDeleteFilter={handleDeleteFilter} onClearAll={handleClearAll} /> */}
|
<SearchFilters filters={filters} />
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<FileUploadIcon />}
|
startIcon={<FileUploadIcon />}
|
||||||
@ -134,8 +78,8 @@ const DepositsTransactionsTable: React.FC<DepositProps> = ({ data }) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={data}
|
rows={filteredTransactions}
|
||||||
columns={depositTransactionsColumns}
|
columns={transactionsColumns}
|
||||||
initialState={{ pagination: { paginationModel } }}
|
initialState={{ pagination: { paginationModel } }}
|
||||||
pageSizeOptions={[50, 100]}
|
pageSizeOptions={[50, 100]}
|
||||||
sx={{ border: 0, cursor: 'pointer' }}
|
sx={{ border: 0, cursor: 'pointer' }}
|
||||||
@ -174,18 +118,13 @@ const DepositsTransactionsTable: React.FC<DepositProps> = ({ data }) => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
||||||
<Button variant="contained" onClick={() => exportData(data, depositTransactionsColumns, fileType, onlyCurrentTable, setOpen)}>
|
<Button variant="contained" onClick={() => exportData(filteredTransactions as unknown as ITransaction[], transactionsColumns, fileType, onlyCurrentTable, setOpen)}>
|
||||||
Export
|
Export
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</StyledPaper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledPaper = styled(Paper)(() => ({
|
export default TransactionsTable
|
||||||
height: "90vh",
|
|
||||||
width: "80vw"
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default DepositsTransactionsTable
|
|
||||||
@ -135,7 +135,7 @@ export const depositTransactionsColumns = [
|
|||||||
|
|
||||||
|
|
||||||
export const currencies = ["USD", "EUR", "GBP"];
|
export const currencies = ["USD", "EUR", "GBP"];
|
||||||
export const states = ["Pending", "Completed", "Failed"];
|
export const states = ["Pending","Inprogress", "Completed", "Failed"];
|
||||||
export const depositMethod = ["Card", "Bank Transfer"];
|
export const depositMethod = ["Card", "Bank Transfer"];
|
||||||
|
|
||||||
export const Labels = [
|
export const Labels = [
|
||||||
@ -153,8 +153,8 @@ export const Labels = [
|
|||||||
options: currencies,
|
options: currencies,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "State",
|
label: "Status",
|
||||||
field: "state",
|
field: "status",
|
||||||
type: "select",
|
type: "select",
|
||||||
options: states,
|
options: states,
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,32 @@
|
|||||||
export interface ITransaction {
|
interface IDepositTransactionsColumns {
|
||||||
[key: string]: string | number; // Replace with actual fields if known, e.g. id: string, amount: number, etc.
|
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[];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export async function getTransactions({ query }: { query: string }) {
|
export async function getTransactions({ transactionType, query }: { transactionType: string, query: string }) {
|
||||||
const res = await fetch(`http://localhost:3000/api/transactions/deposit?${query}`, {
|
const res = await fetch(`http://localhost:3000/api/transactions/${transactionType}?${query}`, {
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// You can parse the error message from the response if your API sends one
|
// Handle error from the API
|
||||||
const errorData = await res.json().catch(() => ({ message: 'Unknown error' }));
|
const errorData = await res.json().catch(() => ({ message: 'Unknown error' }));
|
||||||
throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
|
throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { GridColDef } from "@mui/x-data-grid";
|
|||||||
export type FileType = "csv" | "xls" | "xlsx";
|
export type FileType = "csv" | "xls" | "xlsx";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
|
|
||||||
import type { ITransaction } from "../features/Pages/transactions/types";
|
import type { ITransaction } from "../features/Pages/Transactions/types";
|
||||||
|
|
||||||
|
|
||||||
export const exportData = (
|
export const exportData = (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user