make transactions dynamic

This commit is contained in:
Petropoulos Evangelos 2025-07-12 18:24:43 +03:00
parent 1bc9d28d7f
commit 69c572d70c
18 changed files with 693 additions and 3556 deletions

View 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" },
]

View File

@ -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
});
} }

View 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" },
];

View 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
});
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
export enum TransactionType {
Deposit = "deposit",
Withdraw = "withdraw"
}

View File

@ -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}/>;
} }

View File

@ -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>
);
} }

View 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}/>;
}

View File

@ -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>
); );
}

View File

@ -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>
);
}

View File

@ -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",
}));

View File

@ -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

View File

@ -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

View File

@ -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[];
}

View File

@ -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}`);
} }

View File

@ -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 = (