diff --git a/.vscode/settings.json b/.vscode/settings.json index c5cf6632f8d8c9dd3a503ba10ec94c4f01efe993..d2f1afdc05168415c392035b4aef1bc1351e3e85 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -48,7 +48,9 @@ "ecolyo", "enedis", "Enedis", + "firstname", "grdf", + "lastname", "luxon", "toastify", "wysiwyg", diff --git a/src/components/Consents/Consents.tsx b/src/components/Consents/Consents.tsx index 2d32e819e7150ed2c0e64ff9579e12b95bf2e384..7b9448042067662bfd628fc6fd1541a72a7cebdf 100644 --- a/src/components/Consents/Consents.tsx +++ b/src/components/Consents/Consents.tsx @@ -1,45 +1,49 @@ import { Button, TablePagination, TextField } from '@mui/material' import { - ColDef, - ColGroupDef, CsvExportParams, GridApi, GridReadyEvent, IRowNode, RowSelectedEvent, - ValueFormatterParams, } from 'ag-grid-community' import { AgGridReact } from 'ag-grid-react' import { DateTime } from 'luxon' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useWhoAmI } from '../../API' import { getAxiosXSRFHeader } from '../../axios.config' -import { IConsent } from '../../models/consent.model' -import { ConsentService } from '../../services/consent.service' +import { IGrdfConsent, grdfColumnDef } from '../../models/grdfConsent' +import { ISgeConsent, sgeColumnDefs } from '../../models/sgeConsent.model' +import { GrdfConsentService } from '../../services/grdfConsent.service' +import { SgeConsentService } from '../../services/sgeConsent.service' +import Loader from '../Loader/Loader' import DownloadModal from './DownloadModal' import './agGridOverrides.scss' import styles from './consents.module.scss' -import './muiPaginationOverrides.scss' -const Consents: React.FC = () => { +export const Consents: React.FC<{ type: 'sge' | 'grdf' }> = ({ type }) => { + const isGRDF = type === 'grdf' + + const [isLoading, setIsLoading] = useState(false) const [gridApi, setGridApi] = useState<GridApi | null>(null) const [search, setSearch] = useState<string>('') const [selectedNodes, setSelectedNodes] = useState<IRowNode[]>([]) const [isShowingSelection, setIsShowingSelection] = useState<boolean>(false) const [openDownloadModal, setOpenDownloadModal] = useState<boolean>(false) - const [consents, setConsents] = useState<IConsent[]>([]) + const [consents, setConsents] = useState<ISgeConsent[] | IGrdfConsent[]>([]) const [page, setPage] = useState<number>(0) const [rowsPerPage, setRowsPerPage] = useState<number>(50) const [totalRows, setTotalRows] = useState<number>(50) const { data: user } = useWhoAmI() - const consentService = useMemo(() => { - return new ConsentService() - }, []) const toggleOpenModal = useCallback(() => { setOpenDownloadModal(prev => !prev) }, []) + const consentService = useMemo( + () => (isGRDF ? new GrdfConsentService() : new SgeConsentService()), + [isGRDF] + ) + const defaultColDef = useMemo( () => ({ sortable: true, @@ -48,75 +52,10 @@ const Consents: React.FC = () => { [] ) - const dateFormatter = (data: ValueFormatterParams): string => { - return (data.value as DateTime).toLocaleString() - } - - const [columnDefs] = useState<(ColDef | ColGroupDef)[] | null>([ - { - field: 'ID', - hide: true, - }, - { - field: 'pointID', - headerName: 'N° PDL', - initialWidth: 180, - filter: true, - checkboxSelection: true, - }, - { - field: 'lastname', - headerName: 'Nom', - initialWidth: 180, - filter: true, - cellStyle: { textTransform: 'uppercase' }, - }, - { - field: 'firstname', - headerName: 'Prénom', - initialWidth: 180, - filter: true, - cellStyle: { textTransform: 'capitalize' }, - }, - { - field: 'address', - headerName: 'Adresse', - initialWidth: 300, - filter: true, - flex: 1, - }, - { - field: 'postalCode', - headerName: 'CP', - initialWidth: 80, - filter: true, - }, - { - field: 'city', - headerName: 'Ville', - }, - { - field: 'safetyOnBoarding', - headerName: 'Secours', - initialWidth: 100, - }, - { - field: 'startDate', - valueFormatter: dateFormatter, - headerName: 'Début du consentement', - initialWidth: 150, - filter: true, - sortable: false, - }, - { - field: 'endDate', - valueFormatter: dateFormatter, - headerName: 'Fin du consentement', - initialWidth: 150, - filter: true, - sortable: false, - }, - ]) + const columnDefs = useMemo( + () => (isGRDF ? grdfColumnDef : sgeColumnDefs), + [isGRDF] + ) const handleChangePage = useCallback( ( @@ -139,13 +78,14 @@ const Consents: React.FC = () => { .filter(node => node.isSelected) .map(node => node.data.ID) - newNodes.forEach(node => { + newNodes?.forEach(node => { if (idsToCheck.includes(node.data.ID)) node.setSelected(true, false) }) } }, [gridApi, selectedNodes]) const searchConsents = async () => { + setIsLoading(true) if (user) { const consentPagination = await consentService.searchConsents( search, @@ -159,6 +99,7 @@ const Consents: React.FC = () => { setTotalRows(consentPagination.totalRows) } } + setIsLoading(false) } const handleSearchChange = (newSearch: string) => { @@ -243,23 +184,23 @@ const Consents: React.FC = () => { return () => { window.removeEventListener('resize', handleResize) } - }, [gridApi]) + }, [gridApi, isGRDF]) /** Trigger search when page loads or when admin changes input or pagination */ useEffect(() => { searchConsents() // /!\ Do not change dependencies or effect will not trigger when pagination changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rowsPerPage, page, search]) + }, [rowsPerPage, page, search, isGRDF]) return ( <> <div className="header"> - <h1>Gestion des consentements Enedis</h1> + <h1>Consentements {isGRDF ? 'GRDF' : 'Enedis'}</h1> </div> <div className={styles.content}> <TextField - placeholder="N°PDL (14 chiffres)" + placeholder={`N°${isGRDF ? 'PCE' : 'PDL'} (14 chiffres)`} label="Recherche" value={search} onChange={(e: React.ChangeEvent<HTMLInputElement>) => @@ -272,24 +213,27 @@ const Consents: React.FC = () => { className="ag-theme-alpine-dark" style={{ width: '100%', height: '75vh' }} > - <AgGridReact - onGridReady={onGridReady} - defaultColDef={defaultColDef} - rowHeight={35} - rowData={consents} - columnDefs={columnDefs} - animateRows={true} - rowSelection="multiple" - allowDragFromColumnsToolPanel={false} - onRowSelected={onRowSelected} - sortingOrder={['asc', 'desc']} - rowMultiSelectWithClick={true} - pagination={false} - suppressCellFocus={true} - rowClassRules={{ - expired: params => params.data.endDate < DateTime.now(), - }} - /> + {isLoading && <Loader />} + {!isLoading && ( + <AgGridReact + onGridReady={onGridReady} + defaultColDef={defaultColDef} + rowHeight={35} + rowData={consents} + columnDefs={columnDefs} + animateRows={true} + rowSelection="multiple" + allowDragFromColumnsToolPanel={false} + onRowSelected={onRowSelected} + sortingOrder={['asc', 'desc']} + rowMultiSelectWithClick={true} + pagination={false} + suppressCellFocus={true} + rowClassRules={{ + expired: params => params.data.endDate < DateTime.now(), + }} + /> + )} {!isShowingSelection && ( <TablePagination labelRowsPerPage="Consentements par page" @@ -333,4 +277,3 @@ const Consents: React.FC = () => { </> ) } -export default Consents diff --git a/src/components/Consents/muiPaginationOverrides.scss b/src/components/Consents/muiPaginationOverrides.scss deleted file mode 100644 index a759bd019b0e87beabe0086d6507b8ff39ca87c5..0000000000000000000000000000000000000000 --- a/src/components/Consents/muiPaginationOverrides.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Overrides MaterialUI Paginiation styles - -.MuiMenuItem-root { - color: black !important; -} diff --git a/src/components/Routes/Router.tsx b/src/components/Routes/Router.tsx index 64c8f6cbfaea33ba15f2932a25f06e4ace6bd55f..85051f1b76835a79c69b0d2e68ab390160ac0bba 100644 --- a/src/components/Routes/Router.tsx +++ b/src/components/Routes/Router.tsx @@ -1,6 +1,6 @@ import { Navigate, Route, Routes } from 'react-router-dom' import { useWhoAmI } from '../../API' -import Consents from '../Consents/Consents' +import { Consents } from '../Consents/Consents' import Loader from '../Loader/Loader' import Login from '../Login/Login' import Newsletter from '../Newsletter/Newsletter' @@ -24,9 +24,14 @@ export const links: Record< label: 'Prix', path: '/prices', }, - consents: { - label: 'Consentements', - path: '/consents', + sgeConsents: { + label: 'Consentements SGE', + path: '/consents/sge', + adminOnly: true, + }, + grdfConsents: { + label: 'Consentements GRDF', + path: '/consents/grdf', adminOnly: true, }, } @@ -52,7 +57,16 @@ const Router = () => { <Route path={links.prices.path} element={<Prices />} /> <Route path="/popups" element={<Popups />} /> {user.isAdmin && ( - <Route path={links.consents.path} element={<Consents />} /> + <> + <Route + path={links.sgeConsents.path} + element={<Consents type="sge" />} + /> + <Route + path={links.grdfConsents.path} + element={<Consents type="grdf" />} + /> + </> )} <Route path="/login" element={<Login />} /> <Route diff --git a/src/models/consent.model.ts b/src/models/consent.model.ts deleted file mode 100644 index 8c1714ae30c3b6af5970699bd0c9665758d6a9ba..0000000000000000000000000000000000000000 --- a/src/models/consent.model.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DateTime } from 'luxon' -export interface IConsent - extends Omit<ConsentEntity, 'CreatedAt' | 'endDate' | 'inseeCode'> { - startDate: DateTime - endDate: DateTime -} - -export interface ConsentEntity { - ID: number - CreatedAt: string - endDate: string - firstname: string - lastname: string - pointID: number - address: string - postalCode: string - inseeCode: string - city: string - safetyOnBoarding: boolean -} - -export interface IConsentPagination - extends Omit<ConsentPaginationEntity, 'rows'> { - rows: IConsent[] -} - -export interface ConsentPaginationEntity { - totalRows: number - totalPages: number - rows: ConsentEntity[] -} diff --git a/src/models/grdfConsent.ts b/src/models/grdfConsent.ts new file mode 100644 index 0000000000000000000000000000000000000000..a50b234fa0f2b0bfc2f664526b11fe98a50b7b96 --- /dev/null +++ b/src/models/grdfConsent.ts @@ -0,0 +1,80 @@ +import { ColDef } from 'ag-grid-community' +import { DateTime } from 'luxon' +import { dateFormatter } from '../utils/dateFormatter' + +export interface IGrdfConsent + extends Omit<GrdfConsentEntity, 'CreatedAt' | 'endDate'> { + startDate: DateTime + endDate: DateTime +} + +export interface GrdfConsentEntity { + ID: number + CreatedAt: string + endDate: string + firstname: string + lastname: string + pce: number + postalCode: string +} + +export interface IGrdfConsentPagination + extends Omit<GrdfConsentPaginationEntity, 'rows'> { + rows: IGrdfConsent[] +} + +export interface GrdfConsentPaginationEntity { + totalRows: number + totalPages: number + rows: GrdfConsentEntity[] +} + +export const grdfColumnDef: ColDef[] = [ + { + field: 'ID', + hide: true, + }, + { + field: 'pce', + headerName: 'N° PCE', + initialWidth: 180, + filter: true, + checkboxSelection: true, + }, + { + field: 'lastname', + headerName: 'Nom', + initialWidth: 180, + filter: true, + cellStyle: { textTransform: 'uppercase' }, + }, + { + field: 'firstname', + headerName: 'Prénom', + initialWidth: 180, + filter: true, + cellStyle: { textTransform: 'capitalize' }, + }, + { + field: 'postalCode', + headerName: 'CP', + initialWidth: 80, + filter: true, + }, + { + field: 'startDate', + valueFormatter: dateFormatter, + headerName: 'Début du consentement', + initialWidth: 150, + filter: true, + sortable: false, + }, + { + field: 'endDate', + valueFormatter: dateFormatter, + headerName: 'Fin du consentement', + initialWidth: 150, + filter: true, + sortable: false, + }, +] diff --git a/src/models/sgeConsent.model.ts b/src/models/sgeConsent.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..6746d21cecba68982044501dd2493c26441d6055 --- /dev/null +++ b/src/models/sgeConsent.model.ts @@ -0,0 +1,100 @@ +import { ColDef } from 'ag-grid-community' +import { DateTime } from 'luxon' +import { dateFormatter } from '../utils/dateFormatter' + +export interface ISgeConsent + extends Omit<SgeConsentEntity, 'CreatedAt' | 'endDate' | 'inseeCode'> { + startDate: DateTime + endDate: DateTime +} + +export interface SgeConsentEntity { + ID: number + CreatedAt: string + endDate: string + firstname: string + lastname: string + pointID: number + address: string + postalCode: string + inseeCode: string + city: string + safetyOnBoarding: boolean +} + +export interface ISgeConsentPagination + extends Omit<SgeConsentPaginationEntity, 'rows'> { + rows: ISgeConsent[] +} + +export interface SgeConsentPaginationEntity { + totalRows: number + totalPages: number + rows: SgeConsentEntity[] +} + +export const sgeColumnDefs: ColDef[] = [ + { + field: 'ID', + hide: true, + }, + { + field: 'pointID', + headerName: 'N° PDL', + initialWidth: 180, + filter: true, + checkboxSelection: true, + }, + { + field: 'lastname', + headerName: 'Nom', + initialWidth: 180, + filter: true, + cellStyle: { textTransform: 'uppercase' }, + }, + { + field: 'firstname', + headerName: 'Prénom', + initialWidth: 180, + filter: true, + cellStyle: { textTransform: 'capitalize' }, + }, + { + field: 'address', + headerName: 'Adresse', + initialWidth: 300, + filter: true, + flex: 1, + }, + { + field: 'postalCode', + headerName: 'CP', + initialWidth: 80, + filter: true, + }, + { + field: 'city', + headerName: 'Ville', + }, + { + field: 'safetyOnBoarding', + headerName: 'Secours', + initialWidth: 100, + }, + { + field: 'startDate', + valueFormatter: dateFormatter, + headerName: 'Début du consentement', + initialWidth: 150, + filter: true, + sortable: false, + }, + { + field: 'endDate', + valueFormatter: dateFormatter, + headerName: 'Fin du consentement', + initialWidth: 150, + filter: true, + sortable: false, + }, +] diff --git a/src/services/grdfConsent.service.ts b/src/services/grdfConsent.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..6fe987d2871c6a13450fdcb8b3311ea6dcea294c --- /dev/null +++ b/src/services/grdfConsent.service.ts @@ -0,0 +1,83 @@ +import axios, { AxiosRequestConfig } from 'axios' +import { DateTime } from 'luxon' +import { toast } from 'react-toastify' +import { + GrdfConsentEntity, + GrdfConsentPaginationEntity, + IGrdfConsent, + IGrdfConsentPagination, +} from '../models/grdfConsent' + +export class GrdfConsentService { + /** + * Search for consents + * @param search + * @param limit + * @param page + * @param axiosHeaders + */ + public searchConsents = async ( + search: string, + limit: number, + page: number, + axiosHeaders: AxiosRequestConfig + ): Promise<IGrdfConsentPagination | null> => { + try { + const { data } = await axios.get<GrdfConsentPaginationEntity>( + `/api/admin/grdf/consent?search=${search}&limit=${limit}&page=${page}`, + axiosHeaders + ) + return this.parseConsentPagination(data) + } catch (e) { + if (e.response.status === 403) { + toast.error("Accès refusé : vous n'avez pas les droits nécessaires") + } else { + toast.error('Erreur lors de la récupération des consentements') + } + console.error(e) + return null + } + } + + /** + * Converts consent entity into consent + * @param consentEntity + */ + public parseConsent = (consentEntity: GrdfConsentEntity): IGrdfConsent => { + const startDate = DateTime.fromISO(consentEntity.CreatedAt, { + zone: 'utc', + }).setLocale('fr-FR') + const endDate = DateTime.fromISO(consentEntity.endDate, { + zone: 'utc', + }).setLocale('fr-FR') + + return { + ID: consentEntity.ID, + startDate: startDate, + endDate: endDate, + firstname: consentEntity.firstname, + lastname: consentEntity.lastname, + pce: consentEntity.pce, + postalCode: consentEntity.postalCode, + } + } + + /** + * Converts consent pagination entity into consent pagination + * @param consentPaginationEntity + */ + public parseConsentPagination = ( + consentPaginationEntity: GrdfConsentPaginationEntity + ): IGrdfConsentPagination => { + const rows = consentPaginationEntity.rows.map(consent => + this.parseConsent(consent) + ) + + const consentPagination: IGrdfConsentPagination = { + rows: rows, + totalRows: consentPaginationEntity.totalRows, + totalPages: consentPaginationEntity.totalPages, + } + return consentPagination + } +} diff --git a/src/services/consent.service.ts b/src/services/sgeConsent.service.ts similarity index 71% rename from src/services/consent.service.ts rename to src/services/sgeConsent.service.ts index 4009d65754d095dd4ad84688786a255da80060a5..ad71837d63e862d752dff43c1075c5988545d74e 100644 --- a/src/services/consent.service.ts +++ b/src/services/sgeConsent.service.ts @@ -2,13 +2,13 @@ import axios, { AxiosRequestConfig } from 'axios' import { DateTime } from 'luxon' import { toast } from 'react-toastify' import { - ConsentEntity, - ConsentPaginationEntity, - IConsent, - IConsentPagination, -} from '../models/consent.model' + ISgeConsent, + ISgeConsentPagination, + SgeConsentEntity, + SgeConsentPaginationEntity, +} from '../models/sgeConsent.model' -export class ConsentService { +export class SgeConsentService { /** * Search for consents * @param search @@ -21,14 +21,13 @@ export class ConsentService { limit: number, page: number, axiosHeaders: AxiosRequestConfig - ): Promise<IConsentPagination | null> => { + ): Promise<ISgeConsentPagination | null> => { try { - const { data } = await axios.get( + const { data } = await axios.get<SgeConsentPaginationEntity>( `/api/admin/sge/consent?search=${search}&limit=${limit}&page=${page}`, axiosHeaders ) - const consentPagination = data as ConsentPaginationEntity - return this.parseConsentPagination(consentPagination) + return this.parseConsentPagination(data) } catch (e) { if (e.response.status === 403) { toast.error("Accès refusé : vous n'avez pas les droits nécessaires") @@ -44,11 +43,11 @@ export class ConsentService { * Converts consent entity into consent * @param consentEntity */ - public parseConsent = (consentEntity: ConsentEntity): IConsent => { - const startDate: DateTime = DateTime.fromISO(consentEntity.CreatedAt, { + public parseConsent = (consentEntity: SgeConsentEntity): ISgeConsent => { + const startDate = DateTime.fromISO(consentEntity.CreatedAt, { zone: 'utc', }).setLocale('fr-FR') - const endDate: DateTime = DateTime.fromISO(consentEntity.endDate, { + const endDate = DateTime.fromISO(consentEntity.endDate, { zone: 'utc', }).setLocale('fr-FR') @@ -71,13 +70,13 @@ export class ConsentService { * @param consentPaginationEntity */ public parseConsentPagination = ( - consentPaginationEntity: ConsentPaginationEntity - ): IConsentPagination => { + consentPaginationEntity: SgeConsentPaginationEntity + ): ISgeConsentPagination => { const rows = consentPaginationEntity.rows.map(consent => this.parseConsent(consent) ) - const consentPagination: IConsentPagination = { + const consentPagination: ISgeConsentPagination = { rows: rows, totalRows: consentPaginationEntity.totalRows, totalPages: consentPaginationEntity.totalPages, diff --git a/src/utils/dateFormatter.ts b/src/utils/dateFormatter.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b4858f4d55cd35dd30b55a736a57dece3df61f4 --- /dev/null +++ b/src/utils/dateFormatter.ts @@ -0,0 +1,6 @@ +import { ValueFormatterParams } from 'ag-grid-community' +import { DateTime } from 'luxon' + +export const dateFormatter = (data: ValueFormatterParams): string => { + return (data.value as DateTime).toLocaleString() +}