22
33import { useState , useEffect , useRef } from "react" ;
44import { updateContactInfoAction } from "../actions/contact-info.actions" ;
5+ import { getActeursLocauxDisponibles , type ActeursLocaux } from "../actions/acteurs-locaux.actions" ;
56import { SourceAcquisition , SOURCE_ACQUISITION_LABELS } from "@/shared/domain/value-objects" ;
67
78interface ContactInfoModalProps {
@@ -11,11 +12,9 @@ interface ContactInfoModalProps {
1112 onSuccess : ( ) => void ;
1213}
1314
14- // À l'inscription, l'adresse du ménage n'est pas encore connue (la simulation RGA
15- // n'a pas eu lieu). On propose donc l'option générique ECFR pour couvrir tous les
16- // acteurs locaux (DDT, AMO, Aller-vers). Ces acteurs pourront être proposés
17- // nominativement après la simulation, quand le département sera connu.
18- const SOURCE_OPTIONS : SourceAcquisition [ ] = [
15+ // Options statiques affichées quand aucun acteur local n'est identifié (adresse inconnue).
16+ // ECFR remplace nominativement AMO/Aller-vers quand le département n'est pas encore connu.
17+ const SOURCE_OPTIONS_STATIQUES : SourceAcquisition [ ] = [
1918 SourceAcquisition . ECFR ,
2019 SourceAcquisition . FLYERS ,
2120 SourceAcquisition . MEDIAS ,
@@ -26,15 +25,55 @@ const SOURCE_OPTIONS: SourceAcquisition[] = [
2625 SourceAcquisition . AUTRE ,
2726] ;
2827
28+ // Options statiques affichées après ECFR quand les acteurs locaux sont connus.
29+ const SOURCE_OPTIONS_COMPLEMENTAIRES : SourceAcquisition [ ] = [
30+ SourceAcquisition . FLYERS ,
31+ SourceAcquisition . MEDIAS ,
32+ SourceAcquisition . BULLETIN_COMMUNAL ,
33+ SourceAcquisition . PROS_BATIMENT_IMMOBILIER ,
34+ SourceAcquisition . REUNION_PUBLIQUE_SALON ,
35+ SourceAcquisition . MOTEUR_RECHERCHE ,
36+ SourceAcquisition . AUTRE ,
37+ ] ;
38+
39+ // Valeur encodée pour une option dynamique : "type::nom" (e.g. "amo::Association ABC")
40+ // permet de récupérer à la fois la valeur enum et le nom de la structure.
41+ function encodeOptionDynamique ( type : "amo" | "aller_vers" , nom : string ) : string {
42+ return `${ type } ::${ nom } ` ;
43+ }
44+
45+ function decodeOptionDynamique ( encoded : string ) : { type : SourceAcquisition ; precision : string } | null {
46+ const sep = encoded . indexOf ( "::" ) ;
47+ if ( sep === - 1 ) return null ;
48+ const type = encoded . substring ( 0 , sep ) ;
49+ const precision = encoded . substring ( sep + 2 ) ;
50+ if ( type === "amo" ) return { type : SourceAcquisition . AMO , precision } ;
51+ if ( type === "aller_vers" ) return { type : SourceAcquisition . ALLER_VERS , precision } ;
52+ return null ;
53+ }
54+
2955export default function ContactInfoModal ( { isOpen, defaultEmail, onClose, onSuccess } : ContactInfoModalProps ) {
3056 const [ email , setEmail ] = useState ( defaultEmail || "" ) ;
3157 const [ telephone , setTelephone ] = useState ( "" ) ;
32- const [ sourceAcquisition , setSourceAcquisition ] = useState < string > ( "" ) ;
58+ const [ selectValue , setSelectValue ] = useState < string > ( "" ) ;
3359 const [ sourceAcquisitionPrecision , setSourceAcquisitionPrecision ] = useState ( "" ) ;
3460 const [ error , setError ] = useState < string | null > ( null ) ;
3561 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
62+ const [ acteursLocaux , setActeursLocaux ] = useState < ActeursLocaux | null > ( null ) ;
3663 const dialogRef = useRef < HTMLDialogElement > ( null ) ;
3764
65+ const hasDynamicOptions =
66+ acteursLocaux !== null && ( acteursLocaux . amos . length > 0 || acteursLocaux . allersVers . length > 0 ) ;
67+
68+ // Charger les acteurs locaux au montage
69+ useEffect ( ( ) => {
70+ getActeursLocauxDisponibles ( ) . then ( ( result ) => {
71+ if ( result . success ) {
72+ setActeursLocaux ( result . data ) ;
73+ }
74+ } ) ;
75+ } , [ ] ) ;
76+
3877 // Gérer l'ouverture/fermeture via le DSFR
3978 useEffect ( ( ) => {
4079 const dialog = dialogRef . current ;
@@ -88,12 +127,17 @@ export default function ContactInfoModal({ isOpen, defaultEmail, onClose, onSucc
88127 return ;
89128 }
90129
91- if ( ! sourceAcquisition ) {
130+ if ( ! selectValue ) {
92131 setError ( "Merci d'indiquer comment vous avez connu le fonds" ) ;
93132 return ;
94133 }
95134
96- if ( sourceAcquisition === SourceAcquisition . AUTRE && ! sourceAcquisitionPrecision . trim ( ) ) {
135+ // Décoder la valeur sélectionnée (option dynamique ou statique)
136+ const decoded = decodeOptionDynamique ( selectValue ) ;
137+ const sourceAcquisition = decoded ? decoded . type : ( selectValue as SourceAcquisition ) ;
138+ const precision = decoded ? decoded . precision : sourceAcquisitionPrecision . trim ( ) ;
139+
140+ if ( sourceAcquisition === SourceAcquisition . AUTRE && ! precision ) {
97141 setError ( "Merci de préciser comment vous avez connu le fonds" ) ;
98142 return ;
99143 }
@@ -104,8 +148,7 @@ export default function ContactInfoModal({ isOpen, defaultEmail, onClose, onSucc
104148 emailContact : email . trim ( ) ,
105149 telephone : telephone . trim ( ) ,
106150 sourceAcquisition,
107- sourceAcquisitionPrecision :
108- sourceAcquisition === SourceAcquisition . AUTRE ? sourceAcquisitionPrecision . trim ( ) : null ,
151+ sourceAcquisitionPrecision : precision || null ,
109152 } ) ;
110153
111154 setIsSubmitting ( false ) ;
@@ -117,6 +160,8 @@ export default function ContactInfoModal({ isOpen, defaultEmail, onClose, onSucc
117160 }
118161 } ;
119162
163+ const showPrecisionField = selectValue === SourceAcquisition . AUTRE && ! decodeOptionDynamique ( selectValue ) ;
164+
120165 return (
121166 < dialog ref = { dialogRef } id = "modal-contact-info" className = "fr-modal" aria-labelledby = "modal-contact-info-title" >
122167 < div className = "fr-container fr-container--fluid fr-container-md" >
@@ -191,18 +236,53 @@ export default function ContactInfoModal({ isOpen, defaultEmail, onClose, onSucc
191236 className = "fr-select"
192237 id = "contact-source-acquisition"
193238 name = "sourceAcquisition"
194- value = { sourceAcquisition }
195- onChange = { ( e ) => setSourceAcquisition ( e . target . value ) } >
239+ value = { selectValue }
240+ onChange = { ( e ) => {
241+ setSelectValue ( e . target . value ) ;
242+ setSourceAcquisitionPrecision ( "" ) ;
243+ } } >
196244 < option value = "" > Sélectionnez une option</ option >
197- { SOURCE_OPTIONS . map ( ( value ) => (
198- < option key = { value } value = { value } >
199- { SOURCE_ACQUISITION_LABELS [ value ] }
200- </ option >
201- ) ) }
245+
246+ { hasDynamicOptions && (
247+ < >
248+ { acteursLocaux ! . amos . length > 0 && (
249+ < optgroup label = "AMO (Assistant à Maîtrise d'Ouvrage)" >
250+ { acteursLocaux ! . amos . map ( ( amo ) => (
251+ < option key = { amo . id } value = { encodeOptionDynamique ( "amo" , amo . nom ) } >
252+ { amo . nom }
253+ </ option >
254+ ) ) }
255+ </ optgroup >
256+ ) }
257+ { acteursLocaux ! . allersVers . length > 0 && (
258+ < optgroup label = "Équipe Aller-vers" >
259+ { acteursLocaux ! . allersVers . map ( ( av ) => (
260+ < option key = { av . id } value = { encodeOptionDynamique ( "aller_vers" , av . nom ) } >
261+ { av . nom }
262+ </ option >
263+ ) ) }
264+ </ optgroup >
265+ ) }
266+ < optgroup label = "Autre canal" >
267+ { SOURCE_OPTIONS_COMPLEMENTAIRES . map ( ( value ) => (
268+ < option key = { value } value = { value } >
269+ { SOURCE_ACQUISITION_LABELS [ value ] }
270+ </ option >
271+ ) ) }
272+ </ optgroup >
273+ </ >
274+ ) }
275+
276+ { ! hasDynamicOptions &&
277+ SOURCE_OPTIONS_STATIQUES . map ( ( value ) => (
278+ < option key = { value } value = { value } >
279+ { SOURCE_ACQUISITION_LABELS [ value ] }
280+ </ option >
281+ ) ) }
202282 </ select >
203283 </ div >
204284
205- { sourceAcquisition === SourceAcquisition . AUTRE && (
285+ { showPrecisionField && (
206286 < div className = "fr-form-group fr-mt-2w" >
207287 < label className = "fr-label" htmlFor = "contact-source-acquisition-precision" >
208288 < strong > Pouvez-vous préciser ?</ strong >
0 commit comments