Commit 90544e7e by Arjun Jhukal

updated the player list and action group

parent b9828c34
import PageHeader from '@/components/molecules/PageHeader'
import PlayerListing from '@/components/pages/dashboard/adminDashboard/players'
import { PATH } from '@/routes/PATH'
import React from 'react'
......@@ -9,7 +10,7 @@ export default function Players() {
title='Players'
cta={{ url: PATH.ADMIN.PLAYERS.ADD_PLAYER.ROOT, label: "Add New Player" }}
/>
<PlayerListing />
</>
)
}
import { Box, ClickAwayListener, Fade, IconButton, List, ListItem, Paper, Popper } from '@mui/material'
import { More } from '@wandersonalwes/iconsax-react'
import Link from 'next/link';
import React, { useRef, useState } from 'react'
export default function ActionGroup({
onView, onEdit, onDelete
}: { onView?: string; onEdit?: string; onDelete?: () => void }) {
const anchorRef = useRef<HTMLButtonElement | null>(null)
const [open, setOpen] = useState(false);
const handleToggle = () => setOpen((prev) => !prev);
const handleClose = (event: MouseEvent | TouchEvent) => {
if (anchorRef.current?.contains(event.target as Node)) return;
setOpen(false);
};
const id = open ? 'action' : undefined;
return (
<Box>
<IconButton
aria-describedby={id}
ref={anchorRef}
onClick={handleToggle}
>
<More className='rotate-90' variant="TwoTone" />
</IconButton>
<Popper
open={open}
anchorEl={anchorRef.current}
placement="bottom-end"
transition
style={{ zIndex: 1300 }}
id={id}
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={300}>
<Paper
elevation={3}
sx={{
width: 215,
borderRadius: 2,
mt: 1,
}}
>
<ClickAwayListener onClickAway={handleClose}>
<List>
<ListItem>
<Link href={onView || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>View Profile</Link>
</ListItem>
<ListItem>
<Link href={onView || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>Edit</Link>
</ListItem>
<ListItem>
<Link href={"#"} className='block py-3 px-4 hover:bg-[#FBF4FB]'
onClick={(e) => {
e.preventDefault();
onDelete;
}}
>Delete</Link>
</ListItem>
</List>
</ClickAwayListener>
</Paper>
</Fade>
)}
</Popper>
</Box>
)
}
import { Button } from "@mui/material";
import { Button, IconButton, InputAdornment, OutlinedInput } from "@mui/material";
import SelectField from "../atom/SelectField";
import { DocumentDownload, SearchNormal } from "@wandersonalwes/iconsax-react";
interface TableHeaderProps {
search: string;
setSearch: (value: string) => void;
filterMethod: string;
setFilterMethod: (value: string) => void;
filterMethod?: string;
setFilterMethod?: (value: string) => void;
onDownloadCSV: () => void;
}
......@@ -19,29 +20,43 @@ export default function TableHeader({
return (
<div className="table__header p-4 mb-4 flex justify-between">
<div className="inpute__field relative">
<input
type="search"
<OutlinedInput
placeholder="Search keywords..."
name="search"
id="search"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="rounded-[8px] border-solid border-[1px] border-gray pl-7 outline-none focus:outline-primary-light text-[14px] focus:border-primary"
startAdornment={
<InputAdornment position="start">
<IconButton edge="start">
<SearchNormal size={32} />
</IconButton>
</InputAdornment>
}
/>
</div>
<div className="header-right flex justify-end items-center gap-2">
{filterMethod ? <div className="header-right flex justify-end items-center gap-2">
<SelectField
name="search"
value={filterMethod}
onChange={(e) => setFilterMethod(e.target.value)}
onChange={(e) => setFilterMethod && setFilterMethod(e.target.value)}
options={[
{ value: "all", name: "All Method" },
{ value: "crypto", name: "Crypto" },
{ value: "paypal", name: "USD/Paypal" },
]}
/>
<Button onClick={onDownloadCSV}>Download CSV</Button>
</div>
</div> : ""}
<Button
startIcon={
<DocumentDownload size={16} />
}
onClick={onDownloadCSV} sx={{
borderRadius: "8px",
border: "1px solid var(--Gray, #E0E0E3)",
padding: "8px 16px",
color: "#0E0E11"
}}>Download CSV</Button>
</div>
);
}
"use client";
import React from "react";
import { flexRender, Table } from "@tanstack/react-table";
interface CustomTableProps<TData> {
table: Table<TData>;
}
export default function CustomTable<TData>({ table }: CustomTableProps<TData>) {
return (
<table className="min-w-full border-collapse border border-gray-200 text-left">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="text-[12px] font-[600] text-title p-2 py-6 px-6 bg-light-gray"
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="odd:bg-white even:bg-gray-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-2 py-6 px-6">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
"use client"
import TableHeader from '@/components/molecules/TableHeader';
import CustomLightGallery from '@/components/organism/LightGallery';
import Image from 'next/image'
import Link from 'next/link'
......
import React from 'react'
"use client";
import ActionGroup from '@/components/molecules/Action';
import TableHeader from '@/components/molecules/TableHeader'
import CustomTable from '@/components/organism/Table';
import { useAppDispatch } from '@/hooks/hook';
import { PATH } from '@/routes/PATH';
import { useDeletePlayerByIdMutation, useGetAllPlayerQuery } from '@/services/playerApi';
import { showToast, ToastVariant } from '@/slice/toastSlice';
import { PlayerItem, PlayerProps } from '@/types/player';
import { formatDateTime } from '@/utils/formatDateTime';
import { getInitials } from '@/utils/getInitials';
import { Box } from '@mui/material';
import { ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table';
import { useRouter } from 'next/navigation';
import React, { useMemo, useState } from 'react'
export default function PlayerListing() {
const router = useRouter();
const dispatch = useAppDispatch();
const [search, setSearch] = useState("");
const [sorting, setSorting] = useState<{ id: string; desc: boolean }[]>([]);
const { data, isLoading: loadingPlayer } = useGetAllPlayerQuery();
const filteredData = useMemo(() => {
if (!data) return [];
return data?.data?.data.filter((player: PlayerItem) => {
const name = player.name ?? "";
const email = player.email ?? "";
return (
name.toLowerCase().includes(search.toLowerCase()) ||
email.toLowerCase().includes(search.toLowerCase())
);
});
}, [search, data]);
const [deletePlayer, { isLoading: deletingPlayer }] = useDeletePlayerByIdMutation();
const columns = useMemo<ColumnDef<PlayerItem>[]>(() => [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
checked={table.getIsAllPageRowsSelected()}
onChange={table.getToggleAllPageRowsSelectedHandler()}
/>
),
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
/>
),
size: 50
},
{
accessorKey: 'id',
header: '#ID',
cell: ({ row }) => row.original.id
},
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => {
const { first_name, last_name, name } = row.original;
const initials = getInitials(first_name, last_name);
return (
<Box className="flex justify-start 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">
{first_name} {last_name}
</strong>
<small className="text-[10px] text-para-light font-[500]">{name}</small>
</div>
</Box>
);
},
},
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => (
<span className="text-[12px] font-[500]">{row.original.email}</span>
)
},
{
accessorKey: 'registeredDate',
header: 'Registered Date',
cell: ({ row }) => {
const { date, time } = formatDateTime(row.original.registered_date as string);
return (
<Box>
<span className="text-[12px] font-[500] block">{date}</span>
<small className="text-[10px] text-para-light font-[500]">[{time}]</small>
</Box>
)
}
},
{
accessorKey: 'currentCredit',
header: 'Current Credit',
cell: ({ row }) => (
<span className="text-[12px] font-[500]">{row.original.current_credit || "$0"}</span>
)
},
{
accessorKey: 'totalWithdrawn',
header: 'Total Withdrawn',
cell: ({ row }) => (
<span className="text-[12px] font-[500]">{row.original.total_withdrawl || "$0"}</span>
)
},
{
id: 'action',
header: 'Action',
cell: ({ row }) => (
<ActionGroup
onView={`${PATH.ADMIN.PLAYERS.ROOT}/${row.original.id}`}
onEdit={`${PATH.ADMIN.PLAYERS.EDIT_PLAYER.ROOT}/${row.original.id}`}
onDelete={async () => {
const response = await deletePlayer({ id: row.original.id }).unwrap();
dispatch(
showToast({
message: response.message,
variant: ToastVariant.ERROR
})
)
}}
/>
),
},
], []);
const table = useReactTable({
data: filteredData || [],
columns,
state: { sorting },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
})
return (
<div>PlayerListing</div>
<section className="player__listing_root">
<TableHeader
search={search}
setSearch={setSearch}
onDownloadCSV={() => { }}
/>
<CustomTable table={table} />
</section>
)
}
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery";
import { PlayerProps, PlayerResponseProps } from "@/types/player";
import { PlayerListResponse, PlayerProps, SinlgePlayerResponseProps, } from "@/types/player";
import { GlobalResponse } from "@/types/config";
export const playerApi = createApi({
......@@ -8,7 +8,7 @@ export const playerApi = createApi({
baseQuery: baseQuery,
tagTypes: ["players"],
endpoints: (builder) => ({
createPlayer: builder.mutation<PlayerResponseProps, FormData>({
createPlayer: builder.mutation<PlayerListResponse, FormData>({
query: (body) => ({
url: "/api/admin/add-user",
method: "POST",
......@@ -16,28 +16,28 @@ export const playerApi = createApi({
}),
invalidatesTags: ["players"]
}),
getAllPlayer: builder.query<PlayerResponseProps[], null>({
getAllPlayer: builder.query<PlayerListResponse, void>({
query: () => ({
url: "/api/admin/get-users",
method: "GET"
}),
providesTags: ['players']
}),
getPlayerById: builder.query<PlayerResponseProps, { id: number }>({
getPlayerById: builder.query<SinlgePlayerResponseProps, { id: number }>({
query: ({ id }) => ({
url: `/api/admin/get-user/${id}`,
method: "GET"
}),
providesTags: ['players']
}),
getPlayerBalanceById: builder.query<PlayerResponseProps, { id: number }>({
getPlayerBalanceById: builder.query<SinlgePlayerResponseProps, { id: number }>({
query: ({ id }) => ({
url: `/api/admin/get-balance/${id}`,
method: "GET"
}),
providesTags: ['players']
}),
updatePlayerById: builder.mutation<PlayerResponseProps, { id: number, data: FormData }>({
updatePlayerById: builder.mutation<SinlgePlayerResponseProps, { id: number, data: FormData }>({
query: ({ id, data }) => ({
url: `/api/admin/update-user/${id}`,
method: "POST",
......@@ -45,7 +45,7 @@ export const playerApi = createApi({
}),
invalidatesTags: ["players"]
}),
deletePlayerById: builder.mutation<GlobalResponse, { id: number }>({
deletePlayerById: builder.mutation<GlobalResponse, { id: string }>({
query: ({ id }) => ({
url: `/api/admin/update-user/${id}`,
method: "DELETE",
......
......@@ -165,7 +165,7 @@ export default function Palette(mode: ThemeMode) {
padding: '12px 24px',
textAlign: 'center',
textTransform: 'capitalize',
width: '100%',
maxWidth: 'fit-content',
'&:disabled': {
opacity: 0.2,
cursor: 'not-allowed',
......
import { ImageProps } from "./config";
import { Pagination } from "./game";
export interface PlayerProps {
name: string;
......@@ -14,6 +15,8 @@ export interface PlayerProps {
profile_image: File | null;
}
export const initialPlayerValues: PlayerProps = {
name: "",
email: "",
......@@ -29,10 +32,22 @@ export const initialPlayerValues: PlayerProps = {
};
export interface PlayerItem extends PlayerProps {
id: string | number
id: string;
registered_date: string | Date;
current_credit?: string,
total_withdrawl?: string,
total_deposited?: string
}
export interface PlayerResponseProps {
export interface PlayerListResponse {
data: {
data: PlayerItem[];
pagination: Pagination;
}
message: string;
status: string;
}
export interface SinlgePlayerResponseProps {
data: PlayerItem;
message: string;
status: string;
......
// src/utils/formatDateTime.ts
export function formatDateTime(dateString: string | null | undefined) {
if (!dateString) return { date: "", time: "" };
const dateObj = new Date(dateString);
return {
date: dateObj.toLocaleDateString("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
}),
time: dateObj.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
}),
};
}
// src/utils/getInitials.ts
export function getInitials(firstName?: string, lastName?: string) {
const firstInitial = firstName?.charAt(0).toUpperCase() ?? "";
const lastInitial = lastName?.charAt(0).toUpperCase() ?? "";
return `${firstInitial}${lastInitial}`;
}
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