diff --git a/server/controllers/classroom.ts b/server/controllers/classroom.ts index e78a00193..403efb1de 100644 --- a/server/controllers/classroom.ts +++ b/server/controllers/classroom.ts @@ -19,6 +19,27 @@ export const classroomController = new Controller('/classrooms'); * @return {object} Route API JSON response */ +classroomController.get({ path: '', userType: UserType.ADMIN }, async (_req: Request, res: Response) => { + const villageId = _req.query.villageId as string; + try { + const classroomRepository = AppDataSource.getRepository(Classroom); + let classrooms; + if (villageId) { + const query = classroomRepository.createQueryBuilder('classroom'); + query.where('classroom.villageId = :villageId', { villageId }); + + classrooms = await query.getMany(); + //classrooms = await classroomRepository.find({ where: { villageId: +villageId } }); + } else { + classrooms = await classroomRepository.find(); + } + res.sendJSON(classrooms); + } catch (e) { + console.error(e); + res.status(500).sendJSON({ message: 'An error occurred while fetching classrooms' }); + } +}); + classroomController.get({ path: '/:id', userType: UserType.TEACHER }, async (req: Request, res: Response, next: NextFunction) => { const id = parseInt(req.params.id, 10) || 0; const classroom = await AppDataSource.getRepository(Classroom).findOne({ diff --git a/src/components/admin/dashboard-statistics/ClassroomStats.tsx b/src/components/admin/dashboard-statistics/ClassroomStats.tsx index 0932e7138..9fede9382 100644 --- a/src/components/admin/dashboard-statistics/ClassroomStats.tsx +++ b/src/components/admin/dashboard-statistics/ClassroomStats.tsx @@ -1,14 +1,24 @@ import React, { useState } from 'react'; +import { Box, Tab, Tabs } from '@mui/material'; + +import { OneVillageTable } from '../OneVillageTable'; +import TabPanel from './TabPanel'; +import StatsCard from './cards/StatsCard/StatsCard'; import ClassroomDropdown from './filters/ClassroomDropdown'; 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 { createFamiliesWithoutAccountRows } from './utils/tableCreator'; +import { FamiliesWithoutAccountHeaders } from './utils/tableHeaders'; +import { useGetVillagesStats } from 'src/api/statistics/statistics.get'; +import { useClassrooms } from 'src/services/useClassrooms'; import { useCountries } from 'src/services/useCountries'; import { useVillages } from 'src/services/useVillages'; +import type { ClassroomFilter } from 'types/classroom.type'; +import type { OneVillageTableRow } from 'types/statistics.type'; import type { VillageFilter } from 'types/village.type'; const ClassroomStats = () => { @@ -16,12 +26,16 @@ const ClassroomStats = () => { const [selectedVillage, setSelectedVillage] = useState(''); const [selectedClassroom, setSelectedClassroom] = useState(); const [selectedPhase, setSelectedPhase] = useState('4'); - const [options, setOptions] = useState({ countryIsoCode: '' }); + const [villageFilter, setVillageFilter] = useState({ countryIsoCode: '' }); + const [classroomFilter, setClassroomFilter] = useState({ villageId: '' }); + const [value, setValue] = React.useState(0); const pelicoMessage = 'Merci de sélectionner une classe pour analyser ses statistiques '; const { countries } = useCountries(); - const { villages } = useVillages(options); + const { villages } = useVillages(villageFilter); + const villagesStats = useGetVillagesStats(+selectedVillage, +selectedPhase); + const { classrooms } = useClassrooms(classroomFilter); const handleCountryChange = (country: string) => { setSelectedCountry(country); @@ -30,21 +44,26 @@ const ClassroomStats = () => { }; React.useEffect(() => { - setOptions({ + setVillageFilter({ countryIsoCode: selectedCountry, }); - }, [selectedCountry, selectedPhase]); + setClassroomFilter({ + villageId: selectedVillage, + }); + }, [selectedCountry, selectedPhase, selectedVillage]); + + const [familiesWithoutAccountRows, setFamiliesWithoutAccountRows] = React.useState>([]); + React.useEffect(() => { + if (villagesStats.data?.familiesWithoutAccount) { + setFamiliesWithoutAccountRows(createFamiliesWithoutAccountRows(villagesStats.data?.familiesWithoutAccount)); + } + }, [villagesStats.data?.familiesWithoutAccount]); const handleVillageChange = (village: string) => { setSelectedVillage(village); setSelectedClassroom(''); }; - const classroomsMap = mockClassroomsStats - .filter((classroom) => classroom.villageName === selectedVillage) - .map((classroom) => classroom.classroomId); - const classrooms = [...new Set(classroomsMap)]; - const handleClassroomChange = (classroom: string) => { setSelectedClassroom(classroom); }; @@ -53,9 +72,15 @@ const ClassroomStats = () => { setSelectedPhase(phase); }; + const noDataFoundMessage = 'Pas de données pour le Village-Monde sélectionné'; + + const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { + setValue(newValue); + }; + return ( <> -
+
@@ -68,8 +93,43 @@ const ClassroomStats = () => {
-
- {!selectedClassroom ? : null} + + + + + + +

Statistiques - En classe

+
+ + {!selectedClassroom ? ( + + ) : ( + <> + {noDataFoundMessage}

} + data={familiesWithoutAccountRows} + columns={FamiliesWithoutAccountHeaders} + titleContent={`À surveiller : comptes non créés (${familiesWithoutAccountRows.length})`} + /> + + Nombre de codes enfant créés + Nombre de familles connectées + + + )} +
); }; diff --git a/src/components/admin/dashboard-statistics/filters/ClassroomDropdown.tsx b/src/components/admin/dashboard-statistics/filters/ClassroomDropdown.tsx index a641ccdc4..60196d481 100644 --- a/src/components/admin/dashboard-statistics/filters/ClassroomDropdown.tsx +++ b/src/components/admin/dashboard-statistics/filters/ClassroomDropdown.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 { Classroom } from 'types/classroom.type'; + interface ClassroomDropdownProps { - classrooms: number[]; + classrooms: Classroom[]; onClassroomChange: (classrooms: string) => void; } @@ -27,8 +29,8 @@ export default function ClassroomDropdown({ classrooms, onClassroomChange }: Cla Classe diff --git a/src/services/useClassrooms.ts b/src/services/useClassrooms.ts new file mode 100644 index 000000000..6f517015e --- /dev/null +++ b/src/services/useClassrooms.ts @@ -0,0 +1,50 @@ +import React from 'react'; +import type { QueryFunction } from 'react-query'; +import { useQueryClient, useQuery } from 'react-query'; + +import { axiosRequest } from 'src/utils/axiosRequest'; +import type { Classroom, ClassroomFilter } from 'types/classroom.type'; + +export const useClassrooms = (options?: ClassroomFilter): { classrooms: Classroom[]; setClassrooms(newClassrooms: Classroom[]): void } => { + const queryClient = useQueryClient(); + const buildUrl = () => { + if (!options) return '/classrooms'; + const { villageId } = options; + const queryParams = []; + + if (villageId) queryParams.push(`villageId=${villageId}`); + + return queryParams.length ? `/classrooms?${queryParams.join('&')}` : '/classrooms'; + }; + + const url = buildUrl(); + // The query function for fetching villages, memoized with useCallback + const getClassrooms: QueryFunction = React.useCallback(async () => { + const response = await axiosRequest({ + method: 'GET', + url, + }); + if (response.error) { + return []; + } + return response.data; + }, [url]); + + // Notice that we include `countryIsoCode` in the query key so that the query is refetched + const { data, isLoading, error } = useQuery( + ['classrooms', options], // Include countryIsoCode in the query key to trigger refetching + getClassrooms, + ); + + const setClassrooms = React.useCallback( + (newClassrooms: Classroom[]) => { + queryClient.setQueryData(['classrooms', options], newClassrooms); // Ensure that the query key includes countryIsoCode + }, + [queryClient, options], + ); + + return { + classrooms: isLoading || error ? [] : data || [], + setClassrooms, + }; +}; diff --git a/types/classroom.type.ts b/types/classroom.type.ts index 4c8e2f2cf..020a093be 100644 --- a/types/classroom.type.ts +++ b/types/classroom.type.ts @@ -36,3 +36,7 @@ export interface InitialStateOptionsProps { ownClass: StateOptions; ownClassTimeDelay: StateOptions; } + +export interface ClassroomFilter { + villageId: string; +}