From 71d63d2f55fb53eb1483b6b0670f134a47241f28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?BITARD=20Micha=C3=ABl?= <michael.bitard@beta.gouv.fr>
Date: Thu, 6 Mar 2025 16:58:58 +0000
Subject: [PATCH] =?UTF-8?q?fix(phases):=20on=20affiche=20la=20liste=20des?=
 =?UTF-8?q?=20d=C3=A9marches=20quand=20on=20a=20plusieurs=20d=C3=A9marches?=
 =?UTF-8?q?=20sans=20aucune=20phase=20(pub/pnm-public/camino!1658)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...AvecUnOctroiEnConstructionEtUnTravaux.html | 11 +++-
 packages/ui/src/components/titre.tsx          |  8 ++-
 .../ui/src/components/titre/phase.test.ts     | 57 ++++++++++++++++++-
 packages/ui/src/components/titre/phase.ts     |  9 ++-
 .../titre/titre-timeline.stories.tsx          | 17 ++++++
 ...ies_snapshots_MultipleDemarcheNoPhase.html | 12 ++++
 .../src/components/titre/titre-timeline.tsx   | 33 ++++++++++-
 7 files changed, 139 insertions(+), 8 deletions(-)
 create mode 100644 packages/ui/src/components/titre/titre-timeline.stories_snapshots_MultipleDemarcheNoPhase.html

diff --git a/packages/ui/src/components/titre.stories_snapshots_TitreAvecUnOctroiEnConstructionEtUnTravaux.html b/packages/ui/src/components/titre.stories_snapshots_TitreAvecUnOctroiEnConstructionEtUnTravaux.html
index d3be05d16..d514dbc3a 100644
--- a/packages/ui/src/components/titre.stories_snapshots_TitreAvecUnOctroiEnConstructionEtUnTravaux.html
+++ b/packages/ui/src/components/titre.stories_snapshots_TitreAvecUnOctroiEnConstructionEtUnTravaux.html
@@ -40,7 +40,16 @@
     </div>
     <div class="fr-mb-3w fr-mt-3w" style="height: 1px; width: 100%; background-color: var(--grey-900-175);"></div>
     <!---->
-    <!---->
+    <div class="fr-pt-4w fr-pb-4w">
+      <h2>Démarches</h2>
+      <div class="fr-alert fr-alert--warning">
+        <p class="fr-alert__title fr-h4">Plusieurs démarches sans phase</p>Nous affichons toutes les démarches en mode dégradé, normalement, on ne peut avoir plusieurs démarches que si au moins l'une d'entre elle a une phase.<br><strong>Il faudrait très probablement que l'Octroi aille jusqu'au bout avant de créer d'autres démarches.</strong>
+      </div>
+      <ul>
+        <li><a href="/mocked-href" title="oct" aria-label="oct">Octroi</a></li>
+        <li><a href="/mocked-href" title="dam" aria-label="dam">Déclaration d'arrêt définitif des travaux</a></li>
+      </ul>
+    </div>
     <div>
       <div class="fr-grid-row fr-grid-row--middle">
         <h2 style="margin: 0px;">Octroi</h2>
diff --git a/packages/ui/src/components/titre.tsx b/packages/ui/src/components/titre.tsx
index 93f2cea3d..311cb2146 100644
--- a/packages/ui/src/components/titre.tsx
+++ b/packages/ui/src/components/titre.tsx
@@ -164,7 +164,11 @@ export const PureTitre = defineComponent<Props>(props => {
 
         let demarcheSlug = props.currentDemarcheSlug
 
-        if ((isNullOrUndefinedOrEmpty(demarcheSlug) || isNullOrUndefined(data.demarches.find(({ slug }) => slug === props.currentDemarcheSlug))) && phases.value.length > 0) {
+        if (
+          (isNullOrUndefinedOrEmpty(demarcheSlug) || isNullOrUndefined(data.demarches.find(({ slug }) => slug === props.currentDemarcheSlug))) &&
+          !('erreur' in phases.value) &&
+          phases.value.length > 0
+        ) {
           demarcheSlug = phases.value[phases.value.length - 1][phases.value[phases.value.length - 1].length - 1].slug
         }
 
@@ -324,7 +328,7 @@ export const PureTitre = defineComponent<Props>(props => {
   }
 
   const hasNoPhases = computed<boolean>(() => {
-    return phases.value.length === 0
+    return !('erreur' in phases.value) && phases.value.length === 0
   })
 
   const addDemarchePopup = ref<boolean>(false)
diff --git a/packages/ui/src/components/titre/phase.test.ts b/packages/ui/src/components/titre/phase.test.ts
index aa2e9c16c..7d5269c6f 100644
--- a/packages/ui/src/components/titre/phase.test.ts
+++ b/packages/ui/src/components/titre/phase.test.ts
@@ -5,6 +5,62 @@ import { demarcheIdValidator, demarcheSlugValidator } from 'camino-common/src/de
 import { TitreGetDemarche } from 'camino-common/src/titres'
 import { ETAPE_IS_NOT_BROUILLON, etapeIdValidator, etapeSlugValidator } from 'camino-common/src/etape'
 
+test('phase en erreur', () => {
+  const demarches: TitreGetDemarche[] = [
+    {
+      id: demarcheIdValidator.parse('idMut'),
+      slug: demarcheSlugValidator.parse('slug-mut'),
+      description: null,
+      etapes: [],
+      demarche_type_id: 'mut',
+      demarche_statut_id: 'acp',
+      demarche_date_debut: null,
+      demarche_date_fin: null,
+      ordre: 1,
+    },
+    {
+      id: demarcheIdValidator.parse('idDemPro'),
+      slug: demarcheSlugValidator.parse('dem-slug-pro'),
+      description: null,
+      etapes: [],
+      demarche_type_id: 'pro',
+      demarche_statut_id: 'acp',
+      demarche_date_debut: null,
+      demarche_date_fin: null,
+      ordre: 2,
+    },
+  ]
+  const actual = phaseWithAlterations(demarches, toCaminoDate('2024-07-11'))
+  expect(actual).toMatchInlineSnapshot(`
+  {
+    "demarches": [
+      {
+        "demarche_date_debut": null,
+        "demarche_date_fin": null,
+        "demarche_statut_id": "acp",
+        "demarche_type_id": "mut",
+        "description": null,
+        "etapes": [],
+        "id": "idMut",
+        "ordre": 1,
+        "slug": "slug-mut",
+      },
+      {
+        "demarche_date_debut": null,
+        "demarche_date_fin": null,
+        "demarche_statut_id": "acp",
+        "demarche_type_id": "pro",
+        "description": null,
+        "etapes": [],
+        "id": "idDemPro",
+        "ordre": 2,
+        "slug": "dem-slug-pro",
+      },
+    ],
+    "erreur": "plusieurs démarches sans phase",
+  }
+`)
+})
 test('phase acceptée et publiée', () => {
   const demarches: TitreGetDemarche[] = [
     {
@@ -63,7 +119,6 @@ test('phase acceptée et publiée', () => {
     },
   ]
   const actual = phaseWithAlterations(demarches, toCaminoDate('2024-07-11'))
-  expect(actual[0]).toHaveLength(2)
   expect(actual).toMatchInlineSnapshot(`
     [
       [
diff --git a/packages/ui/src/components/titre/phase.ts b/packages/ui/src/components/titre/phase.ts
index b075f6623..5eb476c61 100644
--- a/packages/ui/src/components/titre/phase.ts
+++ b/packages/ui/src/components/titre/phase.ts
@@ -14,7 +14,9 @@ type PhaseWithDateDebut = OmitDistributive<TitreGetDemarche, 'demarche_date_debu
 
 type DemarcheAlteration = TitreGetDemarche & { date_etape_decision_ok: CaminoDate; events: TitreTimelineEvents[] }
 
-export type PhaseWithAlterations = [PhaseWithDateDebut, ...DemarcheAlteration[]][] | [[OmitDistributive<TitreGetDemarche, 'demarche_date_debut'> & { demarche_date_debut: null }]]
+const erreurPlusieursDemarches = 'plusieurs démarches sans phase' as const
+export type PhaseErreurs = { erreur: string; demarches: Pick<TitreGetDemarche, 'slug' | 'demarche_type_id'>[] }
+export type PhaseWithAlterations = [PhaseWithDateDebut, ...DemarcheAlteration[]][] | [[OmitDistributive<TitreGetDemarche, 'demarche_date_debut'> & { demarche_date_debut: null }]] | PhaseErreurs
 export const phaseWithAlterations = (demarches: TitreGetDemarche[], currentDate: CaminoDate): PhaseWithAlterations => {
   if (isNullOrUndefinedOrEmpty(demarches)) {
     return []
@@ -27,7 +29,10 @@ export const phaseWithAlterations = (demarches: TitreGetDemarche[], currentDate:
   const demarchesUsed: DemarcheSlug[] = simplePhases.map(({ slug }) => slug)
   if (isNullOrUndefinedOrEmpty(simplePhases)) {
     if (demarches.length > 1) {
-      console.error('Le titre a plusieurs démarches sans phase')
+      return {
+        erreur: erreurPlusieursDemarches,
+        demarches,
+      }
     }
 
     return [[{ ...demarches[0], demarche_date_debut: null }]]
diff --git a/packages/ui/src/components/titre/titre-timeline.stories.tsx b/packages/ui/src/components/titre/titre-timeline.stories.tsx
index 7cacdd481..1d5ca401a 100644
--- a/packages/ui/src/components/titre/titre-timeline.stories.tsx
+++ b/packages/ui/src/components/titre/titre-timeline.stories.tsx
@@ -3,6 +3,7 @@ import { demarcheSlugValidator } from 'camino-common/src/demarche'
 import { toCaminoDate } from 'camino-common/src/date'
 import { Phase, TitreTimeline } from './titre-timeline'
 import { titreSlugValidator } from 'camino-common/src/validators/titres'
+import { DEMARCHES_TYPES_IDS } from 'camino-common/src/static/demarchesTypes'
 
 const meta: Meta = {
   title: 'Components/Titre/Timeline',
@@ -261,6 +262,22 @@ export const OneDemarcheNoPhase: StoryFn = () => (
   </div>
 )
 
+export const MultipleDemarcheNoPhase: StoryFn = () => (
+  <div>
+    <TitreTimeline
+      titreSlug={titreSlugValidator.parse('slug-titre')}
+      phasesWithAlterations={{
+        erreur: 'plusieurs démarches sans phase',
+        demarches: [
+          { slug: demarcheSlugValidator.parse('slug-id1'), demarche_type_id: DEMARCHES_TYPES_IDS.Octroi },
+          { slug: demarcheSlugValidator.parse('slug-id2'), demarche_type_id: DEMARCHES_TYPES_IDS.Prolongation },
+        ],
+      }}
+      currentDemarcheSlug={demarcheSlugValidator.parse('slug-demarche2')}
+    />
+  </div>
+)
+
 export const NoDemarcheNoPhase: StoryFn = () => (
   <div>
     <TitreTimeline titreSlug={titreSlugValidator.parse('slug-titre')} phasesWithAlterations={[]} currentDemarcheSlug={demarcheSlugValidator.parse('slug-demarche2')} />
diff --git a/packages/ui/src/components/titre/titre-timeline.stories_snapshots_MultipleDemarcheNoPhase.html b/packages/ui/src/components/titre/titre-timeline.stories_snapshots_MultipleDemarcheNoPhase.html
new file mode 100644
index 000000000..a6e862c04
--- /dev/null
+++ b/packages/ui/src/components/titre/titre-timeline.stories_snapshots_MultipleDemarcheNoPhase.html
@@ -0,0 +1,12 @@
+<div>
+  <div>
+    <h2>Démarches</h2>
+    <div class="fr-alert fr-alert--warning">
+      <p class="fr-alert__title fr-h4">Plusieurs démarches sans phase</p>Nous affichons toutes les démarches en mode dégradé, normalement, on ne peut avoir plusieurs démarches que si au moins l'une d'entre elle a une phase.<br><strong>Il faudrait très probablement que l'Octroi aille jusqu'au bout avant de créer d'autres démarches.</strong>
+    </div>
+    <ul>
+      <li><a href="/mocked-href" title="oct" aria-label="oct">Octroi</a></li>
+      <li><a href="/mocked-href" title="pro" aria-label="pro">Prolongation</a></li>
+    </ul>
+  </div>
+</div>
\ No newline at end of file
diff --git a/packages/ui/src/components/titre/titre-timeline.tsx b/packages/ui/src/components/titre/titre-timeline.tsx
index 78a7d348e..dd971d593 100644
--- a/packages/ui/src/components/titre/titre-timeline.tsx
+++ b/packages/ui/src/components/titre/titre-timeline.tsx
@@ -11,13 +11,15 @@ import { TitreSlug } from 'camino-common/src/validators/titres'
 import { TravauxIcone } from './travaux-icone'
 import { DsfrSeparator } from '../_ui/dsfr-separator'
 import { capitalize } from 'camino-common/src/strings'
+import { PhaseErreurs } from './phase'
+import { Alert } from '../_ui/alert'
 
 type NoPhase = [[Pick<PhaseWithDateDebut, 'slug' | 'demarche_type_id'> & { demarche_date_debut: null }]]
 export type Phase = [PhaseWithDateDebut, ...DemarcheAlteration[]][]
 type TitreTimelineEvents = Pick<TitreGetDemarche, 'slug' | 'demarche_type_id'> & { first_etape_date: CaminoDate | null }
 type Props = {
   titreSlug: TitreSlug
-  phasesWithAlterations: Phase | NoPhase
+  phasesWithAlterations: Phase | NoPhase | PhaseErreurs
   currentDemarcheSlug: DemarcheSlug
   class?: HTMLAttributes['class']
 }
@@ -39,6 +41,33 @@ const isNoPhase = (phase: Phase | NoPhase): phase is NoPhase => {
 const minWidth = 200
 
 export const TitreTimeline: FunctionalComponent<Props> = props => {
+  if ('erreur' in props.phasesWithAlterations) {
+    return (
+      <div>
+        <h2>Démarches</h2>
+        <Alert
+          type="warning"
+          title={'Plusieurs démarches sans phase'}
+          description={
+            <>
+              Nous affichons toutes les démarches en mode dégradé, normalement, on ne peut avoir plusieurs démarches que si au moins l'une d'entre elle a une phase.
+              <br />
+              <strong>Il faudrait très probablement que l'Octroi aille jusqu'au bout avant de créer d'autres démarches.</strong>
+            </>
+          }
+        />
+        <ul>
+          {props.phasesWithAlterations.demarches.map(demarche => (
+            <li>
+              <CaminoRouterLink isDisabled={false} to={{ name: 'titre', params: { id: props.titreSlug }, query: { demarcheSlug: demarche.slug } }} title={demarche.demarche_type_id}>
+                {capitalize(DemarchesTypes[demarche.demarche_type_id].nom)}
+              </CaminoRouterLink>
+            </li>
+          ))}
+        </ul>
+      </div>
+    )
+  }
   if (props.phasesWithAlterations.length === 0 || isNoPhase(props.phasesWithAlterations)) {
     return null
   }
@@ -154,7 +183,7 @@ export const TitreTimeline: FunctionalComponent<Props> = props => {
                         </Fragment>
                       ))}
                     </div>
-                    {index !== props.phasesWithAlterations.length - 1 ? <div style={{ border: '2px solid black' }}></div> : null}
+                    {!('erreur' in props.phasesWithAlterations) && index !== props.phasesWithAlterations.length - 1 ? <div style={{ border: '2px solid black' }}></div> : null}
                   </Fragment>
                 ))}
               </div>
-- 
GitLab