Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refonte du tableau des données indicateur #3528

Open
wants to merge 18 commits into
base: TET-4194/back-indicateurs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ed583ec
Exporte une définition de styles et corrige un tag html
marc-rutkowski Jan 16, 2025
95c5880
Evite une erreur de typage
marc-rutkowski Jan 16, 2025
57cc6a5
Ajoute une fonction de chargement des valeurs indicateur depuis tRPC
marc-rutkowski Jan 16, 2025
bd5e259
Transforme les données pour l'affichage dans le tableau
marc-rutkowski Jan 16, 2025
9080fd4
Ajoute les fonctions CRUD des valeurs indicateur depuis tRPC
marc-rutkowski Jan 16, 2025
097b701
Ajoute les composants pour l'affichage et la saisie d'une valeur d'in…
marc-rutkowski Jan 16, 2025
b4123ea
Ajoute le composant pour l'affichage du nom d'une source de données i…
marc-rutkowski Jan 16, 2025
4800865
Ajoute le dialogue de confirmation avant suppression d'une valeur
marc-rutkowski Jan 16, 2025
9eb5f1e
Ajoute le composant pour l'affichage des années dans l'en-tête du tab…
marc-rutkowski Jan 16, 2025
5f60182
Ajoute la modale d'édition d'un commentaire d'une valeur d'un indicateur
marc-rutkowski Jan 16, 2025
56300fc
Ajoute la modale d'édition des valeurs d'un indicateur
marc-rutkowski Jan 16, 2025
0371eeb
Ajoute le tableau des valeurs d'un indicateur pour un type (objectif …
marc-rutkowski Jan 16, 2025
b92afe8
Ajoute le bouton "Résultat récent en mode privé"
marc-rutkowski Jan 16, 2025
865ebc4
Ajoute les boutons "objectifs/résultats" et "ajouter une année" et le…
marc-rutkowski Jan 16, 2025
48b59c1
Raccorde le nouveau tableau des valeurs dans les pages Indicateur
marc-rutkowski Jan 16, 2025
d9c0f23
N'affiche pas le bouton de suppression d'une année/colonne en mode le…
marc-rutkowski Jan 16, 2025
74252b8
Met à jour vite en v6
marc-rutkowski Jan 16, 2025
4fb2e98
Réduit la largeur des colonnes
marc-rutkowski Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ npm-debug.log
yarn-error.log
testem.log
/typings
vitest.config.mts.timestamp*

# System Files
.DS_Store
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { transformeValeurs } from '@/app/app/pages/collectivite/Indicateurs/Indicateur/detail/transformeValeurs';
import { useIndicateurValeurs } from '@/app/app/pages/collectivite/Indicateurs/useIndicateurValeurs';
import { useCurrentCollectivite } from '@/app/core-logic/hooks/useCurrentCollectivite';
import { Tab, Tabs, useActiveTab } from '@/app/ui/shared/Tabs';
import { Alert, Checkbox, Tooltip } from '@/ui';
import { useEffect } from 'react';
import { Alert } from '@/ui';
import { SOURCE_COLLECTIVITE } from '../../constants';
import { IndicateurTable } from '../../table/indicateur-table';
import { TIndicateurDefinition } from '../../types';
import { IndicateurValuesTable } from './IndicateurValuesTable';
import { useToggleIndicateurConfidentiel } from './useToggleIndicateurConfidentiel';

// un message spécifique doit être affiché pour les indicateurs de la séquestration carbone
const ID_SEQUESTRATION = 'cae_63.';
Expand All @@ -20,69 +15,17 @@ export const IndicateurValuesTabs = ({
definition: TIndicateurDefinition;
importSource?: string;
}) => {
const { activeTab, onChangeTab } = useActiveTab();
const collectivite = useCurrentCollectivite();
const isReadonly =
!collectivite ||
collectivite.readonly ||
(!!importSource && importSource !== SOURCE_COLLECTIVITE);
const { mutate: toggleIndicateurConfidentiel, isLoading } =
useToggleIndicateurConfidentiel(definition);
const { confidentiel } = definition;

const { data: valeursBrutes } = useIndicateurValeurs({
id: definition.id,
importSource,
});
const { objectifs, resultats } = transformeValeurs(
valeursBrutes,
importSource
);

// force l'affichage de l'onglet Résultats sil il n'y a pas d'onglet Objectifs
// quand on passe d'une source de données à une autre
const avecResultats =
!importSource ||
importSource === SOURCE_COLLECTIVITE ||
resultats?.length > 0;
const avecObjectifs =
!importSource ||
importSource === SOURCE_COLLECTIVITE ||
objectifs?.length > 0;

useEffect(() => {
if (activeTab === 1 && !(avecObjectifs && avecResultats)) {
onChangeTab(0);
}
}, [avecObjectifs, avecResultats, activeTab]);

return (
<>
{!isReadonly && (
<>
<div className="flex my-10">
<Tooltip
label="Si le mode privé est activé, le résultat le plus récent n'est plus
consultable par les personnes n’étant pas membres de votre
collectivité. Seuls les autres résultats restent accessibles pour
tous les utilisateurs et la valeur privée reste consultable par
l’ADEME et le service support de la plateforme."
>
<div>
{' '}
{/** Permet de prendre en compte la checkbox + le label (autrement uniquement la checkbox trigger le tooltip) */}
<Checkbox
variant="switch"
label="Résultat récent en mode privé"
checked={confidentiel}
disabled={isLoading}
onChange={() =>
toggleIndicateurConfidentiel(confidentiel || false)
}
/>
</div>
</Tooltip>
</div>
{definition.identifiant?.startsWith(ID_SEQUESTRATION) && (
<Alert
className="mb-8"
Expand All @@ -92,32 +35,14 @@ export const IndicateurValuesTabs = ({
)}
</>
)}
<Tabs defaultActiveTab={activeTab} onChange={onChangeTab}>
{avecResultats ? (
<Tab label="Résultats" icon="checkbox">
<IndicateurValuesTable
definition={definition}
type="resultat"
valeurs={resultats}
valeursBrutes={valeursBrutes!}
isReadonly={isReadonly}
importSource={importSource}
confidentiel={confidentiel}
/>
</Tab>
) : null}
{avecObjectifs ? (
<Tab label="Objectifs" icon="calendar-2">
<IndicateurValuesTable
definition={definition}
type="objectif"
valeurs={objectifs}
valeursBrutes={valeursBrutes!}
isReadonly={isReadonly}
/>
</Tab>
) : null}
</Tabs>
{!!collectivite?.collectivite_id && (
<IndicateurTable
collectiviteId={collectivite.collectivite_id}
definition={definition}
confidentiel={confidentiel}
readonly={!collectivite || collectivite.readonly}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { IndicateurDefinition } from '@/api/indicateurs/domain/definition.schema';
import { Button, HEAD_CELL_STYLE, Notification, TCell, Tooltip } from '@/ui';
import classNames from 'classnames';
import { useState } from 'react';
import { SourceType } from '../types';
import { ConfirmDelete } from './confirm-delete';
import { PreparedData } from './prepare-data';
import { IndicateurSourceValeur } from './use-indicateur-valeurs';

type CellAnneeListProps = {
confidentiel?: boolean;
data: PreparedData;
definition: IndicateurDefinition;
readonly?: boolean;
type: SourceType;
onDelete: (valeur: IndicateurSourceValeur) => void;
};

/** Affiche les cellules des années dans l'en-tête du tableau */
export const CellAnneeList = ({
confidentiel,
data,
definition,
readonly,
type,
onDelete,
}: CellAnneeListProps) => {
const { annees, anneeModePrive, valeursExistantes } = data;
const [toBeDeleted, setToBeDeleted] = useState<IndicateurSourceValeur | null>(
null
);

return annees?.map((annee) => {
const valeur = valeursExistantes.find((v) => v.annee === annee);
const modePrive =
confidentiel && type === 'resultat' && annee === anneeModePrive;

return (
<TCell
key={annee}
className={classNames(
HEAD_CELL_STYLE,
'font-bold text-center relative w-[8.5rem]',
{ '!py-2': modePrive }
)}
>
<div className="flex items-center justify-between">
{modePrive && <ResultatModePrive />}
<span className="w-full">{annee}</span>
{valeur && !readonly && (
<Button
className="!bg-transparent !border-none"
icon="delete-bin-6-line"
variant="outlined"
size="xs"
onClick={() => {
// demande confirmation avant de supprimer
if (
(valeur.objectif ?? false) ||
(valeur.resultat ?? false) ||
valeur.resultatCommentaire ||
valeur.objectifCommentaire
) {
setToBeDeleted(valeur);
} else {
// sauf pour les lignes n'ayant ni valeur ni commentaire
onDelete(valeur);
}
}}
/>
)}
</div>
{toBeDeleted && (
<ConfirmDelete
annee={annee}
valeur={toBeDeleted}
unite={definition.unite}
onDismissConfirm={(confirmed) => {
if (confirmed) {
onDelete(toBeDeleted);
}
setToBeDeleted(null);
}}
/>
)}
</TCell>
);
});
};

const ResultatModePrive = () => (
<Tooltip label={<p className="min-w-max">Le résultat est en mode privé</p>}>
<div>
<Notification icon="lock-fill" classname="!h-10 w-10" />
</div>
</Tooltip>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TCell } from '@/ui';
import { SOURCE_TYPE_LABEL } from '../constants';
import { SourceType } from '../types';

/** Affiche le nom d'une source de données et un rappel de l'unité */
export const CellSourceName = ({
nom,
unite,
}: {
nom: string;
unite: string;
}) => (
<TCell className="font-bold text-sm min-w-40">
{nom} &nbsp;
<sup className="text-primary-9">({unite})</sup>
</TCell>
);

export const getSourceLabel = (source: string, type: SourceType) => {
const label =
SOURCE_TYPE_LABEL[type][0].toUpperCase() + SOURCE_TYPE_LABEL[type].slice(1);
switch (source) {
case 'collectivite':
return `${label} de la collectivité`;
case 'snbc':
return `${label} SNBC territorialisée`;
default:
return `${label} ${source.toUpperCase()}`;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TCell } from '@/ui';
import { InputValue } from './input-value';

// pour formater les chiffres
export const NumFormat = Intl.NumberFormat('fr', { maximumFractionDigits: 3 });

type CellValueProps = {
value: number | '';
readonly?: boolean;
onChange?: (value: number | null) => void;
};

// Affiche une cellule du tableau (chiffre ou champ de saisie)
export const CellValue = ({ value, readonly, onChange }: CellValueProps) => {
return (
<TCell variant={readonly ? 'number' : 'input'}>
{readonly ? (
typeof value === 'number' ? (
NumFormat.format(value)
) : (
''
)
) : (
<InputValue displaySize="sm" value={value} onChange={onChange} />
)}
</TCell>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
Modal,
ModalFooterOKCancel,
Table,
TBody,
TCell,
THead,
THeadCell,
THeadRow,
TRow,
} from '@/ui';
import { useState } from 'react';
import { CellValue } from './cell-value';
import { IndicateurSourceValeur } from './use-indicateur-valeurs';

type ConfirmDeleteProps = {
unite: string;
annee: number;
valeur: IndicateurSourceValeur;
onDismissConfirm: (overwrite: boolean) => void;
};

/** Affiche un dialogue de confirmation avant suppression d'une valeur
*/
export const ConfirmDelete = (props: ConfirmDeleteProps) => {
const { annee, valeur, unite, onDismissConfirm } = props;
const [isOpen, setIsOpen] = useState(true);
const { objectif, objectifCommentaire, resultat, resultatCommentaire } =
valeur;

return (
<Modal
disableDismiss
noCloseButton
size="lg"
title="Confirmer la suppression"
subTitle={`des données de la collectivité pour l'année ${annee}`}
openState={{ isOpen, setIsOpen }}
render={() => (
<>
<p className="text-center mb-0">
Attention, les données existantes pour l&apos;année <b>{annee}</b>{' '}
seront supprimées.
</p>
<Table>
<THead>
<THeadRow>
<THeadCell>&nbsp;</THeadCell>
<THeadCell>Valeur</THeadCell>
<THeadCell>Commentaire</THeadCell>
</THeadRow>
</THead>
<TBody>
<TRow>
<TCell className="font-medium">Résultat ({unite})</TCell>
<CellValue readonly value={resultat ?? ''} />
<TCell>{resultatCommentaire}</TCell>
</TRow>
<TRow>
<TCell className="font-medium">Objectif ({unite})</TCell>
<CellValue readonly value={objectif ?? ''} />
<TCell>{objectifCommentaire}</TCell>
</TRow>
</TBody>
</Table>
</>
)}
renderFooter={({ close }) => (
<ModalFooterOKCancel
btnOKProps={{
children: 'Confirmer',
onClick: () => {
onDismissConfirm(true);
close();
},
}}
btnCancelProps={{
onClick: () => {
onDismissConfirm(false);
close();
},
}}
/>
)}
/>
);
};
Loading
Loading