From 8d643287ca7a9e03c55822f0cecd244386477f52 Mon Sep 17 00:00:00 2001 From: Benjamin Ramet Date: Wed, 25 Sep 2024 15:18:21 +0200 Subject: [PATCH 01/16] feat/Add controller for village statistic and add api route --- .pnp.cjs | 0 .yarn/sdks/eslint/bin/eslint.js | 0 .yarn/sdks/prettier/bin-prettier.js | 0 .yarn/sdks/typescript/bin/tsc | 0 .yarn/sdks/typescript/bin/tsserver | 0 docker-compose.yml | 1 - server/controllers/statistics.ts | 8 ++++++ server/stats/villageStats.ts | 25 +++++++++++++++++++ src/api/statistics/statistics.get.ts | 16 +++++++++++- src/components/Header.tsx | 5 +--- .../dashboard-statistics/VillageStats.tsx | 25 +++++++++++++++---- .../filters/VillageDropdown.tsx | 21 +++++++++------- .../admin/dashboard-statistics/mocks/mocks.ts | 2 +- types/statistics.type.ts | 4 +++ 14 files changed, 86 insertions(+), 21 deletions(-) mode change 100755 => 100644 .pnp.cjs mode change 100755 => 100644 .yarn/sdks/eslint/bin/eslint.js mode change 100755 => 100644 .yarn/sdks/prettier/bin-prettier.js mode change 100755 => 100644 .yarn/sdks/typescript/bin/tsc mode change 100755 => 100644 .yarn/sdks/typescript/bin/tsserver create mode 100644 server/stats/villageStats.ts diff --git a/.pnp.cjs b/.pnp.cjs old mode 100755 new mode 100644 diff --git a/.yarn/sdks/eslint/bin/eslint.js b/.yarn/sdks/eslint/bin/eslint.js old mode 100755 new mode 100644 diff --git a/.yarn/sdks/prettier/bin-prettier.js b/.yarn/sdks/prettier/bin-prettier.js old mode 100755 new mode 100644 diff --git a/.yarn/sdks/typescript/bin/tsc b/.yarn/sdks/typescript/bin/tsc old mode 100755 new mode 100644 diff --git a/.yarn/sdks/typescript/bin/tsserver b/.yarn/sdks/typescript/bin/tsserver old mode 100755 new mode 100644 diff --git a/docker-compose.yml b/docker-compose.yml index 55d75d6d6..09ff47bdb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: backend: build: diff --git a/server/controllers/statistics.ts b/server/controllers/statistics.ts index e4616b2cb..cd47175f4 100644 --- a/server/controllers/statistics.ts +++ b/server/controllers/statistics.ts @@ -16,6 +16,7 @@ import { getMinConnections, getMinDuration, } from '../stats/sessionStats'; +import { getTeacherCreatedAccountsNumber } from '../stats/villageStats'; import { Controller } from './controller'; export const statisticsController = new Controller('/statistics'); @@ -43,3 +44,10 @@ statisticsController.get({ path: '/classrooms' }, async (_req, res) => { classrooms: await getClassroomsInfos(), }); }); + +statisticsController.get({ path: '/villages/:villageId' }, async (_req, res) => { + const villageId = parseInt(_req.params.villageId); + res.sendJSON({ + teacherCreatedAccountsNumber: await getTeacherCreatedAccountsNumber(villageId), + }); +}); diff --git a/server/stats/villageStats.ts b/server/stats/villageStats.ts new file mode 100644 index 000000000..7ad0ca6c7 --- /dev/null +++ b/server/stats/villageStats.ts @@ -0,0 +1,25 @@ +import { Classroom } from '../entities/classroom'; +import { Student } from '../entities/student'; +import { User } from '../entities/user'; +import { Village } from '../entities/village'; +import { AppDataSource } from '../utils/data-source'; + +const userRepository = AppDataSource.getRepository(User); +const studentRepository = AppDataSource.getRepository(Student); + +export const getTeacherCreatedAccountsNumber = async (villageId: number) => { + /* const res = await userRepository + .createQueryBuilder('user') + .select(['user.id AS userId']) + .where('user.villageId = :villageId', { villageId }) + .getCount(); + return res; */ + const userCount = await userRepository + .createQueryBuilder('user') + .innerJoin('user.village', 'village') // Join village associated with user + .innerJoin('classroom', 'classroom', 'classroom.villageId = village.id') // Join classroom on villageId + .innerJoin('student', 'student', 'student.classroomId = classroom.id') // Join student on classroomId + .getCount(); // Get count of users who have a related student + console.log(userCount); + return userCount; +}; diff --git a/src/api/statistics/statistics.get.ts b/src/api/statistics/statistics.get.ts index 1c9d2ae3a..b702d4a2d 100644 --- a/src/api/statistics/statistics.get.ts +++ b/src/api/statistics/statistics.get.ts @@ -1,7 +1,7 @@ import { useQuery } from 'react-query'; import { axiosRequest } from 'src/utils/axiosRequest'; -import type { ClassroomsStats, SessionsStats } from 'types/statistics.type'; +import type { ClassroomsStats, SessionsStats, VillageStats } from 'types/statistics.type'; async function getSessionsStats(phase: number | null): Promise { return ( @@ -13,10 +13,24 @@ async function getSessionsStats(phase: number | null): Promise { ).data; } +async function getVillagesStats(villageId: number | null): Promise { + return ( + await axiosRequest({ + method: 'GET', + baseURL: '/api', + url: `/statistics/villages/${villageId}`, + }) + ).data; +} + export const useGetSessionsStats = (phase: number | null) => { return useQuery(['sessions-stats'], () => getSessionsStats(phase)); }; +export const useGetVillagesStats = (villageId: number | null) => { + return useQuery(['villages-stats'], () => getVillagesStats(villageId)); +}; + async function getClassroomsStats(): Promise { return ( await axiosRequest({ diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 022989074..1b3750247 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -115,12 +115,9 @@ export const Header = () => { > {hasAccessToNewAdmin && goToPage('/admin/newportal/create')}>Portail admin} {hasAccessToOldAdmin && goToPage('/admin/villages')}>Admin (old)} - goToPage('/mon-compte')}>Mon compte {user.type !== UserType.FAMILY && goToPage('/mes-videos')}>Mes vidéos} - - {user.type === UserType.TEACHER ? goToPage('/familles/1')}>Mes familles : null}{' '} - + {user.type === UserType.TEACHER ? goToPage('/familles/1')}>Mes familles : null}{' '} Se déconnecter diff --git a/src/components/admin/dashboard-statistics/VillageStats.tsx b/src/components/admin/dashboard-statistics/VillageStats.tsx index 9a6eb4be6..c921a10fc 100644 --- a/src/components/admin/dashboard-statistics/VillageStats.tsx +++ b/src/components/admin/dashboard-statistics/VillageStats.tsx @@ -1,15 +1,18 @@ import React, { useState } from 'react'; +import StatsCard from './cards/StatsCard/StatsCard'; import CountriesDropdown from './filters/CountriesDropdown'; import PhaseDropdown from './filters/PhaseDropdown'; import VillageDropdown from './filters/VillageDropdown'; import { mockClassroomsStats } from './mocks/mocks'; import { PelicoCard } from './pelico-card'; import styles from './styles/charts.module.css'; +import { useGetVillagesStats } from 'src/api/statistics/statistics.get'; const VillageStats = () => { + const villagesStats = useGetVillagesStats(1); const [selectedCountry, setSelectedCountry] = useState(''); - const [selectedVillage, setSelectedVillage] = useState(''); + const [selectedVillage, setSelectedVillage] = useState<{ name: string; id: number }>(); const pelicoMessage = 'Merci de sélectionner un village-monde pour analyser ses statistiques '; @@ -20,15 +23,19 @@ const VillageStats = () => { setSelectedCountry(country); }; - const villagesMap = mockClassroomsStats.filter((village) => village.classroomCountryCode === selectedCountry).map((village) => village.villageName); + const villagesMap = mockClassroomsStats + .filter((village) => village.classroomCountryCode === selectedCountry) + .map((village) => ({ name: village.villageName, id: village.villageId })); const villages = [...new Set(villagesMap)]; - const handleVillageChange = (village: string) => { + const handleVillageChange = (village: { name: string; id: number }) => { setSelectedVillage(village); }; - + // eslint-disable-next-line no-console + console.log('Villages stats', villagesStats.data?.teacherCreatedAccountsNumber); return ( <> +

{selectedVillage?.id}

@@ -40,7 +47,15 @@ const VillageStats = () => {
- {!selectedVillage ? : null} + {!selectedVillage ? ( + + ) : ( +
+ Nombre de profs ayant créé des comptes famille + Nombre de codes enfant créés + Nombre de familles connectées +
+ )} ); }; diff --git a/src/components/admin/dashboard-statistics/filters/VillageDropdown.tsx b/src/components/admin/dashboard-statistics/filters/VillageDropdown.tsx index 367681a29..332371652 100644 --- a/src/components/admin/dashboard-statistics/filters/VillageDropdown.tsx +++ b/src/components/admin/dashboard-statistics/filters/VillageDropdown.tsx @@ -8,27 +8,30 @@ import type { SelectChangeEvent } from '@mui/material/Select'; import Select from '@mui/material/Select'; interface VillageDropdownProps { - villages: string[]; - onVillageChange: (vm: string) => void; + villages: { name: string; id: number }[]; + onVillageChange: (vm: { name: string; id: number }) => void; } export default function VillageDropdown({ villages, onVillageChange }: VillageDropdownProps) { - const [village, setVillage] = React.useState(''); + const [village, setVillage] = React.useState<{ name: string; id: number }>(); const handleChange = (event: SelectChangeEvent) => { - const selectedVillage = event.target.value as string; - setVillage(selectedVillage); - onVillageChange(selectedVillage); + const selectedVillage = event.target.value; + const currentSelection = villages.find((vil) => vil.id === +selectedVillage); + if (currentSelection) { + setVillage(currentSelection); + onVillageChange(currentSelection); + } }; return ( Village-monde - {villages.map((village) => ( - - {village} + + {village.name} ))} diff --git a/src/components/admin/dashboard-statistics/mocks/mocks.ts b/src/components/admin/dashboard-statistics/mocks/mocks.ts index 881b50587..aface84cb 100644 --- a/src/components/admin/dashboard-statistics/mocks/mocks.ts +++ b/src/components/admin/dashboard-statistics/mocks/mocks.ts @@ -36,7 +36,7 @@ export const mockClassroomsStats: ClassroomsStats[] = [ { classroomId: 3, classroomCountryCode: 'FR', - villageId: 1, + villageId: 456, villageName: 'Village C', commentsCount: 40, videosCount: 2, diff --git a/types/statistics.type.ts b/types/statistics.type.ts index 2e9816573..ef02b3ab8 100644 --- a/types/statistics.type.ts +++ b/types/statistics.type.ts @@ -25,3 +25,7 @@ export interface SessionsStats { connectedClassroomsCount: number; contributedClassroomsCount: number; } + +export interface VillageStats { + teacherCreatedAccountsNumber: number; +} From 318e4d4b389185bb859f46bc7fddf12b0b4aec32 Mon Sep 17 00:00:00 2001 From: Benjamin Ramet Date: Fri, 27 Sep 2024 09:07:22 +0200 Subject: [PATCH 02/16] Add reqs for connected families & children code --- server/controllers/statistics.ts | 6 ++- server/stats/classroomStats.ts | 2 - server/stats/villageStats.ts | 44 ++++++++++++------- src/components/Header.tsx | 4 +- .../dashboard-statistics/VillageStats.tsx | 12 ++--- .../admin/newportal/manage/access/index.tsx | 1 - types/statistics.type.ts | 4 +- 7 files changed, 46 insertions(+), 27 deletions(-) diff --git a/server/controllers/statistics.ts b/server/controllers/statistics.ts index cd47175f4..95a7eda8f 100644 --- a/server/controllers/statistics.ts +++ b/server/controllers/statistics.ts @@ -16,7 +16,7 @@ import { getMinConnections, getMinDuration, } from '../stats/sessionStats'; -import { getTeacherCreatedAccountsNumber } from '../stats/villageStats'; +import { getChildrenCodesCount, getFamilyAccountsCount, getConnectedFamiliesCount } from '../stats/villageStats'; import { Controller } from './controller'; export const statisticsController = new Controller('/statistics'); @@ -48,6 +48,8 @@ statisticsController.get({ path: '/classrooms' }, async (_req, res) => { statisticsController.get({ path: '/villages/:villageId' }, async (_req, res) => { const villageId = parseInt(_req.params.villageId); res.sendJSON({ - teacherCreatedAccountsNumber: await getTeacherCreatedAccountsNumber(villageId), + familyAccountsCount: await getFamilyAccountsCount(villageId), + childrenCodesCount: await getChildrenCodesCount(villageId), + connectedFamiliesCount: await getConnectedFamiliesCount(villageId), }); }); diff --git a/server/stats/classroomStats.ts b/server/stats/classroomStats.ts index cce84c85e..3a4ad76c6 100644 --- a/server/stats/classroomStats.ts +++ b/server/stats/classroomStats.ts @@ -1,8 +1,6 @@ import { UserType } from '../../types/user.type'; import { Activity } from '../entities/activity'; import { Classroom } from '../entities/classroom'; -import { Comment } from '../entities/comment'; -import { Video } from '../entities/video'; import { AppDataSource } from '../utils/data-source'; const classroomRepository = AppDataSource.getRepository(Classroom); diff --git a/server/stats/villageStats.ts b/server/stats/villageStats.ts index 7ad0ca6c7..633e6e7fb 100644 --- a/server/stats/villageStats.ts +++ b/server/stats/villageStats.ts @@ -1,25 +1,39 @@ -import { Classroom } from '../entities/classroom'; import { Student } from '../entities/student'; import { User } from '../entities/user'; -import { Village } from '../entities/village'; import { AppDataSource } from '../utils/data-source'; const userRepository = AppDataSource.getRepository(User); const studentRepository = AppDataSource.getRepository(Student); -export const getTeacherCreatedAccountsNumber = async (villageId: number) => { - /* const res = await userRepository - .createQueryBuilder('user') - .select(['user.id AS userId']) - .where('user.villageId = :villageId', { villageId }) +export const getChildrenCodesCount = async (villageId: number) => { + const childrenCodeCount = await studentRepository + .createQueryBuilder('student') + .innerJoin('student.classroom', 'classroom') + .innerJoin('classroom.village', 'village') + .where('classroom.villageId = :villageId', { villageId }) .getCount(); - return res; */ - const userCount = await userRepository + return childrenCodeCount; +}; + +export const getFamilyAccountsCount = async (villageId: number) => { + const familyAccountsCount = await userRepository .createQueryBuilder('user') - .innerJoin('user.village', 'village') // Join village associated with user - .innerJoin('classroom', 'classroom', 'classroom.villageId = village.id') // Join classroom on villageId - .innerJoin('student', 'student', 'student.classroomId = classroom.id') // Join student on classroomId - .getCount(); // Get count of users who have a related student - console.log(userCount); - return userCount; + .innerJoin('user.village', 'village') + .innerJoin('classroom', 'classroom', 'classroom.villageId = village.id') + .innerJoin('student', 'student', 'student.classroomId = classroom.id') + .where('classroom.villageId = :villageId', { villageId }) + .groupBy('user.id') + .getCount(); + return familyAccountsCount; +}; + +export const getConnectedFamiliesCount = async (villageId: number) => { + const connectedFamiliesCount = await studentRepository + .createQueryBuilder('student') + .innerJoin('classroom', 'classroom', 'classroom.id = student.classroomId') + .where('classroom.villageId = :villageId', { villageId }) + .andWhere('student.numLinkedAccount >= 1') + .getCount(); + + return connectedFamiliesCount; }; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 1b3750247..b19da43f9 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -117,7 +117,9 @@ export const Header = () => { {hasAccessToOldAdmin && goToPage('/admin/villages')}>Admin (old)} goToPage('/mon-compte')}>Mon compte {user.type !== UserType.FAMILY && goToPage('/mes-videos')}>Mes vidéos} - {user.type === UserType.TEACHER ? goToPage('/familles/1')}>Mes familles : null}{' '} + + {user.type === UserType.TEACHER ? goToPage('/familles/1')}>Mes familles : null}{' '} + Se déconnecter diff --git a/src/components/admin/dashboard-statistics/VillageStats.tsx b/src/components/admin/dashboard-statistics/VillageStats.tsx index c921a10fc..fc4cbffb0 100644 --- a/src/components/admin/dashboard-statistics/VillageStats.tsx +++ b/src/components/admin/dashboard-statistics/VillageStats.tsx @@ -32,10 +32,12 @@ const VillageStats = () => { setSelectedVillage(village); }; // eslint-disable-next-line no-console - console.log('Villages stats', villagesStats.data?.teacherCreatedAccountsNumber); + console.log('Villages stats', villagesStats.data); + console.log('Villages stats', villagesStats.data?.childrenCodesCount); + console.log('Villages stats', villagesStats.data?.familyAccountsCount); + console.log('Village Stats', villagesStats.data?.connectedFamiliesCount); return ( <> -

{selectedVillage?.id}

@@ -51,9 +53,9 @@ const VillageStats = () => { ) : (
- Nombre de profs ayant créé des comptes famille - Nombre de codes enfant créés - Nombre de familles connectées + Nombre de profs ayant créé des comptes famille + Nombre de codes enfant créés + Nombre de familles connectées
)} diff --git a/src/pages/admin/newportal/manage/access/index.tsx b/src/pages/admin/newportal/manage/access/index.tsx index db114a15a..8555ba70c 100644 --- a/src/pages/admin/newportal/manage/access/index.tsx +++ b/src/pages/admin/newportal/manage/access/index.tsx @@ -93,7 +93,6 @@ const FeatureFlagsTest: React.FC = () => { const handleNewFeatureFlagChange = async (event: SelectChangeEvent) => { const featureFlagName = event.target.value; - // Find the feature flag object from the featureFlags state using the selected feature flag name const selectedFeatureFlag = (featureFlags || []).find((flag) => flag.name === featureFlagName); diff --git a/types/statistics.type.ts b/types/statistics.type.ts index ef02b3ab8..3f644e48a 100644 --- a/types/statistics.type.ts +++ b/types/statistics.type.ts @@ -27,5 +27,7 @@ export interface SessionsStats { } export interface VillageStats { - teacherCreatedAccountsNumber: number; + childrenCodesCount: number; + familyAccountsCount: number; + connectedFamiliesCount: number; } From 456487983bfb1d5afac0489bcf59c649e31e8e13 Mon Sep 17 00:00:00 2001 From: Benjamin Ramet Date: Mon, 30 Sep 2024 14:42:33 +0200 Subject: [PATCH 03/16] feat: Fetch countries and corresponding village to filter stats --- server/controllers/village.ts | 19 +++++++- src/api/statistics/statistics.get.ts | 4 +- src/api/villages/villages.get.ts | 11 +++-- .../dashboard-statistics/VillageStats.tsx | 28 +++++------ .../cards/StatsCard/StatsCard.tsx | 2 +- .../filters/CountriesDropdown.tsx | 8 ++-- src/services/useCountries.ts | 48 +++++++++---------- src/services/useVillages.ts | 18 ++++--- 8 files changed, 80 insertions(+), 58 deletions(-) diff --git a/server/controllers/village.ts b/server/controllers/village.ts index 9361d954d..5a2aaec57 100644 --- a/server/controllers/village.ts +++ b/server/controllers/village.ts @@ -14,8 +14,23 @@ const villageController = new Controller('/villages'); //--- Get all villages --- villageController.get({ path: '', userType: UserType.OBSERVATOR }, async (_req: Request, res: Response) => { - const villages = await AppDataSource.getRepository(Village).find(); - res.sendJSON(villages); + const countryIsoCode = _req.query.countryIsoCode as string; + try { + // Fetch villages based on countryIsoCode if it's provided + const villageRepository = AppDataSource.getRepository(Village); + const villages = countryIsoCode + ? await villageRepository + .createQueryBuilder('village') + .where('village.countryCodes LIKE :countryIsoCode', { countryIsoCode: `%${countryIsoCode.toUpperCase()}%` }) + .getMany() + : await villageRepository // Fetch all villages if no countryIsoCode is provided + .createQueryBuilder('student') + .getMany(); + res.sendJSON(villages); + } catch (e) { + console.error(e); + res.status(500).sendJSON({ message: 'An error occurred while fetching villages' }); + } }); //--- Get one village --- diff --git a/src/api/statistics/statistics.get.ts b/src/api/statistics/statistics.get.ts index b702d4a2d..a2a7318bf 100644 --- a/src/api/statistics/statistics.get.ts +++ b/src/api/statistics/statistics.get.ts @@ -28,7 +28,9 @@ export const useGetSessionsStats = (phase: number | null) => { }; export const useGetVillagesStats = (villageId: number | null) => { - return useQuery(['villages-stats'], () => getVillagesStats(villageId)); + return useQuery(['villages-stats', villageId], () => getVillagesStats(villageId), { + enabled: villageId !== null, + }); }; async function getClassroomsStats(): Promise { diff --git a/src/api/villages/villages.get.ts b/src/api/villages/villages.get.ts index 12568153e..544ab8ef9 100644 --- a/src/api/villages/villages.get.ts +++ b/src/api/villages/villages.get.ts @@ -3,16 +3,19 @@ import type { Village } from 'server/entities/village'; import { axiosRequest } from 'src/utils/axiosRequest'; -async function getVillages(): Promise { +async function getVillages(countryIsoCode?: string): Promise { + const url = countryIsoCode ? `/villages?countryIsoCode=${countryIsoCode}` : '/villages'; return ( await axiosRequest({ method: 'GET', baseURL: '/api', - url: '/villages', + url: url, }) ).data; } -export const useGetVillages = () => { - return useQuery(['villages'], getVillages); +export const useGetVillages = (countryIsoCode?: string) => { + return useQuery(['villages', countryIsoCode], () => getVillages(countryIsoCode), { + enabled: !!countryIsoCode || countryIsoCode === undefined, + }); }; diff --git a/src/components/admin/dashboard-statistics/VillageStats.tsx b/src/components/admin/dashboard-statistics/VillageStats.tsx index fc4cbffb0..1d178af3f 100644 --- a/src/components/admin/dashboard-statistics/VillageStats.tsx +++ b/src/components/admin/dashboard-statistics/VillageStats.tsx @@ -4,38 +4,32 @@ import StatsCard from './cards/StatsCard/StatsCard'; import CountriesDropdown from './filters/CountriesDropdown'; import PhaseDropdown from './filters/PhaseDropdown'; import VillageDropdown from './filters/VillageDropdown'; -import { mockClassroomsStats } from './mocks/mocks'; import { PelicoCard } from './pelico-card'; import styles from './styles/charts.module.css'; import { useGetVillagesStats } from 'src/api/statistics/statistics.get'; +import { useCountries } from 'src/services/useCountries'; +import { useVillages } from 'src/services/useVillages'; const VillageStats = () => { - const villagesStats = useGetVillagesStats(1); - const [selectedCountry, setSelectedCountry] = useState(''); - const [selectedVillage, setSelectedVillage] = useState<{ name: string; id: number }>(); + const [selectedCountry, setSelectedCountry] = useState(''); // Default to 'FR' initially + const [selectedVillage, setSelectedVillage] = useState(null); const pelicoMessage = 'Merci de sélectionner un village-monde pour analyser ses statistiques '; - const countriesMap = mockClassroomsStats.map((country) => country.classroomCountryCode); - const countries = [...new Set(countriesMap)]; // avoid duplicates + const { countries } = useCountries(); + + const { villages } = useVillages(selectedCountry); // Dynamically pass the selected country + const villagesStats = useGetVillagesStats(selectedVillage); const handleCountryChange = (country: string) => { setSelectedCountry(country); + setSelectedVillage(null); }; - const villagesMap = mockClassroomsStats - .filter((village) => village.classroomCountryCode === selectedCountry) - .map((village) => ({ name: village.villageName, id: village.villageId })); - const villages = [...new Set(villagesMap)]; - const handleVillageChange = (village: { name: string; id: number }) => { - setSelectedVillage(village); + setSelectedVillage(village.id); }; - // eslint-disable-next-line no-console - console.log('Villages stats', villagesStats.data); - console.log('Villages stats', villagesStats.data?.childrenCodesCount); - console.log('Villages stats', villagesStats.data?.familyAccountsCount); - console.log('Village Stats', villagesStats.data?.connectedFamiliesCount); + return ( <>
diff --git a/src/components/admin/dashboard-statistics/cards/StatsCard/StatsCard.tsx b/src/components/admin/dashboard-statistics/cards/StatsCard/StatsCard.tsx index 5806d779f..f80c767f3 100644 --- a/src/components/admin/dashboard-statistics/cards/StatsCard/StatsCard.tsx +++ b/src/components/admin/dashboard-statistics/cards/StatsCard/StatsCard.tsx @@ -4,7 +4,7 @@ import styles from './StatsCard.module.css'; interface StatsCardProps { children: React.ReactNode; - data: number; + data: number | undefined; } const StatsCard = ({ children, data }: StatsCardProps) => { return ( diff --git a/src/components/admin/dashboard-statistics/filters/CountriesDropdown.tsx b/src/components/admin/dashboard-statistics/filters/CountriesDropdown.tsx index 6d4174cd6..b569e96d0 100644 --- a/src/components/admin/dashboard-statistics/filters/CountriesDropdown.tsx +++ b/src/components/admin/dashboard-statistics/filters/CountriesDropdown.tsx @@ -7,8 +7,10 @@ import MenuItem from '@mui/material/MenuItem'; import type { SelectChangeEvent } from '@mui/material/Select'; import Select from '@mui/material/Select'; +import type { Country } from 'types/country.type'; + interface CountriesDropdownProps { - countries: string[]; + countries: Country[]; onCountryChange: (country: string) => void; } @@ -27,8 +29,8 @@ export default function CountriesDropdown({ countries, onCountryChange }: Countr Pays diff --git a/src/services/useCountries.ts b/src/services/useCountries.ts index 6508d8ecb..a0be28b1c 100644 --- a/src/services/useCountries.ts +++ b/src/services/useCountries.ts @@ -1,24 +1,24 @@ -import React from 'react'; -import type { QueryFunction } from 'react-query'; -import { useQuery } from 'react-query'; - -import { axiosRequest } from 'src/utils/axiosRequest'; -import type { Country } from 'types/country.type'; - -export const useCountries = (): { countries: Country[] } => { - const getCountries: QueryFunction = React.useCallback(async () => { - const response = await axiosRequest({ - method: 'GET', - url: '/countries', - }); - if (response.error) { - return []; - } - return response.data; - }, []); - const { data, isLoading, error } = useQuery(['countries'], getCountries); - - return { - countries: isLoading || error ? [] : data || [], - }; -}; +import React from 'react'; +import type { QueryFunction } from 'react-query'; +import { useQuery } from 'react-query'; + +import { axiosRequest } from 'src/utils/axiosRequest'; +import type { Country } from 'types/country.type'; + +export const useCountries = (): { countries: Country[] } => { + const getCountries: QueryFunction = React.useCallback(async () => { + const response = await axiosRequest({ + method: 'GET', + url: '/countries', + }); + if (response.error) { + return []; + } + return response.data; + }, []); + const { data, isLoading, error } = useQuery(['countries'], getCountries); + + return { + countries: isLoading || error ? [] : data || [], + }; +}; diff --git a/src/services/useVillages.ts b/src/services/useVillages.ts index 08fe9993b..ad2ad4da2 100644 --- a/src/services/useVillages.ts +++ b/src/services/useVillages.ts @@ -6,26 +6,32 @@ import { useQueryClient, useQuery } from 'react-query'; import { axiosRequest } from 'src/utils/axiosRequest'; import type { Village } from 'types/village.type'; -export const useVillages = (): { villages: Village[]; setVillages(newVillages: Village[]): void } => { +export const useVillages = (countryIsoCode?: string): { villages: Village[]; setVillages(newVillages: Village[]): void } => { const queryClient = useQueryClient(); + // The query function for fetching villages, memoized with useCallback const getVillages: QueryFunction = React.useCallback(async () => { const response = await axiosRequest({ method: 'GET', - url: '/villages', + url: countryIsoCode ? `/villages?countryIsoCode=${countryIsoCode}` : '/villages', }); if (response.error) { return []; } return response.data; - }, []); - const { data, isLoading, error } = useQuery(['villages'], getVillages); + }, [countryIsoCode]); + + // Notice that we include `countryIsoCode` in the query key so that the query is refetched + const { data, isLoading, error } = useQuery( + ['villages', countryIsoCode], // Include countryIsoCode in the query key to trigger refetching + getVillages, + ); const setVillages = React.useCallback( (newVillages: Village[]) => { - queryClient.setQueryData(['villages'], newVillages); + queryClient.setQueryData(['villages', countryIsoCode], newVillages); // Ensure that the query key includes countryIsoCode }, - [queryClient], + [queryClient, countryIsoCode], ); return { From 26457d66c1c424f44bd67d52e6b1738384e5d3ef Mon Sep 17 00:00:00 2001 From: Benjamin Ramet Date: Mon, 30 Sep 2024 16:28:10 +0200 Subject: [PATCH 04/16] feat: update styles for in family tab --- .../DashboardStatsNav.tsx | 4 +- .../dashboard-statistics/VillageStats.tsx | 88 ++++++++++++++----- .../cards/StatsCard/StatsCard.module.css | 16 +++- .../filters/CountriesDropdown.tsx | 19 ++-- .../filters/VillageDropdown.tsx | 10 +-- .../styles/charts.module.css | 1 + 6 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/components/admin/dashboard-statistics/DashboardStatsNav.tsx b/src/components/admin/dashboard-statistics/DashboardStatsNav.tsx index fea75d4e6..8ddb30ece 100644 --- a/src/components/admin/dashboard-statistics/DashboardStatsNav.tsx +++ b/src/components/admin/dashboard-statistics/DashboardStatsNav.tsx @@ -23,7 +23,7 @@ function CustomTabPanel(props: TabPanelProps) { return (