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