357 lines
10 KiB
TypeScript
357 lines
10 KiB
TypeScript
"use client";
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import {
|
|
Box,
|
|
TextField,
|
|
IconButton,
|
|
InputAdornment,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Checkbox,
|
|
Paper,
|
|
MenuItem,
|
|
InputLabel,
|
|
Select,
|
|
FormControl,
|
|
SelectChangeEvent,
|
|
} from "@mui/material";
|
|
import { debounce } from "@mui/material/utils";
|
|
import SearchIcon from "@mui/icons-material/Search";
|
|
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
|
import AddIcon from "@mui/icons-material/Add";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import StatusChangeDialog from "../../DataTable/StatusChangeDialog";
|
|
import { HistoryModal } from "@/app/components/HistoryModal/HistoryModal";
|
|
import { getTransactionsHistory } from "@/app/services/transactionsHistory";
|
|
|
|
export interface TableColumn<T> {
|
|
field: keyof T | string;
|
|
headerName: string;
|
|
render?: (value: unknown, row: T) => React.ReactNode;
|
|
}
|
|
|
|
interface MenuItemOption {
|
|
value: string;
|
|
label?: string;
|
|
}
|
|
|
|
interface DynamicTableProps<T extends { id: string | number }> {
|
|
data: {
|
|
rows: T[];
|
|
columns: TableColumn<T>[];
|
|
actions: MenuItemOption[];
|
|
};
|
|
searchParamKey?: string;
|
|
}
|
|
interface Transaction {
|
|
date: string;
|
|
method: string;
|
|
amount: number | string;
|
|
status: string;
|
|
}
|
|
|
|
export function ApproveTable<T extends { id: string | number }>({
|
|
data,
|
|
searchParamKey = "merchantId",
|
|
}: DynamicTableProps<T>) {
|
|
const { rows, columns, actions } = data;
|
|
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
|
|
const [selected, setSelected] = useState<(string | number)[]>([]);
|
|
const [search, setSearch] = useState("");
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [historyModal, setHistoryModal] = useState(false);
|
|
const [reason, setReason] = useState<string>("");
|
|
const [action, setAction] = useState("");
|
|
const [transactionsHistoryResponse, setTransactionsHistoryResponse] =
|
|
useState<{
|
|
deposits: Transaction[];
|
|
withdrawals: Transaction[];
|
|
}>({
|
|
deposits: [],
|
|
withdrawals: [],
|
|
});
|
|
|
|
useEffect(() => {
|
|
const urlValue = searchParams.get(searchParamKey) ?? "";
|
|
setSearch(urlValue);
|
|
}, [searchParams, searchParamKey]);
|
|
|
|
const updateURL = useMemo(
|
|
() =>
|
|
debounce((value: string) => {
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
if (value) params.set(searchParamKey, value);
|
|
else params.delete(searchParamKey);
|
|
|
|
router.replace(`?${params.toString()}`, { scroll: false });
|
|
}, 400),
|
|
[router, searchParams, searchParamKey]
|
|
);
|
|
|
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = e.target.value;
|
|
setSearch(value);
|
|
updateURL(value);
|
|
};
|
|
|
|
const handleCheckboxChange = (id: string | number, checked: boolean) => {
|
|
setSelected(prev => (checked ? [...prev, id] : prev.filter(x => x !== id)));
|
|
};
|
|
|
|
const handleToggleAll = (checked: boolean) => {
|
|
setSelected(checked ? rows.map(r => r.id) : []);
|
|
};
|
|
|
|
const handleActionChange = (e: SelectChangeEvent<string>) => {
|
|
const selectedAction = e.target.value;
|
|
setAction(selectedAction);
|
|
if (selected.length > 0) {
|
|
setModalOpen(true);
|
|
console.log("Selected Ids", selected);
|
|
console.log("Selected Action:", selectedAction);
|
|
} else {
|
|
alert("No rows selected for this action!");
|
|
}
|
|
};
|
|
|
|
const handleStatusSave = () => {
|
|
console.log(
|
|
`Status changed for row with ID ${selected}. New status: ${action}. Reason: ${reason}`
|
|
);
|
|
setModalOpen(false);
|
|
setReason("");
|
|
};
|
|
|
|
// NEW: plus button handler
|
|
const handlePlusClick = async (row: T) => {
|
|
console.log("Plus clicked for", row);
|
|
const params = `userId=${row.id}`;
|
|
const response = await getTransactionsHistory({ query: params });
|
|
setTransactionsHistoryResponse(response);
|
|
setHistoryModal(true);
|
|
};
|
|
|
|
const stickySecondColLeft = 48;
|
|
|
|
return (
|
|
<>
|
|
<Box p={2}>
|
|
<Box
|
|
mb={2}
|
|
display="flex"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
>
|
|
<TextField
|
|
variant="outlined"
|
|
placeholder="Search..."
|
|
size="small"
|
|
value={search}
|
|
onChange={handleSearchChange}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton>
|
|
<SearchIcon />
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
<Box sx={{ width: 180, display: "flex", justifyContent: "center" }}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Action</InputLabel>
|
|
<Select
|
|
value={action}
|
|
label="Action"
|
|
onChange={handleActionChange}
|
|
size="small"
|
|
>
|
|
{actions.map(item => (
|
|
<MenuItem key={item.value} value={item.value}>
|
|
{item.label ?? item.value}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
</Box>
|
|
|
|
<TableContainer
|
|
component={Paper}
|
|
sx={{
|
|
overflowX: "auto",
|
|
maxHeight: 500,
|
|
width: "calc(100vw - 300px)",
|
|
}}
|
|
>
|
|
<Table
|
|
stickyHeader
|
|
size="small"
|
|
sx={{
|
|
minWidth: 1200,
|
|
}}
|
|
>
|
|
<TableHead>
|
|
<TableRow>
|
|
{/* sticky select-all checkbox */}
|
|
<TableCell
|
|
padding="checkbox"
|
|
sx={{
|
|
position: "sticky",
|
|
left: 0,
|
|
backgroundColor: "white",
|
|
zIndex: 3,
|
|
}}
|
|
>
|
|
<Checkbox
|
|
checked={selected.length === rows.length && rows.length > 0}
|
|
indeterminate={
|
|
selected.length > 0 && selected.length < rows.length
|
|
}
|
|
onChange={e => handleToggleAll(e.target.checked)}
|
|
/>
|
|
</TableCell>
|
|
|
|
{/* NEW: sticky plus column header (empty) */}
|
|
<TableCell
|
|
padding="checkbox"
|
|
sx={{
|
|
position: "sticky",
|
|
left: stickySecondColLeft,
|
|
backgroundColor: "white",
|
|
zIndex: 2,
|
|
width: 48,
|
|
maxWidth: 48,
|
|
}}
|
|
/>
|
|
|
|
{columns.map((col, i) => (
|
|
<TableCell key={i}>{col.headerName}</TableCell>
|
|
))}
|
|
</TableRow>
|
|
</TableHead>
|
|
|
|
<TableBody>
|
|
{rows.map((row, idx) => (
|
|
<TableRow key={idx}>
|
|
{/* sticky checkbox per-row */}
|
|
<TableCell
|
|
padding="checkbox"
|
|
sx={{
|
|
position: "sticky",
|
|
left: 0,
|
|
backgroundColor: "white",
|
|
zIndex: 2,
|
|
}}
|
|
>
|
|
<Checkbox
|
|
checked={selected.includes(row.id)}
|
|
onChange={e =>
|
|
handleCheckboxChange(row.id, e.target.checked)
|
|
}
|
|
/>
|
|
</TableCell>
|
|
|
|
{/* NEW: sticky plus button cell */}
|
|
<TableCell
|
|
padding="checkbox"
|
|
sx={{
|
|
position: "sticky",
|
|
left: stickySecondColLeft,
|
|
backgroundColor: "white",
|
|
zIndex: 1,
|
|
width: 48,
|
|
maxWidth: 48,
|
|
}}
|
|
>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => handlePlusClick(row)}
|
|
sx={{ p: 0.5 }}
|
|
aria-label="expand"
|
|
>
|
|
<AddIcon fontSize="small" />
|
|
</IconButton>
|
|
</TableCell>
|
|
|
|
{columns.map((col, colIdx) => {
|
|
const value = row[col.field as keyof T];
|
|
|
|
if (col.field === "id") {
|
|
return (
|
|
<TableCell
|
|
key={colIdx}
|
|
sx={{
|
|
p: "6px 16px",
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: "grid",
|
|
gridTemplateColumns: "1fr auto",
|
|
alignItems: "center",
|
|
width: "100%",
|
|
}}
|
|
>
|
|
<Box sx={{ textAlign: "center" }}>
|
|
{value as React.ReactNode}
|
|
</Box>
|
|
|
|
<IconButton
|
|
size="small"
|
|
component="a"
|
|
href={`/details/${value}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{ p: 0.5, ml: 1 }}
|
|
>
|
|
<OpenInNewIcon fontSize="small" />
|
|
</IconButton>
|
|
</Box>
|
|
</TableCell>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<TableCell key={colIdx}>
|
|
{value as React.ReactNode}
|
|
</TableCell>
|
|
);
|
|
})}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</Box>
|
|
|
|
<StatusChangeDialog
|
|
open={modalOpen}
|
|
newStatus={action}
|
|
reason={reason}
|
|
setReason={setReason}
|
|
handleClose={() => setModalOpen(false)}
|
|
handleSave={handleStatusSave}
|
|
/>
|
|
<HistoryModal
|
|
open={historyModal}
|
|
onClose={() => {
|
|
setHistoryModal(false);
|
|
setTransactionsHistoryResponse({ deposits: [], withdrawals: [] });
|
|
}}
|
|
deposits={transactionsHistoryResponse.deposits}
|
|
withdrawals={transactionsHistoryResponse.withdrawals}
|
|
/>
|
|
</>
|
|
);
|
|
}
|