Commit aaff4469 by Arjun Jhukal

made anylytics dynamic

parent 7f758c97
import { IconButton } from '@mui/material'
import { ArrowLeft, More } from '@wandersonalwes/iconsax-react'
"use client";
import { useGetAnalyticsQuery } from '@/services/dashboardApi';
import { AnalyticsProps } from '@/types/dashboard';
import { formatDateTime } from '@/utils/formatDateTime';
import { Box, ClickAwayListener, Fade, IconButton, List, ListItem, Paper, Popper } from '@mui/material';
import { ArrowLeft, More } from '@wandersonalwes/iconsax-react';
import { ApexOptions } from 'apexcharts';
import React from 'react'
import React, { useRef, useState } from 'react';
import Chart from "react-apexcharts";
type AnalyticsType = "today" | "this_week" | "this_month" | "this_year" | "all_time";
const getColorByKey = (key: string, isProfit: boolean) => {
const palette: Record<string, string> = {
total_deposits: "#22C55E",
total_withdraw: "#E14A5E",
total_transactions: "#3B82F6",
total_users: "#F59E0B",
};
return isProfit ? palette[key] || "#6B7280" : "#EF4444";
};
// Skeleton loader
const AnalyticsSkeleton = () => (
<div className="col-span-1">
<div className="analytics__card p-4 rounded-[8px] border border-gray-100 shadow-sm animate-pulse">
<div className="flex justify-between items-center mb-3">
<div className="w-[80px] h-[10px] bg-gray-200 rounded"></div>
<div className="w-[20px] h-[20px] bg-gray-200 rounded-full"></div>
</div>
<div className="w-[120px] h-[10px] bg-gray-200 rounded mb-2"></div>
<div className="flex justify-between items-end">
<div>
<div className="w-[60px] h-[16px] bg-gray-200 rounded mb-2"></div>
<div className="w-[40px] h-[10px] bg-gray-200 rounded"></div>
</div>
<div className="w-[140px] h-[80px] bg-gray-200 rounded"></div>
</div>
</div>
</div>
);
export default function AdminAnalytics() {
const charts = [
{
title: 'Total Revenue',
subtitle: 'Jul 23 - Jul 30 (2025)',
value: '$2.689M',
change: 16,
isIncrease: true,
chartOptions: {
chart: { type: 'area', sparkline: { enabled: true } },
stroke: { curve: 'smooth', width: 2 },
fill: {
type: 'gradient',
gradient: {
shade: 'light',
type: 'vertical',
shadeIntensity: 1,
gradientToColors: ['#E14A5E'],
opacityFrom: 1,
opacityTo: 0,
stops: [0, 100],
},
},
colors: ['#E14A5E'],
tooltip: { enabled: true },
} as ApexOptions,
chartSeries: [{ name: 'Revenue', data: [400, 600, 700, 1200, 1400, 1800, 2500] }],
},
{
title: 'New Users',
subtitle: 'Jul 23 - Jul 30 (2025)',
value: '3.2K',
change: 8,
isIncrease: true,
chartOptions: {
chart: { type: 'area', sparkline: { enabled: true } },
stroke: { curve: 'straight', width: 2 },
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'vertical',
shadeIntensity: 0.8,
gradientToColors: ['#22C55E'],
opacityFrom: 0.8,
opacityTo: 0,
stops: [0, 100],
},
},
colors: ['#22C55E'],
tooltip: { enabled: true },
} as ApexOptions,
chartSeries: [{ name: 'Users', data: [200, 400, 600, 700, 900, 1200, 1500] }],
},
{
title: 'Churn Rate',
subtitle: 'Jul 23 - Jul 30 (2025)',
value: '5.4%',
change: -4,
isIncrease: false,
chartOptions: {
chart: { type: 'area', sparkline: { enabled: true } },
stroke: { curve: 'smooth', width: 2 },
fill: {
type: 'gradient',
gradient: {
shade: 'light',
type: 'vertical',
shadeIntensity: 1,
gradientToColors: ['#F59E0B'],
opacityFrom: 1,
opacityTo: 0,
stops: [0, 100],
},
},
colors: ['#F59E0B'],
tooltip: { enabled: true },
} as ApexOptions,
chartSeries: [{ name: 'Churn', data: [8, 7, 6, 5, 5, 4, 3] }],
},
{
title: 'Support Tickets',
subtitle: 'Jul 23 - Jul 30 (2025)',
value: '1.2K',
change: -12,
isIncrease: false,
chartOptions: {
chart: { type: 'area', sparkline: { enabled: true } },
stroke: { curve: 'stepline', width: 2 },
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'vertical',
shadeIntensity: 0.8,
gradientToColors: ['#3B82F6'],
opacityFrom: 0.7,
opacityTo: 0,
stops: [0, 100],
},
const [currentType, setCurrentType] = useState<AnalyticsType>("this_week");
const { data, isLoading } = useGetAnalyticsQuery({ type: currentType });
const [openIndex, setOpenIndex] = useState<number | null>(null);
const anchorRefs = useRef<(HTMLButtonElement | null)[]>([]);
const handleToggle = (index: number) => {
setOpenIndex(prev => (prev === index ? null : index));
};
const handleClose = (event: MouseEvent | TouchEvent) => {
if (anchorRefs.current.some(ref => ref?.contains(event.target as Node))) return;
setOpenIndex(null);
};
const handleSelect = (type: AnalyticsType) => {
setCurrentType(type);
setOpenIndex(null);
};
if (isLoading) {
return (
<div className="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 analytics__wrapper mb-5">
{Array.from({ length: 4 }).map((_, i) => (
<AnalyticsSkeleton key={i} />
))}
</div>
);
}
if (!data?.data) return null;
const analyticsData = data.data;
const chartKeys = Object.keys(analyticsData) as (keyof typeof analyticsData)[];
const charts = chartKeys.map((key) => {
const item: AnalyticsProps = analyticsData[key];
const color = getColorByKey(key, item.profit);
const chartOptions: ApexOptions = {
chart: { type: "area", sparkline: { enabled: true } },
stroke: { curve: "smooth", width: 2 },
fill: {
type: "gradient",
gradient: {
shade: "light",
type: "vertical",
shadeIntensity: 1,
gradientToColors: [color],
opacityFrom: 0.8,
opacityTo: 0,
stops: [0, 100],
},
colors: ['#3B82F6'],
tooltip: { enabled: true },
} as ApexOptions,
chartSeries: [{ name: 'Tickets', data: [50, 45, 40, 38, 35, 30, 25] }],
},
];
// const chartSeries = [
// {
// name: "Revenue",
// data: [400, 600, 700, 1200, 1400, 1800, 2500],
// },
// ];
},
colors: [color],
tooltip: { enabled: true },
};
const chartSeries = [
{ name: item.name, data: item.breakdown ?? [0, 0, 0, 0, 0, 0, 0] },
];
return {
key,
title: item.name,
value: item.current,
change: item.percent,
isIncrease: item.profit,
message: item.message,
start_date: item.start_date,
end_date: item.end_date,
chartOptions,
chartSeries,
};
});
return (
<div className="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 analytics__wrapper mb-5">
{charts.map((chart, index) => (
<div className="col-span-1" key={index} >
<div className="analytics__card p-4 rounded-[8px]">
<div className="card__title flex justify-between items-center mb-2">
<div className="title">
<strong className='text-[12px] leading-[120%] text-title font-[500] mb-1 block'>Total Revenue</strong>
<span className="text-[10px] text-para-light leading-[120%] font-[500]">Jul 23 - Jul 30 (2025)</span>
</div>
<div className="card__action">
<IconButton>
<More className='rotate-90' variant='TwoTone' />
</IconButton>
</div>
</div>
<div className="card__content">
<div className="flex gap-2 items-end justify-between mb-2">
<div className="content flex gap-2 items-center">
<strong className='text-[16px] leading-[120%] font-[600] text-title'>$2.689M</strong>
<p className={`flex gap-1 items-center ${chart.isIncrease ? "increase" : "decrease"}`}>
<span className="arrow w-[18px] h-[18px] rounded-full flex justify-center items-center "><ArrowLeft size={12} /></span>16%</p>
{charts.map((chart, index) => {
const { date: startDate } = formatDateTime(chart.start_date);
const { date } = formatDateTime(chart.end_date);
const open = openIndex === index;
const id = open ? `analytics-action-${index}` : undefined;
return (
<div className="col-span-1" key={index}>
<div className="analytics__card p-4 rounded-[8px]">
<div className="card__title flex justify-between items-center mb-2">
<div className="title">
<strong className='text-[12px] leading-[120%] text-title font-[500] mb-1 block'>{chart.title}</strong>
<span className="text-[10px] text-para-light leading-[120%] font-[500]">{startDate} - {date}</span>
</div>
<div className="relative aspect-[72/68] w-full max-w-[144px] h-[100px] text-right">
<Chart
options={chart.chartOptions}
series={chart.chartSeries}
type="area"
width="100%"
height="100%"
/>
<Box>
<IconButton
aria-describedby={id}
ref={(el: HTMLButtonElement | null) => {
anchorRefs.current[index] = el;
}}
onClick={() => handleToggle(index)}
>
<More className="rotate-90" variant="TwoTone" />
</IconButton>
<Popper
open={open}
anchorEl={anchorRefs.current[index]}
placement="bottom-end"
transition
style={{ zIndex: 1300 }}
id={id}
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={200}>
<Paper
elevation={3}
sx={{
width: 180,
borderRadius: 2,
mt: 1,
}}
>
<ClickAwayListener onClickAway={handleClose}>
<List>
{[
{ label: "Today", value: "today" },
{ label: "This Week", value: "this_week" },
{ label: "This Month", value: "this_month" },
{ label: "This Year", value: "this_year" },
{ label: "All Time", value: "all_time" },
].map((item) => (
<ListItem key={item.value}>
<button
onClick={() => handleSelect(item.value as AnalyticsType)}
className={`block w-full text-left py-2 px-4 hover:bg-[#FBF4FB] text-sm ${currentType === item.value ? "font-semibold text-primary" : ""}`}
>
{item.label}
</button>
</ListItem>
))}
</List>
</ClickAwayListener>
</Paper>
</Fade>
)}
</Popper>
</Box>
</div>
<div className="card__content">
<div className="flex gap-2 items-end justify-between mb-2">
<div className="content flex gap-2 items-center">
<strong className='text-[16px] leading-[120%] font-[600] text-title'>${chart.value}</strong>
<p className={`flex gap-1 items-center ${chart.isIncrease ? "increase" : "decrease"}`}>
<span className="arrow w-[18px] h-[18px] rounded-full flex justify-center items-center ">
<ArrowLeft size={12} />
</span>
{chart.change}%
</p>
</div>
<div className="relative aspect-[72/68] w-full max-w-[144px] h-[100px] text-right">
<Chart
options={chart.chartOptions}
series={chart.chartSeries}
type="area"
width="100%"
height="100%"
/>
</div>
</div>
<p className='text-[12px] font-[500] text-para-light'>{chart.message}</p>
</div>
<p className='text-[12px] font-[500] text-para-light'>compared to previous week</p>
</div>
</div>
</div>
))}
);
})}
</div>
)
);
}
......@@ -19,13 +19,12 @@ export default function AdminHeader() {
const [page, setPage] = React.useState(1);
const [pageSize, setPageSize] = React.useState(10);
const { data } = useGetAllNotificationQuery({ page: page, per_page: pageSize });
console.log(data);
return (
<Box className='flex items-center gap-4 justify-between w-full'>
<AdminSearchBar />
<div className="right flex items-center gap-4">
<CreatNewRecord />
<NotificationPage notifications={data?.data?.data || []} pagination={data?.data?.pagination}/>
<NotificationPage notifications={data?.data?.data || []} pagination={data?.data?.pagination} />
<Profile />
</div>
</Box>
......
......@@ -51,7 +51,6 @@ export default function PlayerDetailPage({ id }: { id: number }) {
const { date } = formatDateTime(data?.data?.registered_date as string);
console.log("user balance", userBalance);
return (
<>
<section className="player__detail__intro mb-12 lg:mb-16">
......
......@@ -35,7 +35,6 @@ export default function BannerSlider() {
const { data, isLoading } = useGetAllBannerQuery();
const [updateBanner, { isLoading: updating }] = useUpdateBannerMutation();
console.log("banner data", data);
const formik = useFormik({
initialValues: {
banners: data?.data?.length
......
......@@ -34,7 +34,6 @@ export default function TransactionTable({ user_id, game_id, search }: { user_id
const { data, isLoading: loadingTransaction } = useGetAllTransactionQuery(queryArgs);
console.log("all transaction", data);
const tableData = useMemo(() => data?.data?.data || [], [data]);
const columns = useMemo<ColumnDef<SingleDepositProps>[]>(() => [
......
......@@ -17,7 +17,6 @@ export default function MenuPage() {
const dispatch = useAppDispatch();
const [selectedMenus, setSelectedMenus] = React.useState<any[]>([]);
console.log("menus", { menus, selectedMenus });
React.useEffect(() => {
......
......@@ -12,6 +12,7 @@ import { settingApi } from "@/services/settingApi";
import { pageApi } from "@/services/pageApi";
import { notificationApi } from "@/services/notificationApi";
import { menuApi, userMenuApi } from "@/services/menuApi";
import { dashboardApi } from "@/services/dashboardApi";
export const store = configureStore({
reducer: {
......@@ -29,6 +30,7 @@ export const store = configureStore({
[notificationApi.reducerPath]: notificationApi.reducer,
[menuApi.reducerPath]: menuApi.reducer,
[userMenuApi.reducerPath]: userMenuApi.reducer,
[dashboardApi.reducerPath]: dashboardApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
......@@ -43,6 +45,7 @@ export const store = configureStore({
.concat(notificationApi.middleware)
.concat(menuApi.middleware)
.concat(userMenuApi.middleware)
.concat(dashboardApi.middleware)
})
export type RootState = ReturnType<typeof store.getState>;
......
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery";
import { AnalyticsResponse } from "@/types/dashboard";
export const dashboardApi = createApi({
reducerPath: "dashboardApi",
baseQuery: baseQuery,
tagTypes: ["Analytics", "Transactions"],
endpoints: (builder) => ({
getAnalytics: builder.query<AnalyticsResponse, { type: string }>({
query: ({ type }) => ({
url: `/api/admin/dashboard/overview?type=${type}`,
method: "GET",
}),
providesTags: ["Analytics"],
}),
getAdminTransactions: builder.query<any, void>({
query: () => ({
url: `/api/admin/dashboard/deposits`,
method: "GET",
}),
providesTags: ["Transactions"],
})
})
})
export const { useGetAnalyticsQuery, useGetAdminTransactionsQuery } = dashboardApi;
\ No newline at end of file
export type AnalyticsProps = {
name: string,
start_date: string,
end_date: string,
current: number,
previous: number,
percent: number,
profit: boolean,
message: string,
breakdown: number[]
}
export interface AnalyticsResponse {
data: {
total_deposits: AnalyticsProps,
total_transactions: AnalyticsProps,
total_users: AnalyticsProps,
total_withdraw: AnalyticsProps,
}
}
\ No newline at end of file
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