diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/CollectivitePageLayout/CollectivitePageLayout.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/CollectivitePageLayout/CollectivitePageLayout.tsx
index f430323f25..5564529fa8 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/CollectivitePageLayout/CollectivitePageLayout.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/CollectivitePageLayout/CollectivitePageLayout.tsx
@@ -1,12 +1,12 @@
+import classNames from 'classnames';
import React, { useEffect, useMemo, useState } from 'react';
-import SideNavContainer, { SideNavContainerProps } from './SideNavContainer';
+import Panel from './Panel/Panel';
import {
PanelProvider,
usePanelDispatch,
usePanelState,
} from './Panel/PanelContext';
-import classNames from 'classnames';
-import Panel from './Panel/Panel';
+import SideNavContainer, { SideNavContainerProps } from './SideNavContainer';
type Props = {
children: React.ReactNode;
@@ -62,7 +62,7 @@ const PageLayout = ({ children, sideNav, dataTest }: Props) => {
return (
{/** Side nav */}
{sideNav && (
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/FichesActionLiees.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/FichesActionLiees.tsx
index afb5db1cad..ef74499c59 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/FichesActionLiees.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/FichesActionLiees.tsx
@@ -26,7 +26,7 @@ export const FichesActionLiees = (props: TFichesActionProps) => {
const isReadonly = collectivite?.readonly ?? false;
return (
- <>
+
{
- >
+
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/Indicateur.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/Indicateur.tsx
index 408731d8ea..8503fc29c8 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/Indicateur.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/Indicateur.tsx
@@ -8,8 +8,7 @@ import {
import { useCollectiviteId } from '@/app/core-logic/hooks/params';
import { redirect } from 'next/navigation';
import { useParams } from 'react-router-dom';
-import { IndicateurPersonnalise } from './IndicateurPersonnalise';
-import { IndicateurPredefini } from './IndicateurPredefini';
+import IndicateurDetail from '../detail/IndicateurDetail';
/**
* Affiche le détail d'un indicateur
@@ -42,18 +41,12 @@ const Indicateur = () => {
}
return (
-
- {isPerso ? (
-
- ) : (
-
- )}
-
+ {...{ indicateurId, isPerso }}
+ />
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/HeaderIndicateur.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/HeaderIndicateur.tsx
index 7ff3113de8..0859e2068e 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/HeaderIndicateur.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/HeaderIndicateur.tsx
@@ -52,7 +52,7 @@ export const HeaderIndicateur = ({
return (
-
+ {!isReadonly && (
+
+ )}
);
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurInfoLiees.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurInfoLiees.tsx
index 4588a239d3..b1dc3b79ef 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurInfoLiees.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurInfoLiees.tsx
@@ -56,7 +56,7 @@ export const IndicateurInfoLiees = (props: TIndicateurInfoLieesProps) => {
.filter((id) => !!id) as string[];
return (
- <>
+
{/** personne pilote */}
{
/>
)}
- >
+
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurValuesTabs.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurValuesTabs.tsx
index 0c14b787d7..c7c4d84766 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurValuesTabs.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/Indicateur/detail/IndicateurValuesTabs.tsx
@@ -57,7 +57,34 @@ export const IndicateurValuesTabs = ({
}, [avecObjectifs, avecResultats, activeTab]);
return (
- <>
+
+
+ {avecResultats ? (
+
+
+
+ ) : null}
+ {avecObjectifs ? (
+
+
+
+ ) : null}
+
+
{!isReadonly && (
<>
@@ -92,32 +119,6 @@ export const IndicateurValuesTabs = ({
)}
>
)}
-
- {avecResultats ? (
-
-
-
- ) : null}
- {avecObjectifs ? (
-
-
-
- ) : null}
-
- >
+
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/components/BadgeIndicateurPerso.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/components/BadgeIndicateurPerso.tsx
index 848302d804..4e357876a8 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/components/BadgeIndicateurPerso.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/components/BadgeIndicateurPerso.tsx
@@ -10,7 +10,7 @@ const BadgeIndicateurPerso = ({ size }: Props) => {
size={size}
light
iconPosition="left"
- icon="line-chart-line"
+ icon="user-line"
/>
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/ActionsLiees.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/ActionsLiees.tsx
new file mode 100644
index 0000000000..b6245a5076
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/ActionsLiees.tsx
@@ -0,0 +1,33 @@
+import { EmptyCard } from '@/ui';
+import ActionPicto from '../../PlansActions/FicheAction/ActionsLiees/ActionPicto';
+import ActionsLieesListe from '../../PlansActions/FicheAction/ActionsLiees/ActionsLieesListe';
+
+type Props = {
+ actionsIds: string[];
+};
+
+const ActionsLiees = ({ actionsIds }: Props) => {
+ const isEmpty = actionsIds.length === 0;
+
+ return isEmpty ? (
+
}
+ title="Aucune action des référentiels n'est liée !"
+ size="xs"
+ />
+ ) : (
+
+
+
+ Actions du référentiel associées
+
+
+
+
+ );
+};
+
+export default ActionsLiees;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DescriptionIndicateurInput.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DescriptionIndicateurInput.tsx
new file mode 100644
index 0000000000..00a33f6a91
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DescriptionIndicateurInput.tsx
@@ -0,0 +1,26 @@
+import { AutoResizedTextarea, Field } from '@/ui';
+
+type Props = {
+ description: string | undefined | null;
+ disabled?: boolean;
+ updateDescription: (value: string) => void;
+};
+
+const DescriptionIndicateurInput = ({
+ description,
+ disabled,
+ updateDescription,
+}: Props) => {
+ return (
+
+ updateDescription(evt.currentTarget.value)}
+ />
+
+ );
+};
+
+export default DescriptionIndicateurInput;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DonneesIndicateur.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DonneesIndicateur.tsx
new file mode 100644
index 0000000000..504eb04346
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/DonneesIndicateur.tsx
@@ -0,0 +1,80 @@
+import { ImportSourcesSelector } from '../Indicateur/detail/ImportSourcesSelector';
+import IndicateurDetailChart from '../Indicateur/detail/IndicateurDetailChart';
+import { IndicateurInfoLiees } from '../Indicateur/detail/IndicateurInfoLiees';
+import { IndicateurValuesTabs } from '../Indicateur/detail/IndicateurValuesTabs';
+import { useIndicateurImportSources } from '../Indicateur/detail/useImportSources';
+import { TIndicateurDefinition } from '../types';
+import DescriptionIndicateurInput from './DescriptionIndicateurInput';
+import UniteIndicateurInput from './UniteIndicateurInput';
+
+type Props = {
+ definition: TIndicateurDefinition;
+ isPerso?: boolean;
+ isReadonly?: boolean;
+ updateUnite: (value: string) => void;
+ updateDescription: (value: string) => void;
+};
+
+const DonneesIndicateur = ({
+ definition,
+ isPerso = false,
+ isReadonly = false,
+ updateUnite,
+ updateDescription,
+}: Props) => {
+ const { description, commentaire, unite, rempli, titre, titreLong } =
+ definition;
+
+ const { sources, currentSource, setCurrentSource } =
+ useIndicateurImportSources(definition.id);
+
+ return (
+
+ {!!sources?.length && (
+
+ )}
+
+ {/* Graphe */}
+
+
+ {/* Tableau */}
+
+
+ {/* Description */}
+
+
+ {/* Infos liées - à déplacer */}
+
+
+ {/* Unité personnalisée - à déplacer */}
+ {isPerso && (
+
+ )}
+
+ );
+};
+
+export default DonneesIndicateur;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/FichesLiees.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/FichesLiees.tsx
new file mode 100644
index 0000000000..b8b9df07ec
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/FichesLiees.tsx
@@ -0,0 +1,85 @@
+import { Button, EmptyCard } from '@/ui';
+import { useState } from 'react';
+import { objectToSnake } from 'ts-case-convert';
+import FichePicto from '../../PlansActions/FicheAction/FichesLiees/FichePicto';
+import ModaleFichesLiees from '../../PlansActions/FicheAction/FichesLiees/ModaleFichesLiees';
+import FichesActionListe from '../../PlansActions/ToutesLesFichesAction/FichesActionListe';
+import {
+ useFichesActionLiees,
+ useUpdateFichesActionLiees,
+} from '../Indicateur/useFichesActionLiees';
+import { TIndicateurDefinition } from '../types';
+
+type Props = {
+ definition: TIndicateurDefinition;
+ isReadonly: boolean;
+};
+
+const FichesLiees = ({ definition, isReadonly }: Props) => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const { data: fiches } = useFichesActionLiees(definition);
+ const fichesLiees = (fiches ? objectToSnake(fiches) : []).map((f) => f.id);
+
+ const { mutate: updateFichesActionLiees } =
+ useUpdateFichesActionLiees(definition);
+
+ const isEmpty = fiches.length === 0;
+
+ return (
+ <>
+ {isEmpty ? (
+ }
+ title="Aucune fiche action de vos plans d'actions n'est liée !"
+ isReadonly={isReadonly}
+ actions={[
+ {
+ children: 'Lier une fiche action',
+ icon: 'link',
+ onClick: () => setIsModalOpen(true),
+ },
+ ]}
+ size="xs"
+ />
+ ) : (
+
+
+
Fiches des plans liées
+ {!isReadonly && (
+ setIsModalOpen(true)}
+ >
+ Lier une fiche action
+
+ )}
+
+
+
+ )}
+
+ {isModalOpen && (
+
+ )}
+ >
+ );
+};
+
+export default FichesLiees;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurDetail.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurDetail.tsx
new file mode 100644
index 0000000000..bb77cf935a
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurDetail.tsx
@@ -0,0 +1,22 @@
+import { useIndicateurDefinition } from '../Indicateur/useIndicateurDefinition';
+import IndicateurLayout from './IndicateurLayout';
+
+type Props = {
+ dataTest?: string;
+ indicateurId: number | string;
+ isPerso?: boolean;
+};
+
+const IndicateurDetail = ({
+ dataTest,
+ indicateurId,
+ isPerso = false,
+}: Props) => {
+ const definition = useIndicateurDefinition(indicateurId);
+
+ if (!definition) return null;
+
+ return ;
+};
+
+export default IndicateurDetail;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurLayout.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurLayout.tsx
new file mode 100644
index 0000000000..59153dd59f
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurLayout.tsx
@@ -0,0 +1,160 @@
+import { referentielToName } from '@/app/app/labels';
+import BadgeIndicateurPerso from '@/app/app/pages/collectivite/Indicateurs/components/BadgeIndicateurPerso';
+import { useCurrentCollectivite } from '@/app/core-logic/hooks/useCurrentCollectivite';
+import ScrollTopButton from '@/app/ui/buttons/ScrollTopButton';
+import { BadgeACompleter } from '@/app/ui/shared/Badge/BadgeACompleter';
+import { Badge, Tab, Tabs } from '@/ui';
+import { HeaderIndicateur } from '../Indicateur/detail/HeaderIndicateur';
+import { useUpdateIndicateurDefinition } from '../Indicateur/useUpdateIndicateurDefinition';
+import BadgeOpenData from '../components/BadgeOpenData';
+import { TIndicateurDefinition } from '../types';
+import ActionsLiees from './ActionsLiees';
+import DonneesIndicateur from './DonneesIndicateur';
+import FichesLiees from './FichesLiees';
+import IndicateurToolbar from './IndicateurToolbar';
+import SousIndicateurs from './SousIndicateurs';
+
+type IndicateurLayoutProps = {
+ dataTest?: string;
+ definition: TIndicateurDefinition;
+ isPerso?: boolean;
+};
+
+const IndicateurLayout = ({
+ dataTest,
+ definition,
+ isPerso = false,
+}: IndicateurLayoutProps) => {
+ const { enfants, sansValeur, rempli, titre } = definition;
+
+ const { mutate: updateDefinition } = useUpdateIndicateurDefinition();
+
+ const collectivite = useCurrentCollectivite();
+
+ const collectiviteId = collectivite?.collectivite_id;
+ const isReadonly = !collectivite || collectivite?.readonly;
+
+ const composeSansAgregation = !!enfants && enfants.length > 0 && sansValeur;
+ const composeAvecAgregation = !!enfants && enfants.length > 0 && !sansValeur;
+
+ // Mise à jour des champs de l'indicateur
+ const handleUpdate = (
+ name: 'description' | 'commentaire' | 'unite' | 'titre',
+ value: string
+ ) => {
+ const trimmedValue = value.trim();
+ if (collectiviteId && trimmedValue !== definition[name]) {
+ updateDefinition({ ...definition, [name]: trimmedValue });
+ }
+ };
+
+ const handleTitreUpdate = (value: string) => handleUpdate('titre', value);
+
+ const handleCommentaireUpdate = (value: string) =>
+ handleUpdate('commentaire', value);
+
+ const handleUniteUpdate = (value: string) => handleUpdate('unite', value);
+
+ /**
+ * TEMPORARY: currently, description input feeds two columns:
+ * `description` column in `indicateur_definition`
+ * `commentaire` column in `indicateur_collectivite` (via the hardcoded ['commentaire'] prop).
+ *
+ * TO DO: remove this function and change
+ * handleUpdate('description', e.target.value) to handleUpdate('commentaire', e.target.value).
+ *
+ * Related to this PR: https://github.com/incubateur-ademe/territoires-en-transitions/pull/3313.
+ */
+ const handleDescriptionUpdate = (value: string) => {
+ const trimmedValue = value.trim();
+ updateDefinition({
+ ...definition,
+ description: trimmedValue,
+ commentaire: trimmedValue,
+ });
+ };
+
+ return (
+
+
+
+
+
+ {/* Liste des badges */}
+ {!composeSansAgregation && (
+
+
+ {isPerso && }
+ {definition.participationScore && (
+
+ )}
+ {definition.hasOpenData && }
+
+ )}
+
+ {/* Menu export / infos / suppression */}
+
+
+
+ {composeSansAgregation ? (
+ // Groupe d'indicateurs sans agrégation
+
+ ) : (
+ // Indicateur sans enfant, groupe d'indicateurs avec agrégation,
+ // ou indicateur personnalisé
+
+ {/* Données */}
+
+
+ isPerso
+ ? handleDescriptionUpdate(value)
+ : handleCommentaireUpdate(value)
+ }
+ />
+
+
+ {/* Sous indicateurs */}
+ {composeAvecAgregation ? (
+
+
+
+ ) : undefined}
+
+ {/* Actions des référentiels liées */}
+ {!isPerso ? (
+
+ a.id)}
+ />
+
+ ) : undefined}
+
+ {/* Fiches des plans liées */}
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default IndicateurLayout;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurToolbar.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurToolbar.tsx
new file mode 100644
index 0000000000..fe7976d2bb
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/IndicateurToolbar.tsx
@@ -0,0 +1,89 @@
+import { ToolbarIconButton } from '@/app/ui/buttons/ToolbarIconButton';
+import { Modal, ModalFooterOKCancel } from '@/ui';
+import classNames from 'classnames';
+import { useState } from 'react';
+import { IndicateurSidePanelToolbar } from '../Indicateur/IndicateurSidePanelToolbar';
+import { useExportIndicateurs } from '../Indicateur/useExportIndicateurs';
+import { useDeleteIndicateurPerso } from '../Indicateur/useRemoveIndicateurPerso';
+import { TIndicateurDefinition } from '../types';
+
+type Props = {
+ definition: TIndicateurDefinition;
+ collectiviteId: number;
+ isPerso?: boolean;
+ isReadonly?: boolean;
+ className?: string;
+};
+
+const IndicateurToolbar = ({
+ definition,
+ collectiviteId,
+ isPerso = false,
+ isReadonly = false,
+ className,
+}: Props) => {
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+
+ const { mutate: exportIndicateurs, isLoading } = useExportIndicateurs(
+ isPerso ? 'app/indicateurs/perso' : 'app/indicateurs/predefini',
+ [definition]
+ );
+
+ const { mutate: deleteIndicateurPerso } = useDeleteIndicateurPerso(
+ collectiviteId,
+ definition.id
+ );
+
+ return (
+ <>
+
+ exportIndicateurs()}
+ />
+ {!isReadonly && isPerso && (
+ setIsDeleteModalOpen(true)}
+ />
+ )}
+ {!isPerso && }
+
+
+ {isDeleteModalOpen && (
+ (
+ close(),
+ }}
+ btnOKProps={{
+ 'aria-label': 'Supprimer',
+ children: 'Supprimer',
+ onClick: () => {
+ deleteIndicateurPerso();
+ close();
+ },
+ }}
+ />
+ )}
+ />
+ )}
+ >
+ );
+};
+
+export default IndicateurToolbar;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/SousIndicateurs.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/SousIndicateurs.tsx
new file mode 100644
index 0000000000..8a4d7c11af
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/SousIndicateurs.tsx
@@ -0,0 +1,21 @@
+import IndicateursListe from '../lists/indicateurs-list';
+
+type Props = {
+ enfantsIds: number[];
+};
+
+const SousIndicateurs = ({ enfantsIds }: Props) => {
+ if (!enfantsIds?.length) return null;
+
+ return (
+
+ );
+};
+
+export default SousIndicateurs;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/UniteIndicateurInput.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/UniteIndicateurInput.tsx
new file mode 100644
index 0000000000..77298e33fe
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/detail/UniteIndicateurInput.tsx
@@ -0,0 +1,26 @@
+import { Field, Input } from '@/ui';
+import { useState } from 'react';
+
+type Props = {
+ unite: string;
+ disabled?: boolean;
+ updateUnite: (value: string) => void;
+};
+
+const UniteIndicateurInput = ({ unite, disabled, updateUnite }: Props) => {
+ const [uniteInput, setUniteInput] = useState(unite);
+
+ return (
+
+ setUniteInput(evt.target.value)}
+ onBlur={(evt) => updateUnite(evt.target.value)}
+ disabled={disabled}
+ />
+
+ );
+};
+
+export default UniteIndicateurInput;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/badge-list.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/badge-list.tsx
index 60402a6366..0471c967e1 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/badge-list.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/badge-list.tsx
@@ -1,18 +1,13 @@
-import classNames from 'classnames';
-
import { Indicateurs } from '@/api';
-import {
- ExportIndicateursPageName,
- useExportIndicateurs,
-} from '@/app/app/pages/collectivite/Indicateurs/Indicateur/useExportIndicateurs';
+import { ExportIndicateursPageName } from '@/app/app/pages/collectivite/Indicateurs/Indicateur/useExportIndicateurs';
import FilterBadges, {
CustomFilterBadges,
useFiltersToBadges,
} from '@/app/ui/shared/filters/filter-badges';
-import { Badge } from '@/ui';
+import ExportButton from './export-button';
type Props = {
- pageName: ExportIndicateursPageName; // tracking
+ pageName?: ExportIndicateursPageName; // tracking
definitions?: Indicateurs.domain.IndicateurListItem[];
filters: Indicateurs.FetchFiltre;
customFilterBadges?: CustomFilterBadges;
@@ -30,42 +25,26 @@ const BadgeList = ({
isEmpty,
isLoading,
}: Props) => {
- // fonction d'export
- const { mutate: exportIndicateurs, isLoading: isDownloadingExport } =
- useExportIndicateurs(pageName, definitions);
-
const { data: filterBadges } = useFiltersToBadges({
filters,
customValues: customFilterBadges,
});
+ const displayBadgesList = !!filterBadges?.length;
+ const displayExportButton = !isEmpty && !isLoading && !!pageName;
+
+ if (!displayBadgesList && !pageName) return null;
+
return (
- {!!filterBadges?.length && (
+ {displayBadgesList && (
)}
- {!isEmpty && !isLoading && (
- exportIndicateurs()}
- >
-
-
+ {displayExportButton && (
+ 0}
+ />
)}
);
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/export-button.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/export-button.tsx
new file mode 100644
index 0000000000..a0c33de269
--- /dev/null
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/export-button.tsx
@@ -0,0 +1,45 @@
+import { Indicateurs } from '@/api';
+import {
+ ExportIndicateursPageName,
+ useExportIndicateurs,
+} from '@/app/app/pages/collectivite/Indicateurs/Indicateur/useExportIndicateurs';
+import { Badge } from '@/ui';
+import classNames from 'classnames';
+
+type Props = {
+ definitions?: Indicateurs.domain.IndicateurListItem[];
+ pageName: ExportIndicateursPageName; // tracking
+ isFiltered: boolean;
+};
+
+const ExportButton = ({ definitions, pageName, isFiltered }: Props) => {
+ // fonction d'export
+ const { mutate: exportIndicateurs, isLoading: isDownloadingExport } =
+ useExportIndicateurs(pageName, definitions);
+
+ return (
+ exportIndicateurs()}
+ >
+
+
+ );
+};
+
+export default ExportButton;
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/indicateurs-list.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/indicateurs-list.tsx
index 6815e83330..104416307d 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/indicateurs-list.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/Indicateurs/lists/indicateurs-list/indicateurs-list.tsx
@@ -22,6 +22,7 @@ import { makeCollectiviteIndicateursUrl } from '@/app/app/paths';
import { useCurrentCollectivite } from '@/app/core-logic/hooks/useCurrentCollectivite';
import { CustomFilterBadges } from '@/app/ui/shared/filters/filter-badges';
import { OpenState } from '@/ui/utils/types';
+import classNames from 'classnames';
type sortByOptionsType = {
label: string;
@@ -50,7 +51,7 @@ const sortByOptions: sortByOptionsType[] = [
];
type Props = {
- settings: (openState: OpenState) => React.ReactNode;
+ settings?: (openState: OpenState) => React.ReactNode;
filtres?: Indicateurs.FetchFiltre;
customFilterBadges?: CustomFilterBadges;
resetFilters?: () => void;
@@ -59,7 +60,8 @@ type Props = {
/** Rend les cartes indicateurs éditables */
isEditable?: boolean;
// pour le tracking
- pageName: ExportIndicateursPageName;
+ pageName?: ExportIndicateursPageName;
+ menuContainerClassname?: string;
};
/** Liste de fiches action avec tri et options de fitlre */
@@ -74,6 +76,7 @@ const IndicateursListe = ({
settings,
isEditable,
maxNbOfCards = 9,
+ menuContainerClassname,
}: Props) => {
const tracker = useEventTracker('app/indicateurs/tous');
@@ -153,8 +156,13 @@ const IndicateursListe = ({
const [displayGraphs, setDisplayGraphs] = useState(true);
return (
- <>
-
+
+
{/** Tri */}
{/** Bouton d'édition des filtres (une modale avec bouton ou un ButtonMenu) */}
- {settings({ isOpen: isSettingsOpen, setIsOpen: setIsSettingsOpen })}
+ {settings?.({ isOpen: isSettingsOpen, setIsOpen: setIsSettingsOpen })}
{/** Liste des filtres appliqués et bouton d'export */}
)}
- >
+
);
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionCard.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionCard.tsx
index 968435debd..20bb480b0f 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionCard.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionCard.tsx
@@ -7,8 +7,8 @@ import { useState } from 'react';
import { QueryKey } from 'react-query';
import BadgePriorite from '../../components/BadgePriorite';
import BadgeStatut from '../../components/BadgeStatut';
-import { generateTitle } from '../data/utils';
import ModaleSuppression from '../Header/actions/ModaleSuppression';
+import { generateTitle } from '../data/utils';
import FicheActionFooterInfo from './FicheActionFooterInfo';
import ModifierFicheModale from './ModifierFicheModale';
@@ -118,7 +118,7 @@ const FicheActionCard = ({
dataTest="FicheActionCarte"
id={carteId}
className={classNames(
- 'h-full px-4 py-[1.125rem] !gap-3 !text-grey-8 !shadow-none transition',
+ 'h-full !p-4 !gap-2 !text-grey-8 !shadow-none transition',
{
'hover:border-primary-3 hover:!bg-primary-1': !isNotClickable,
}
@@ -155,23 +155,27 @@ const FicheActionCard = ({
}
footer={
// Bas de la carte
-
- {/* Date de dernière modification */}
- {!!ficheAction.modifiedAt && (
-
- Modifié {getModifiedSince(ficheAction.modifiedAt)}
-
- )}
-
+
{/* Personnes pilote et date de fin prévisionnelle */}
+
+ {/* Date de dernière modification */}
+ {!!ficheAction.modifiedAt && (
+ <>
+
+
+ Modifié {getModifiedSince(ficheAction.modifiedAt)}
+
+ >
+ )}
}
>
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionFooterInfo.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionFooterInfo.tsx
index bf3b0bdd01..734eb6f8d5 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionFooterInfo.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/Carte/FicheActionFooterInfo.tsx
@@ -1,80 +1,109 @@
import { Personne } from '@/api/collectivites';
import { getTextFormattedDate } from '@/app/utils/formatUtils';
+import { Tag } from '@/domain/collectivites';
import { Icon, Tooltip } from '@/ui';
import classNames from 'classnames';
import { isBefore, startOfToday } from 'date-fns';
-import { Fragment } from 'react';
type FicheActionFooterInfoProps = {
pilotes: Personne[] | null | undefined;
+ services: Tag[] | null | undefined;
dateDeFin: string | null | undefined;
ameliorationContinue: boolean | null | undefined;
};
const FicheActionFooterInfo = ({
pilotes,
+ services,
dateDeFin,
ameliorationContinue,
}: FicheActionFooterInfoProps) => {
const hasPilotes = !!pilotes && pilotes.length > 0;
+ const hasServices = !!services && services.length > 0;
const hasDateDeFin = !!dateDeFin;
- if (!hasPilotes && !hasDateDeFin && !ameliorationContinue) return null;
-
const isLate = hasDateDeFin && isBefore(new Date(dateDeFin), startOfToday());
return (
-
- {/* Personnes pilote */}
- {hasPilotes && (
-
-
- {pilotes[0].nom}
- {pilotes.length > 1 && (
-
- {pilotes.map((pilote, i) => (
- {pilote.nom}
- ))}
-
- }
- >
-
- +{pilotes.length - 1}
-
-
- )}
-
- )}
-
+
{/* Date de fin prévisionnelle */}
{!!dateDeFin && (
-
- {hasPilotes &&
}
-
-
- {getTextFormattedDate({
- date: dateDeFin,
- shortMonth: true,
- })}
-
-
+
+
+ {getTextFormattedDate({
+ date: dateDeFin,
+ shortMonth: true,
+ })}
+
)}
{/* Action récurrente */}
{!hasDateDeFin && ameliorationContinue && (
-
- {hasPilotes &&
}
-
-
- Tous les ans
+
+
+ Tous les ans
+
+ )}
+
+ {/* Personnes pilote */}
+ {hasPilotes && (
+ <>
+ {(hasDateDeFin || ameliorationContinue) && (
+
+ )}
+
+
+ {pilotes[0].nom}
+ {pilotes.length > 1 && (
+
+ {pilotes.map((pilote, i) => (
+ {pilote.nom}
+ ))}
+
+ }
+ >
+
+ +{pilotes.length - 1}
+
+
+ )}
+
+ >
+ )}
+
+ {/* Services pilote */}
+ {hasServices && (
+ <>
+ {(hasDateDeFin || ameliorationContinue || hasPilotes) && (
+
+ )}{' '}
+
+
+ {services[0].nom}
+ {services.length > 1 && (
+
+ {services.map((service, i) => (
+ {service.nom}
+ ))}
+
+ }
+ >
+
+ +{services.length - 1}
+
+
+ )}
-
+ >
)}
);
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/FichesLiees/ModaleFichesLiees.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/FichesLiees/ModaleFichesLiees.tsx
index 83ec36f38f..4d5e9dd7f6 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/FichesLiees/ModaleFichesLiees.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/FicheAction/FichesLiees/ModaleFichesLiees.tsx
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
type ModaleFichesLieesProps = {
isOpen: boolean;
setIsOpen: (opened: boolean) => void;
- currentFicheId: number;
+ currentFicheId: number | null;
linkedFicheIds: number[];
updateLinkedFicheIds: (ficheIds: number[]) => void;
};
diff --git a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/ToutesLesFichesAction/FichesActionListe.tsx b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/ToutesLesFichesAction/FichesActionListe.tsx
index 0754829b50..c6a3066a65 100644
--- a/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/ToutesLesFichesAction/FichesActionListe.tsx
+++ b/app.territoiresentransitions.react/src/app/pages/collectivite/PlansActions/ToutesLesFichesAction/FichesActionListe.tsx
@@ -1,5 +1,5 @@
import classNames from 'classnames';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import {
FetchOptions,
@@ -23,6 +23,7 @@ import FilterBadges, {
CustomFilterBadges,
useFiltersToBadges,
} from '@/app/ui/shared/filters/filter-badges';
+import { isEqual } from 'es-toolkit';
import { FicheResume } from 'packages/api/src/plan-actions';
import ActionsGroupeesMenu from '../ActionsGroupees/ActionsGroupeesMenu';
import EmptyFichePicto from '../FicheAction/FichesLiees/EmptyFichePicto';
@@ -60,7 +61,7 @@ const sortByOptions: sortByOptionsType[] = [
];
type Props = {
- settings: (openState: OpenState) => React.ReactNode;
+ settings?: (openState: OpenState) => React.ReactNode;
filtres: Filtre;
customFilterBadges?: CustomFilterBadges;
resetFilters?: () => void;
@@ -68,6 +69,7 @@ type Props = {
sortSettings?: SortFicheActionSettings;
enableGroupedActions?: boolean;
isReadOnly?: boolean;
+ containerClassName?: string;
};
/** Liste de fiches action avec tri et options de fitlre */
@@ -82,9 +84,12 @@ const FichesActionListe = ({
maxNbOfCards = 15,
enableGroupedActions = false,
isReadOnly,
+ containerClassName,
}: Props) => {
const collectiviteId = useCollectiviteId();
+ const filtresLocal = useRef(filtres);
+
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isGroupedActionsOn, setIsGroupedActionsOn] = useState(false);
const [selectedFiches, setSelectedFiches] = useState
([]);
@@ -153,7 +158,10 @@ const FichesActionListe = ({
};
useEffect(() => {
- setCurrentPage(1);
+ if (!isEqual(filtres, filtresLocal.current)) {
+ filtresLocal.current = filtres;
+ setCurrentPage(1);
+ }
}, [filtres]);
useEffect(() => {
@@ -198,9 +206,14 @@ const FichesActionListe = ({
)}
{hasFiches && (
- <>
-
-
+
+
+
{/** Tri */}
@@ -257,7 +270,7 @@ const FichesActionListe = ({
displaySize="sm"
/>
{/** Bouton d'édition des filtres (une modale avec bouton ou un ButtonMenu) */}
- {settings({
+ {settings?.({
isOpen: isSettingsOpen,
setIsOpen: setIsSettingsOpen,
})}
@@ -365,7 +378,7 @@ const FichesActionListe = ({
)}
- >
+
)}
>
);
diff --git a/packages/api/src/collectivites/shared/domain/filtre-ressource-liees.schema.ts b/packages/api/src/collectivites/shared/domain/filtre-ressource-liees.schema.ts
index 4c2f2307a3..697dfe2a2f 100644
--- a/packages/api/src/collectivites/shared/domain/filtre-ressource-liees.schema.ts
+++ b/packages/api/src/collectivites/shared/domain/filtre-ressource-liees.schema.ts
@@ -17,6 +17,8 @@ export const filtreRessourceLieesSchema = z.object({
financeurIds: z.coerce.number().array().optional(),
partenaireIds: z.coerce.number().array().optional(),
libreTagsIds: z.coerce.number().array().optional(),
+
+ indicateurIds: z.coerce.number().array().optional(),
});
export type FiltreRessourceLiees = z.infer
;
diff --git a/packages/api/src/indicateurs/domain/fetch-options.schema.ts b/packages/api/src/indicateurs/domain/fetch-options.schema.ts
index 6106407c09..ac769e1f60 100644
--- a/packages/api/src/indicateurs/domain/fetch-options.schema.ts
+++ b/packages/api/src/indicateurs/domain/fetch-options.schema.ts
@@ -24,6 +24,7 @@ export type FiltreSpecifique = z.infer;
*/
export const filtreSchema = filtreRessourceLieesSchema
.pick({
+ indicateurIds: true,
thematiqueIds: true,
planActionIds: true,
utilisateurPiloteIds: true,