Commit 90544e7e by Arjun Jhukal

updated the player list and action group

parent b9828c34
import PageHeader from '@/components/molecules/PageHeader' import PageHeader from '@/components/molecules/PageHeader'
import PlayerListing from '@/components/pages/dashboard/adminDashboard/players'
import { PATH } from '@/routes/PATH' import { PATH } from '@/routes/PATH'
import React from 'react' import React from 'react'
...@@ -9,7 +10,7 @@ export default function Players() { ...@@ -9,7 +10,7 @@ export default function Players() {
title='Players' title='Players'
cta={{ url: PATH.ADMIN.PLAYERS.ADD_PLAYER.ROOT, label: "Add New Player" }} 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 SelectField from "../atom/SelectField";
import { DocumentDownload, SearchNormal } from "@wandersonalwes/iconsax-react";
interface TableHeaderProps { interface TableHeaderProps {
search: string; search: string;
setSearch: (value: string) => void; setSearch: (value: string) => void;
filterMethod: string; filterMethod?: string;
setFilterMethod: (value: string) => void; setFilterMethod?: (value: string) => void;
onDownloadCSV: () => void; onDownloadCSV: () => void;
} }
...@@ -19,29 +20,43 @@ export default function TableHeader({ ...@@ -19,29 +20,43 @@ export default function TableHeader({
return ( return (
<div className="table__header p-4 mb-4 flex justify-between"> <div className="table__header p-4 mb-4 flex justify-between">
<div className="inpute__field relative"> <div className="inpute__field relative">
<input <OutlinedInput
type="search"
placeholder="Search keywords..." placeholder="Search keywords..."
name="search" name="search"
id="search" id="search"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} 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>
<div className="header-right flex justify-end items-center gap-2"> {filterMethod ? <div className="header-right flex justify-end items-center gap-2">
<SelectField <SelectField
name="search" name="search"
value={filterMethod} value={filterMethod}
onChange={(e) => setFilterMethod(e.target.value)} onChange={(e) => setFilterMethod && setFilterMethod(e.target.value)}
options={[ options={[
{ value: "all", name: "All Method" }, { value: "all", name: "All Method" },
{ value: "crypto", name: "Crypto" }, { value: "crypto", name: "Crypto" },
{ value: "paypal", name: "USD/Paypal" }, { 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> </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" "use client"
import TableHeader from '@/components/molecules/TableHeader';
import CustomLightGallery from '@/components/organism/LightGallery'; import CustomLightGallery from '@/components/organism/LightGallery';
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' 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() { 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 ( 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 { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery"; import { baseQuery } from "./baseQuery";
import { PlayerProps, PlayerResponseProps } from "@/types/player"; import { PlayerListResponse, PlayerProps, SinlgePlayerResponseProps, } from "@/types/player";
import { GlobalResponse } from "@/types/config"; import { GlobalResponse } from "@/types/config";
export const playerApi = createApi({ export const playerApi = createApi({
...@@ -8,7 +8,7 @@ export const playerApi = createApi({ ...@@ -8,7 +8,7 @@ export const playerApi = createApi({
baseQuery: baseQuery, baseQuery: baseQuery,
tagTypes: ["players"], tagTypes: ["players"],
endpoints: (builder) => ({ endpoints: (builder) => ({
createPlayer: builder.mutation<PlayerResponseProps, FormData>({ createPlayer: builder.mutation<PlayerListResponse, FormData>({
query: (body) => ({ query: (body) => ({
url: "/api/admin/add-user", url: "/api/admin/add-user",
method: "POST", method: "POST",
...@@ -16,28 +16,28 @@ export const playerApi = createApi({ ...@@ -16,28 +16,28 @@ export const playerApi = createApi({
}), }),
invalidatesTags: ["players"] invalidatesTags: ["players"]
}), }),
getAllPlayer: builder.query<PlayerResponseProps[], null>({ getAllPlayer: builder.query<PlayerListResponse, void>({
query: () => ({ query: () => ({
url: "/api/admin/get-users", url: "/api/admin/get-users",
method: "GET" method: "GET"
}), }),
providesTags: ['players'] providesTags: ['players']
}), }),
getPlayerById: builder.query<PlayerResponseProps, { id: number }>({ getPlayerById: builder.query<SinlgePlayerResponseProps, { id: number }>({
query: ({ id }) => ({ query: ({ id }) => ({
url: `/api/admin/get-user/${id}`, url: `/api/admin/get-user/${id}`,
method: "GET" method: "GET"
}), }),
providesTags: ['players'] providesTags: ['players']
}), }),
getPlayerBalanceById: builder.query<PlayerResponseProps, { id: number }>({ getPlayerBalanceById: builder.query<SinlgePlayerResponseProps, { id: number }>({
query: ({ id }) => ({ query: ({ id }) => ({
url: `/api/admin/get-balance/${id}`, url: `/api/admin/get-balance/${id}`,
method: "GET" method: "GET"
}), }),
providesTags: ['players'] providesTags: ['players']
}), }),
updatePlayerById: builder.mutation<PlayerResponseProps, { id: number, data: FormData }>({ updatePlayerById: builder.mutation<SinlgePlayerResponseProps, { id: number, data: FormData }>({
query: ({ id, data }) => ({ query: ({ id, data }) => ({
url: `/api/admin/update-user/${id}`, url: `/api/admin/update-user/${id}`,
method: "POST", method: "POST",
...@@ -45,7 +45,7 @@ export const playerApi = createApi({ ...@@ -45,7 +45,7 @@ export const playerApi = createApi({
}), }),
invalidatesTags: ["players"] invalidatesTags: ["players"]
}), }),
deletePlayerById: builder.mutation<GlobalResponse, { id: number }>({ deletePlayerById: builder.mutation<GlobalResponse, { id: string }>({
query: ({ id }) => ({ query: ({ id }) => ({
url: `/api/admin/update-user/${id}`, url: `/api/admin/update-user/${id}`,
method: "DELETE", method: "DELETE",
......
...@@ -165,7 +165,7 @@ export default function Palette(mode: ThemeMode) { ...@@ -165,7 +165,7 @@ export default function Palette(mode: ThemeMode) {
padding: '12px 24px', padding: '12px 24px',
textAlign: 'center', textAlign: 'center',
textTransform: 'capitalize', textTransform: 'capitalize',
width: '100%', maxWidth: 'fit-content',
'&:disabled': { '&:disabled': {
opacity: 0.2, opacity: 0.2,
cursor: 'not-allowed', cursor: 'not-allowed',
......
import { ImageProps } from "./config"; import { ImageProps } from "./config";
import { Pagination } from "./game";
export interface PlayerProps { export interface PlayerProps {
name: string; name: string;
...@@ -14,6 +15,8 @@ export interface PlayerProps { ...@@ -14,6 +15,8 @@ export interface PlayerProps {
profile_image: File | null; profile_image: File | null;
} }
export const initialPlayerValues: PlayerProps = { export const initialPlayerValues: PlayerProps = {
name: "", name: "",
email: "", email: "",
...@@ -29,10 +32,22 @@ export const initialPlayerValues: PlayerProps = { ...@@ -29,10 +32,22 @@ export const initialPlayerValues: PlayerProps = {
}; };
export interface PlayerItem extends 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; data: PlayerItem;
message: string; message: string;
status: 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