ironGym/webapp/src/components/dashboard/home/membersOverviewChart.tsx
2025-06-21 05:05:18 +03:00

598 lines
28 KiB
TypeScript

import { useEffect, useState } from 'react'
import { useAppSelector } from '@/redux/store';
import { useTranslations } from 'next-intl';
import dynamic from 'next/dynamic';
import Cookies from 'universal-cookie';
import { useDispatch } from 'react-redux';
import { AppDispatch } from '@/redux/store';
import { setCurrentMembersGeneralOverviewDuration } from '@/redux/features/statistics-slice'
import { Loader2 } from 'lucide-react';
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
loading: () => <div className="flex items-center justify-center h-full">
<Loader2 className="w-8 h-8 animate-spin" />
</div>
});
export default function MembersOverviewChart()
{
// get needed redux state
const report = useAppSelector((state) => state.statisticsReducer.value.report)
const currentMembersGeneralOverviewDuration = useAppSelector((state) => state.statisticsReducer.value.currentMembersGeneralOverviewDuration)
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
// declare global variables
const dispatch = useDispatch<AppDispatch>()
const cookies = new Cookies();
const t = useTranslations('statistics');
const locale = cookies.get("NEXT_LOCALE")
const isRtl = locale == 'ar' ? true : false
const isDark = themeType == 'dark' ? true : false
const [chartSeries, setChartSeries] = useState<{ name: string; data: number[] }[]>([]);
const [labels, setLabels] = useState<string[]>([])
const [isLoading, setIsLoading] = useState(true);
// revenue Chart
// prepare chart series and handle the transaction between currentMembersGeneralOverviewDuration
useEffect(() => {
if (!report?.membersGeneralOverview) {
setIsLoading(true);
return;
}
setIsLoading(true);
try {
// if chart duration is set to this week
if (currentMembersGeneralOverviewDuration == 'thisWeek') {
// set the labels
setLabels([t('sat'), t('sun'), t('mon'), t('tue'), t('wed'), t('thu'), t('fri')])
// Initialize default data structure
const defaultWeekData = {
sat: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
sun: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
mon: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
tue: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
wed: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
thu: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 },
fri: { totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0, totalMansMembers: 0, totalGirlsMembers: 0 }
};
// Use data from report if available, otherwise use default data
const weekData = report?.membersGeneralOverview?.value?.thisWeek?.days || defaultWeekData;
// prepare the chart series
setChartSeries([
{
name: t('totalMembers'),
data: ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'].map(day => weekData[day]?.totalMembers || 0),
},
{
name: t('totalActiveSubs'),
data: ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'].map(day => weekData[day]?.totalActiveSubs || 0),
},
{
name: t('totalUnActiveSubs'),
data: ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'].map(day => weekData[day]?.totalUnActiveSubs || 0),
},
{
name: t('totalMansMembers'),
data: ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'].map(day => weekData[day]?.totalMansMembers || 0),
},
{
name: t('totalGirlsMembers'),
data: ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'].map(day => weekData[day]?.totalGirlsMembers || 0),
},
]);
}
// if chart duration is set to this month
else if(currentMembersGeneralOverviewDuration == 'thisMonth')
{
let thisMonthTotalSum : {
week : number,
thisWeekSum : {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[];
if(report?.membersGeneralOverview?.value.thisMonth)
{
// if we founded a data for the current month
thisMonthTotalSum = report?.membersGeneralOverview?.value.thisMonth.weeks.map((v : any , i : number) : {
week: number,
thisWeekSum : {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
} => {
// we init the this week sum
let thisWeekSum = {
"totalMembers": 0,
"totalActiveSubs": 0,
"totalUnActiveSubs": 0,
"totalMansMembers": 0,
"totalGirlsMembers": 0
}
// we map through this month weeks
v.days.map((w : {
'sat': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'sun': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'mon': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'tue': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'wed': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'thu': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'fri': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
}
} , i : number) => {
// we loop through week days and we sum there data
Object.keys(w).forEach(function(key : "sat" | "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "_id" , index : number) {
if(key == '_id') return
thisWeekSum.totalMembers = thisWeekSum.totalMembers + w[key].totalMembers
thisWeekSum.totalActiveSubs = thisWeekSum.totalActiveSubs + w[key].totalActiveSubs
thisWeekSum.totalUnActiveSubs = thisWeekSum.totalUnActiveSubs + w[key].totalUnActiveSubs
thisWeekSum.totalMansMembers = thisWeekSum.totalMansMembers + w[key].totalMansMembers
thisWeekSum.totalGirlsMembers = thisWeekSum.totalGirlsMembers + w[key].totalGirlsMembers
})
})
// each week we return there sum data
return {
week: v.week,
thisWeekSum: thisWeekSum
}
})
let labels : [] = [];
thisMonthTotalSum.forEach(function(v , i) {
labels.push(t('week')+ ' ' +thisMonthTotalSum[i]["week"])
})
// set the labels
setLabels(labels)
// we setup chart_series ( chart data )
let a : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
} => {
let data = thisMonthTotalSum.map((v2 : {
week: number,
thisWeekSum: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
},i2 : number) => {
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v;
return v2.thisWeekSum[index];
})
// return the line data
return {
name: t(v),
data: data
};
})
setChartSeries(a)
}
// if there was no data we use default return
else {
setChartSeries([
{
name: '1'+t('week'),
data: [0,0,0,0,0],
},
{
name: '2'+t('week'),
data: [0,0,0,0,0],
},
{
name: '3'+t('week'),
data: [0,0,0,0,0],
},
{
name: '4'+t('week'),
data: [0,0,0,0,0],
}
])
}
}
// if chart duration is set to this month
else if(currentMembersGeneralOverviewDuration == 'thisYear')
{
// set the labels
setLabels([t('1month'), t('2month'), t('3month'), t('4month'), t('5month'), t('6month'), t('7month'), t('8month'), t('9month'), t('10month'), t('11month')])
// check if there is current month report
let thisYearTotalSum : {
month: number,
thisMonthWeeksSum: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[]
if(report?.membersGeneralOverview?.value.thisYear)
{
// if we founded a data for the current month
thisYearTotalSum = report?.membersGeneralOverview?.value.thisYear.months.map((v : any , i : number) : {
month : number,
thisMonthWeeksSum : {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
} => {
let summedWeeks = v.weeks.map((w : any , i : number) => {
// we map through this month weeks
let weekSum = w.days.map((d : {
'sat': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'sun': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'mon': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'tue': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'wed': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'thu': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
},
'fri': {
'totalMembers': number,
'totalActiveSubs': number,
'totalUnActiveSubs': number,
'totalMansMembers': number,
'totalGirlsMembers': number,
}
} , i : number) => {
// we init the this week sum
let thisWeekSum = {
"totalMembers": 0,
"totalActiveSubs": 0,
"totalUnActiveSubs": 0,
"totalMansMembers": 0,
"totalGirlsMembers": 0
}
// we loop through week days and we sum there data
Object.keys(d).forEach(function(key : "sat" | "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "_id" , index : number) {
if(key == '_id') return
thisWeekSum.totalMembers = thisWeekSum.totalMembers + d[key].totalMembers
thisWeekSum.totalActiveSubs = thisWeekSum.totalActiveSubs + d[key].totalActiveSubs
thisWeekSum.totalUnActiveSubs = thisWeekSum.totalUnActiveSubs + d[key].totalUnActiveSubs
thisWeekSum.totalMansMembers = thisWeekSum.totalMansMembers + d[key].totalMansMembers
thisWeekSum.totalGirlsMembers = thisWeekSum.totalGirlsMembers + d[key].totalGirlsMembers
})
// each week we return there sum data
return thisWeekSum
})
return {
week: w.week,
weekSum: weekSum
}
})
let thisMonthWeeksSum = {
"totalMembers": 0,
"totalActiveSubs": 0,
"totalUnActiveSubs": 0,
"totalMansMembers": 0,
"totalGirlsMembers": 0
}
summedWeeks.map((v : {week: number, weekSum: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}[]},i : number) => {
thisMonthWeeksSum.totalMembers += v.weekSum[0].totalMembers
thisMonthWeeksSum.totalActiveSubs += v.weekSum[0].totalActiveSubs
thisMonthWeeksSum.totalUnActiveSubs += v.weekSum[0].totalUnActiveSubs
thisMonthWeeksSum.totalMansMembers += v.weekSum[0].totalMansMembers
thisMonthWeeksSum.totalGirlsMembers += v.weekSum[0].totalGirlsMembers
})
return {
month: v.month,
thisMonthWeeksSum: thisMonthWeeksSum
}
})
// prepare the labels
let labels : [] = [];
thisYearTotalSum.forEach(function(v , i) {
labels.push(t(thisYearTotalSum[i]["month"]+"month"))
})
setLabels(labels)
// we setup chart_series ( chart data )
let a : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
} => {
let data = thisYearTotalSum.map((v2 : {
month: number,
thisMonthWeeksSum: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
},i2 : number) => {
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v;
return v2.thisMonthWeeksSum[index];
})
// return the line data
return {
name: t(v),
data: data
};
})
setChartSeries(a)
}
// if there was no data we use default return
else {
setChartSeries([
{
name: '1'+t('week'),
data: [0,0,0,0,0],
},
{
name: '2'+t('week'),
data: [0,0,0,0,0],
},
{
name: '3'+t('week'),
data: [0,0,0,0,0],
},
{
name: '4'+t('week'),
data: [0,0,0,0,0],
}
])
}
}
} catch (error) {
console.error('Error preparing chart data:', error);
} finally {
setIsLoading(false);
}
}, [report, currentMembersGeneralOverviewDuration, t]);
// Chart options
const chartOptions: ApexCharts.ApexOptions = {
chart: {
type: 'bar',
height: 350,
stacked: true,
toolbar: {
show: true,
},
zoom: {
enabled: true,
},
foreColor: isDark ? '#fff' : '#333',
fontFamily: isRtl ? 'Tajawal, sans-serif' : 'Inter, sans-serif',
},
responsive: [
{
breakpoint: 480,
options: {
legend: {
position: 'bottom',
offsetY: 10,
},
},
},
],
plotOptions: {
bar: {
horizontal: false,
borderRadius: 5,
dataLabels: {
total: {
enabled: true,
style: {
fontSize: '13px',
fontWeight: 900,
},
},
},
},
},
xaxis: {
type: 'category',
categories: labels,
labels: {
style: {
colors: isDark ? '#fff' : '#333',
},
},
},
yaxis: {
labels: {
style: {
colors: isDark ? '#fff' : '#333',
},
},
},
legend: {
position: 'right',
offsetY: 40,
labels: {
colors: isDark ? '#fff' : '#333',
},
},
fill: {
opacity: 1,
},
tooltip: {
theme: isDark ? 'dark' : 'light',
y: {
formatter: function (val) {
return val.toString();
},
},
},
};
if (isLoading || !report?.membersGeneralOverview) {
return (
<div className="w-full h-full flex items-center justify-center min-h-[400px]">
<Loader2 className="w-8 h-8 animate-spin" />
</div>
);
}
return (
<div className="w-full h-full">
<div className="flex flex-col">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">{t('membersGeneralOverview')}</h3>
<div className="flex gap-2">
<button
onClick={() => dispatch(setCurrentMembersGeneralOverviewDuration('thisWeek'))}
className={`px-3 py-1 rounded-md text-sm ${currentMembersGeneralOverviewDuration === 'thisWeek' ? 'bg-primary text-white' : 'bg-gray-200 dark:bg-gray-700'}`}
>
{t('thisWeek')}
</button>
<button
onClick={() => dispatch(setCurrentMembersGeneralOverviewDuration('thisMonth'))}
className={`px-3 py-1 rounded-md text-sm ${currentMembersGeneralOverviewDuration === 'thisMonth' ? 'bg-primary text-white' : 'bg-gray-200 dark:bg-gray-700'}`}
>
{t('thisMonth')}
</button>
<button
onClick={() => dispatch(setCurrentMembersGeneralOverviewDuration('thisYear'))}
className={`px-3 py-1 rounded-md text-sm ${currentMembersGeneralOverviewDuration === 'thisYear' ? 'bg-primary text-white' : 'bg-gray-200 dark:bg-gray-700'}`}
>
{t('thisYear')}
</button>
</div>
</div>
<div className="w-full h-[400px] mt-4">
<ReactApexChart
options={chartOptions}
series={chartSeries}
type="bar"
height="100%"
/>
</div>
</div>
</div>
)
}