Commit 9382c249 by Arjun Jhukal

updated the filter by game functionality at admin transaction table

parent 189f6192
import { ArrowUp, ArrowDown } from '@wandersonalwes/iconsax-react';
export default function SortableHeader({ column, label }: { column: any; label: string }) {
const sortState = column.getIsSorted(); // asc | desc | false
const arrow =
sortState === "asc" ? <ArrowUp size={14} /> :
sortState === "desc" ? <ArrowDown size={14} /> : null;
return (
<p
onClick={() => column.toggleSorting()}
className="flex items-center gap-1 cursor-pointer"
>
{label} {arrow}
</p>
);
};
...@@ -3,6 +3,7 @@ import { Button, IconButton, InputAdornment, OutlinedInput, useMediaQuery } from ...@@ -3,6 +3,7 @@ import { Button, IconButton, InputAdornment, OutlinedInput, useMediaQuery } from
import SelectField from "../atom/SelectField"; import SelectField from "../atom/SelectField";
import { DocumentDownload, SearchNormal } from "@wandersonalwes/iconsax-react"; import { DocumentDownload, SearchNormal } from "@wandersonalwes/iconsax-react";
import React from "react"; import React from "react";
import Filter from "../organism/Filter";
interface FilterOption { interface FilterOption {
value: string; value: string;
...@@ -101,6 +102,8 @@ export default function TableHeader({ ...@@ -101,6 +102,8 @@ export default function TableHeader({
/> />
)} )}
{/* <Filter /> */}
{/* Download Button */} {/* Download Button */}
{onDownloadCSV && <Button {onDownloadCSV && <Button
startIcon={!downMD && <DocumentDownload size={16} />} startIcon={!downMD && <DocumentDownload size={16} />}
...@@ -116,6 +119,7 @@ export default function TableHeader({ ...@@ -116,6 +119,7 @@ export default function TableHeader({
{downMD ? <DocumentDownload size={16} /> : downloading ? "Downloading..." : "Download CSV"} {downMD ? <DocumentDownload size={16} /> : downloading ? "Downloading..." : "Download CSV"}
</Button>} </Button>}
</div> </div>
</div> </div>
); );
} }
"use client"; "use client";
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { import {
Box, Box,
...@@ -11,12 +12,12 @@ import { ...@@ -11,12 +12,12 @@ import {
List, List,
ListItem, ListItem,
Divider, Divider,
Dialog,
DialogContent,
} from "@mui/material"; } from "@mui/material";
import { ArrowDown2 } from "@wandersonalwes/iconsax-react"; import { ArrowDown2 } from "@wandersonalwes/iconsax-react";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import CustomDateRangePicker from "../molecules/DateRangePicker"; import CustomDateRangePicker from '../molecules/DateRangePicker';
interface FilterOption { interface FilterOption {
label: string; label: string;
...@@ -24,11 +25,11 @@ interface FilterOption { ...@@ -24,11 +25,11 @@ interface FilterOption {
} }
interface FilterProps { interface FilterProps {
option: FilterOption[]; option?: FilterOption[];
currentFilter: number | null; currentFilter?: number | null;
setFilterDays: React.Dispatch<React.SetStateAction<number | null>>; setFilterDays?: React.Dispatch<React.SetStateAction<number | null>>;
customRange: { startDate: string; endDate: string }; customRange?: { startDate: string; endDate: string };
setCustomRange: React.Dispatch< setCustomRange?: React.Dispatch<
React.SetStateAction<{ startDate: string; endDate: string }> React.SetStateAction<{ startDate: string; endDate: string }>
>; >;
} }
...@@ -42,12 +43,12 @@ export default function Filter({ ...@@ -42,12 +43,12 @@ export default function Filter({
}: FilterProps) { }: FilterProps) {
const anchorRef = useRef<HTMLButtonElement | null>(null); const anchorRef = useRef<HTMLButtonElement | null>(null);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [customRangeOpen, setCustomRangeOpen] = useState(false); const [showCustomRangeModal, setShowCustomRangeModal] = useState(false);
const [startDate, setStartDate] = useState<Dayjs | null>( const [startDate, setStartDate] = useState<Dayjs | null>(
customRange.startDate ? dayjs(customRange.startDate) : null customRange?.startDate ? dayjs(customRange.startDate) : null
); );
const [endDate, setEndDate] = useState<Dayjs | null>( const [endDate, setEndDate] = useState<Dayjs | null>(
customRange.endDate ? dayjs(customRange.endDate) : null customRange?.endDate ? dayjs(customRange.endDate) : null
); );
const handleToggle = () => setOpen((prev) => !prev); const handleToggle = () => setOpen((prev) => !prev);
...@@ -58,31 +59,44 @@ export default function Filter({ ...@@ -58,31 +59,44 @@ export default function Filter({
}; };
const handleSelect = (value: number | null, label: string) => { const handleSelect = (value: number | null, label: string) => {
if (label === "Custom range") {
setOpen(false);
setCustomRange({ startDate: "", endDate: "" }); setShowCustomRangeModal(true);
setStartDate(null); } else {
setEndDate(null); // Reset custom range when selecting predefined filters
setStartDate(null);
setFilterDays(value); setEndDate(null);
setOpen(false); setCustomRange && setCustomRange({ startDate: "", endDate: "" });
setFilterDays && setFilterDays(value);
setOpen(false);
}
}; };
const handleApplyCustomRange = () => { const handleApplyCustomRange = () => {
if (startDate && endDate) { if (startDate && endDate) {
setCustomRange({ setCustomRange && setCustomRange({
startDate: startDate.format("YYYY-MM-DD"), startDate: startDate.format("YYYY-MM-DD"),
endDate: endDate.format("YYYY-MM-DD"), endDate: endDate.format("YYYY-MM-DD"),
}); });
setFilterDays(null); setFilterDays && setFilterDays(null);
setCustomRangeOpen(false); setShowCustomRangeModal(false);
} }
}; };
const handleResetCustomRange = () => {
setStartDate(null);
setEndDate(null);
setShowCustomRangeModal(false);
};
const id = open ? "filter-popper" : undefined; const id = open ? "filter-popper" : undefined;
// Determine if custom range is active
const isCustomRangeActive = customRange?.startDate && customRange?.endDate;
return ( return (
<Box> <Box>
{/* 🔽 Filter Button */}
<IconButton <IconButton
aria-describedby={id} aria-describedby={id}
ref={anchorRef} ref={anchorRef}
...@@ -95,6 +109,7 @@ export default function Filter({ ...@@ -95,6 +109,7 @@ export default function Filter({
</Typography> </Typography>
</IconButton> </IconButton>
{/* Filter Dropdown */}
<Popper <Popper
id={id} id={id}
open={open} open={open}
...@@ -115,26 +130,33 @@ export default function Filter({ ...@@ -115,26 +130,33 @@ export default function Filter({
> >
<ClickAwayListener onClickAway={handleClose}> <ClickAwayListener onClickAway={handleClose}>
<Box className="p-3"> <Box className="p-3">
<List className="!p-0 mb-3"> <List className="!p-0">
{option.map((item) => ( {option && option.map((item) => (
<ListItem <ListItem
key={item.label} key={item.label}
className={`!px-3 !py-1.5 text-sm rounded-md cursor-pointer ${currentFilter === item.value className={`!px-3 !py-1.5 text-sm rounded-md cursor-pointer ${currentFilter === item.value
? "bg-[#652CA0] text-white" ? "bg-[#652CA0] text-white"
: "" : ""
}`} }`}
onClick={() => handleSelect(item.value, item.label)} onClick={() =>
handleSelect(
item.value,
item.label
)
}
> >
{item.label} {item.label}
</ListItem> </ListItem>
))} ))}
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1 }} />
<ListItem <ListItem
onClick={() => { onClick={() =>
setOpen(false); handleSelect(null, "Custom range")
setCustomRangeOpen(true); }
}} className={`!px-3 !py-1.5 text-sm rounded-md cursor-pointer ${isCustomRangeActive
className="!px-3 !py-1.5 text-sm rounded-md cursor-pointer" ? "bg-[#652CA0] text-white"
: ""
}`}
> >
Custom range Custom range
</ListItem> </ListItem>
...@@ -146,44 +168,32 @@ export default function Filter({ ...@@ -146,44 +168,32 @@ export default function Filter({
)} )}
</Popper> </Popper>
<Popper {/* Custom Range Modal */}
open={customRangeOpen} <Dialog
anchorEl={anchorRef.current} open={showCustomRangeModal}
placement="bottom-end" onClose={() => setShowCustomRangeModal(false)}
transition maxWidth="xs"
fullWidth
PaperProps={{
sx: {
borderRadius: 3,
backgroundColor: "transparent",
boxShadow: "none",
padding: "20px",
},
}}
> >
{({ TransitionProps }) => ( <DialogContent sx={{ p: 0 }}>
<Fade {...TransitionProps} timeout={300}> <CustomDateRangePicker
<Paper elevation={3} sx={{ borderRadius: 3, mt: 1 }}> startDate={startDate}
<ClickAwayListener onClickAway={(event) => { endDate={endDate}
// Ignore clicks on MUI Select menus (rendered in Portal) onStartDateChange={setStartDate}
if ((event.target as HTMLElement).closest('.MuiPopover-root, .MuiBox-root,.MuiInputBase-root')) { onEndDateChange={setEndDate}
return; onApply={handleApplyCustomRange}
} onReset={handleResetCustomRange}
setCustomRangeOpen(false); />
}}> </DialogContent>
<Box className="p-3 w-[300px]"> </Dialog>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<CustomDateRangePicker
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
onApply={handleApplyCustomRange}
onReset={() => {
setStartDate(null);
setEndDate(null);
setCustomRangeOpen(false);
}}
/>
</LocalizationProvider>
</Box>
</ClickAwayListener>
</Paper>
</Fade>
)}
</Popper>
</Box> </Box>
); );
} }
\ No newline at end of file
"use client"; "use client";
import SortableHeader from '@/components/atom/SortableHeader';
import TableHeader from '@/components/molecules/TableHeader'; import TableHeader from '@/components/molecules/TableHeader';
import CustomTable from '@/components/organism/Table'; import CustomTable from '@/components/organism/Table';
import { useAppDispatch } from '@/hooks/hook'; import { useAppDispatch } from '@/hooks/hook';
import { useDownloadTransactionMutation } from '@/services/downloadApi'; import { useDownloadTransactionMutation } from '@/services/downloadApi';
import { useGetAllGamesQuery } from '@/services/gameApi';
import { useGetAllTransactionQuery } from '@/services/transaction'; import { useGetAllTransactionQuery } from '@/services/transaction';
import { showToast, ToastVariant } from '@/slice/toastSlice'; import { showToast, ToastVariant } from '@/slice/toastSlice';
import { StatusOptions } from '@/types/config'; import { StatusOptions } from '@/types/config';
...@@ -21,6 +23,8 @@ import { ArrowDown, ArrowUp } from '@wandersonalwes/iconsax-react'; ...@@ -21,6 +23,8 @@ import { ArrowDown, ArrowUp } from '@wandersonalwes/iconsax-react';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
export type TransactionStatusProps = "success" | "failed" | "pending"; export type TransactionStatusProps = "success" | "failed" | "pending";
export type TransactionTypeProps = "deposit" | "withdrawl";
export default function TransactionTable({ user_id, game_id, search, setSearch }: { user_id?: string; game_id?: number, search: string, setSearch?: (newvalue: string) => void }) { export default function TransactionTable({ user_id, game_id, search, setSearch }: { user_id?: string; game_id?: number, search: string, setSearch?: (newvalue: string) => void }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
...@@ -30,6 +34,8 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -30,6 +34,8 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [rowSelection, setRowSelection] = useState({}); const [rowSelection, setRowSelection] = useState({});
const [status, setStatus] = React.useState<TransactionStatusProps | undefined>(); const [status, setStatus] = React.useState<TransactionStatusProps | undefined>();
const [selectedGame, setSelectedGame] = React.useState("");
const [selectedTransactionType, setSelectedTransationType] = React.useState<TransactionTypeProps | string>("");
const queryArgs = useMemo( const queryArgs = useMemo(
() => ({ () => ({
page, page,
...@@ -37,9 +43,11 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -37,9 +43,11 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
search: search || "", search: search || "",
game_id, game_id,
user_id, user_id,
status status,
selectedGame,
selectedTransactionType
}), }),
[page, pageSize, search, game_id, user_id, status] [page, pageSize, search, game_id, user_id, status, selectedGame, selectedTransactionType]
); );
const { data, isLoading: loadingTransaction } = useGetAllTransactionQuery(queryArgs); const { data, isLoading: loadingTransaction } = useGetAllTransactionQuery(queryArgs);
...@@ -50,43 +58,21 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -50,43 +58,21 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
const columns = useMemo<ColumnDef<SingleDepositProps>[]>(() => [ const columns = useMemo<ColumnDef<SingleDepositProps>[]>(() => [
{ {
accessorKey: "id", accessorKey: "id",
header: ({ column }) => { header: ({ column }) => <SortableHeader column={column} label="#ID" />,
// Determine arrow: show Asc by default if not sorted
const sortState = column.getIsSorted();
const arrow =
sortState === "asc" || sortState === null ? (
<ArrowUp size={14} />
) : sortState === "desc" ? (
<ArrowDown size={14} />
) : null;
return (
<p
onClick={() => column.toggleSorting()}
className="flex items-center gap-1 cursor-pointer"
>
#ID {arrow}
</p>
);
},
// cell:({row})=>(
// <span className="text-center">{row.original.id}</span>
// )
}, },
{ {
accessorKey: "name", accessorKey: "name",
header: "Player Name", header: ({ column }) => <SortableHeader column={column} label="Player Name" />,
cell: ({ row }) => { cell: ({ row }) => {
const { first_name, last_name } = row.original; const { first_name, last_name } = row.original;
const initials = getInitials(first_name, last_name); const initials = getInitials(first_name, last_name);
return ( return (
<Box className="flex justify-start items-center gap-2"> <Box className="flex items-center gap-2">
<small className="text-[10px] w-[24px] h-[24px] flex items-center justify-center uppercase rounded-[4px] bg-[#1EB41B]/10 font-[500] text-[#1EB41B]"> <small className="text-[10px] w-[24px] h-[24px] flex items-center justify-center uppercase rounded-[4px] bg-[#1EB41B]/10 font-[500] text-[#1EB41B]">
{initials} {initials}
</small> </small>
<div className="name-detail"> <div>
<strong className="text-primary block text-[12px] leading-[120%] font-[500] capitalize"> <strong className="text-primary text-[12px] font-[500] capitalize">
{first_name} {last_name} {first_name} {last_name}
</strong> </strong>
<small className="text-[10px] text-para-light font-[500]"> <small className="text-[10px] text-para-light font-[500]">
...@@ -99,39 +85,34 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -99,39 +85,34 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
}, },
{ {
accessorKey: "method", accessorKey: "method",
header: "Method", header: ({ column }) => <SortableHeader column={column} label="Method" />,
}, },
{ {
accessorKey: "game_name", accessorKey: "game_name",
header: "Game Name", header: ({ column }) => <SortableHeader column={column} label="Game Name" />,
}, },
{ {
accessorKey: "type", accessorKey: "type",
header: "Type", header: ({ column }) => <SortableHeader column={column} label="Type" />,
cell: ({ row }) => { cell: ({ row }) => {
const status = row.original.status.toLowerCase(); const status = row.original.status.toLowerCase();
const display = status.charAt(0).toUpperCase() + status.slice(1); const display = status.charAt(0).toUpperCase() + status.slice(1);
return ( return (
<span <span className={`px-2 py-1 max-w-[60px] block lg: text-[10px] text-white status rounded-[8px] text-center ${status}`} > {display} </span >
className={`px-2 py-1 max-w-[60px] block lg:text-[10px] text-white status rounded-[8px] text-center ${status}`}
>
{display}
</span>
); );
}, },
}, },
{ {
accessorKey: "amount", accessorKey: "amount",
header: "Amount USD", header: ({ column }) => <SortableHeader column={column} label="Amount USD" />,
}, },
{ {
accessorKey: "sweepcoins", accessorKey: "sweepcoins",
header: "Sweepcoins", header: ({ column }) => <SortableHeader column={column} label="Sweepcoins" />,
}, },
{ {
accessorKey: "transaction_date", accessorKey: "transaction_date",
header: "Transaction Date", header: ({ column }) => <SortableHeader column={column} label="Transaction Date" />,
cell: ({ row }) => { cell: ({ row }) => {
const { date, time } = formatDateTime(row.original.transaction_date as string); const { date, time } = formatDateTime(row.original.transaction_date as string);
return ( return (
...@@ -144,6 +125,7 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -144,6 +125,7 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
}, },
], []); ], []);
const table = useReactTable({ const table = useReactTable({
data: tableData, data: tableData,
columns, columns,
...@@ -156,6 +138,8 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -156,6 +138,8 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
// onRowSelectionChange: setRowSelection, // onRowSelectionChange: setRowSelection,
}); });
const { data: games, isLoading } = useGetAllGamesQuery();
return ( return (
<div className="border-gray border-solid border-[1px] rounded-[8px] lg:rounded-[16px]"> <div className="border-gray border-solid border-[1px] rounded-[8px] lg:rounded-[16px]">
...@@ -230,6 +214,25 @@ export default function TransactionTable({ user_id, game_id, search, setSearch } ...@@ -230,6 +214,25 @@ export default function TransactionTable({ user_id, game_id, search, setSearch }
options: StatusOptions, options: StatusOptions,
placeholder: "Filter by status", placeholder: "Filter by status",
}, },
{
value: selectedGame || "",
setValue: (value) => setSelectedGame(value as string),
options: games?.data?.data.map((game) => ({
label: game.name,
value: game.id.toString(),
})) || [],
placeholder: "Filter by Game",
},
{
value: selectedTransactionType || "",
setValue: (value) => setSelectedTransationType(value as string),
options: [
{ label: "All", value: "" },
{ label: "Withdrawn", value: "withdrawn" },
{ label: "Deposit", value: "deposit" },
],
placeholder: "Filter by Transaction Type",
},
]} ]}
/> />
......
...@@ -60,8 +60,8 @@ export const transactionApi = createApi({ ...@@ -60,8 +60,8 @@ export const transactionApi = createApi({
}, },
providesTags: ["Withdrawl"] providesTags: ["Withdrawl"]
}), }),
getAllTransaction: builder.query<DepositListProps, QueryParams & { status?: TransactionStatusProps; user_id?: string | number; game_id?: string | number }>({ getAllTransaction: builder.query<DepositListProps, QueryParams & { status?: TransactionStatusProps; user_id?: string | number; game_id?: string | number, selectedGame?: string; selectedTransactionType?: string }>({
query: ({ search, page, per_page, user_id, game_id, status }) => { query: ({ search, page, per_page, user_id, game_id, status, selectedGame, selectedTransactionType }) => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (search) params.append('search', search); if (search) params.append('search', search);
if (page) params.append('page', page.toString()); if (page) params.append('page', page.toString());
...@@ -69,6 +69,8 @@ export const transactionApi = createApi({ ...@@ -69,6 +69,8 @@ export const transactionApi = createApi({
if (user_id) params.append('user', user_id.toString()); if (user_id) params.append('user', user_id.toString());
if (game_id) params.append('game', game_id.toString()); if (game_id) params.append('game', game_id.toString());
if (status) params.append('status', status.toString()); if (status) params.append('status', status.toString());
if (selectedGame) params.append('game', selectedGame.toString());
if (selectedTransactionType) params.append('type', selectedTransactionType.toString());
const queryString = params.toString(); const queryString = params.toString();
return { return {
url: `/api/admin/transactions${queryString ? `?${queryString}` : ''}`, url: `/api/admin/transactions${queryString ? `?${queryString}` : ''}`,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment