Add v1 1220

This commit is contained in:
yznahmad 2025-06-26 22:22:51 +03:00
parent 54778f63a7
commit 37535762c5
53 changed files with 1094 additions and 1077 deletions

35
webapp/-.gitignore Normal file
View File

@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -5,18 +5,18 @@ module.exports = {
exec_mode: 'cluster',
instances: 1, // Or a number of instances
script: 'node_modules/next/dist/bin/next',
args: 'start -p 5000',
args: 'start -p 3000',
env_local: {
DB_URI:"mongodb+srv://xxx",
NEXT_PUBLIC_API_BASE:"https://xxx"
DB_URI:"mongodb://root:mongodb_str_p%40ss@localhost:27017/Infinity?authSource=admin",
NEXT_PUBLIC_API_BASE:"http://localhost:3000"
},
env_development: {
DB_URI:"mongodb+srv://xxx",
NEXT_PUBLIC_API_BASE:"https://xxx"
DB_URI:"mongodb://root:mongodb_str_p%40ss@localhost:27017/Infinity?authSource=admin",
NEXT_PUBLIC_API_BASE:"http://localhost:3000"
},
env_production: {
DB_URI:"mongodb+srv://xxx",
NEXT_PUBLIC_API_BASE:"https://xxx"
DB_URI:"mongodb://root:mongodb_str_p%40ss@localhost:27017/Infinity?authSource=admin",
NEXT_PUBLIC_API_BASE:"http://localhost:3000"
}
}
]

View File

@ -3,14 +3,9 @@ const nextConfig = {
typescript: {
ignoreBuildErrors: true,
},
output: 'standalone',
experimental: {
// Disable server-side rendering for API routes during build
// to prevent database connection attempts
serverComponentsExternalPackages: ['mongoose'],
env: {
NEXT_PUBLIC_API_BASE: process.env.NEXT_PUBLIC_API_BASE,
},
// For App Router, use generateStaticParams in your page components
// instead of exportPathMap
async redirects() {
return [
{ // we redirect '/' to '/dashboard
@ -20,10 +15,6 @@ const nextConfig = {
},
]
},
// Skip type checking during build
eslint: {
ignoreDuringBuilds: true,
},
}
module.exports = nextConfig

View File

@ -22,9 +22,9 @@
"@tailwindcss/typography": "^0.5.9",
"@tippyjs/react": "^4.2.6",
"@types/bcrypt": "^5.0.0",
"@types/node": "20.4.4",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/redux-persist": "^4.0.0",
"apexcharts": "^3.42.0",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
@ -33,7 +33,6 @@
"eslint-config-next": "^14.0.1",
"formik": "^2.4.2",
"formik-wizard-form": "^2.1.0",
"lucide-react": "^0.519.0",
"mongoose": "^7.4.1",
"next": "^13.4.12",
"next-intl": "^2.19.0",
@ -54,9 +53,6 @@
"typescript": "5.1.6",
"universal-cookie": "^4.0.4",
"yup": "^1.2.0"
},
"devDependencies": {
"@types/node": "^24.0.3"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -1606,13 +1602,9 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"node_modules/@types/node": {
"version": "24.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz",
"integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
}
"version": "20.4.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz",
"integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -1666,27 +1658,6 @@
"@types/react": "*"
}
},
"node_modules/@types/redux-persist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/redux-persist/-/redux-persist-4.0.0.tgz",
"integrity": "sha512-VyDn75G7+Xkebp5Lxowk2t7HeT4VLSy92nyoQu1dZxk+ySBBdvV0yVR/upE4z24eVFxMp6Fo3CisQpkl4v1k3w==",
"license": "MIT",
"dependencies": {
"redux": "^3.6.0"
}
},
"node_modules/@types/redux-persist/node_modules/redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"license": "MIT",
"dependencies": {
"lodash": "^4.2.1",
"lodash-es": "^4.2.1",
"loose-envify": "^1.1.0",
"symbol-observable": "^1.0.3"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -4680,15 +4651,6 @@
"node": ">=10"
}
},
"node_modules/lucide-react": {
"version": "0.519.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.519.0.tgz",
"integrity": "sha512-cLJyjRKBJFzaZ/+1oIeQaH7XUdxKOYU3uANcGSrKdIZWElmNbRAm8RXKiTJS7AWLCBOS8b7A497Al/kCHozd+A==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@ -5941,7 +5903,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"license": "MIT",
"peerDependencies": {
"redux": ">4.0.0"
}
@ -6731,15 +6692,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/synckit": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
@ -7052,12 +7004,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"license": "MIT"
},
"node_modules/universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",

View File

@ -23,9 +23,9 @@
"@tailwindcss/typography": "^0.5.9",
"@tippyjs/react": "^4.2.6",
"@types/bcrypt": "^5.0.0",
"@types/node": "20.4.4",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/redux-persist": "^4.0.0",
"apexcharts": "^3.42.0",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
@ -34,7 +34,6 @@
"eslint-config-next": "^14.0.1",
"formik": "^2.4.2",
"formik-wizard-form": "^2.1.0",
"lucide-react": "^0.519.0",
"mongoose": "^7.4.1",
"next": "^13.4.12",
"next-intl": "^2.19.0",
@ -55,8 +54,5 @@
"typescript": "5.1.6",
"universal-cookie": "^4.0.4",
"yup": "^1.2.0"
},
"devDependencies": {
"@types/node": "^24.0.3"
}
}

View File

@ -1,6 +1,6 @@
'use client';
import { useEffect, useCallback } from 'react'
import { useEffect } from 'react'
import GeneralLook from '@/components/dashboard/home/generalLook'
import MembersOverviewChart from '@/components/dashboard/home/membersOverviewChart'
import ServicesSubscriptions from '@/components/dashboard/home/servicesSubscriptions'
@ -12,60 +12,41 @@ import { AppDispatch } from '@/redux/store';
import { load } from '@/redux/features/statistics-slice'
import CircularProgress from '@mui/material/CircularProgress';
import { useTranslations } from 'next-intl';
import Button from '@mui/material/Button';
import { RefreshCw } from 'lucide-react';
export default function Page() {
const dispatch = useDispatch<AppDispatch>()
const dispatch = useDispatch()
const t = useTranslations('statistics');
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 fetchData = useCallback(async () => {
try {
await dispatch(load({ forceRefresh: true }));
} catch (error) {
console.error('Failed to fetch dashboard data:', error);
}
}, [dispatch]);
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(loadedFirstTime) return;
if(isLoading) return;
async function a()
{
await dispatch(load())
}
a()
}, [])
if (isLoading && !report) {
if(isLoading)
{
return (
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
<CircularProgress color="secondary" size={22} />
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
</div>
<>
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
<CircularProgress color="secondary" size={22} />
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
</div>
</>
)
}
return (
<>
<main className="min-h-[250px] max-h-[750px]">
<div className="w-full flex justify-end mb-4">
<Button
variant="outlined"
size="small"
onClick={fetchData}
disabled={isLoading}
startIcon={<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />}
className="flex items-center gap-2"
>
{t('refresh') || 'Refresh'}
</Button>
</div>
<main className="min-h-[250px] max-h-[1000px]">
<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">
<GeneralLook/>
@ -75,9 +56,9 @@ export default function Page() {
<ServicesSubscriptions />
<WorkersJobTypes />
</section>
{/* <section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[350px] items-stretch">
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[350px] items-stretch">
<IncomeOutcome />
</section> */}
</section>
</main>
</main>
</>

View File

@ -10,11 +10,6 @@ import { ReactNode } from 'react'
import FullScreenLoader from "@/components/common/fullScreenLoader";
import { load } from '@/redux/features/settings-slice'
import Cookies from 'universal-cookie';
const cookies = new Cookies();
// HOC for auth pages ( only accept auth user )
export default function LocaleLayout({children} : { children : ReactNode }) {
@ -50,9 +45,6 @@ export default function LocaleLayout({children} : { children : ReactNode }) {
// if wasnt logged in this will fire
function NotLoggedInCallback()
{
console.log('SERVER ::: not logged in')
cookies.remove('authToken', { path: '/' });
// reset the state to there initial value
return router.push(notAuthRedirectPage)
}
return (

View File

@ -34,7 +34,6 @@ export default function LocaleLayout({children} : { children : ReactNode }) {
// if logged in this will fire
function LoggedInCallback()
{
console.log('SERVER ::: logged in')
return router.push(authRedirectPage)
}

View File

@ -156,7 +156,7 @@ export default function Index() {
</form>
<div className="ltr:rotate-180 w-[216px] h-screen lg:flex hidden justify-start items-start bg-primary dark:bg-primary-dark">
<svg className="[&_*]:text-secondary-light [&_*]:fill-secondary-light [&_*]:dark:fill-secondary-dark" width="216" height="970" viewBox="0 0 216 970" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_5265)">
<g clipPath="url(#clip0_3_5265)">
<path d="M108 970L89.9776 958.481C72.2251 946.895 35.7751 923.521 35.9776 900.618C35.7751 877.648 72.2251 854.812 100.778 831.236C129.6 808.333 151.2 785.431 151.2 761.854C151.2 739.019 129.6 716.049 122.378 693.146C115.425 669.772 122.175 646.667 136.823 623.764C151.2 600.457 172.8 577.285 176.378 554.382C180.225 531.21 165.375 507.903 147.623 485C129.6 461.895 108 438.521 104.423 415.618C100.575 392.648 115.425 369.813 129.6 346.236C143.775 323.333 158.625 300.431 158.423 276.854C158.625 254.019 143.775 231.049 126.023 208.146C108 184.772 86.4 161.667 82.8225 138.764C78.975 115.457 93.8251 92.2847 108 69.382C122.175 46.2097 137.025 22.9028 143.978 11.4514L151.2 4.50017e-06L216 1.66767e-06L216 11.5187C216 23.1048 216 46.4792 216 69.3819C216 92.3521 216 115.188 216 138.764C216 161.667 216 184.569 216 208.146C216 230.981 216 253.951 216 276.854C216 300.228 216 323.333 216 346.236C216 369.543 216 392.715 216 415.618C216 438.79 216 462.097 216 485C216 508.105 216 531.479 216 554.382C216 577.352 216 600.187 216 623.764C216 646.667 216 669.569 216 693.146C216 715.981 216 738.951 216 761.854C216 785.228 216 808.333 216 831.236C216 854.543 216 877.715 216 900.618C216 923.79 216 947.097 216 958.549L216 970L108 970Z" fill="currentColor"/>
</g>
<defs>

View File

@ -25,8 +25,10 @@ export default async function RootLayout({children, params: {locale}} : {childre
return (
<html lang={locale} dir={['ar' , 'fa'].includes(locale) ? 'rtl' : 'ltr'}>
<body>
<head>
<link rel="icon" href="/favicon.ico" sizes="any" />
</head>
<body suppressHydrationWarning={true}>
<NextIntlClientProvider locale={locale} messages={messages}>
<ReduxProvider>
{children}

View File

@ -65,7 +65,7 @@ export async function POST(req : Request)
$set: {
"token" : {
value: authToken,
expiresAt: Math.floor(Date.now() / 1000) + 30 * 60 * 60,
expiresAt: Math.floor(Date.now() / 1000) + 3 * 60 * 60,
}
}
})
@ -100,37 +100,33 @@ export async function POST(req : Request)
// revalidate every 10 seconds
export const revalidate = 10;
// handle the check authToken validation
export async function GET(req: Request)
{
try {
// connect to the database first
await dbConnect();
await dbConnect(); // Add await here, dbConnect might be async
// get the request params
const { searchParams } = new URL(req.url)
const authToken = searchParams.get('authToken')
// trow error if invalid auth token
if(!authToken)
{
throw {
specialError : 'invalidRequest'
}
}
else if(authToken == 'null')
if(!authToken || authToken == 'null')
{
throw {
specialError : 'invalidRequest'
}
}
// if there was a valid authToken cookie
else
{
// we try to get the user by the authToken
let userDoc = await userModel.findOne({'token.value': authToken})
// if the authToken is invalid we go return
if(!userDoc)
if(!userDoc || !userDoc.token )
{
throw {
specialError : 'invalidToken'
@ -138,9 +134,8 @@ export async function GET(req: Request)
}
// if the authToken if valid we check if it expired or not yet
// we throw error if expired
if(userDoc.token.expiresAt < (Date.now() / 1000)) //1000 for 3 hours
if(userDoc.token.expiresAt < (Date.now() / 1000))
{
console.log('SERVER ::: token expired')
throw {
specialError : 'expiredToken'
}
@ -168,6 +163,7 @@ export async function GET(req: Request)
}
else
{
console.error("Unhandled error in auth GET API:", e);
return NextResponse.json({
success: false,
message: "serverError",

View File

@ -125,7 +125,7 @@ export async function GET(req:Request)
{
// get the page
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
range : number[] = [(parseInt(page?page : '0') - 1) * 20, 20];
// get the docs
const docs = await memberModel.find({}).skip(range[0]).limit(range[1]),
// get the size of the docs
@ -193,7 +193,7 @@ export async function DELETE(req:Request)
const { searchParams } = new URL(req.url)
const page = searchParams.get('page'),
_id : string | null = searchParams.get('_id'),
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
range : number[] = [(parseInt(page?page : '0') - 1) * 20, 20];
// delete the doc
await memberModel.findByIdAndRemove(_id)
// get the docs by page

View File

@ -10,10 +10,355 @@ import { IStatisticsSchema } from '@/types/IDB'
// set the revalidate variable
export const revalidate = 5;
/**
* Updates the members general overview statistics for the current week, month, and year in the database.
* This function calculates total members, active/unactive subscriptions, and gender-based counts
* for each day of the current week, aggregates them into weeks for the current month,
* and then into months for the current year, storing it in the statisticsModel.
*/
async function updateMembersOverviewStatistics() {
try {
await dbConnect(); // Ensure DB connection
// Define week days in order, starting from Saturday as per frontend
const weekDaysOrder = ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'];
const now = new Date();
// Set to the start of today (local time)
now.setHours(0, 0, 0, 0);
const currentUnix = Math.floor(new Date().getTime() / 1000);
// --- Calculate thisWeek data ---
let currentDayOfWeek = now.getDay(); // 0 (Sunday) to 6 (Saturday)
let daysToSubtract;
if (currentDayOfWeek === 6) { // If today is Saturday
daysToSubtract = 0;
} else { // For any other day, calculate how many days back to last Saturday
daysToSubtract = (currentDayOfWeek + 1);
}
const startOfWeek = new Date(now);
startOfWeek.setDate(now.getDate() - daysToSubtract);
startOfWeek.setHours(0, 0, 0, 0); // Ensure it's the very start of Saturday
let thisWeekDaysData: { [key: string]: { [key: string]: number } } = {};
for (let i = 0; i < 7; i++) {
const day = new Date(startOfWeek);
day.setDate(startOfWeek.getDate() + i);
const endOfDay = new Date(day);
endOfDay.setDate(day.getDate() + 1); // Move to the next day
const endOfDayUnix = Math.floor(endOfDay.getTime() / 1000);
// Calculate cumulative totals up to this day (inclusive)
const totalMembers = await memberModel.countDocuments({
registerAt_unix: { $lt: endOfDayUnix }
});
const totalActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $gt: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalUnActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $lte: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalMansMembers = await memberModel.countDocuments({
gendre: 'm',
registerAt_unix: { $lt: endOfDayUnix }
});
const totalGirlsMembers = await memberModel.countDocuments({
gendre: 'w',
registerAt_unix: { $lt: endOfDayUnix }
});
thisWeekDaysData[weekDaysOrder[i]] = {
totalMembers,
totalActiveSubs,
totalUnActiveSubs,
totalMansMembers,
totalGirlsMembers,
};
}
const thisWeekData = {
year: startOfWeek.getFullYear(),
month: startOfWeek.getMonth(), // 0-indexed month
week: Math.ceil(startOfWeek.getDate() / 7), // Simplified week number
days: thisWeekDaysData
};
// --- Calculate thisMonth data ---
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
startOfMonth.setHours(0, 0, 0, 0);
let thisMonthWeeksData = [];
let tempDate = new Date(startOfMonth);
let weekCounter = 1;
while (tempDate.getMonth() === now.getMonth()) {
const startOfWeekInMonth = new Date(tempDate);
startOfWeekInMonth.setDate(tempDate.getDate() - ((tempDate.getDay() + 1) % 7)); // Back to last Saturday
startOfWeekInMonth.setHours(0, 0, 0, 0);
if (startOfWeekInMonth.getMonth() !== now.getMonth() && startOfWeekInMonth.getTime() < startOfMonth.getTime()) {
startOfWeekInMonth.setTime(startOfMonth.getTime());
}
let weekSum = {
totalMembers: 0,
totalActiveSubs: 0,
totalUnActiveSubs: 0,
totalMansMembers: 0,
totalGirlsMembers: 0
};
let daysInWeek = {};
let lastDayOfWeek = null;
for (let i = 0; i < 7; i++) {
const dayInWeek = new Date(startOfWeekInMonth);
dayInWeek.setDate(startOfWeekInMonth.getDate() + i);
// Only consider days within the current month
if (dayInWeek.getMonth() === now.getMonth()) {
const endOfDay = new Date(dayInWeek);
endOfDay.setDate(dayInWeek.getDate() + 1);
const endOfDayUnix = Math.floor(endOfDay.getTime() / 1000);
// Calculate cumulative totals up to this day (inclusive)
const totalMembers = await memberModel.countDocuments({
registerAt_unix: { $lt: endOfDayUnix }
});
const totalActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $gt: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalUnActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $lte: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalMansMembers = await memberModel.countDocuments({
gendre: 'm',
registerAt_unix: { $lt: endOfDayUnix }
});
const totalGirlsMembers = await memberModel.countDocuments({
gendre: 'w',
registerAt_unix: { $lt: endOfDayUnix }
});
daysInWeek[weekDaysOrder[i]] = {
totalMembers,
totalActiveSubs,
totalUnActiveSubs,
totalMansMembers,
totalGirlsMembers,
};
// Keep track of the last day's totals for the week sum
lastDayOfWeek = {
totalMembers,
totalActiveSubs,
totalUnActiveSubs,
totalMansMembers,
totalGirlsMembers,
};
}
}
// Set week sum to the cumulative total at the end of the week
if (lastDayOfWeek) {
weekSum = lastDayOfWeek;
}
thisMonthWeeksData.push({
week: weekCounter,
days: daysInWeek,
thisWeekSum: weekSum // This matches the frontend's expected structure for thisMonthTotalSum
});
tempDate.setDate(tempDate.getDate() + 7); // Move to the next week
weekCounter++;
}
const thisMonthData = {
year: now.getFullYear(),
month: now.getMonth(),
weeks: thisMonthWeeksData
};
// --- Calculate thisYear data ---
let thisYearMonthsDataForFrontendAndDB = []; // This will contain months with full weeks data and total sum
for (let month = 0; month < 12; month++) {
const startOfMonthOfYear = new Date(now.getFullYear(), month, 1);
startOfMonthOfYear.setHours(0, 0, 0, 0);
let monthlyWeeksData = []; // Weeks data for this specific month
let tempMonthDateForYear = new Date(startOfMonthOfYear);
let monthWeekCounterForYear = 1;
let totalMonthSum = { // Sum for the entire month for the year view
totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0,
totalMansMembers: 0, totalGirlsMembers: 0
};
while (tempMonthDateForYear.getMonth() === month) {
const startOfWeekInMonthForYear = new Date(tempMonthDateForYear);
startOfWeekInMonthForYear.setDate(tempMonthDateForYear.getDate() - ((tempMonthDateForYear.getDay() + 1) % 7));
startOfWeekInMonthForYear.setHours(0, 0, 0, 0);
if (startOfWeekInMonthForYear.getMonth() !== month && startOfWeekInMonthForYear.getTime() < startOfMonthOfYear.getTime()) {
startOfWeekInMonthForYear.setTime(startOfMonthOfYear.getTime());
}
let daysInMonthlyWeekForYear = {};
let currentWeeklySumForYear = { // Sum for the current week within the month
totalMembers: 0, totalActiveSubs: 0, totalUnActiveSubs: 0,
totalMansMembers: 0, totalGirlsMembers: 0
};
for (let i = 0; i < 7; i++) {
const dayInMonthlyWeekForYear = new Date(startOfWeekInMonthForYear);
dayInMonthlyWeekForYear.setDate(startOfWeekInMonthForYear.getDate() + i);
if (dayInMonthlyWeekForYear.getMonth() === month) {
const endOfDay = new Date(dayInMonthlyWeekForYear);
endOfDay.setDate(dayInMonthlyWeekForYear.getDate() + 1);
const endOfDayUnix = Math.floor(endOfDay.getTime() / 1000);
// Calculate cumulative totals up to this day (inclusive)
const totalMembers = await memberModel.countDocuments({
registerAt_unix: { $lt: endOfDayUnix }
});
const totalActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $gt: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalUnActiveSubs = await memberModel.countDocuments({
planExpAt_unix: { $lte: currentUnix },
registerAt_unix: { $lt: endOfDayUnix }
});
const totalMansMembers = await memberModel.countDocuments({
gendre: 'm',
registerAt_unix: { $lt: endOfDayUnix }
});
const totalGirlsMembers = await memberModel.countDocuments({
gendre: 'w',
registerAt_unix: { $lt: endOfDayUnix }
});
daysInMonthlyWeekForYear[weekDaysOrder[i]] = {
totalMembers,
totalActiveSubs,
totalUnActiveSubs,
totalMansMembers,
totalGirlsMembers,
};
// Keep the last day's totals as the week sum (cumulative total at end of week)
currentWeeklySumForYear = {
totalMembers,
totalActiveSubs,
totalUnActiveSubs,
totalMansMembers,
totalGirlsMembers,
};
}
}
monthlyWeeksData.push({
week: monthWeekCounterForYear,
days: daysInMonthlyWeekForYear,
thisWeekSum: currentWeeklySumForYear // Sum for this specific week
});
// Keep the last week's totals as the month sum (cumulative total at end of month)
totalMonthSum = {
totalMembers: currentWeeklySumForYear.totalMembers,
totalActiveSubs: currentWeeklySumForYear.totalActiveSubs,
totalUnActiveSubs: currentWeeklySumForYear.totalUnActiveSubs,
totalMansMembers: currentWeeklySumForYear.totalMansMembers,
totalGirlsMembers: currentWeeklySumForYear.totalGirlsMembers,
};
tempMonthDateForYear.setDate(tempMonthDateForYear.getDate() + 7);
monthWeekCounterForYear++;
}
thisYearMonthsDataForFrontendAndDB.push({
month: month,
weeks: monthlyWeeksData, // This is crucial for frontend's thisYear.months[x].weeks
thisMonthWeeksSum: totalMonthSum // The rolled-up sum for the month
});
}
const thisYearData = {
year: now.getFullYear(),
months: thisYearMonthsDataForFrontendAndDB // Both frontend and DB will use this detailed structure
};
// --- Update the statistics document ---
let statistics = await statisticsModel.findOne({});
if (!statistics) {
statistics = new statisticsModel({
listsTracking: {
thisWeek: thisWeekData,
thisMonth: thisMonthData,
thisYear: thisYearData, // Use the detailed data for thisYear
months: [thisMonthData], // Store current month's full data in array
years: [thisYearData] // Store current year's full detailed data in array
}
});
await statistics.save();
} else {
// Update thisWeek, thisMonth, thisYear directly (for current period display)
statistics.listsTracking.thisWeek = thisWeekData;
statistics.listsTracking.thisMonth = thisMonthData;
statistics.listsTracking.thisYear = thisYearData; // Update with the detailed data
// Update or add current month in listsTracking.months array (for historical list)
const currentMonthIndex = statistics.listsTracking.months.findIndex(m => m.year === thisMonthData.year && m.month === thisMonthData.month);
if (currentMonthIndex !== -1) {
statistics.listsTracking.months[currentMonthIndex] = thisMonthData;
} else {
// To avoid too many empty entries if listsTracking.months is for *all* months in current year
// and not just a historical log. If it's a fixed size array (12 months),
// we can update by index. If it's a dynamic historical log, push is fine.
// Assuming it's for current year's months, better to update by index or push if month is not there.
// For simplicity and matching current structure, push if not found.
statistics.listsTracking.months.push(thisMonthData);
}
// Update or add current year in listsTracking.years array (for historical list)
const currentYearIndex = statistics.listsTracking.years.findIndex(y => y.year === thisYearData.year);
if (currentYearIndex !== -1) {
statistics.listsTracking.years[currentYearIndex] = thisYearData; // Update with full detailed data
} else {
statistics.listsTracking.years.push(thisYearData); // Push full detailed data
}
await statistics.save();
}
console.log("Members overview statistics updated successfully for this week, month, and year.");
} catch (error) {
console.error("Error updating members overview statistics:", error);
}
}
// GET METHOD
export async function GET(req:Request)
{
try{
await updateMembersOverviewStatistics(); // Call the updater function first
// connect to the db
dbConnect();
// get needed docs
@ -25,8 +370,7 @@ export async function GET(req:Request)
let expiredSubscriptionsCount : number = await memberModel.count({planExpAt_unix: {$lte : Math.floor(new Date().getTime() / 1000)}})
let servicesCount : number = await servicesModel.count({})
let activeServicesCount : number = await servicesModel.count({status: "active"})
let disActiveServicesCount : number = servicesCount - activeServicesCount //await servicesModel.count({status: "inactive"})
let disActiveServicesCount : number = await servicesModel.count({status: "active"})
let servicesNameAndSubscribers :
{
_id: Types.ObjectId,

View File

@ -73,7 +73,7 @@ export async function GET(req:Request)
{
// get the page
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
range : number[] = [(parseInt(page?page : '0') - 1) * 10, 10];
// get the docs
const docs = await workerModel.find({}).skip(range[0]).limit(range[1]),
// get the size of the docs
@ -141,7 +141,7 @@ export async function DELETE(req:Request)
const { searchParams } = new URL(req.url)
const page = searchParams.get('page'),
_id : string | null = searchParams.get('_id'),
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
range : number[] = [(parseInt(page?page : '0') - 1) * 10, 10];
// delete the doc
await workerModel.findByIdAndRemove(_id)
// get the docs by page

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewEquipment')}</h3>
</button>

View File

@ -63,7 +63,9 @@ export default function AddPopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('addNewEquipment')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('addNewEquipment')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewExpense')}</h3>
</button>

View File

@ -63,7 +63,8 @@ export default function AddPopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('addNewExpense')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('addNewExpense')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -90,8 +90,8 @@ export default function Header () {
)}
<button onClick={handleLogout} className="ltr:rotate-180 rtl:ml-2 ltr:mr-2">
<svg className="stroke-text dark:stroke-text-dark" width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00195 7C9.01406 4.82497 9.11051 3.64706 9.87889 2.87868C10.7576 2 12.1718 2 15.0002 2H16.0002C18.8286 2 20.2429 2 21.1215 2.87868C22.0002 3.75736 22.0002 5.17157 22.0002 8V16C22.0002 18.8284 22.0002 20.2426 21.1215 21.1213C20.2429 22 18.8286 22 16.0002 22H15.0002C12.1718 22 10.7576 22 9.87889 21.1213C9.11051 20.3529 9.01406 19.175 9.00195 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<path d="M15 12H2M2 12L5.5 9M2 12L5.5 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.00195 7C9.01406 4.82497 9.11051 3.64706 9.87889 2.87868C10.7576 2 12.1718 2 15.0002 2H16.0002C18.8286 2 20.2429 2 21.1215 2.87868C22.0002 3.75736 22.0002 5.17157 22.0002 8V16C22.0002 18.8284 22.0002 20.2426 21.1215 21.1213C20.2429 22 18.8286 22 16.0002 22H15.0002C12.1718 22 10.7576 22 9.87889 21.1213C9.11051 20.3529 9.01406 19.175 9.00195 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
<path d="M15 12H2M2 12L5.5 9M2 12L5.5 15" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</div>

View File

@ -1,174 +1,125 @@
import { useTranslations } from 'next-intl';
import { useAppSelector } from '@/redux/store';
import { useEffect } from 'react';
import { constants } from 'node:buffer';
export default function GeneralLook() {
export default function GeneralLook()
{
const t = useTranslations('statistics');
const report = useAppSelector((state) => state.statisticsReducer.value.report)
if (!report) {
return (
<div className="lg:w-1/3 w-full flex flex-col lg:gap-7 gap-3">
<div className="w-full h-[200px] flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900 dark:border-gray-100"></div>
</div>
</div>
);
}
const stats = [
{
icon: 'members',
value: report?.membersCount?.value ?? 0,
label: t('totalMembers'),
bgColor: 'bg-info'
},
{
icon: 'activeMembers',
value: report?.activeMembersCount?.value ?? 0,
label: t('totalActiveMembers'),
bgColor: 'bg-success'
},
{
icon: 'disActiveMembers',
value: report?.disActiveMembersCount?.value ?? 0,
label: t('totalUnActiveMembers'),
bgColor: 'bg-error'
}
];
const stats2 = [
{
icon: 'services',
value: report?.servicesCount?.value ?? 0,
label: t('totalServices'),
bgColor: 'bg-info'
},
{
icon: 'activeServices',
value: report?.activeServicesCount?.value ?? 0,
label: t('totalActiveServices'),
bgColor: 'bg-success'
},
{
icon: 'disActiveServices',
value: report?.disActiveServicesCount?.value ?? 0,
label: t('totalUnActiveServices'),
bgColor: 'bg-error'
}
];
const stats3 = [
{
icon: 'activeSubscriptions',
value: report?.activeSubscriptionsCount?.value ?? 0,
label: t('totalActiveSubscriptions'),
bgColor: 'bg-info'
},
{
icon: 'expiredSoonSubscriptions',
value: report?.expiredSoonSubscriptionsCount?.value ?? 0,
label: t('expiredSoonSubscriptionsCount'),
bgColor: 'bg-warning'
},
{
icon: 'expiredSubscriptions',
value: report?.expiredSubscriptionsCount?.value ?? 0,
label: t('totalExpiredSubscriptions'),
bgColor: 'bg-error'
}
];
return (
<div className="lg:w-1/3 w-full flex flex-col lg:gap-7 gap-3">
{/* {stats.map((stat, index) => (
<div key={index} className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
<>
<div className="lg:w-1/3 w-full flex flex-col lg:gap-7 gap-3">
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
<div className="w-full h-full text-start flex items-center gap-2">
<span className={`w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md ${stat.bgColor} flex items-center justify-center`}>
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white" />
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{stat.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{stat.label}</p>
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.membersCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalMembers')}</p>
</span>
</div>
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-success flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeMembersCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveMembers')}</p>
</span>
</div>
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.disActiveMembersCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalUnActiveMembers')}</p>
</span>
</div>
</div>
))} */}
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
{stats.map((stat, index) => (
<div key={index} className="w-full h-full text-start flex items-center gap-2">
<span className={`w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md ${stat.bgColor} flex items-center justify-center`}>
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white" />
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{stat.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{stat.label}</p>
</span>
</div>
))}
</div>
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
{stats2.map((stat, index) => (
<div key={index} className="w-full h-full text-start flex items-center gap-2">
<span className={`w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md ${stat.bgColor} flex items-center justify-center`}>
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{stat.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{stat.label}</p>
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.servicesCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalServices')}</p>
</span>
</div>
))}
</div>
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
{stats3.map((stat, index) => (
<div key={index} className="w-full h-full text-start flex items-center gap-2">
<span className={`w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md ${stat.bgColor} flex items-center justify-center`}>
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-success flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeServicesCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveServices')}</p>
</span>
</div>
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{ ((report?.servicesCount?.value ?? 0) - (report?.activeServicesCount?.value ?? 0))}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalUnActiveServices')}</p>
</span>
</div>
</div>
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{stat.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{stat.label}</p>
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeSubscriptionsCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveSubscriptions')}</p>
</span>
</div>
))}
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-warning flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.expiredSoonSubscriptionsCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('expiredSoonSubscriptionsCount')}</p>
</span>
</div>
<div className="w-full h-full text-start flex items-center gap-2">
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
</svg>
</span>
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.expiredSubscriptionsCount.value}</p>
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalExpiredSubscriptions')}</p>
</span>
</div>
</div>
</div>
</div >
</>
)
}

View File

@ -2,7 +2,6 @@ import { useAppSelector } from '@/redux/store';
import { useTranslations } from 'next-intl';
import dynamic from 'next/dynamic';
import Cookies from 'universal-cookie';
import { useEffect, useState } from 'react';
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
});
@ -10,66 +9,45 @@ const ReactApexChart = dynamic(() => import('react-apexcharts'), {
export default function IncomeOutcome()
{
// get needed redux state
const report = useAppSelector((state) => state.statisticsReducer.value.report);
const settings = useAppSelector((state) => state.settingsReducer.value);
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType);
const [isClient, setIsClient] = useState(false);
const report = useAppSelector((state) => state.statisticsReducer.value.report)
const currencySymbol = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings.currencySymbol) ?? ''
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
// declare global variables
const cookies = new Cookies();
const t = useTranslations('statistics');
const locale = cookies.get("NEXT_LOCALE");
const isRtl = locale === 'ar';
const isDark = themeType === 'dark';
// Set isClient to true after component mounts
useEffect(() => {
setIsClient(true);
}, []);
// if loading or no report or settings not loaded, show loading spinner
if (!report || !isClient || !settings?.appGeneralSettings) {
return (
<div className="w-full h-[350px] flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900 dark:border-gray-100"></div>
</div>
);
}
// Get currency symbol with fallback to '$' if not available
const currencySymbol = settings.appGeneralSettings.currencySymbol || '$';
const locale = cookies.get("NEXT_LOCALE")
const isRtl = locale == 'ar' ? true : false
const isDark = themeType == 'dark' ? true : false
// if loading show load screen
if(!report) return null // Return null or a loading indicator if report is not yet available
// prepare chart data
const data = [report?.totalIncome || 0, report?.totalOutcome || 0];
// Ensure totalIncome and totalOutcome are numbers, default to 0 if undefined/null
let data = [report?.totalIncome ?? 0 , report?.totalOutcome ?? 0]
// prepare chart labels
const labels = [t('income'), t('outcome')];
let labels = [t('income') , t('outcome')]
// prepare chart options
const options = {
series: [{
var options = {
series: [{
name: currencySymbol,
data: data
}],
annotations: {
annotations: {
points: [{
x: t('income'),
seriesIndex: 0,
label: {
borderColor: '#775DD0',
offsetY: 0,
style: {
color: '#fff',
background: '#775DD0',
},
}
x: t('incomes'),
seriesIndex: 0,
label: {
borderColor: '#775DD0',
offsetY: 0,
style: {
color: '#fff',
background: '#775DD0',
},
}
}]
},
chart: {
height: 350,
type: 'bar',
toolbar: {
show: false
}
},
plotOptions: {
bar: {
@ -92,7 +70,6 @@ export default function IncomeOutcome()
horizontal: 10,
vertical: 5,
},
show: false
},
fill: {
type: isDark ? '' : 'gradient',
@ -104,7 +81,7 @@ export default function IncomeOutcome()
stops: isDark ? [100, 100] : [45, 100],
},
},
colors: ['#0263FF', '#E3342F'],
colors: ['#0263FF' , '#E3342F'],
dataLabels: {
enabled: false
},
@ -114,7 +91,7 @@ export default function IncomeOutcome()
},
grid: {
row: {
colors: [isDark ? '#333' : '#fff', '#f2f2f2']
colors: [isDark ? '#333' : '#fff', '#f2f2f2'] // Adjust the grid row color for dark mode
},
},
xaxis: {
@ -124,68 +101,39 @@ export default function IncomeOutcome()
offsetY: 5,
style: {
colors: isDark ? '#fff' : '#000',
fontSize: '12px',
cssClass: 'apexcharts-xaxis-title',
},
formatter: function(value: string) {
return value;
}
},
categories: labels,
axisBorder: {
show: false
},
axisTicks: {
show: false
},
tickPlacement: 'on'
},
yaxis: {
tickAmount: 7,
labels: {
offsetX: isRtl ? -30 : -10,
offsetY: 0,
style: {
colors: isDark ? '#fff' : '#000',
}
}
fontSize: '12px',
cssClass: 'apexcharts-yaxis-title',
},
},
},
tooltip: {
theme: isDark ? 'dark' : 'light',
y: {
formatter: function(val: number) {
return `${currencySymbol} ${val}`;
}
}
x: {
show: true,
},
},
responsive: [{
breakpoint: 480,
options: {
legend: {
position: 'bottom',
offsetY: 10,
horizontalAlign: 'center',
fontSize: '12px',
markers: {
width: 10,
height: 10,
offsetX: isRtl ? 5 : -5,
},
itemMargin: {
horizontal: 10,
vertical: 5,
},
},
}
}]
};
// return the ui
return (
<div className="w-full h-auto p-5 shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
<h3 className="text-xl font-bold mb-5">{t('incomeOutcomeGeneralOverview')}</h3>
<div className="w-full h-[350px]">
<ReactApexChart
options={options}
series={options.series}
type="bar"
height="100%"
width="100%"
/>
<>
<div className="flex flex-col gap-5 [&_*]:dark:!text-text-dark p-5 w-full shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
<h3 className="text-xl font-bold">{t('incomeOutcomeGeneralOverview')}</h3>
<ReactApexChart series={options.series} options={options} type="bar" height={350} width={'100%'} />
</div>
</div>
);
</>
)
}

View File

@ -6,31 +6,52 @@ import Cookies from 'universal-cookie';
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
});
import { useDispatch } from 'react-redux';
import { AppDispatch } from '@/redux/store';
import { useAppDispatch } from '@/redux/store';
import { setCurrentMembersGeneralOverviewDuration } from '@/redux/features/statistics-slice'
export default function MembersOverviewChart() {
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 dispatch = useAppDispatch()
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<{}[]>([]);
const [labels, setLabels] = useState<string[]>([])
const [chartSeries , setChartSeries] = useState<{}[]>([]);
const [labels , setLabels] = useState<string[]>([])
// revenue Chart
// prepare chart series and handle the transaction between currentMembersGeneralOverviewDuration
useEffect(() => {
// Optional: Re-add a robust check for initial report data, if needed as a fallback.
// For now, let's focus on debugging the data flow within the existing logic.
if (!report || !report.membersGeneralOverview || !report.membersGeneralOverview.value) {
//console.log("MembersOverviewChart - Report or general overview data is not available. Setting default.");
setChartSeries([
{ name: t('totalMembers'), data: [0,0,0,0,0,0,0] },
{ name: t('totalActiveSubs'), data: [0,0,0,0,0,0,0] },
{ name: t('totalUnActiveSubs'), data: [0,0,0,0,0,0,0] },
{ name: t('totalMansMembers'), data: [0,0,0,0,0,0,0] },
{ name: t('totalGirlsMembers'), data: [0,0,0,0,0,0,0] },
]);
setLabels([t('sat') , t('sun') , t('mon') , t('tue') , t('wed') , t('thu') , t('fri')]);
return;
}
// if chart duration is set to this week
if (currentMembersGeneralOverviewDuration == 'thisWeek') {
if(currentMembersGeneralOverviewDuration == 'thisWeek')
{
// set the labels
setLabels([t('sat'), t('sun'), t('mon'), t('tue'), t('wed'), t('thu'), t('fri')])
const newLabelsThisWeek = [t('sat') , t('sun') , t('mon') , t('tue') , t('wed') , t('thu') , t('fri')];
setLabels(newLabelsThisWeek);
// init this weekdata
let data_thisWeek = {
'sat': {
@ -84,57 +105,96 @@ export default function MembersOverviewChart() {
}
}
// if there is a data from the backend we overwrite the currentData
// if(report?.membersGeneralOverview?.value.thisWeek.days) {
// data_thisWeek = report?.membersGeneralOverview?.value.thisWeek.days;
// }
// To this:
console.log( report?.membersGeneralOverview?.value?.thisWeek?.days)
if (report?.membersGeneralOverview?.value?.thisWeek?.days) {
if(report.membersGeneralOverview.value.thisWeek?.days) {
data_thisWeek = report.membersGeneralOverview.value.thisWeek.days;
}
// prepare the chart series
setChartSeries([
const newChartSeriesThisWeek = [
{
name: t('totalMembers'),
data: [data_thisWeek['sat']['totalMembers'], data_thisWeek['sun']['totalMembers'], data_thisWeek['mon']['totalMembers'], data_thisWeek['tue']['totalMembers'], data_thisWeek['wed']['totalMembers'], data_thisWeek['thu']['totalMembers'], data_thisWeek['fri']['totalMembers']],
data: [
data_thisWeek['sat']?.['totalMembers'] || 0,
data_thisWeek['sun']?.['totalMembers'] || 0,
data_thisWeek['mon']?.['totalMembers'] || 0,
data_thisWeek['tue']?.['totalMembers'] || 0,
data_thisWeek['wed']?.['totalMembers'] || 0,
data_thisWeek['thu']?.['totalMembers'] || 0,
data_thisWeek['fri']?.['totalMembers'] || 0
],
},
{
name: t('totalActiveSubs'),
data: [data_thisWeek['sat']['totalActiveSubs'], data_thisWeek['sun']['totalActiveSubs'], data_thisWeek['mon']['totalActiveSubs'], data_thisWeek['tue']['totalActiveSubs'], data_thisWeek['wed']['totalActiveSubs'], data_thisWeek['thu']['totalActiveSubs'], data_thisWeek['fri']['totalActiveSubs']],
data: [
data_thisWeek['sat']?.['totalActiveSubs'] || 0,
data_thisWeek['sun']?.['totalActiveSubs'] || 0,
data_thisWeek['mon']?.['totalActiveSubs'] || 0,
data_thisWeek['tue']?.['totalActiveSubs'] || 0,
data_thisWeek['wed']?.['totalActiveSubs'] || 0,
data_thisWeek['thu']?.['totalActiveSubs'] || 0,
data_thisWeek['fri']?.['totalActiveSubs'] || 0
],
},
{
name: t('totalUnActiveSubs'),
data: [data_thisWeek['sat']['totalUnActiveSubs'], data_thisWeek['sun']['totalUnActiveSubs'], data_thisWeek['mon']['totalUnActiveSubs'], data_thisWeek['tue']['totalUnActiveSubs'], data_thisWeek['wed']['totalUnActiveSubs'], data_thisWeek['thu']['totalUnActiveSubs'], data_thisWeek['fri']['totalUnActiveSubs']],
data: [
data_thisWeek['sat']?.['totalUnActiveSubs'] || 0,
data_thisWeek['sun']?.['totalUnActiveSubs'] || 0,
data_thisWeek['mon']?.['totalUnActiveSubs'] || 0,
data_thisWeek['tue']?.['totalUnActiveSubs'] || 0,
data_thisWeek['wed']?.['totalUnActiveSubs'] || 0,
data_thisWeek['thu']?.['totalUnActiveSubs'] || 0,
data_thisWeek['fri']?.['totalUnActiveSubs'] || 0
],
},
{
name: t('totalMansMembers'),
data: [data_thisWeek['sat']['totalMansMembers'], data_thisWeek['sun']['totalMansMembers'], data_thisWeek['mon']['totalMansMembers'], data_thisWeek['tue']['totalMansMembers'], data_thisWeek['wed']['totalMansMembers'], data_thisWeek['thu']['totalMansMembers'], data_thisWeek['fri']['totalMansMembers']],
data: [
data_thisWeek['sat']?.['totalMansMembers'] || 0,
data_thisWeek['sun']?.['totalMansMembers'] || 0,
data_thisWeek['mon']?.['totalMansMembers'] || 0,
data_thisWeek['tue']?.['totalMansMembers'] || 0,
data_thisWeek['wed']?.['totalMansMembers'] || 0,
data_thisWeek['thu']?.['totalMansMembers'] || 0,
data_thisWeek['fri']?.['totalMansMembers'] || 0
],
},
{
name: t('totalGirlsMembers'),
data: [data_thisWeek['sat']['totalGirlsMembers'], data_thisWeek['sun']['totalGirlsMembers'], data_thisWeek['mon']['totalGirlsMembers'], data_thisWeek['tue']['totalGirlsMembers'], data_thisWeek['wed']['totalGirlsMembers'], data_thisWeek['thu']['totalGirlsMembers'], data_thisWeek['fri']['totalGirlsMembers']],
data: [
data_thisWeek['sat']?.['totalGirlsMembers'] || 0,
data_thisWeek['sun']?.['totalGirlsMembers'] || 0,
data_thisWeek['mon']?.['totalGirlsMembers'] || 0,
data_thisWeek['tue']?.['totalGirlsMembers'] || 0,
data_thisWeek['wed']?.['totalGirlsMembers'] || 0,
data_thisWeek['thu']?.['totalGirlsMembers'] || 0,
data_thisWeek['fri']?.['totalGirlsMembers'] || 0
],
},
])
];
setChartSeries(newChartSeriesThisWeek);
}
// if chart duration is set to this month
else if (currentMembersGeneralOverviewDuration == 'thisMonth') {
let thisMonthTotalSum: {
week: number,
thisWeekSum: {
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(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: {
thisMonthTotalSum = (report.membersGeneralOverview.value.thisMonth.weeks || []).map((v : any , i : number) : {
week : number,
thisWeekSum : {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
@ -144,118 +204,61 @@ export default function MembersOverviewChart() {
} => {
// 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,
"totalMembers": 0,
"totalActiveSubs": 0,
"totalUnActiveSubs": 0,
"totalMansMembers": 0,
"totalGirlsMembers": 0
}
}, 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
})
})
// we map through this month weeks
if (!v || !v.days) {
return {
week: v?.week || i,
thisWeekSum: thisWeekSum
};
}
// Since backend now provides cumulative totals, use the last day's values as the week total
if (v.days && v.days.length > 0) {
const lastDay = v.days[v.days.length - 1];
const dayKeys = ['fri', 'thu', 'wed', 'tue', 'mon', 'sun', 'sat'];
// Find the last day with data
for (const dayKey of dayKeys) {
if (lastDay[dayKey] && Object.keys(lastDay[dayKey]).length > 0) {
thisWeekSum = {
totalMembers: lastDay[dayKey]['totalMembers'] || 0,
totalActiveSubs: lastDay[dayKey]['totalActiveSubs'] || 0,
totalUnActiveSubs: lastDay[dayKey]['totalUnActiveSubs'] || 0,
totalMansMembers: lastDay[dayKey]['totalMansMembers'] || 0,
totalGirlsMembers: lastDay[dayKey]['totalGirlsMembers'] || 0,
};
break;
}
}
}
// each week we return there sum data
return {
week: v.week,
week: v?.week || i,
thisWeekSum: thisWeekSum
}
})
let labels: [] = [];
thisMonthTotalSum.forEach(function (v, i) {
labels.push(t('week') + ' ' + thisMonthTotalSum[i]["week"])
const newLabelsThisMonth : string[] = [];
thisMonthTotalSum.forEach(function(v , i) {
newLabelsThisMonth.push(t('week')+ ' ' +thisMonthTotalSum[i]["week"])
})
// set the labels
setLabels(labels)
setLabels(newLabelsThisMonth)
// we setup chart_series ( chart data )
let a: {
let newChartSeriesThisMonth : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[] = ['totalMembers', 'totalActiveSubs', 'totalUnActiveSubs', 'totalMansMembers', 'totalGirlsMembers'].map((v: string, i: number): {
data: number[]
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
name: string,
data: {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
"totalMansMembers": number,
"totalGirlsMembers": number
}
data: 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];
let data = thisMonthTotalSum.map((v2 : any) => {
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v as any;
return v2?.thisWeekSum?.[index] || 0;
})
// return the line data
return {
@ -263,36 +266,31 @@ export default function MembersOverviewChart() {
data: data
};
})
setChartSeries(a)
setChartSeries(newChartSeriesThisMonth)
}
// 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],
}
])
{ name: t('totalMembers'), data: [0,0,0,0] },
{ name: t('totalActiveSubs'), data: [0,0,0,0] },
{ name: t('totalUnActiveSubs'), data: [0,0,0,0] },
{ name: t('totalMansMembers'), data: [0,0,0,0] },
{ name: t('totalGirlsMembers'), data: [0,0,0,0] },
]);
setLabels([t('week')+' 1', t('week')+' 2', t('week')+' 3', t('week')+' 4']);
}
}
// if chart duration is set to this month
else if (currentMembersGeneralOverviewDuration == 'thisYear') {
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')])
const newLabelsThisYear = [t('1month'), t('2month'), t('3month'), t('4month'), t('5month'), t('6month'), t('7month'), t('8month'), t('9month'), t('10month'), t('11month'),t('12month') ];
setLabels(newLabelsThisYear);
// check if there is current month report
let thisYearTotalSum: {
let thisYearTotalSum : {
month: number,
thisMonthWeeksSum: {
"totalMembers": number,
@ -301,12 +299,13 @@ export default function MembersOverviewChart() {
"totalMansMembers": number,
"totalGirlsMembers": number
}
}[]
if (report?.membersGeneralOverview?.value.thisYear) {
}[] = [];
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: {
thisYearTotalSum = (report.membersGeneralOverview.value.thisYear.months || []).map((v : any , i : number) : {
month : number,
thisMonthWeeksSum : {
"totalMembers": number,
"totalActiveSubs": number,
"totalUnActiveSubs": number,
@ -314,186 +313,94 @@ export default function MembersOverviewChart() {
"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
}
})
// Initialize thisMonthWeeksSum
let thisMonthWeeksSum = {
"totalMembers": 0,
"totalActiveSubs": 0,
"totalUnActiveSubs": 0,
"totalMansMembers": 0,
"totalGirlsMembers": 0
};
if (!v || !v.weeks) {
return {
month: v?.month !== undefined ? (v.month + 1) : (i + 1),
thisMonthWeeksSum: thisMonthWeeksSum
};
}
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
// Since backend now provides cumulative totals, use the last week's values as the month total
if (v.weeks && v.weeks.length > 0) {
const lastWeek = v.weeks[v.weeks.length - 1];
if (lastWeek && lastWeek.days && lastWeek.days.length > 0) {
const lastDay = lastWeek.days[lastWeek.days.length - 1];
const dayKeys = ['fri', 'thu', 'wed', 'tue', 'mon', 'sun', 'sat'];
// Find the last day with data in the last week
for (const dayKey of dayKeys) {
if (lastDay[dayKey] && Object.keys(lastDay[dayKey]).length > 0) {
thisMonthWeeksSum = {
totalMembers: lastDay[dayKey]['totalMembers'] || 0,
totalActiveSubs: lastDay[dayKey]['totalActiveSubs'] || 0,
totalUnActiveSubs: lastDay[dayKey]['totalUnActiveSubs'] || 0,
totalMansMembers: lastDay[dayKey]['totalMansMembers'] || 0,
totalGirlsMembers: lastDay[dayKey]['totalGirlsMembers'] || 0,
};
break;
}
}
}
}, i2: number) => {
let index: 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v;
return v2.thisMonthWeeksSum[index];
})
}
return { month: v?.month !== undefined ? (v.month + 1) : (i + 1), thisMonthWeeksSum: thisMonthWeeksSum };
});
// prepare the labels - ensure correct month order from 1 to 12
const newLabelsThisYear : string[] = [];
for (let monthIndex = 1; monthIndex <= 12; monthIndex++) {
newLabelsThisYear.push(t(monthIndex + "month"));
}
setLabels(newLabelsThisYear)
// we setup chart_series ( chart data )
let newChartSeriesThisYear : {
name: string,
data: number[]
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
name: string,
data: number[]
} => {
let data = [];
// Ensure we have data for all 12 months in correct order
for (let monthIndex = 1; monthIndex <= 12; monthIndex++) {
const monthData = thisYearTotalSum.find(item => item.month === monthIndex);
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v as any;
data.push(monthData?.thisMonthWeeksSum?.[index] || 0);
}
// return the line data
return {
name: t(v),
data: data
};
})
setChartSeries(a)
setChartSeries(newChartSeriesThisYear)
}
// if there was no data we use default return
else {
const defaultYearLabels = [t('1month'), t('2month'), t('3month'), t('4month'), t('5month'), t('6month'), t('7month'), t('8month'), t('9month'), t('10month'), t('11month'), t('12month')];
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],
}
])
{ name: t('totalMembers'), data: [0,0,0,0,0,0,0,0,0,0,0,0] },
{ name: t('totalActiveSubs'), data: [0,0,0,0,0,0,0,0,0,0,0,0] },
{ name: t('totalUnActiveSubs'), data: [0,0,0,0,0,0,0,0,0,0,0,0] },
{ name: t('totalMansMembers'), data: [0,0,0,0,0,0,0,0,0,0,0,0] },
{ name: t('totalGirlsMembers'), data: [0,0,0,0,0,0,0,0,0,0,0,0] },
]);
setLabels(defaultYearLabels);
}
}
}, [currentMembersGeneralOverviewDuration])
}, [report , currentMembersGeneralOverviewDuration , isRtl , isDark , t])
// prepare the chart options
const options: any = {
const options : any = {
series: chartSeries,
options: {
chart: {
@ -523,7 +430,7 @@ export default function MembersOverviewChart() {
left: -7,
top: 22,
},
colors: ['#38C172', '#38C172', '#E3342F', '#0263FF', '#FF30F7'],
colors: ['#38C172', '#38C172' , '#E3342F' , '#0263FF', '#FF30F7'],
markers: {
//discrete: [
// {
@ -567,11 +474,12 @@ export default function MembersOverviewChart() {
tickAmount: 7,
labels: {
formatter: (value: number) => {
if (value > 1000) {
if(value > 1000)
{
return parseInt(value.toFixed(0)) + t('k')
}
else {
return parseInt(value.toFixed(0))
return parseInt(value.toFixed(0))
}
},
offsetX: isRtl ? -30 : -10,

View File

@ -2,7 +2,6 @@ import { useAppSelector } from '@/redux/store';
import { useTranslations } from 'next-intl';
import dynamic from 'next/dynamic';
import Cookies from 'universal-cookie';
import { useState, useEffect } from 'react';
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
});
@ -10,56 +9,34 @@ const ReactApexChart = dynamic(() => import('react-apexcharts'), {
export default function ServicesSubscriptions()
{
// get redux needed state
const report = useAppSelector((state) => state.statisticsReducer.value.report);
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType);
const [isClient, setIsClient] = useState(false);
const report = useAppSelector((state) => state.statisticsReducer.value.report)
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
// declare global variables
const cookies = new Cookies();
const t = useTranslations('statistics');
const locale = cookies.get("NEXT_LOCALE");
const isRtl = locale === 'ar';
const isDark = themeType === 'dark';
// Set isClient to true after component mounts
useEffect(() => {
setIsClient(true);
}, []);
// if loading or no report, show loading spinner
if (!report || !isClient) {
return (
<div className="w-full h-[350px] flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900 dark:border-gray-100"></div>
</div>
);
}
// setup chart data with null checks
const data = report?.servicesNameAndSubscribers?.value?.map((v: {
_id: string;
name: string;
totalSubscribers: number;
}) => v?.totalSubscribers) || [];
// setup chart labels with null checks
const labels = report?.servicesNameAndSubscribers?.value?.map((v: {
_id: string;
name: string;
totalSubscribers: number;
}) => v?.name) || [];
// Return empty state if no data
if (data.length === 0 || labels.length === 0) {
return (
<div className="w-full h-[350px] flex items-center justify-center">
<p>{t('noDataAvailable')}</p>
</div>
);
}
const locale = cookies.get("NEXT_LOCALE")
const isRtl = locale == 'ar' ? true : false
const isDark = themeType == 'dark' ? true : false
// if loading show load screen
if(!report) return
// setup chart data
let data = report?.servicesNameAndSubscribers.value.map((v : {
_id: string,
name: string,
totalSubscribers: number,
} , i : number) => {
return v?.totalSubscribers;
})
// setup chart labels
let labels = report?.servicesNameAndSubscribers.value.map((v : {
_id: string,
name: string,
totalSubscribers: number,
} , i : number) => {
return v?.name;
})
// setup chart options
const options = {
var options = {
series: [{
name: t('subscription'),
data: data

View File

@ -2,7 +2,6 @@ import { useAppSelector } from '@/redux/store';
import { useTranslations } from 'next-intl';
import dynamic from 'next/dynamic';
import Cookies from 'universal-cookie';
import { useState, useEffect } from 'react';
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
});
@ -10,56 +9,34 @@ const ReactApexChart = dynamic(() => import('react-apexcharts'), {
export default function WorkersJobTypes()
{
// get needed redux state
const report = useAppSelector((state) => state.statisticsReducer.value.report);
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType);
const [isClient, setIsClient] = useState(false);
const report = useAppSelector((state) => state.statisticsReducer.value.report)
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
// declare global variables
const cookies = new Cookies();
const t = useTranslations('statistics');
const locale = cookies.get("NEXT_LOCALE");
const isRtl = locale === 'ar';
const isDark = themeType === 'dark';
// Set isClient to true after component mounts
useEffect(() => {
setIsClient(true);
}, []);
// if loading or no report, show loading spinner
if (!report || !isClient) {
return (
<div className="w-full h-[350px] flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900 dark:border-gray-100"></div>
</div>
);
}
// prepare chart data with null checks
const data = report?.workersNameAndJobType?.value?.map((v: {
_id: string;
jobType: string;
count: number;
}) => v?.count) || [];
// prepare chart labels with null checks
const labels = report?.workersNameAndJobType?.value?.map((v: {
_id: string;
jobType: string;
count: number;
}) => v?.jobType) || [];
// Return empty state if no data
if (data.length === 0 || labels.length === 0) {
return (
<div className="w-full h-[350px] flex items-center justify-center">
<p>{t('noDataAvailable')}</p>
</div>
);
}
const locale = cookies.get("NEXT_LOCALE")
const isRtl = locale == 'ar' ? true : false
const isDark = themeType == 'dark' ? true : false
// if loading show load screen
if(!report) return
// prepare chart data
let data = report?.workersNameAndJobType.value.map((v : {
_id: string,
jobType: string,
count: number,
} , i : number) => {
return v?.count;
})
// prepare chart labels
let labels = report?.workersNameAndJobType.value.map((v : {
_id: string,
jobType: string,
count: number,
} , i : number) => {
return v?.jobType;
})
// prepare chart options
const options = {
var options = {
series: [{
name: t('worker'),
data: data

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewIncome')}</h3>
</button>

View File

@ -63,7 +63,8 @@ export default function AddPopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('addNewIncome')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('addNewIncome')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -38,8 +38,6 @@ export default function List()
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
<circle cx="20" cy="29" r="2" fill="currentColor"/>
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
</svg>
<input onChange={(async(e) => {
if(e.target.value.length == 0)
@ -127,38 +125,30 @@ export default function List()
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{v.payMonth} {appGeneralSettings.currencySymbol}</span>
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{v.planDelay + ' ' + t('months')}</span>
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">
{v.active ?
(v.planExpAt_unix - (Date.now() / 1000) > 259200 ?
<span className="flex gap-1 text-success">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('trueActive')}
</span>
:
(v.planExpAt_unix - (Date.now() / 1000) > 0 ?
<span className="flex gap-1 text-warning">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('expireVerySoon')}
</span>
:
<span className="flex gap-1 text-error">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('falseActive')}
</span>
)
)
{v.planExpAt_unix - (Date.now() / 1000) > 259200 ?
<span className="flex gap-1 text-success">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('trueActive')}
</span>
:
(
v.planExpAt_unix - (Date.now() / 1000) > 0 ?
<span className="flex gap-1 text-warning">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('expireVerySoon')}
</span>
:
<span className="flex gap-1 text-error">
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
</svg>
{t('inactive')}
{t('falseActive')}
</span>
)
}
</span>
<span className="lg:w-full w-[100px] h-full text-start flex justify-start items-center gap-3">

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewMember')}</h3>
</button>

View File

@ -83,7 +83,8 @@ export default function AddPopUp()
// dialog text content contain a small description to the current form part
}
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
<h1 className="text-[18px] font-tajawal">{t('memberAddText1')}</h1>
<div> <h1 className="text-[18px] font-tajawal">{t('memberAddText1')}</h1> </div>
</DialogContentText>
<div className="
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80

View File

@ -85,7 +85,8 @@ export default function ServiceDetailsPopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('memberDetails')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('memberDetails')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 20) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 20)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -66,7 +66,8 @@ export default function UpdatePopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('updateMember')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('updateMember')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
@ -121,7 +122,8 @@ export default function UpdatePopUp()
// dialog text content contain a small description to the current form part
}
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
<h1 className="text-[18px] font-tajawal">{t('memberUpdateText1')}</h1>
<div> <h1 className="text-[18px] font-tajawal">{t('memberUpdateText1')}</h1> </div>
</DialogContentText>
<div className="
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80

View File

@ -181,7 +181,8 @@ export default function UpdateSubPopUp()
// This is the title of the pop up
}
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
<h1 className="text-[25px] font-tajawal">{t('memberSubscriptionRenewal')}</h1>
<div> <h1 className="text-[25px] font-tajawal">{t('memberSubscriptionRenewal')}</h1> </div>
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
@ -241,7 +242,8 @@ export default function UpdateSubPopUp()
// dialog text content contain a small description to the current form part
}
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
<h1 className="text-[18px] font-tajawal">{t('memberSubscriptionRenewalText1')}</h1>
<div> <h1 className="text-[18px] font-tajawal">{t('memberSubscriptionRenewalText1')}</h1> </div>
</DialogContentText>
<div className="
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewProduct')}</h3>
</button>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewService')}</h3>
</button>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
// show the pagination ui
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -8,14 +8,14 @@ import AnimateHeight from 'react-animate-height';
import { useState, useEffect } from 'react';
import { useAppSelector } from "@/redux/store";
import { usePathname } from 'next/navigation';
import { AppDispatch } from '@/redux/store';
import { useAppDispatch } from '@/redux/store';
import Cookies from 'universal-cookie';
export default function Sidebar () {
// init classess
const cookies = new Cookies({ path: '/' });
const dispatch = useDispatch<AppDispatch>();
const dispatch = useAppDispatch();
const t = useTranslations('sidebar');
const pathname = usePathname()
const prevPathName = useRef(pathname)
@ -23,12 +23,10 @@ export default function Sidebar () {
// get states
const sidebarState = useAppSelector((state) => state.sidebarReducer.value.state);
const sidebarItems = useAppSelector((state) => state.sidebarReducer.value.items);
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings) || {
showLogo: true,
logo: '',
appName: '',
appNameEN: ''
};
const appGeneralSettings = useAppSelector((state) => {
//console.log("Redux state:", state.settingsReducer.value);
return state.settingsReducer.value.appGeneralSettings;
});
// handle sidebar menus states
const [currentMenu, setCurrentMenu] = useState<string>('');
const toggleMenu = (value: string) => {
@ -48,18 +46,13 @@ export default function Sidebar () {
<nav className={`fixed top-0 ltr:left-0 rtl:right-0 lg:relative shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-[99] h-screen bg-primary-dark dark:bg-primary-dark duration-300 ${sidebarState ? 'min-w-[260px] max-w-[260px] ' : 'ltr:translate-x-[-260px] rtl:translate-x-[260px] w-0'}`}>
<header className="w-full max-h-[64px] flex justify-between items-center px-[18px] py-[14px] [&_*]:text-text-light bg-secondary-dark">
<div className="flex justify-center items-center flex gap-[15px]">
{appGeneralSettings?.showLogo && appGeneralSettings?.logo && (
<div className="w-12 h-16 flex items-center justify-center relative"
dangerouslySetInnerHTML={{ __html: appGeneralSettings.logo }} />
)}
<h1 className="text-[25px] font-light">
{local === 'ar' ? (appGeneralSettings?.appName || '') : (appGeneralSettings?.appNameEN || '')}
</h1>
<div className={`w-12 h-16 flex items-center justify-center relative ${appGeneralSettings.showLogo ? '' : 'hidden'}`} dangerouslySetInnerHTML={{ __html: appGeneralSettings.logo || '' }} />
<h1 className="text-[25px] font-light">{local == 'ar' ? appGeneralSettings.appName : appGeneralSettings.appNameEN}</h1>
</div>
<button onClick={() => {dispatch(toggleSidebar())}} className="rounded-full rtl:rotate-180 hover:bg-primary/5 p-[6px]">
<svg width="23" height="23" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.4166 26.9166L9.91663 17L18.4166 7.08331" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path opacity="0.5" d="M24.083 26.9166L15.583 17L24.083 7.08331" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.4166 26.9166L9.91663 17L18.4166 7.08331" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path opacity="0.5" d="M24.083 26.9166L15.583 17L24.083 7.08331" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</header>

View File

@ -22,8 +22,8 @@ export default function AddNewButton()
flex justify-between items-center gap-3
">
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
<path fillRule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
</svg>
<h3>{t('addNewWorker')}</h3>
</button>

View File

@ -18,11 +18,11 @@ export default function ListPagination() {
return (
<>
{Math.ceil(total / 4) > 1 ? (
{Math.ceil(total / 10) > 1 ? (
<div className="flex justify-center items-center py-12">
<Pagination
sx={{ direction: 'ltr' }}
count={Math.ceil(total / 4)}
count={Math.ceil(total / 10)}
page={currentPage}
onChange={handlePagination}
variant="outlined"

View File

@ -338,64 +338,55 @@
"statistics": {
"totalMembers": "إجمالي الأعضاء",
"totalActiveMembers": "إجمالي الأعضاء النشطين",
"totalUnActiveMembers": "إجمالي الأعضاء غير النشطين",
"totalUnActiveMembers": "إجمالي الأعضاء الغير نشطين",
"totalServices": "إجمالي الخدمات",
"totalActiveServices": "إجمالي الخدمات النشطة",
"totalUnActiveServices": "إجمالي الخدمات غير النشطة",
"totalUnActiveServices": "إجمالي الخدمات الغير نشطة",
"totalActiveSubscriptions": "إجمالي الاشتراكات النشطة",
"totalExpiredSubscriptions": "إجمالي الاشتراكات المنتهية",
"expiredSoonSubscriptionsCount": "اشتراكات تنتهي قريباً",
"totalActiveSubs": "إجمالي الاشتراكات النشطة",
"totalUnActiveSubs": "إجمالي الاشتراكات غير النشطة",
"totalMansMembers": "إجمالي الذكور",
"totalGirlsMembers": "إجمالي الإناث",
"expiredSoonSubscriptionsCount": "اشتراكات تنتهي قريبا",
"totalActiveSubs": "اجمالي الاشتراكات الفعالة",
"totalUnActiveSubs": "اجمالي الاشتراكات المعلقة",
"totalMansMembers": "اجمالي الذكور",
"totalGirlsMembers": "اجمالي الاناث",
"sat": "السبت",
"sun": "الأحد",
"sun": "الاحد",
"mon": "الاثنين",
"tue": "الثلاثاء",
"wed": "الأربعاء",
"wed": "الاربعاء",
"thu": "الخميس",
"fri": "الجمعة",
"membersGeneralOverview": "نظرة عامة على الأعضاء",
"sevicesGeneralOverview": "نظرة عامة على الخدمات",
"workersGeneralOverview": "نظرة عامة على العمال",
"thisWeek": "هذا الأسبوع",
"thisMonth": "هذا الشهر",
"thisYear": "هذه السنة",
"monthly": "شهرياً",
"yearly": "سنوياً",
"week": "أسبوع",
"1month": "يناير",
"2month": "فبراير",
"3month": "مارس",
"4month": "أبريل",
"5month": "مايو",
"6month": "يونيو",
"7month": "يوليو",
"8month": "أغسطس",
"9month": "سبتمبر",
"10month": "أكتوبر",
"11month": "نوفمبر",
"12month": "ديسمبر",
"loading": "جاري التحميل...",
"membersGeneralOverview": "نظرة عامة عن الاعضاء",
"sevicesGeneralOverview": "نظرة عامة عن الخدمات",
"workersGeneralOverview": "نظرة عامة عن العمال",
"thisWeek": "هاذا الاسبوع",
"thisMonth": "هاذا الشهر",
"thisYear": "هاذا العام",
"monthly": "شهري",
"yearly": "سنوي",
"week": "الاسبوع",
"1month": "واحد",
"2month": "اثنان",
"3month": "ثلاثة",
"4month": "اربعة",
"5month": "خمسة",
"6month": "سته",
"7month": "سبعة",
"8month": "ثمانية",
"9month": "تسعة",
"10month": "عشرة",
"11month": "احد عشر",
"12month": "اثنا عشر",
"loading": "جاري التحميل",
"subscription": "اشتراك",
"worker": "عامل",
"outcome": "مصروف",
"income": "دخل",
"outcome": "النفقات",
"income": "الايرادات",
"netProfit": "صافي الربح",
"incomeOutcomeGeneralOverview": "نظرة عامة على الدخل والمصروفات",
"noDataAvailable": "لا توجد بيانات متاحة",
"members": "الأعضاء",
"subscriptions": "الاشتراكات",
"activeSubscriptions": "الاشتراكات النشطة",
"expiredSubscriptions": "الاشتراكات المنتهية",
"soonToExpire": "تنتهي قريباً",
"male": "ذكر",
"female": "أنثى",
"total": "الإجمالي",
"active": "نشط",
"inactive": "غير نشط",
"incomeOutcomeGeneralOverview": "نظرة عامة عن الايرادات والنفقات",
"services": "الخدمات",
"workers": "العمال"
"workers": "العمال",
"incomes": "الايرادات",
"sales": "مبيعات"
}
}

View File

@ -388,18 +388,10 @@
"income": "Income",
"netProfit": "Net profit",
"incomeOutcomeGeneralOverview": "General Income and outcome overview",
"noDataAvailable": "No data available",
"members": "Members",
"subscriptions": "Subscriptions",
"activeSubscriptions": "Active Subscriptions",
"expiredSubscriptions": "Expired Subscriptions",
"soonToExpire": "Soon to Expire",
"male": "Male",
"female": "Female",
"total": "Total",
"active": "Active",
"inactive": "Inactive",
"services": "Services",
"workers": "Workers"
"workers": "Workers",
"incomes": "Incomes",
"sales": "Sales"
}
}

View File

@ -4,42 +4,10 @@
* * source : https://mongoosejs.com/docs/6.x/docs/typescript.html
*/
// export default async function validateAuthToken(authToken: string | undefined): Promise<boolean> {
// try {
// if (!authToken) {
// console.warn('No auth token provided');
// return false;
// }
// const apiBase = process.env.NEXT_PUBLIC_API_BASE;
// if (!apiBase) {
// console.error('NEXT_PUBLIC_API_BASE environment variable is not set');
// return false;
// }
// const url = new URL('https://irongym.yznapps.com/api/auth', apiBase);
// url.searchParams.append('authToken', authToken);
// const response = await fetch(url.toString());
// if (!response.ok) {
// console.error('Auth API request failed with status:', response.status);
// return false;
// }
// const data = await response.json();
// return !!data?.success;
// } catch (error) {
// console.error('Error validating auth token:', error);
// return false;
// }
// }
export default async function validateAuthToken(authToken : string | undefined) : Promise<boolean | undefined>
{
let data : {
success : boolean,
} = await (await fetch("https://irongym.yznapps.com/api/auth?authToken="+authToken)).json()
} = await (await fetch(process.env.NEXT_PUBLIC_API_BASE+"/api/auth?authToken="+authToken)).json()
return data.success
}

View File

@ -1,6 +1,7 @@
import { createAsyncThunk , createSlice , PayloadAction , current } from "@reduxjs/toolkit";
import axios from 'axios'
import { fireAlert } from "./alert-slice";
import { refresh as refreshStatistics } from "./statistics-slice";
import { truncate } from "fs";
// set the const variables for the slice
const ACTION_NAME = 'members'
@ -235,6 +236,8 @@ const add = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after adding a member
thunkAPI.dispatch(refreshStatistics())
return data
}
else {
@ -309,6 +312,8 @@ const delete_ = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after deleting a member
thunkAPI.dispatch(refreshStatistics())
return {
success: true,
id: actionPayload.id,
@ -350,6 +355,8 @@ const update = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after updating a member
thunkAPI.dispatch(refreshStatistics())
return data
}
else
@ -386,6 +393,8 @@ const updateSub = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after updating member subscription
thunkAPI.dispatch(refreshStatistics())
return data
}
else

View File

@ -1,6 +1,7 @@
import { createAsyncThunk , createSlice , PayloadAction } from "@reduxjs/toolkit";
import axios from 'axios'
import { fireAlert } from "./alert-slice";
import { refresh as refreshStatistics } from "./statistics-slice";
// set the const variables for the slice
const ACTION_NAME = 'services'
// slice initial value state
@ -100,6 +101,8 @@ const add = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after adding a service
thunkAPI.dispatch(refreshStatistics())
return data
}
else {
@ -174,6 +177,8 @@ const delete_ = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after deleting a service
thunkAPI.dispatch(refreshStatistics())
return {
success: true,
id: actionPayload.id,
@ -209,6 +214,8 @@ const update = createAsyncThunk(
success: true,
message: "actionDoneWithSuccess",
}))
// Refresh statistics after updating a service
thunkAPI.dispatch(refreshStatistics())
return data
}
else

View File

@ -140,11 +140,25 @@ export const settings = createSlice({
state.value.isLoadingSettings = true;
})
builder.addCase(load.fulfilled, (state : IinitialState , action) => {
if(action.payload.success) {
state.value.appGeneralSettings = action.payload.data.settings
if(action.payload.success)
{
// Safely merge incoming settings with existing settings
// This ensures that if action.payload.data.settings is undefined or null,
// the existing appGeneralSettings (from initialState) is preserved.
state.value.appGeneralSettings = {
...state.value.appGeneralSettings, // Keep existing properties
...(action.payload.data?.settings || {}) // Merge incoming data, default to empty object if undefined
};
state.value.isLoadingSettings = false;
state.value.loadedFirstTime = true;
}
else
{
// If loading failed, ensure loading state is false but keep existing data
state.value.isLoadingSettings = false;
// You might want to reset loadedFirstTime to false here if the load was truly unsuccessful
// state.value.loadedFirstTime = false;
}
})
// update ability
builder.addCase(update.pending, (state : IinitialState , action) => {

View File

@ -1,4 +1,4 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { createAsyncThunk , createSlice , PayloadAction } from "@reduxjs/toolkit";
import axios from 'axios'
import { fireAlert } from "./alert-slice";
@ -6,13 +6,13 @@ import { fireAlert } from "./alert-slice";
const ACTION_NAME = 'statistics'
// slice initial value state
type IinitialState = {
value: IValueState;
value : IValueState;
}
// state value type
type IValueState = {
isLoading: boolean,
isLoading : boolean,
loadedFirstTime: boolean,
report:
report :
{
membersCount: {
name: string,
@ -53,7 +53,7 @@ type IValueState = {
membersGeneralOverview: {
name: "generalMembersOverView",
value: {
thisWeek: {
thisWeek : {
'year': number,
'month': number,
'week': number,
@ -109,7 +109,7 @@ type IValueState = {
}
}
},
months: {
months : {
'year': number,
'month': number,
'weeks': {
@ -167,7 +167,7 @@ type IValueState = {
}[]
}[]
}[],
years: {
years : {
'year': number,
'months': {
'month': number,
@ -388,54 +388,86 @@ const initialState = {
} as IinitialState
// thunks
// load data ability
const load = createAsyncThunk(
'statistics/load',
async (actionPayload: { forceRefresh?: boolean } = {}, thunkAPI) => {
export const load = createAsyncThunk(
`${ACTION_NAME}/load`,
async (_, thunkAPI) => {
try {
// Add a timestamp to the URL to prevent caching when forceRefresh is true
const timestamp = actionPayload.forceRefresh ? `?t=${Date.now()}` : '';
let { data } = await axios.get(`/api/user/actions/${ACTION_NAME}${timestamp}`)
if (data.success) {
return data
} else {
return { success: false }
}
} catch (err) {
console.error('Error loading statistics:', err);
return { success: false }
const response = await axios.get('/api/user/actions/statistics');
return response.data;
} catch (error: any) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
thunkAPI.dispatch(fireAlert({
message: message,
type: 'error'
}))
return thunkAPI.rejectWithValue(message);
}
}
)
);
// refresh data ability (force reload)
export const refresh = createAsyncThunk(
`${ACTION_NAME}/refresh`,
async (_, thunkAPI) => {
try {
const response = await axios.get('/api/user/actions/statistics');
return response.data;
} catch (error: any) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
thunkAPI.dispatch(fireAlert({
message: message,
type: 'error'
}))
return thunkAPI.rejectWithValue(message);
}
}
);
// init the slice
export const statistics = createSlice({
name: ACTION_NAME,
initialState,
reducers: {
setCurrentMembersGeneralOverviewDuration: (state, action: PayloadAction<'thisWeek' | 'thisMonth' | 'thisYear'>) => {
setCurrentMembersGeneralOverviewDuration : (state , action: PayloadAction<'thisWeek' | 'thisMonth' | 'thisYear'>) => {
state.value.currentMembersGeneralOverviewDuration = action.payload
},
resetLoadedFirstTime: (state) => {
state.value.loadedFirstTime = false
}
},
extraReducers: (builder) => {
// load ability
builder.addCase(load.pending, (state: IinitialState, action) => {
builder.addCase(load.pending, (state : IinitialState , action) => {
state.value.isLoading = true;
})
builder.addCase(load.fulfilled, (state: IinitialState, action) => {
state.value.isLoading = false;
if (action.payload?.success && action.payload.data) {
builder.addCase(load.fulfilled, (state : IinitialState , action) => {
if (action.payload.success) {
state.value.isLoading = false;
state.value.loadedFirstTime = true;
state.value.report = action.payload.data;
const { membersGeneralOverview } = action.payload.data;
if (membersGeneralOverview?.value?.thisWeek?.days) {
state.value.report.membersGeneralOverview = membersGeneralOverview;
}
}
})
builder.addCase(load.rejected, (state: IinitialState, action) => {
state.value.isLoading = false;
console.error('Failed to load statistics:', action.error);
// refresh ability
builder.addCase(refresh.pending, (state : IinitialState , action) => {
state.value.isLoading = true;
})
builder.addCase(refresh.fulfilled, (state : IinitialState , action) => {
if (action.payload.success) {
state.value.isLoading = false;
state.value.report = action.payload.data;
const { membersGeneralOverview } = action.payload.data;
if (membersGeneralOverview?.value?.thisWeek?.days) {
state.value.report.membersGeneralOverview = membersGeneralOverview;
}
}
})
}
})
// export reducers
export const { setCurrentMembersGeneralOverviewDuration } = statistics.actions;
export const { setCurrentMembersGeneralOverviewDuration, resetLoadedFirstTime } = statistics.actions;
// export extra reducers
export { load }
// export { load }
// export the slice
export default statistics.reducer;

View File

@ -7,17 +7,10 @@
*/
import React from "react"
import { store } from './store'
import { Provider } from "react-redux"
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './store';
export function ReduxProvider({children} : {children : React.ReactNode})
{
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{children}
</PersistGate>
</Provider>
)
return <Provider store={store}>{children}</Provider>
}

View File

@ -3,14 +3,11 @@
* @description Redux store , all the app state goes right here
* * source : https://www.youtube.com/watch?v=Yokjzp91A4o
*/
import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { combineReducers } from 'redux';
import authReducer from './features/auth-slice';
import themeTypeReducer from './features/theme-slice';
import alertReducer from './features/alert-slice';
import { TypedUseSelectorHook , useSelector } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit'
import authReducer from './features/auth-slice'
import themeTypeReducer from './features/theme-slice'
import alertReducer from './features/alert-slice'
import settingsReducer from './features/settings-slice';
import sidebarReducer from './features/sidebar-slice';
import servicesReducer from './features/services-slice';
@ -21,50 +18,29 @@ import statisticsReducer from './features/statistics-slice';
import equipmentsReducer from './features/equipments-slice';
import expensesReducer from './features/expenses-slice';
import incomesReducer from './features/incomes-slice';
import { useDispatch } from 'react-redux';
// Combine all reducers
const rootReducer = combineReducers({
authReducer, // handle auth states
themeTypeReducer, // handle app theme
alertReducer, // handle app alert
settingsReducer, // handle the app settings
sidebarReducer, // handle dashboard sidebar state
servicesReducer, // handle the services page state
membersReducer, // handle the members page state
workersReducer, // handle the workers page state
productsReducer, // handle the products page state
statisticsReducer, // handle the statistics state
equipmentsReducer, // handle the equipments state
expensesReducer, // handle the expenses state
incomesReducer, // handle the incomes state
});
// Persist config
const persistConfig = {
key: 'root',
version: 1,
storage,
whitelist: ['settingsReducer', 'authReducer', 'themeTypeReducer'], // Only persist these reducers
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Configure the store
// init the store
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export const persistor = persistStore(store);
// Export types
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Export the store selector
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
reducer: { // get state and do some work & update it after that
authReducer, // handle auth states
themeTypeReducer, // handle app theme
alertReducer, // handle app alert
settingsReducer, // handle the app settings
sidebarReducer, // handle dashboard sidebar state
servicesReducer, // handle the services page state
membersReducer, // handle the members page state
workersReducer, // handle the workers page state
productsReducer, // handle the products page state
statisticsReducer, // handle the statistics state
equipmentsReducer, // handle the equipments state
expensesReducer, // handle the expenses state
incomesReducer, // handle the incomes state
}
})
// export the needed types
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// export the store selector and dispatch
export const useAppSelector : TypedUseSelectorHook<RootState> = useSelector
export const useAppDispatch = () => useDispatch<AppDispatch>();

View File

@ -2,7 +2,6 @@
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["node"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,