Merge pull request #6 from mitchell131/fetch-data-from-api
Fetch data from api
This commit is contained in:
commit
2864bf8cdc
25
payment-iq/app/api/transactions/route.ts
Normal file
25
payment-iq/app/api/transactions/route.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
||||||
|
const state = searchParams.get('state');
|
||||||
|
const user = searchParams.get('user');
|
||||||
|
|
||||||
|
let filteredTransactions = [...transactionDummyData];
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.user.toString() === user
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(filteredTransactions);
|
||||||
|
}
|
||||||
37
payment-iq/app/components/searchFilter/SearchFilters.tsx
Normal file
37
payment-iq/app/components/searchFilter/SearchFilters.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// components/SearchFilters.js
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Chip, Typography, Button } from '@mui/material';
|
||||||
|
|
||||||
|
const SearchFilters = ({ filters, onDeleteFilter, onClearAll }) => {
|
||||||
|
const renderChip = (label, value, key) => (
|
||||||
|
<Chip
|
||||||
|
key={key}
|
||||||
|
label={
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: key === 'state' ? 'bold' : 'normal' }}>
|
||||||
|
{label} {value}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
onDelete={() => onDeleteFilter(key)}
|
||||||
|
sx={{ mr: 1, mb: 1 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box display="flex" alignItems="center" flexWrap="wrap" sx={{ p: 2 }}>
|
||||||
|
{filters.user && renderChip('User', filters.user, 'user')}
|
||||||
|
{filters.state && renderChip('State', filters.state, 'state')}
|
||||||
|
{filters.startDate && renderChip('Start Date', filters.startDate, 'startDate')}
|
||||||
|
|
||||||
|
{Object.values(filters).some(Boolean) && (
|
||||||
|
<Button
|
||||||
|
onClick={onClearAll}
|
||||||
|
sx={{ ml: 1, textDecoration: 'underline', color: 'black' }}
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchFilters;
|
||||||
@ -1,207 +1,189 @@
|
|||||||
import React, { useState } from "react";
|
// app/transactions/page.tsx
|
||||||
import {
|
'use client';
|
||||||
Box,
|
|
||||||
Grid,
|
|
||||||
TextField,
|
|
||||||
MenuItem,
|
|
||||||
Button,
|
|
||||||
InputLabel,
|
|
||||||
FormControl,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} 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";
|
|
||||||
|
|
||||||
const currencies = ["USD", "EUR", "GBP"];
|
import { useState } from 'react';
|
||||||
const states = ["Pending", "Completed", "Failed"];
|
|
||||||
const transactionTypes = ["Credit", "Debit"];
|
|
||||||
const paymentMethods = ["Card", "Bank Transfer"];
|
|
||||||
const psps = ["Stripe", "PayPal"];
|
|
||||||
const merchants = ["Amazon", "eBay"];
|
|
||||||
|
|
||||||
export default function SearchFilterForm() {
|
export default function TransactionsPage() {
|
||||||
const [form, setForm] = useState({
|
const [userId, setUserId] = useState('');
|
||||||
keyword: "",
|
const [state, setState] = useState('');
|
||||||
transactionId: "",
|
const [statusCode, setStatusCode] = useState('');
|
||||||
transactionReferenceId: "",
|
const [transactions, setTransactions] = useState<any[]>([]);
|
||||||
user: "",
|
const [loading, setLoading] = useState(false);
|
||||||
currency: "",
|
|
||||||
state: "",
|
|
||||||
statusDescription: "",
|
|
||||||
transactionType: "",
|
|
||||||
paymentMethod: "",
|
|
||||||
psps: "",
|
|
||||||
initialPsps: "",
|
|
||||||
merchants: "",
|
|
||||||
startDate: null,
|
|
||||||
endDate: null,
|
|
||||||
lastUpdatedFrom: null,
|
|
||||||
lastUpdatedTo: null,
|
|
||||||
minAmount: "",
|
|
||||||
maxAmount: "",
|
|
||||||
channel: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleChange = (field: string, value: any) => {
|
const fetchTransactions = async () => {
|
||||||
setForm((prev) => ({ ...prev, [field]: value }));
|
setLoading(true);
|
||||||
};
|
try {
|
||||||
|
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 resetForm = () => {
|
const response = await fetch(url.toString());
|
||||||
setForm({
|
const data = await response.json();
|
||||||
keyword: "",
|
setTransactions(data.transactions);
|
||||||
transactionId: "",
|
} catch (error) {
|
||||||
transactionReferenceId: "",
|
console.error('Error fetching transactions:', error);
|
||||||
user: "",
|
} finally {
|
||||||
currency: "",
|
setLoading(false);
|
||||||
state: "",
|
}
|
||||||
statusDescription: "",
|
|
||||||
transactionType: "",
|
|
||||||
paymentMethod: "",
|
|
||||||
psps: "",
|
|
||||||
initialPsps: "",
|
|
||||||
merchants: "",
|
|
||||||
startDate: null,
|
|
||||||
endDate: null,
|
|
||||||
lastUpdatedFrom: null,
|
|
||||||
lastUpdatedTo: null,
|
|
||||||
minAmount: "",
|
|
||||||
maxAmount: "",
|
|
||||||
channel: "",
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
<div className="p-4">
|
||||||
<Box p={2}>
|
<h1 className="text-xl font-bold mb-4">Transaction Search</h1>
|
||||||
<Typography variant="h6" gutterBottom>
|
|
||||||
Search
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container spacing={2}>
|
<div className="flex gap-4 mb-4">
|
||||||
{[
|
<div>
|
||||||
{ label: "Keyword", field: "keyword", type: "text" },
|
<label className="block text-sm mb-1">User ID</label>
|
||||||
{ label: "Transaction ID", field: "transactionId", type: "text" },
|
<input
|
||||||
{
|
type="text"
|
||||||
label: "Transaction Reference ID",
|
value={userId}
|
||||||
field: "transactionReferenceId",
|
onChange={(e) => setUserId(e.target.value)}
|
||||||
type: "text",
|
className="border p-2 rounded text-sm"
|
||||||
},
|
placeholder="Filter by user ID"
|
||||||
{ 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 }) => (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
item
|
|
||||||
xs={12}
|
|
||||||
alignItems="center"
|
|
||||||
spacing={1}
|
|
||||||
key={field}
|
|
||||||
>
|
|
||||||
<Grid item xs={4}>
|
|
||||||
<Typography variant="body2" fontWeight={600}>
|
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={8}>
|
|
||||||
{type === "text" && (
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
size="small"
|
|
||||||
value={form[field]}
|
|
||||||
onChange={(e) => handleChange(field, e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
{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} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Buttons */}
|
<div>
|
||||||
<Grid item xs={12} display="flex" justifyContent="flex-end" gap={2}>
|
<label className="block text-sm mb-1">State</label>
|
||||||
<Button
|
<input
|
||||||
variant="outlined"
|
type="text"
|
||||||
startIcon={<RefreshIcon />}
|
value={state}
|
||||||
onClick={resetForm}
|
onChange={(e) => setState(e.target.value)}
|
||||||
|
className="border p-2 rounded text-sm"
|
||||||
|
placeholder="Filter by state"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm mb-1">Status Code</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={statusCode}
|
||||||
|
onChange={(e) => setStatusCode(e.target.value)}
|
||||||
|
className="border p-2 rounded text-sm"
|
||||||
|
placeholder="Filter by status code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-end">
|
||||||
|
<button
|
||||||
|
onClick={fetchTransactions}
|
||||||
|
disabled={loading}
|
||||||
|
className="bg-blue-500 text-white px-4 py-2 rounded text-sm"
|
||||||
>
|
>
|
||||||
Reset
|
{loading ? 'Loading...' : 'Search'}
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
</div>
|
||||||
variant="contained"
|
</div>
|
||||||
startIcon={<SearchIcon />}
|
|
||||||
onClick={() => console.log("Apply Filter", form)}
|
{transactions.length > 0 ? (
|
||||||
>
|
<div className="border rounded overflow-hidden">
|
||||||
Apply Filter
|
<table className="min-w-full">
|
||||||
</Button>
|
<thead className="bg-gray-100">
|
||||||
</Grid>
|
<tr>
|
||||||
</Grid>
|
<th className="py-2 px-4 text-left text-sm">ID</th>
|
||||||
</Box>
|
<th className="py-2 px-4 text-left text-sm">User</th>
|
||||||
</LocalizationProvider>
|
<th className="py-2 px-4 text-left text-sm">State</th>
|
||||||
|
<th className="py-2 px-4 text-left text-sm">Status Code</th>
|
||||||
|
<th className="py-2 px-4 text-left text-sm">Created</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{transactions.map((tx) => (
|
||||||
|
<tr key={tx.id} className="border-t">
|
||||||
|
<td className="py-2 px-4 text-sm">{tx.id}</td>
|
||||||
|
<td className="py-2 px-4 text-sm">{tx.user}</td>
|
||||||
|
<td className="py-2 px-4 text-sm">{tx.state}</td>
|
||||||
|
<td className="py-2 px-4 text-sm">{tx.pspStatusCode}</td>
|
||||||
|
<td className="py-2 px-4 text-sm">{tx.created}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-4 text-sm">
|
||||||
|
{loading ? 'Loading transactions...' : 'No transactions found'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// mocks/handlers.ts
|
||||||
|
import { http, HttpResponse } from 'msw';
|
||||||
|
import { transactionDummyData } from './transactionData';
|
||||||
|
|
||||||
|
export const handlers = [
|
||||||
|
http.get('https://api.example.com/transactions', ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// Get query parameters
|
||||||
|
const userId = url.searchParams.get('userId');
|
||||||
|
const state = url.searchParams.get('state');
|
||||||
|
const statusCode = url.searchParams.get('statusCode');
|
||||||
|
|
||||||
|
// Filter transactions based on query parameters
|
||||||
|
let filteredTransactions = [...transactionDummyData];
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.user.toString() === userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.pspStatusCode.toString() === statusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
transactions: filteredTransactions,
|
||||||
|
count: filteredTransactions.length
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// mocks/transactionData.ts
|
||||||
|
export const transactionDummyData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
merchandId: 100987998,
|
||||||
|
transactionID: 1049131973,
|
||||||
|
user: 1,
|
||||||
|
created: "2025-06-18 10:10:30",
|
||||||
|
state: "FAILED",
|
||||||
|
statusDescription: "ERR_ABOVE_LIMIT",
|
||||||
|
pspStatusCode: 100501,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
merchandId: 100987998,
|
||||||
|
transactionID: 1049131973,
|
||||||
|
user: 2,
|
||||||
|
created: "2025-06-18 10:10:30",
|
||||||
|
state: "FAILED",
|
||||||
|
statusDescription: "ERR_ABOVE_LIMIT",
|
||||||
|
pspStatusCode: 100501,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
merchandId: 100987998,
|
||||||
|
transactionID: 1049131973,
|
||||||
|
user: 3,
|
||||||
|
created: "2025-06-18 10:10:30",
|
||||||
|
state: "FAILED",
|
||||||
|
statusDescription: "ERR_ABOVE_LIMIT",
|
||||||
|
pspStatusCode: 100501,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
// This ensures this component is rendered only on the client side
|
// This ensures this component is rendered only on the client side
|
||||||
"use client";
|
|
||||||
|
|
||||||
import TransactionTable from "@/app/features/Pages/Transactions/Transactions";
|
import TransactionTable from "@/app/features/Pages/Transactions/Transactions";
|
||||||
|
|
||||||
|
|
||||||
export default function TransactionPage() {
|
export default function TransactionPage() {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "70%" }}>
|
||||||
{/* This page will now be rendered on the client-side */}
|
{/* This page will now be rendered on the client-side */}
|
||||||
<TransactionTable />
|
<TransactionTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const states = ["Pending", "Completed", "Failed"];
|
|||||||
const transactionTypes = ["Credit", "Debit"];
|
const transactionTypes = ["Credit", "Debit"];
|
||||||
const paymentMethods = ["Card", "Bank Transfer"];
|
const paymentMethods = ["Card", "Bank Transfer"];
|
||||||
|
|
||||||
export default function AdvancedSearch({setForm, form, resetForm}) {
|
export default function AdvancedSearch({ setForm, form, resetForm }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleChange = (field: string, value: any) => {
|
const handleChange = (field: string, value: any) => {
|
||||||
@ -172,7 +172,7 @@ export default function AdvancedSearch({setForm, form, resetForm}) {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Box sx={{width: '185px'}}>
|
<Box sx={{ width: '185px' }}>
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
@ -205,4 +205,4 @@ export default function AdvancedSearch({setForm, form, resetForm}) {
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|||||||
@ -21,16 +21,13 @@ import * as XLSX from "xlsx";
|
|||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
import { DataGrid } from "@mui/x-data-grid";
|
import { DataGrid } from "@mui/x-data-grid";
|
||||||
import { columns } from "./constants";
|
import { columns } from "./constants";
|
||||||
import { rows } from "./mockData";
|
// import { rows } from "./mockData";
|
||||||
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch";
|
import AdvancedSearch from "../../AdvancedSearch/AdvancedSearch";
|
||||||
|
import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
||||||
|
|
||||||
const paginationModel = { page: 0, pageSize: 50 };
|
const paginationModel = { page: 0, pageSize: 50 };
|
||||||
|
|
||||||
export default function TransactionTable() {
|
export default function TransactionTable() {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
|
||||||
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
|
||||||
const [filteredRows, setFilteredRows] = useState<typeof rows>([])
|
|
||||||
|
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
keyword: "",
|
keyword: "",
|
||||||
@ -78,54 +75,14 @@ export default function TransactionTable() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const filterRows = (rows1, filters) => {
|
const [fileType, setFileType] = useState<"csv" | "xls" | "xlsx">("csv");
|
||||||
// debugger
|
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
||||||
return rows1.filter(row => {
|
|
||||||
const hasTransactionIdFilter = filters.transactionID !== "";
|
|
||||||
const hasStateFilter = filters.state !== "";
|
|
||||||
|
|
||||||
|
|
||||||
if (hasTransactionIdFilter && hasStateFilter) {
|
|
||||||
console.log(1234)
|
|
||||||
// Return rows that match BOTH filters
|
|
||||||
return row.transactionID == filters.transactionID && row.state.toLowerCase() === filters.state.toLowerCase();
|
|
||||||
} else if (hasTransactionIdFilter) {
|
|
||||||
console.log(12345)
|
|
||||||
// Return rows that match merchandId only
|
|
||||||
return row.transactionID == filters.transactionID;
|
|
||||||
} else if (hasStateFilter) {
|
|
||||||
// Return rows that match state only
|
|
||||||
console.log(123456)
|
|
||||||
return row.state.toLowerCase() === filters.state.toLowerCase();
|
|
||||||
} else {
|
|
||||||
console.log(1234567)
|
|
||||||
// No filters applied, return all rows
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(form)
|
|
||||||
console.log(filterRows(rows, { transactionID: form.transactionID, state: form.state }))
|
|
||||||
setFilteredRows(filterRows(rows, { transactionID: form.transactionID, state: form.state }));
|
|
||||||
}, [form])
|
|
||||||
|
|
||||||
// useEffect(()=>{
|
|
||||||
// if(form?.transactionId){
|
|
||||||
// setFilteredRows(rows.filter(row => (row.merchandId.toString() === form.transactionId) && (row.state !== "" && (row.state === form.state))));
|
|
||||||
// } else{
|
|
||||||
// setFilteredRows(rows)
|
|
||||||
// }
|
|
||||||
// },[form])
|
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
const exportRows = onlyCurrentTable ? rows.slice(0, 5) : rows;
|
const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions;
|
||||||
const exportData = [
|
const exportData = [
|
||||||
columns.map((col) => col.headerName),
|
columns.map((col) => col.headerName),
|
||||||
// @ts-expect-error - Dynamic field access from DataGrid columns
|
|
||||||
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
|
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -146,6 +103,43 @@ export default function TransactionTable() {
|
|||||||
setOpen(false);
|
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 (
|
return (
|
||||||
<StyledPaper>
|
<StyledPaper>
|
||||||
<Stack
|
<Stack
|
||||||
@ -163,6 +157,7 @@ export default function TransactionTable() {
|
|||||||
sx={{ width: 300 }}
|
sx={{ width: 300 }}
|
||||||
/>
|
/>
|
||||||
<AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} />
|
<AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} />
|
||||||
|
<SearchFilters filters={form} onDeleteFilter={handleDeleteFilter} onClearAll={handleClearAll} />
|
||||||
{/* <RightTemporaryDrawer /> */}
|
{/* <RightTemporaryDrawer /> */}
|
||||||
{/* <SearchFilterForm /> */}
|
{/* <SearchFilterForm /> */}
|
||||||
<Button
|
<Button
|
||||||
@ -175,11 +170,20 @@ export default function TransactionTable() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={filteredRows}
|
rows={transactions}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
initialState={{ pagination: { paginationModel } }}
|
initialState={{ pagination: { paginationModel } }}
|
||||||
pageSizeOptions={[50, 100]}
|
pageSizeOptions={[50, 100]}
|
||||||
sx={{ border: 0 }}
|
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 */}
|
{/* Export Dialog */}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { store } from '../../lib/store';
|
import { store } from '../redux/store';
|
||||||
|
|
||||||
export function Providers({ children }: { children: ReactNode }) {
|
export function Providers({ children }: { children: ReactNode }) {
|
||||||
return <Provider store={store}>{children}</Provider>;
|
return <Provider store={store}>{children}</Provider>;
|
||||||
|
|||||||
11
payment-iq/app/redux/store.ts
Normal file
11
payment-iq/app/redux/store.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import advancedSearchReducer from './advanedSearch/advancedSearchSlice';
|
||||||
|
import transactionsReducer from './transactions/transactionsSlice';
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
advancedSearch: advancedSearchReducer,
|
||||||
|
transactions: transactionsReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
3
payment-iq/app/redux/transactions/selectors.ts
Normal file
3
payment-iq/app/redux/transactions/selectors.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { RootState } from "../types";
|
||||||
|
|
||||||
|
export const selectTransactions = (state: RootState) => state.transactions.data;
|
||||||
83
payment-iq/app/redux/transactions/transactionsSlice.ts
Normal file
83
payment-iq/app/redux/transactions/transactionsSlice.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
5
payment-iq/app/redux/types.ts
Normal file
5
payment-iq/app/redux/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { store } from "./store";
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
29
payment-iq/app/test1/page.tsx
Normal file
29
payment-iq/app/test1/page.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import SearchFilters from '../components/searchFilter/SearchFilters';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [filters, setFilters] = useState({
|
||||||
|
user: '42',
|
||||||
|
state: 'FAILED',
|
||||||
|
startDate: '2025-06-28 23:25',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDeleteFilter = (key) => {
|
||||||
|
setFilters((prev) => ({ ...prev, [key]: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearAll = () => {
|
||||||
|
setFilters({ user: null, state: null, startDate: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SearchFilters
|
||||||
|
filters={filters}
|
||||||
|
onDeleteFilter={handleDeleteFilter}
|
||||||
|
onClearAll={handleClearAll}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit';
|
|
||||||
import advancedSearchReducer from '@/app/features/AdvancedSearch/store/advancedSearchSlice';
|
|
||||||
|
|
||||||
export const store = configureStore({
|
|
||||||
reducer: {
|
|
||||||
advancedSearch: advancedSearchReducer,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
|
||||||
export type AppDispatch = typeof store.dispatch;
|
|
||||||
|
|
||||||
@ -21,6 +21,7 @@
|
|||||||
// ];
|
// ];
|
||||||
|
|
||||||
|
|
||||||
|
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
|
|
||||||
export const handlers = [
|
export const handlers = [
|
||||||
@ -78,8 +79,6 @@ export const handlers = [
|
|||||||
category: category || 'general',
|
category: category || 'general',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(1234, mockProducts)
|
|
||||||
|
|
||||||
// Sort products if sort parameter provided
|
// Sort products if sort parameter provided
|
||||||
if (sort === 'price') {
|
if (sort === 'price') {
|
||||||
mockProducts.sort((a, b) => a.price - b.price);
|
mockProducts.sort((a, b) => a.price - b.price);
|
||||||
@ -95,4 +94,38 @@ export const handlers = [
|
|||||||
sortBy: sort,
|
sortBy: sort,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
http.get('https://api.example.com/transactions', ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// Get query parameters
|
||||||
|
const userId = url.searchParams.get('userId');
|
||||||
|
const state = url.searchParams.get('state');
|
||||||
|
const statusCode = url.searchParams.get('statusCode');
|
||||||
|
|
||||||
|
// Filter transactions based on query parameters
|
||||||
|
let filteredTransactions = [...transactionDummyData];
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.user.toString() === userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode) {
|
||||||
|
filteredTransactions = filteredTransactions.filter(
|
||||||
|
tx => tx.pspStatusCode.toString() === statusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
transactions: filteredTransactions,
|
||||||
|
count: filteredTransactions.length
|
||||||
|
});
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user