add audits

This commit is contained in:
Petropoulos Evangelos 2025-07-22 17:51:44 +03:00
parent 36b85ddf1a
commit b110390253
22 changed files with 336 additions and 431 deletions

View File

@ -0,0 +1,88 @@
import { GridColDef } from "@mui/x-data-grid";
export const AuditColumns: GridColDef[] = [
{ field: "actionType", headerName: "Action Type", width: 130 },
{
field: "timeStampOfTheAction",
headerName: "Timestamp of the action",
width: 130,
},
{ field: "adminUsername", headerName: "Admin username", width: 130 },
{ field: "adminId", headerName: "Admin ID", width: 130 },
{ field: "affectedUserId", headerName: "Affected user ID", width: 130 },
{ field: "adminIPAddress", headerName: "Admin IP address", width: 130 },
{ field: "reasonNote", headerName: "Reason/Note", width: 130 },
];
export const AuditData = [
{
id: "1",
actionType: "Create",
timeStampOfTheAction: "2023-03-01T12:00:00",
adminUsername: "admin1",
adminId: "12345",
affectedUserId: "67890",
adminIPAddress: "192.168.1.1",
reasonNote: "New user created",
},
{
id: "2",
actionType: "Update",
timeStampOfTheAction: "2023-03-02T12:00:00",
adminUsername: "admin2",
adminId: "54321",
affectedUserId: "09876",
adminIPAddress: "192.168.2.2",
reasonNote: "User details updated",
},
{
id: "3",
actionType: "Delete",
timeStampOfTheAction: "2023-03-03T12:00:00",
adminUsername: "admin3",
adminId: "98765",
affectedUserId: "45678",
adminIPAddress: "192.168.3.3",
reasonNote: "User deleted",
},
{
id: "4",
actionType: "Create",
timeStampOfTheAction: "2023-03-04T12:00:00",
adminUsername: "admin4",
adminId: "98765",
affectedUserId: "45678",
adminIPAddress: "192.168.3.3",
reasonNote: "New user created",
},
{
id: "5",
actionType: "Update",
timeStampOfTheAction: "2023-03-05T12:00:00",
adminUsername: "admin2",
adminId: "98765",
affectedUserId: "45678",
adminIPAddress: "192.168.3.3",
reasonNote: "User details updated",
},
];
export const AuditSearchLabels = [
{ label: "Action Type", field: "actionType", type: "text" },
{ label: "Date / Time", field: "dateTime", type: "date" },
{
label: "affectedUserId",
field: "Affected user ID",
type: "text",
},
{
label: "Admin ID",
field: "adminId",
type: "text",
},
{
label: "Admin username",
field: "adminUsername",
type: "text",
},
];

View File

@ -0,0 +1,52 @@
import { NextRequest, NextResponse } from "next/server";
import { AuditColumns, AuditData, AuditSearchLabels } from "./mockData";
import { formatToDateTimeString } from "@/app/utils/formatDate";
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const actionType = searchParams.get("actionType");
const affectedUserId = searchParams.get("affectedUserId");
const adminId = searchParams.get("adminId");
const adminUsername = searchParams.get("adminUsername");
const timeStampOfTheAction = searchParams.get("dateTime");
let filteredRows = [...AuditData];
if (actionType) {
filteredRows = filteredRows.filter(
(tx) => tx.actionType.toLocaleLowerCase() === actionType.toLocaleLowerCase(),
);
}
if (affectedUserId) {
filteredRows = filteredRows.filter(
(tx) => tx.affectedUserId.toLowerCase() === affectedUserId.toLowerCase(),
);
}
if (adminId) {
filteredRows = filteredRows.filter(
(tx) => tx.adminId === adminId,
);
}
if (adminUsername) {
filteredRows = filteredRows.filter(
(tx) => tx.adminUsername === adminUsername,
);
}
if (timeStampOfTheAction) {
filteredRows = filteredRows.filter(
(tx) =>
tx.timeStampOfTheAction.split(" ")[0] ===
formatToDateTimeString(timeStampOfTheAction).split(" ")[0],
);
}
return NextResponse.json({
tableRows: filteredRows,
tableColumns: AuditColumns,
tableSearchLabels: AuditSearchLabels,
});
}

View File

@ -1,5 +1,9 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { depositTransactionDummyData, depositTransactionsColumns, depositTransactionsSearchLabels } from "./mockData"; import {
depositTransactionDummyData,
depositTransactionsColumns,
depositTransactionsSearchLabels,
} from "./mockData";
import { formatToDateTimeString } from "@/app/utils/formatDate"; import { formatToDateTimeString } from "@/app/utils/formatDate";
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
@ -12,46 +16,47 @@ export async function GET(request: NextRequest) {
const transactionId = searchParams.get("transactionId"); const transactionId = searchParams.get("transactionId");
const dateTime = searchParams.get("dateTime"); const dateTime = searchParams.get("dateTime");
let filteredTransactions = [...depositTransactionDummyData]; let filteredTransactions = [...depositTransactionDummyData];
if (userId) { if (userId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.userId.toString() === userId (tx) => tx.userId.toString() === userId,
); );
} }
if (status) { if (status) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.status.toLowerCase() === status.toLowerCase() (tx) => tx.status.toLowerCase() === status.toLowerCase(),
); );
} }
if (depositMethod) { if (depositMethod) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.depositMethod.toLowerCase() === depositMethod.toLowerCase() (tx) => tx.depositMethod.toLowerCase() === depositMethod.toLowerCase(),
); );
} }
if (merchandId) { if (merchandId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.merchandId.toString() === merchandId (tx) => tx.merchandId.toString() === merchandId,
); );
} }
if (transactionId) { if (transactionId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.transactionId.toString() === transactionId (tx) => tx.transactionId.toString() === transactionId,
); );
} }
if (dateTime) { if (dateTime) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.dateTime.split(" ")[0] === formatToDateTimeString(dateTime).split(" ")[0] (tx) =>
tx.dateTime.split(" ")[0] ===
formatToDateTimeString(dateTime).split(" ")[0],
); );
} }
return NextResponse.json({ return NextResponse.json({
filteredTransactions: filteredTransactions, tableRows: filteredTransactions,
transactionsSearchLabels: depositTransactionsSearchLabels, tableSearchLabels: depositTransactionsSearchLabels,
transactionsColumns: depositTransactionsColumns tableColumns: depositTransactionsColumns,
}); });
} }

View File

@ -1,5 +1,9 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { withdrawalTransactionDummyData, withdrawalTransactionsColumns, withdrawalTransactionsSearchLabels } from "./mockData" import {
withdrawalTransactionDummyData,
withdrawalTransactionsColumns,
withdrawalTransactionsSearchLabels,
} from "./mockData";
import { formatToDateTimeString } from "@/app/utils/formatDate"; import { formatToDateTimeString } from "@/app/utils/formatDate";
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
@ -10,36 +14,38 @@ export async function GET(request: NextRequest) {
const dateTime = searchParams.get("dateTime"); const dateTime = searchParams.get("dateTime");
const withdrawalMethod = searchParams.get("withdrawalMethod"); const withdrawalMethod = searchParams.get("withdrawalMethod");
let filteredTransactions = [...withdrawalTransactionDummyData]; let filteredTransactions = [...withdrawalTransactionDummyData];
if (userId) { if (userId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.userId.toString() === userId (tx) => tx.userId.toString() === userId,
); );
} }
if (status) { if (status) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.status.toLowerCase() === status.toLowerCase() (tx) => tx.status.toLowerCase() === status.toLowerCase(),
); );
} }
if (withdrawalMethod) { if (withdrawalMethod) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.withdrawalMethod.toLowerCase() === withdrawalMethod.toLowerCase() (tx) =>
tx.withdrawalMethod.toLowerCase() === withdrawalMethod.toLowerCase(),
); );
} }
if (dateTime) { if (dateTime) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
(tx) => tx.dateTime.split(" ")[0] === formatToDateTimeString(dateTime).split(" ")[0] (tx) =>
tx.dateTime.split(" ")[0] ===
formatToDateTimeString(dateTime).split(" ")[0],
); );
} }
return NextResponse.json({ return NextResponse.json({
filteredTransactions: filteredTransactions, tableRows: filteredTransactions,
transactionsColumns: withdrawalTransactionsColumns, tableSearchLabels: withdrawalTransactionsSearchLabels,
transactionsSearchLabels: withdrawalTransactionsSearchLabels tableColumns: withdrawalTransactionsColumns,
}); });
} }

View File

@ -0,0 +1,22 @@
import DataTable from "@/app/features/DataTable/DataTable";
import { getAudits } from "@/app/services/audits";
export default async function AuditPage({
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 data = await getAudits({ query });
return <DataTable data={data} />;
}

View File

@ -1,5 +1,4 @@
import DataTable from "@/app/features/DataTable/DataTable";
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({
@ -17,8 +16,8 @@ export default async function DepositTransactionPage({
} }
} }
const query = new URLSearchParams(safeParams).toString(); const query = new URLSearchParams(safeParams).toString();
const transactionType = 'deposit'; const transactionType = "deposits";
const data = await getTransactions({ transactionType, query }); const data = await getTransactions({ transactionType, query });
return <TransactionsTable res={data}/>; return <DataTable data={data} />;
} }

View File

@ -1,23 +1,3 @@
import TransactionsTable from "@/app/features/pages/transactions/TransactionsTable"; export default async function HistoryTransactionPage() {
import { getTransactions } from "@/app/services/transactions"; return <div>History Transactions Page</div>;
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}/>;
} }

View File

@ -1,4 +1,4 @@
import TransactionsTable from "@/app/features/pages/transactions/TransactionsTable"; import DataTable from "@/app/features/DataTable/DataTable";
import { getTransactions } from "@/app/services/transactions"; import { getTransactions } from "@/app/services/transactions";
export default async function WithdrawalTransactionPage({ export default async function WithdrawalTransactionPage({
@ -16,8 +16,8 @@ export default async function WithdrawalTransactionPage({
} }
} }
const query = new URLSearchParams(safeParams).toString(); const query = new URLSearchParams(safeParams).toString();
const transactionType = 'withdrawal'; const transactionType = "withdrawal";
const data = await getTransactions({ transactionType, query }); const data = await getTransactions({ transactionType, query });
return <TransactionsTable res={data}/>; return <DataTable data={data} />;
} }

View File

@ -17,16 +17,9 @@ 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 { useSearchParams, useRouter } from "next/navigation";
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { ISearchLabel } from "../pages/transactions/types";
export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
interface ILabel {
label: string;
field: string;
type: string;
options?: string[];
}
export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -37,7 +30,6 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
setFormValues(initialParams); setFormValues(initialParams);
}, [searchParams]); }, [searchParams]);
const updateURL = useMemo( const updateURL = useMemo(
() => () =>
debounce((newValues: Record<string, string>) => { debounce((newValues: Record<string, string>) => {
@ -47,7 +39,7 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
}); });
router.push(`?${updatedParams.toString()}`); router.push(`?${updatedParams.toString()}`);
}, 500), }, 500),
[router] [router],
); );
const handleFieldChange = (field: string, value: string) => { const handleFieldChange = (field: string, value: string) => {
@ -61,7 +53,8 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
router.push("?"); router.push("?");
}; };
const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { 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" ||
@ -73,7 +66,7 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
}; };
return ( return (
<Box sx={{ width: '185px' }}> <Box sx={{ width: "185px" }}>
<Button <Button
sx={{ sx={{
borderRadius: "8px", borderRadius: "8px",
@ -137,7 +130,9 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
fullWidth fullWidth
size="small" size="small"
value={formValues[field] || ""} value={formValues[field] || ""}
onChange={(e) => handleFieldChange(field, e.target.value)} onChange={(e) =>
handleFieldChange(field, e.target.value)
}
/> />
)} )}
@ -145,7 +140,9 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
<FormControl fullWidth size="small"> <FormControl fullWidth size="small">
<Select <Select
value={formValues[field] || ""} value={formValues[field] || ""}
onChange={(e) => handleFieldChange(field, e.target.value)} onChange={(e) =>
handleFieldChange(field, e.target.value)
}
displayEmpty displayEmpty
> >
<MenuItem value=""> <MenuItem value="">
@ -162,15 +159,17 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
{type === "date" && ( {type === "date" && (
<DatePicker <DatePicker
value={formValues[field] ? new Date(formValues[field]) : null} value={
formValues[field] ? new Date(formValues[field]) : null
}
onChange={(newValue) => onChange={(newValue) =>
handleFieldChange( handleFieldChange(
field, field,
newValue?.toISOString() || "" newValue?.toISOString() || "",
) )
} }
slotProps={{ slotProps={{
textField: { fullWidth: true, size: "small" } textField: { fullWidth: true, size: "small" },
}} }}
/> />
)} )}

View File

@ -18,27 +18,28 @@ import {
} from "@mui/material"; } from "@mui/material";
import FileUploadIcon from "@mui/icons-material/FileUpload"; import FileUploadIcon from "@mui/icons-material/FileUpload";
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch"; import AdvancedSearch from "../AdvancedSearch/AdvancedSearch";
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 { IDataTable } from "./types";
interface IDepositProps {
res: ITransaction; interface IDataTableProps<TRow, TColumn> {
data: IDataTable<TRow, TColumn>;
} }
const TransactionsTable = ({ res }: IDepositProps) => { export type TWithId = { id: number };
const { const DataTable = <TRow extends TWithId, TColumn extends GridColDef>(
filteredTransactions, data: IDataTableProps<TRow, TColumn>,
transactionsColumns, ) => {
transactionsSearchLabels, const { tableRows, tableColumns, tableSearchLabels } = data.data;
} = res;
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
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 [rows, setRows] = useState<TRow[]>(tableRows);
const filters = Object.fromEntries(searchParams.entries()); const filters = Object.fromEntries(searchParams.entries());
@ -55,16 +56,19 @@ const TransactionsTable = ({ res }: IDepositProps) => {
); );
}; };
const [rows, setRows] = useState(filteredTransactions); const getColumnsWithDropdown = (columns: TColumn[]): GridColDef[] => {
const getColumnsWithDropdown = (columns: GridColDef[]) => { return columns?.map((col) => {
return columns.map((col) => {
if (col.field === "actions") { if (col.field === "actions") {
return { return {
...col, ...col,
renderCell: (params: GridRenderCellParams) => { renderCell: (params: GridRenderCellParams) => {
const row = filteredTransactions.find((r) => r.id === params.id); const row = tableRows.find((r) => r.id === params.id) as {
const options = row?.options; id: number;
status?: string;
options?: { value: string; label: string }[];
};
const options = row?.options;
if (!options) return params.value; if (!options) return params.value;
return ( return (
@ -79,9 +83,9 @@ const TransactionsTable = ({ res }: IDepositProps) => {
"& .MuiOutlinedInput-notchedOutline": { border: "none" }, "& .MuiOutlinedInput-notchedOutline": { border: "none" },
"& .MuiSelect-select": { py: 0.5 }, "& .MuiSelect-select": { py: 0.5 },
}} }}
onClick={(e) => e.stopPropagation()} // Prevent row click when selecting onClick={(e) => e.stopPropagation()}
> >
{options.map((option: { value: string; label: string }) => ( {options.map((option) => (
<MenuItem key={option.value} value={option.value}> <MenuItem key={option.value} value={option.value}>
{option.label} {option.label}
</MenuItem> </MenuItem>
@ -91,6 +95,7 @@ const TransactionsTable = ({ res }: IDepositProps) => {
}, },
}; };
} }
return col; return col;
}); });
}; };
@ -110,7 +115,7 @@ const TransactionsTable = ({ res }: IDepositProps) => {
onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)} onChange={(e) => console.log(`setSearchQuery(${e.target.value})`)}
sx={{ width: 300 }} sx={{ width: 300 }}
/> />
<AdvancedSearch labels={transactionsSearchLabels} /> <AdvancedSearch labels={tableSearchLabels} />
<SearchFilters filters={filters} /> <SearchFilters filters={filters} />
<Button <Button
variant="outlined" variant="outlined"
@ -122,8 +127,8 @@ const TransactionsTable = ({ res }: IDepositProps) => {
</Stack> </Stack>
<DataGrid <DataGrid
rows={filteredTransactions} rows={tableRows}
columns={getColumnsWithDropdown(transactionsColumns) as GridColDef[]} columns={getColumnsWithDropdown(tableColumns)}
initialState={{ initialState={{
pagination: { paginationModel: { pageSize: 50 } }, pagination: { paginationModel: { pageSize: 50 } },
}} }}
@ -175,8 +180,8 @@ const TransactionsTable = ({ res }: IDepositProps) => {
variant="contained" variant="contained"
onClick={() => onClick={() =>
exportData( exportData(
filteredTransactions as unknown as ITransaction[], tableRows,
transactionsColumns, tableColumns,
fileType, fileType,
onlyCurrentTable, onlyCurrentTable,
setOpen, setOpen,
@ -191,4 +196,4 @@ const TransactionsTable = ({ res }: IDepositProps) => {
); );
}; };
export default TransactionsTable; export default DataTable;

View File

@ -0,0 +1,12 @@
interface ISearchLabel {
label: string;
field: string;
type: string;
options?: string[];
}
export interface IDataTable<TRow, TColumn> {
tableRows: TRow[];
tableColumns: TColumn[];
tableSearchLabels: ISearchLabel[];
}

View File

@ -1,168 +0,0 @@
import { GridColDef } from "@mui/x-data-grid";
export const columns: GridColDef[] = [
{ field: "merchandId", headerName: "Merchant ID", width: 130 },
{ field: "transactionID", headerName: "Transaction ID", width: 130 },
{ field: "user", headerName: "User", width: 75 },
{ field: "created", headerName: "Created", type: "number", width: 130 },
{ field: "state", headerName: "State", type: "number", width: 130 },
{
field: "statusDescription",
headerName: "Status Description",
type: "number",
width: 130,
},
{
field: "statusCode",
headerName: "Status Code",
type: "number",
width: 130,
},
{
field: "pspStatusCode",
headerName: "PSP Status Code",
type: "number",
width: 130,
},
{
field: "pspStatusMessage",
headerName: "PSP Status Message",
type: "number",
width: 90,
},
{ field: "psp", headerName: "PSP", type: "number", width: 90 },
{ field: "pspAccount", headerName: "PSP Account", type: "number", width: 90 },
{ field: "initPSP", headerName: "Init PSP", type: "number", width: 90 },
{
field: "initPSPAccout",
headerName: "Init PSP Account",
type: "number",
width: 90,
},
{ field: "pspService", headerName: "PSP Service", type: "number", width: 90 },
{
field: "transactionType",
headerName: "Transaction Type",
type: "number",
width: 90,
},
{
field: "paymentMethod",
headerName: "Payment Method",
type: "number",
width: 90,
},
{ field: "rules", headerName: "Rules", type: "number", width: 90 },
{ field: "amount", headerName: "Amount", type: "number", width: 90 },
{ field: "fee", headerName: "Fee", type: "number", width: 90 },
{
field: "transactionAmount",
headerName: "Transaction Amount",
type: "number",
width: 90,
},
// { field: 'baseAmount', headerName: 'Base Amount', type: 'number', width: 90 },
// { field: 'baseFee', headerName: 'Base Fee', type: 'number', width: 90 },
// { field: 'baseTransaction', headerName: 'Base Transaction', type: 'number', width: 90 },
// { field: 'pspFee', headerName: 'PSP Fee', type: 'number', width: 90 },
// { field: 'basePspFee', headerName: 'Base PSP Fee', type: 'number', width: 90 },
// { field: 'authAmount', headerName: 'Auth Amount', type: 'number', width: 90 },
// { field: 'baseAuthAmount', headerName: 'Base Auth Amount', type: 'number', width: 90 },
// { field: 'userBalance', headerName: 'User Balance', type: 'number', width: 90 },
// { field: 'updated', headerName: 'Updated', type: 'number', width: 90 },
// { field: 'userIp', headerName: 'User IP', type: 'number', width: 90 },
// { field: 'channel', headerName: 'Channel', type: 'number', width: 90 },
// { field: 'depositType', headerName: 'Deposit Type', type: 'number', width: 90 },
// { field: 'userEmal', headerName: 'User Emal', type: 'number', width: 90 },
// { field: 'userCategory', headerName: 'User Category', type: 'number', width: 90 },
// { field: 'userCountry', headerName: 'User Country', type: 'number', width: 90 },
// { field: 'userAccount', headerName: 'User Account', type: 'number', width: 90 },
// { field: 'bankName', headerName: 'Bank Name', type: 'number', width: 90 },
// { field: 'pspUserReference', headerName: 'PSP User Reference', type: 'number', width: 90 },
// { field: 'pspFraudScore', headerName: 'PSP Fraud Score', type: 'number', width: 90 },
// { field: 'fraudStatus', headerName: 'FraudStatus', type: 'number', width: 90 },
// { field: 'blocked', headerName: 'Blocked', type: 'number', width: 90 },
// { field: 'abuse', headerName: 'Abuse', type: 'number', width: 90 },
// { field: 'kycStatus', headerName: 'KYC Status', type: 'number', width: 90 },
// { field: 'kycPSPName', headerName: 'KYC PSP Name', type: 'number', width: 90 },
// { field: 'kycPSPStatus', headerName: 'KYC PSP Status', type: 'number', width: 90 },
// { field: 'kycIdStatus', headerName: 'KYC ID Status', type: 'number', width: 90 },
// { field: 'kycAddressStatus', headerName: 'KYC Address Status', type: 'number', width: 90 },
// { field: 'kycAgeStatus', headerName: 'KYC Age Status', type: 'number', width: 90 },
// { field: 'kycPEPAndSanction', headerName: 'KYC PEP And Sanction', type: 'number', width: 90 },
// { field: 'pspReferenceId', headerName: 'PSPReferenceID', type: 'number', width: 90 },
// { field: 'siteReferenceId', headerName: 'Site Reference ID', type: 'number', width: 90 },
// { field: 'info', headerName: 'Info', type: 'number', width: 90 },
// { field: 'accountHolder', headerName: 'Account Holder', type: 'number', width: 90 },
// { field: 'firstName', headerName: 'First Name', type: 'number', width: 90 },
// { field: 'lastName', headerName: 'Last Name', type: 'number', width: 90 },
// { field: 'street', headerName: 'Street', type: 'number', width: 90 },
// { field: 'city', headerName: 'City', type: 'number', width: 90 },
// { field: 'zip', headerName: 'ZIP', type: 'number', width: 90 },
// { field: 'dob', headerName: 'DOB', type: 'number', width: 90 },
// { field: 'mobile', headerName: 'Mobile', type: 'number', width: 90 },
// { field: 'lastUpdatedBy', headerName: 'Last Updated By', type: 'number', width: 90 },
// { field: 'ipCity', headerName: 'IP City', type: 'number', width: 90 },
// { field: 'ipRegion', headerName: 'IP Region', type: 'number', width: 90 },
// { field: 'ipCountry', headerName: 'IP Country', type: 'number', width: 90 },
// { field: 'cardIssuerCountry', headerName: 'Card Issuer Country', type: 'number', width: 90 },
// { field: 'cardBand', headerName: 'Card Band', type: 'number', width: 90 },
// { field: 'cardCategory', headerName: 'Card Category', type: 'number', width: 90 },
// { field: 'cardIssuerName', headerName: 'Card Issuer Name', type: 'number', width: 90 },
// { field: 'inn', headerName: 'INN', type: 'number', width: 90 },
// { field: 'cardType', headerName: 'Card Type', type: 'number', width: 90 },
// { field: 'firstAttempt', headerName: 'First Attempt', type: 'number', width: 90 },
// { field: 'firstSuccessful', headerName: 'First Successful', type: 'number', width: 90 },
// { field: 'firstTransaction', headerName: 'First Transaction', type: 'number', width: 90 },
// { field: 'firstPspAcountAttempt', headerName: 'First PSP Acount Attempt', type: 'number', width: 90 },
// { field: 'firstPspAcountSuccessful', headerName: 'First PSP Acount Successful', type: 'number', width: 90 },
// { field: 'originTransactionId', headerName: 'Origin Transaction ID', type: 'number', width: 90 },
// { field: 'transactionReferenceId', headerName: 'Transaction Reference ID', type: 'number', width: 90 },
];
export const depositTransactionsColumns = [
{ field: "userId", headerName: "User ID", width: 130 },
{ field: "merchandId", headerName: "Merchant ID", width: 130 },
{ field: "transactionId", headerName: "Transaction ID", width: 130 },
{ field: "depositMethod", headerName: "Deposit Method", width: 130 },
{ field: "status", headerName: "Status", width: 130 },
{ field: "amount", headerName: "Amount", width: 130 },
{ field: "currency", headerName: "Currency", width: 130 },
{ field: "dateTime", headerName: "Date / Time", width: 130 },
{ field: "errorInfo", headerName: "Error Info", width: 130 },
{ field: "fraudScore", headerName: "Fraud Score", width: 130 },
]
export const currencies = ["USD", "EUR", "GBP"];
export const states = ["Pending","Inprogress", "Completed", "Failed"];
export const depositMethod = ["Card", "Bank Transfer"];
export const Labels = [
{ label: "User", field: "userId", type: "text" },
{ label: "Transaction ID", field: "transactionId", type: "text" },
{
label: "Transaction Reference ID",
field: "transactionReferenceId",
type: "text",
},
{
label: "Currency",
field: "currency",
type: "select",
options: currencies,
},
{
label: "Status",
field: "status",
type: "select",
options: states,
},
{
label: "Payment Method",
field: "depositMethod",
type: "select",
options: depositMethod,
},
{ label: "Date / Time", field: "dateTime", type: "date" },
]

View File

@ -1,38 +0,0 @@
interface IDepositTransactionsColumns {
field: string;
headerName: string;
width: number;
}
interface IOptions {
value: string;
label: string;
}
interface IFilteredTransactions {
id: number;
userId: number;
merchandId: number;
transactionId: number;
depositMethod: string;
status: string;
options: IOptions[];
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

@ -14,6 +14,8 @@ import SettingsIcon from "@mui/icons-material/Settings";
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import HistoryIcon from '@mui/icons-material/History'; import HistoryIcon from '@mui/icons-material/History';
import FactCheckIcon from "@mui/icons-material/FactCheck";
import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces"; import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces";
@ -75,6 +77,7 @@ export const PAGE_LINKS: ISidebarLink[] = [
], ],
}, },
{ title: "Account IQ", path: "/dashboard/account-iq", icon: InsightsIcon }, { title: "Account IQ", path: "/dashboard/account-iq", icon: InsightsIcon },
{ title: "Audits", path: "/dashboard/audits", icon: FactCheckIcon },
// { title: 'Documentation', path: '/documentation', icon: DescriptionIcon }, // { title: 'Documentation', path: '/documentation', icon: DescriptionIcon },
// { title: 'Support', path: '/support', icon: SupportAgentIcon }, // { title: 'Support', path: '/support', icon: SupportAgentIcon },
// { title: 'System Status', path: '/system-status', icon: WarningAmberIcon }, // { title: 'System Status', path: '/system-status', icon: WarningAmberIcon },

View File

@ -1,11 +1,8 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from "@reduxjs/toolkit";
import advancedSearchReducer from './advanedSearch/advancedSearchSlice'; import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
import transactionsReducer from './transactions/transactionsSlice';
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
advancedSearch: advancedSearchReducer, advancedSearch: advancedSearchReducer,
transactions: transactionsReducer,
}, },
}); });

View File

@ -1,3 +0,0 @@
import { RootState } from "../types";
export const selectTransactions = (state: RootState) => state.transactions.data;

View File

@ -1,83 +0,0 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
interface ITransactionsData {
id: number;
merchandId: number;
transactionID: number;
user: number;
created: string;
state: string;
statusDescription: string;
pspStatusCode: number;
}
interface ITransactionsState {
data: ITransactionsData[];
loading: boolean;
error: null | string;
totalTransactions: number
}
const initialState: ITransactionsState = {
data: [],
loading: false,
error: null,
totalTransactions: 0,
}
const transactionsSlice = createSlice({
name: 'transactions',
initialState,
reducers: {
},
extraReducers: (builder) => {
builder
.addCase(getTransactions.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(getTransactions.fulfilled, (state, action) => {
state.data = action.payload.transactions;
state.totalTransactions = action.payload.count;
state.loading = false;
})
.addCase(getTransactions.rejected, (state, action) => {
state.error = action.error.message || "Failed to fetch categories";
state.loading = false;
state.data = [];
})
}
},
);
export default transactionsSlice.reducer;
export const getTransactions = createAsyncThunk(
'transactions/getTransactions',
async (
{
userId = '',
state = '',
statusCode = '',
}: { userId?: string; state?: string; statusCode?: string } = {}
) => {
const url = new URL('https://api.example.com/transactions');
if (userId) url.searchParams.append('userId', userId);
if (state) url.searchParams.append('state', state);
if (statusCode) url.searchParams.append('statusCode', statusCode);
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Failed to fetch transactions');
}
const data = await response.json();
return data; // Let the reducer store this
}
);

View File

@ -0,0 +1,22 @@
export async function getAudits({
query,
}: {
query: string;
}) {
const res = await fetch(
`http://localhost:3000/api/dashboard/audits?${query}`,
{
cache: "no-store",
},
);
if (!res.ok) {
// Handle error from the API
const errorData = await res
.json()
.catch(() => ({ message: "Unknown error" }));
throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
}
return res.json();
}

View File

@ -1,11 +1,23 @@
export async function getTransactions({ transactionType, query }: { transactionType: string, query: string }) { export async function getTransactions({
const res = await fetch(`http://localhost:3000/api/transactions/${transactionType}?${query}`, { transactionType,
query,
}: {
transactionType: string;
query: string;
}) {
const res = await fetch(
`http://localhost:3000/api/dashboard/transactions/${transactionType}?${query}`,
{
cache: "no-store", cache: "no-store",
}); },
);
if (!res.ok) { if (!res.ok) {
// Handle error from the API // 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

@ -1,24 +1,19 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
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 { GridColDef } from "@mui/x-data-grid";
import type { ITransaction } from "../features/pages/transactions/types"; export const exportData = <TRow, TColumn extends GridColDef>(
rows: TRow[],
columns: TColumn[],
export const exportData = (
transactions: ITransaction[],
columns: GridColDef[],
fileType: FileType = "csv", fileType: FileType = "csv",
onlyCurrentTable = false, onlyCurrentTable = false,
setOpen: (open: boolean) => void setOpen: (open: boolean) => void,
) => { ) => {
const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions; const exportRows = onlyCurrentTable ? rows.slice(0, 5) : rows;
const exportData = [ const exportData = [
columns.map((col) => col.headerName), columns.map((col) => col.headerName),
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")), ...exportRows.map((row) => columns.map((col) => (row as Record<string, unknown>)[col.field] ?? "")),
]; ];
const worksheet = XLSX.utils.aoa_to_sheet(exportData); const worksheet = XLSX.utils.aoa_to_sheet(exportData);