Amélioration de la modale Investiisement
This commit is contained in:
1 parent
44b8e5eda0
commit
c7bbe5dee6
6 files changed
+698
-810
No files matched your search
@@ -0,0 +1,485 @@
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import Modal from './Modal.jsx';
|
||||
import InvSelect from './InvSelect.jsx';
|
||||
import CountrySelect from './CountrySelect.jsx';
|
||||
import { addMonthsFE, addMonthsFinDeMoisFE, lastDayOfMonthFE, dayOfMonth } from '../utils/dateEcheances.js';
|
||||
|
||||
/**
|
||||
* InvestissementFormModal — formulaire partagé de création/édition d'un investissement.
|
||||
*
|
||||
* Utilisé par Investissements.jsx (création + édition depuis la liste) et
|
||||
* InvestissementDetail.jsx (édition depuis la fiche). Comme RembFormModal, ce composant
|
||||
* est volontairement "bête" côté état : le parent garde la propriété du form (state,
|
||||
* effets de bord au changement de plateforme comme le préremplissage des catégories/secteurs
|
||||
* ou la résolution du détenteur, chargement des comptes du détenteur…). Ce composant se
|
||||
* contente des cascades de champs qui ne dépendent que du form lui-même (dates, durée, type
|
||||
* de prêt, méthode de remboursement) et du rendu JSX.
|
||||
*
|
||||
* Depuis le 29/08/26, l'affichage se fait en 3 étapes (assistant) plutôt qu'en un seul
|
||||
* écran : (1) Informations générales, (2) Données financières et échéancier,
|
||||
* (3) Catégories, secteurs, autres. L'étape courante (`step`) est un état interne au
|
||||
* composant, non exposé au parent, et se réinitialise à 1 à chaque ouverture de la modale
|
||||
* (transition `open` false → true). "Suivant" valide les champs obligatoires (natifs
|
||||
* `required`) de l'étape affichée via `reportValidity()` avant d'avancer ; "Précédent" ne
|
||||
* valide rien. Le bouton Supprimer (édition) reste visible sur les 3 étapes.
|
||||
*
|
||||
* Props :
|
||||
* open, onClose — état d'ouverture
|
||||
* editingId — id en édition, falsy si création (contrôle le titre,
|
||||
* l'affichage du champ Statut, et le libellé du bouton)
|
||||
* form — objet form courant (voir `empty`/`emptyForm` des pages appelantes)
|
||||
* onFieldChange(key, value) — pour les champs simples, sans effet de bord
|
||||
* onFormPatch(patchOrUpdater) — pour les cascades (objet complet à fusionner, ou
|
||||
* fonction (prev) => next pour les cas sensibles au Strict Mode)
|
||||
* err — message d'erreur à afficher
|
||||
* onSubmit — handler de soumission
|
||||
* onDelete, confirmingDelete, setConfirmingDelete
|
||||
* — flux de suppression optionnel (affiché si onDelete fourni)
|
||||
* plats — liste des plateformes
|
||||
* multiDetenteur — bool, affiche le détenteur de chaque plateforme dans le select
|
||||
* investisseurs — liste des détenteurs (select Détenteur, avec optgroups Famille/Entreprises)
|
||||
* comptesInvestisseur — comptes du détenteur courant (select Compte de réception, étape 2)
|
||||
* onPlateformeChange(platformId) — délégué au parent : résolution du détenteur, méthode héritée,
|
||||
* reset du compte, préremplissage catégories/secteurs
|
||||
* categoriesInv, secteursInv — listes pour les sélecteurs Catégories/Secteurs d'investissement
|
||||
* onCategoryAdded, onSectorAdded — callbacks après création d'une catégorie/d'un secteur via InvSelect
|
||||
* tauxRevisionWarning — JSX optionnel affiché sous le champ Taux annuel (étape 2)
|
||||
* dateCibleRevisionWarning — JSX optionnel affiché sous le champ Date cible (étape 2)
|
||||
* restructurationBlock — JSX optionnel affiché après le champ Date cible, étape 2 (pleine largeur)
|
||||
* width — largeur de la modale (défaut 720)
|
||||
*/
|
||||
export default function InvestissementFormModal({
|
||||
open, onClose, editingId,
|
||||
form, onFieldChange, onFormPatch,
|
||||
err, onSubmit, onDelete, confirmingDelete, setConfirmingDelete,
|
||||
plats = [], multiDetenteur = false,
|
||||
investisseurs = [],
|
||||
comptesInvestisseur = [],
|
||||
onPlateformeChange,
|
||||
categoriesInv = [], secteursInv = [],
|
||||
onCategoryAdded, onSectorAdded,
|
||||
tauxRevisionWarning = null,
|
||||
dateCibleRevisionWarning = null,
|
||||
restructurationBlock = null,
|
||||
width = 720,
|
||||
}) {
|
||||
// Vestige côté données : mémorise la dernière souscription valide saisie.
|
||||
// N'est lu nulle part dans le code actuel — conservé tel quel (écriture sans effet)
|
||||
// pour ne rien changer au comportement observable pendant le regroupement des deux formulaires.
|
||||
const lastValidSouscriptionRef = useRef('');
|
||||
|
||||
const formRef = useRef(null);
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
// Repart de l'étape 1 à chaque (ré)ouverture de la modale, création comme édition.
|
||||
useEffect(() => {
|
||||
if (open) setStep(1);
|
||||
}, [open]);
|
||||
|
||||
const stepTitles = [
|
||||
"Informations générales de l'investissement",
|
||||
'Données financières et échéancier',
|
||||
'Catégories, secteurs, autres',
|
||||
];
|
||||
|
||||
const goNext = () => {
|
||||
if (formRef.current && !formRef.current.reportValidity()) return;
|
||||
setStep(s => Math.min(3, s + 1));
|
||||
};
|
||||
const goPrev = () => setStep(s => Math.max(1, s - 1));
|
||||
|
||||
const platMethode = plats.find(p => String(p.id) === String(form.plateforme_id))?.methode_remboursement;
|
||||
const showFinDeMois = (form.type_remb === 'in_fine' || form.type_remb === 'differe')
|
||||
&& dayOfMonth(form.date_premiere_echeance) > 27;
|
||||
|
||||
const handleMethodeChange = (methode) => {
|
||||
const defaultCompte = methode === 'compte_courant'
|
||||
? (comptesInvestisseur.find(c => c.type === 'compte_courant') ?? comptesInvestisseur[0])
|
||||
: null;
|
||||
onFormPatch({ methode_remboursement: methode, nom_compte_courant: defaultCompte?.nom || '', compte_id: String(defaultCompte?.id || '') });
|
||||
};
|
||||
|
||||
const handleCompteChange = (compteId) => {
|
||||
const c = comptesInvestisseur.find(c => String(c.id) === compteId);
|
||||
onFormPatch({ compte_id: compteId, nom_compte_courant: c?.nom || '' });
|
||||
};
|
||||
|
||||
const handleTypeRembChange = (t) => {
|
||||
const next = {
|
||||
...form, type_remb: t,
|
||||
freq_interets: t === 'differe' ? 'in_fine' : (form.freq_interets === 'in_fine' ? 'mensuel' : form.freq_interets),
|
||||
};
|
||||
if (t === 'differe') {
|
||||
// Recalculer la date d'échéance depuis souscription+durée (prioritaire sur l'ancienne valeur)
|
||||
if (form.date_souscription && form.duree_mois) {
|
||||
const dpe = addMonthsFE(form.date_souscription, 1);
|
||||
const cible = addMonthsFE(dpe, Number(form.duree_mois) - 1);
|
||||
next.date_premiere_echeance = cible;
|
||||
next.date_cible = cible;
|
||||
} else if (form.date_premiere_echeance) {
|
||||
next.date_cible = form.date_premiere_echeance;
|
||||
}
|
||||
} else {
|
||||
if (form.date_souscription) {
|
||||
next.date_premiere_echeance = addMonthsFE(form.date_souscription, 1);
|
||||
if (next.date_premiere_echeance && form.duree_mois) {
|
||||
next.date_cible = addMonthsFE(next.date_premiere_echeance, Number(form.duree_mois) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
onFormPatch(next);
|
||||
};
|
||||
|
||||
const handleDureeChange = (duree) => {
|
||||
const next = { ...form, duree_mois: duree };
|
||||
if (duree) {
|
||||
if (form.type_remb === 'differe' && form.date_souscription) {
|
||||
const tempDpe = addMonthsFE(form.date_souscription, 1);
|
||||
const cible = addMonthsFE(tempDpe, Number(duree) - 1);
|
||||
next.date_premiere_echeance = cible;
|
||||
next.date_cible = cible;
|
||||
} else if (form.date_premiere_echeance) {
|
||||
next.date_cible = addMonthsFE(form.date_premiere_echeance, Number(duree) - 1);
|
||||
}
|
||||
}
|
||||
onFormPatch(next);
|
||||
};
|
||||
|
||||
const handleSouscriptionChange = (souscription) => {
|
||||
onFormPatch(prev => {
|
||||
const next = { ...prev, date_souscription: souscription };
|
||||
if (!souscription || souscription.length !== 10) return next;
|
||||
if (prev.type_remb === 'differe') {
|
||||
if (prev.duree_mois) {
|
||||
const tempDpe = addMonthsFE(souscription, 1);
|
||||
const cible = addMonthsFE(tempDpe, Number(prev.duree_mois) - 1);
|
||||
next.date_premiere_echeance = cible;
|
||||
next.date_cible = cible;
|
||||
}
|
||||
} else {
|
||||
// Pour in_fine / amortissable : recalcul systématique de premiere_echeance
|
||||
// (cohérent avec le comportement de "differe" et évite les faux négatifs
|
||||
// du test isAutoCalc quand premiere_echeance ne vaut pas exactement souscription+1m)
|
||||
next.date_premiere_echeance = addMonthsFE(souscription, 1);
|
||||
if (next.date_premiere_echeance && prev.duree_mois) {
|
||||
next.date_cible = addMonthsFE(next.date_premiere_echeance, Number(prev.duree_mois) - 1);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
if (souscription && souscription.length === 10) {
|
||||
lastValidSouscriptionRef.current = souscription;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDpeChange = (dpe) => {
|
||||
const next = { ...form, date_premiere_echeance: dpe };
|
||||
// Si le jour passe à ≤ 27 on désactive fin-de-mois
|
||||
if (dayOfMonth(dpe) <= 27) next.echeance_fin_de_mois = false;
|
||||
if (form.type_remb === 'differe') {
|
||||
next.date_cible = dpe;
|
||||
} else if (dpe && form.duree_mois) {
|
||||
next.date_cible = next.echeance_fin_de_mois
|
||||
? addMonthsFinDeMoisFE(dpe, Number(form.duree_mois) - 1)
|
||||
: addMonthsFE(dpe, Number(form.duree_mois) - 1);
|
||||
}
|
||||
onFormPatch(next);
|
||||
};
|
||||
|
||||
const handleFinDeMoisChange = (checked) => {
|
||||
onFormPatch(prev => {
|
||||
const next = { ...prev, echeance_fin_de_mois: checked };
|
||||
if (checked && prev.date_premiere_echeance) {
|
||||
const newDpe = lastDayOfMonthFE(prev.date_premiere_echeance);
|
||||
next.date_premiere_echeance = newDpe;
|
||||
if (prev.type_remb === 'differe') {
|
||||
next.date_cible = newDpe;
|
||||
} else if (newDpe && prev.duree_mois) {
|
||||
next.date_cible = addMonthsFinDeMoisFE(newDpe, Number(prev.duree_mois) - 1);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={editingId ? 'Modifier l\'investissement' : 'Nouvel investissement'}
|
||||
onClose={onClose}
|
||||
width={width}
|
||||
footer={
|
||||
<div style={{ display: 'flex', alignItems: 'center', width: '100%', gap: 8 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
{editingId && onDelete && (
|
||||
!confirmingDelete ? (
|
||||
<button className="danger" onClick={() => setConfirmingDelete(true)}>
|
||||
Supprimer
|
||||
</button>
|
||||
) : (
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ fontSize: 13, color: 'var(--danger, #ef4444)', whiteSpace: 'nowrap' }}>
|
||||
Supprimer définitivement ?
|
||||
</span>
|
||||
<button onClick={() => setConfirmingDelete(false)}>Non</button>
|
||||
<button className="danger" onClick={onDelete}>Oui, supprimer</button>
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{step === 1 && <button onClick={onClose}>Annuler</button>}
|
||||
{step > 1 && <button onClick={goPrev}>Précédent</button>}
|
||||
{step < 3 && <button className="primary" onClick={goNext}>Suivant</button>}
|
||||
{step === 3 && <button className="primary" onClick={onSubmit}>{editingId ? 'Enregistrer' : 'Créer'}</button>}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* ── Indicateur d'étape ── */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>
|
||||
Étape {step} : {stepTitles[step - 1]}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
{[1, 2, 3].map(n => (
|
||||
<div key={n} style={{
|
||||
flex: 1, height: 5, borderRadius: 4,
|
||||
background: n <= step ? 'var(--primary)' : 'var(--border)',
|
||||
}} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form ref={formRef} onSubmit={onSubmit}>
|
||||
{err && <div className="error">{err}</div>}
|
||||
<div className="form-grid">
|
||||
|
||||
{/* ══ Étape 1 : Informations générales ══ */}
|
||||
{step === 1 && (<>
|
||||
<div>
|
||||
<label>Plateforme *</label>
|
||||
<select required value={form.plateforme_id} onChange={e => onPlateformeChange(e.target.value)}>
|
||||
<option value="">—</option>
|
||||
{plats.map(p => <option key={p.id} value={p.id}>{p.nom}{multiDetenteur && p.investisseur_nom ? ` — ${p.investisseur_nom}` : ''}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Détenteur *</label>
|
||||
<select required value={form.investisseur_id} onChange={e => onFieldChange('investisseur_id', e.target.value)}>
|
||||
<option value="">—</option>
|
||||
{investisseurs.filter(i => i.type !== 'entreprise').length > 0 && (
|
||||
<optgroup label="Famille">
|
||||
{investisseurs.filter(i => i.type !== 'entreprise').map(i => (
|
||||
<option key={i.id} value={i.id}>{i.nom}{i.is_principal ? ' (principal)' : ''}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
{investisseurs.filter(i => i.type === 'entreprise').length > 0 && (
|
||||
<optgroup label="Entreprises">
|
||||
{investisseurs.filter(i => i.type === 'entreprise').map(i => (
|
||||
<option key={i.id} value={i.id}>{i.nom}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ gridColumn: 'span 2' }}>
|
||||
<label>Nom du projet *</label>
|
||||
<input required value={form.nom_projet} onChange={e => onFieldChange('nom_projet', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Émetteur</label>
|
||||
<input value={form.emetteur}
|
||||
onChange={e => onFieldChange('emetteur', e.target.value)}
|
||||
placeholder="Nom de l'emprunteur"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Référence</label>
|
||||
<input value={form.reference}
|
||||
onChange={e => onFieldChange('reference', e.target.value)}
|
||||
placeholder="N° de dossier, code projet…"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Pays d'exposition</label>
|
||||
<CountrySelect
|
||||
value={form.pays_exposition || 'FR'}
|
||||
onChange={code => onFieldChange('pays_exposition', code)}
|
||||
/>
|
||||
</div>
|
||||
{editingId && (
|
||||
<div>
|
||||
<label>Statut</label>
|
||||
<select value={form.statut} onChange={e => onFieldChange('statut', e.target.value)}>
|
||||
<option value="en_cours">En cours</option>
|
||||
<option value="rembourse">Remboursé</option>
|
||||
<option value="en_retard">En retard</option>
|
||||
<option value="procedure">Procédure</option>
|
||||
<option value="cloture">Clôturé</option>
|
||||
<option value="perte_definitive">Perte définitive</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</>)}
|
||||
|
||||
{/* ══ Étape 2 : Données financières et échéancier ══ */}
|
||||
{step === 2 && (<>
|
||||
<div>
|
||||
<label>Montant investi (€) *</label>
|
||||
<input type="number" step="0.01" min="0" required value={form.montant_investi}
|
||||
onChange={e => onFieldChange('montant_investi', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Taux annuel (%) *</label>
|
||||
<input type="number" step="0.01" required value={form.taux_interet}
|
||||
onChange={e => onFieldChange('taux_interet', e.target.value)} />
|
||||
{tauxRevisionWarning}
|
||||
</div>
|
||||
<div>
|
||||
<label>Durée (mois) *</label>
|
||||
<input type="number" min="0" required value={form.duree_mois} onChange={e => handleDureeChange(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Type de prêt</label>
|
||||
<select value={form.type_remb} onChange={e => handleTypeRembChange(e.target.value)}>
|
||||
<option value="in_fine">Prêt in fine</option>
|
||||
<option value="amortissable">Prêt amortissable</option>
|
||||
<option value="differe">Prêt différé</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Fréquence intérêts</label>
|
||||
<select
|
||||
value={form.type_remb === 'differe' ? 'in_fine' : form.freq_interets}
|
||||
disabled={form.type_remb === 'differe'}
|
||||
onChange={e => onFieldChange('freq_interets', e.target.value)}
|
||||
>
|
||||
{form.type_remb === 'differe' ? (
|
||||
<option value="in_fine">In fine (unique)</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="mensuel">Mensuel</option>
|
||||
<option value="trimestriel">Trimestriel</option>
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Date souscription *</label>
|
||||
<input type="date" required value={form.date_souscription} onChange={e => handleSouscriptionChange(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>{form.type_remb === 'differe' ? "Date d'échéance (versement unique) *" : 'Date 1ère échéance *'}</label>
|
||||
<input type="date" required value={form.date_premiere_echeance} onChange={e => handleDpeChange(e.target.value)} />
|
||||
{/* ── Case "Dernier jour du mois" — visible si jour > 27 ── */}
|
||||
{showFinDeMois && (
|
||||
<label style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
marginTop: 6, cursor: 'pointer', userSelect: 'none',
|
||||
fontSize: 'var(--fs-sm)', color: 'var(--text)',
|
||||
width: 'fit-content',
|
||||
}}>
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ width: 'auto', flexShrink: 0 }}
|
||||
checked={form.echeance_fin_de_mois}
|
||||
onChange={e => handleFinDeMoisChange(e.target.checked)}
|
||||
/>
|
||||
Dernier jour du mois
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label>Date cible contractuelle{form.type_remb === 'differe' ? ' — calculée' : ''}</label>
|
||||
<input type="date" value={form.date_cible}
|
||||
readOnly={form.type_remb === 'differe'}
|
||||
style={form.type_remb === 'differe' ? { opacity: 0.6, cursor: 'default' } : undefined}
|
||||
onChange={e => {
|
||||
if (form.type_remb === 'differe') return;
|
||||
onFieldChange('date_cible', e.target.value);
|
||||
}}
|
||||
/>
|
||||
{dateCibleRevisionWarning}
|
||||
</div>
|
||||
{platMethode && (platMethode === 'choix_investisseur' ? (
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<select value={form.methode_remboursement} onChange={e => handleMethodeChange(e.target.value)}>
|
||||
<option value="">— Non renseignée —</option>
|
||||
<option value="portefeuille">Porte-monnaie de la plateforme</option>
|
||||
<option value="compte_courant">Compte courant de l'investisseur</option>
|
||||
</select>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<input readOnly value={platMethode === 'compte_courant' ? "Compte courant de l'investisseur" : 'Porte-monnaie de la plateforme'}
|
||||
style={{ background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'default' }} />
|
||||
</div>
|
||||
))}
|
||||
{form.methode_remboursement === 'compte_courant' && (
|
||||
<div>
|
||||
<label>Compte de réception *</label>
|
||||
{comptesInvestisseur.length > 0 ? (
|
||||
<select required value={form.compte_id} onChange={e => handleCompteChange(e.target.value)}>
|
||||
<option value="">— Choisir un compte —</option>
|
||||
{comptesInvestisseur.map(c => (
|
||||
<option key={c.id} value={c.id}>{c.nom}{c.banque ? ` — ${c.banque}` : ''}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<p className="text-muted" style={{ fontSize: 'var(--fs-sm)', margin: '4px 0' }}>
|
||||
Aucun compte défini pour ce détenteur. <a href="/settings?section=comptes" target="_blank" rel="noreferrer">Créer un compte →</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{restructurationBlock}
|
||||
</>)}
|
||||
|
||||
{/* ══ Étape 3 : Catégories, secteurs, autres ══ */}
|
||||
{step === 3 && (<>
|
||||
<div style={{ gridColumn: 'span 2' }}>
|
||||
<label>Notes</label>
|
||||
<textarea rows={3} value={form.notes}
|
||||
onChange={e => onFieldChange('notes', e.target.value)}
|
||||
placeholder="Informations complémentaires…"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Catégories d'investissement */}
|
||||
<div style={{ gridColumn: 'span 3' }}>
|
||||
<label>Catégories d'investissement</label>
|
||||
<InvSelect
|
||||
items={categoriesInv}
|
||||
selected={form.categories_inv_ids || []}
|
||||
onChange={ids => onFieldChange('categories_inv_ids', ids)}
|
||||
addApiPath="/categories-inv"
|
||||
onItemAdded={onCategoryAdded}
|
||||
emptyLabel="Aucune catégorie d'investissement"
|
||||
addLabel="Ajouter une catégorie d'investissement"
|
||||
inputPlaceholder="Nom de la catégorie…"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Secteurs d'investissement */}
|
||||
<div style={{ gridColumn: 'span 3' }}>
|
||||
<label>Secteurs d'investissement</label>
|
||||
<InvSelect
|
||||
items={secteursInv}
|
||||
selected={form.secteurs_inv_ids || []}
|
||||
onChange={ids => onFieldChange('secteurs_inv_ids', ids)}
|
||||
addApiPath="/secteurs-inv"
|
||||
onItemAdded={onSectorAdded}
|
||||
emptyLabel="Aucun secteur d'investissement"
|
||||
addLabel="Ajouter un secteur d'investissement"
|
||||
inputPlaceholder="Nom du secteur…"
|
||||
/>
|
||||
</div>
|
||||
</>)}
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user