Add 30 n12345688400
This commit is contained in:
parent
672b965f4d
commit
071d586f29
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useCallback } from 'react'
|
||||||
import GeneralLook from '@/components/dashboard/home/generalLook'
|
import GeneralLook from '@/components/dashboard/home/generalLook'
|
||||||
import MembersOverviewChart from '@/components/dashboard/home/membersOverviewChart'
|
import MembersOverviewChart from '@/components/dashboard/home/membersOverviewChart'
|
||||||
import ServicesSubscriptions from '@/components/dashboard/home/servicesSubscriptions'
|
import ServicesSubscriptions from '@/components/dashboard/home/servicesSubscriptions'
|
||||||
@ -12,41 +12,60 @@ import { AppDispatch } from '@/redux/store';
|
|||||||
import { load } from '@/redux/features/statistics-slice'
|
import { load } from '@/redux/features/statistics-slice'
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { RefreshCw } from 'lucide-react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
|
||||||
const dispatch = useDispatch<AppDispatch>()
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
const t = useTranslations('statistics');
|
const t = useTranslations('statistics');
|
||||||
|
|
||||||
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
const loadedFirstTime = useAppSelector((state) => state.statisticsReducer.value.loadedFirstTime)
|
|
||||||
const isLoading = useAppSelector((state) => state.statisticsReducer.value.isLoading)
|
const isLoading = useAppSelector((state) => state.statisticsReducer.value.isLoading)
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchData = useCallback(async () => {
|
||||||
if(loadedFirstTime) return;
|
try {
|
||||||
if(isLoading) return;
|
await dispatch(load({ forceRefresh: true }));
|
||||||
async function a()
|
} catch (error) {
|
||||||
{
|
console.error('Failed to fetch dashboard data:', error);
|
||||||
await dispatch(load({}))
|
|
||||||
}
|
}
|
||||||
a()
|
}, [dispatch]);
|
||||||
}, [])
|
|
||||||
|
|
||||||
if(isLoading)
|
useEffect(() => {
|
||||||
{
|
// Always fetch fresh data when the component mounts
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
// Set up interval to refresh data every 5 minutes
|
||||||
|
const intervalId = setInterval(fetchData, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
// Clean up interval on component unmount
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [fetchData]);
|
||||||
|
|
||||||
|
if (isLoading && !report) {
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
|
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
|
||||||
<CircularProgress color="secondary" size={22} />
|
<CircularProgress color="secondary" size={22} />
|
||||||
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
|
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="min-h-[250px] max-h-[750px]">
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<div className="w-full flex justify-end mb-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={fetchData}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
|
||||||
|
{t('refresh')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main className="w-full min-w-screen min-h-screen h-auto flex flex-col lg:gap-7 gap-3 pb-[50px]">
|
<main className="w-full min-w-screen min-h-screen h-auto flex flex-col lg:gap-7 gap-3 pb-[50px]">
|
||||||
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[500px] items-stretch">
|
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[500px] items-stretch">
|
||||||
<GeneralLook/>
|
<GeneralLook/>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { createAsyncThunk , createSlice , PayloadAction } from "@reduxjs/toolkit";
|
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { fireAlert } from "./alert-slice";
|
import { fireAlert } from "./alert-slice";
|
||||||
|
|
||||||
@ -6,13 +6,13 @@ import { fireAlert } from "./alert-slice";
|
|||||||
const ACTION_NAME = 'statistics'
|
const ACTION_NAME = 'statistics'
|
||||||
// slice initial value state
|
// slice initial value state
|
||||||
type IinitialState = {
|
type IinitialState = {
|
||||||
value : IValueState;
|
value: IValueState;
|
||||||
}
|
}
|
||||||
// state value type
|
// state value type
|
||||||
type IValueState = {
|
type IValueState = {
|
||||||
isLoading : boolean,
|
isLoading: boolean,
|
||||||
loadedFirstTime: boolean,
|
loadedFirstTime: boolean,
|
||||||
report :
|
report:
|
||||||
{
|
{
|
||||||
membersCount: {
|
membersCount: {
|
||||||
name: string,
|
name: string,
|
||||||
@ -53,7 +53,7 @@ type IValueState = {
|
|||||||
membersGeneralOverview: {
|
membersGeneralOverview: {
|
||||||
name: "generalMembersOverView",
|
name: "generalMembersOverView",
|
||||||
value: {
|
value: {
|
||||||
thisWeek : {
|
thisWeek: {
|
||||||
'year': number,
|
'year': number,
|
||||||
'month': number,
|
'month': number,
|
||||||
'week': number,
|
'week': number,
|
||||||
@ -109,7 +109,7 @@ type IValueState = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
months : {
|
months: {
|
||||||
'year': number,
|
'year': number,
|
||||||
'month': number,
|
'month': number,
|
||||||
'weeks': {
|
'weeks': {
|
||||||
@ -167,7 +167,7 @@ type IValueState = {
|
|||||||
}[]
|
}[]
|
||||||
}[]
|
}[]
|
||||||
}[],
|
}[],
|
||||||
years : {
|
years: {
|
||||||
'year': number,
|
'year': number,
|
||||||
'months': {
|
'months': {
|
||||||
'month': number,
|
'month': number,
|
||||||
@ -390,19 +390,19 @@ const initialState = {
|
|||||||
// load data ability
|
// load data ability
|
||||||
const load = createAsyncThunk(
|
const load = createAsyncThunk(
|
||||||
'statistics/load',
|
'statistics/load',
|
||||||
async (actionPayload : {}, thunkAPI) => {
|
async (actionPayload: { forceRefresh?: boolean } = {}, thunkAPI) => {
|
||||||
try {
|
try {
|
||||||
let { data } = await axios.get(`/api/user/actions/${ACTION_NAME}`)
|
// Add a timestamp to the URL to prevent caching when forceRefresh is true
|
||||||
if(data.success)
|
const timestamp = actionPayload.forceRefresh ? `?t=${Date.now()}` : '';
|
||||||
{
|
let { data } = await axios.get(`/api/user/actions/${ACTION_NAME}${timestamp}`)
|
||||||
|
if (data.success) {
|
||||||
return data
|
return data
|
||||||
|
} else {
|
||||||
|
return { success: false }
|
||||||
}
|
}
|
||||||
else
|
} catch (err) {
|
||||||
{
|
console.error('Error loading statistics:', err);
|
||||||
return { success : false }
|
return { success: false }
|
||||||
}
|
|
||||||
}catch(err) {
|
|
||||||
return { success : false }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -411,19 +411,25 @@ export const statistics = createSlice({
|
|||||||
name: ACTION_NAME,
|
name: ACTION_NAME,
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setCurrentMembersGeneralOverviewDuration : (state , action: PayloadAction<'thisWeek' | 'thisMonth' | 'thisYear'>) => {
|
setCurrentMembersGeneralOverviewDuration: (state, action: PayloadAction<'thisWeek' | 'thisMonth' | 'thisYear'>) => {
|
||||||
state.value.currentMembersGeneralOverviewDuration = action.payload
|
state.value.currentMembersGeneralOverviewDuration = action.payload
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
// load ability
|
// load ability
|
||||||
builder.addCase(load.pending, (state : IinitialState , action) => {
|
builder.addCase(load.pending, (state: IinitialState, action) => {
|
||||||
state.value.isLoading = true;
|
state.value.isLoading = true;
|
||||||
})
|
})
|
||||||
builder.addCase(load.fulfilled, (state : IinitialState , action) => {
|
builder.addCase(load.fulfilled, (state: IinitialState, action) => {
|
||||||
state.value.isLoading = false;
|
state.value.isLoading = false;
|
||||||
|
if (action.payload?.success && action.payload.data) {
|
||||||
state.value.loadedFirstTime = true;
|
state.value.loadedFirstTime = true;
|
||||||
state.value.report = action.payload.data;
|
state.value.report = action.payload.data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
builder.addCase(load.rejected, (state: IinitialState, action) => {
|
||||||
|
state.value.isLoading = false;
|
||||||
|
console.error('Failed to load statistics:', action.error);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user