Modification modale plateforme
This commit is contained in:
1 parent
0c99887846
commit
9ea078302d
7 files changed
+437
-222
No files matched your search
@@ -1,9 +1,9 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { api } from '../api.js';
|
||||
import InvSelect from '../components/InvSelect.jsx';
|
||||
import ConfirmModal from '../components/ConfirmModal.jsx';
|
||||
import Modal from '../components/Modal.jsx';
|
||||
import PlateformeFormModal from '../components/PlateformeFormModal.jsx';
|
||||
import ResultBanner from '../components/ResultBanner.jsx';
|
||||
import CountrySelect, { COUNTRIES, FlagIcon } from '../components/CountrySelect.jsx';
|
||||
import { usePagination } from '../hooks/usePagination.js';
|
||||
@@ -45,7 +45,8 @@ function IconUpload() {
|
||||
const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—';
|
||||
const FISCALITE_LABELS = { flat_tax: 'Flat tax (30%)', sans_fiscalite_locale: 'Sans fiscalité locale', avec_fiscalite_locale: 'Avec fiscalité locale' };
|
||||
const CATEGORIE_PLATEFORME_LABELS = { psfp: 'Prestataire de Services de Financement Participatif', psan: 'Prestataire de Services sur Actifs Numériques' };
|
||||
const EMPTY_REF = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', methode_remboursement: 'portefeuille', type_pret_defaut: '', freq_interets_defaut: '', description: '', categories: [], categories_inv_ids: [], secteurs_inv_ids: [] };
|
||||
const FRAIS_PLATEFORME_LABELS = { aucun: 'Aucun frais', entree_gestion_directe: "Frais d'entrée + Frais de gestion (Prélèvement direct)" };
|
||||
const EMPTY_REF = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', frais_plateforme: 'aucun', methode_remboursement: 'portefeuille', type_pret_defaut: '', freq_interets_defaut: '', description: '', categories: [], categories_inv_ids: [], secteurs_inv_ids: [] };
|
||||
|
||||
/* ── Modal création / édition référentiel ─────────────────────── */
|
||||
function RefModal({ row, onClose, onSaved }) {
|
||||
@@ -61,6 +62,7 @@ function RefModal({ row, onClose, onSaved }) {
|
||||
taux_fiscalite_locale: row.taux_fiscalite_locale != null ? String(row.taux_fiscalite_locale) : '',
|
||||
type_produit_fiscal: row.type_produit_fiscal || '2TT',
|
||||
categorie_plateforme: row.categorie_plateforme || 'psfp',
|
||||
frais_plateforme: row.frais_plateforme || 'aucun',
|
||||
methode_remboursement: row.methode_remboursement || 'portefeuille',
|
||||
type_pret_defaut: row.type_pret_defaut || '',
|
||||
freq_interets_defaut: row.freq_interets_defaut || '',
|
||||
@@ -96,6 +98,7 @@ function RefModal({ row, onClose, onSaved }) {
|
||||
? Number(form.taux_fiscalite_locale) : null,
|
||||
type_produit_fiscal: form.type_produit_fiscal,
|
||||
categorie_plateforme: form.categorie_plateforme || 'psfp',
|
||||
frais_plateforme: form.frais_plateforme || 'aucun',
|
||||
methode_remboursement: form.methode_remboursement || 'portefeuille',
|
||||
type_pret_defaut: form.type_pret_defaut || null,
|
||||
freq_interets_defaut: form.freq_interets_defaut || null,
|
||||
@@ -143,118 +146,165 @@ function RefModal({ row, onClose, onSaved }) {
|
||||
finally { setSaving(false); }
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open title={isNew ? 'Ajouter au référentiel' : `Modifier — ${row.nom}`}
|
||||
onClose={onClose} width={640}
|
||||
footer={<>
|
||||
<button type="button" onClick={onClose} disabled={saving}>Annuler</button>
|
||||
<button className="primary" form="ref-form" type="submit" disabled={saving}>{saving ? '…' : isNew ? 'Créer' : 'Enregistrer'}</button>
|
||||
</>}>
|
||||
<form id="ref-form" onSubmit={handleSubmit}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div><label>Nom *</label><input value={form.nom} onChange={e => set('nom', e.target.value)} placeholder="Ex : October, Lendosphere…" /></div>
|
||||
<div><label>URL</label><input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" /></div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
||||
<div>
|
||||
<label>Domiciliation</label>
|
||||
<CountrySelect
|
||||
value={form.domiciliation}
|
||||
onChange={code => set('domiciliation', code)}
|
||||
showCode
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Fiscalité</label>
|
||||
{form.domiciliation === 'FR'
|
||||
? <input value="Flat tax (automatique)" disabled />
|
||||
: <select value={form.fiscalite} onChange={e => set('fiscalite', e.target.value)}>
|
||||
<option value="sans_fiscalite_locale">Sans fiscalité locale</option>
|
||||
<option value="avec_fiscalite_locale">Avec fiscalité locale</option>
|
||||
</select>}
|
||||
</div>
|
||||
</div>
|
||||
{fiscalite === 'avec_fiscalite_locale' && (
|
||||
<div><label>Taux fiscalité locale (%)</label>
|
||||
<input type="number" min="0" max="100" step="0.1" value={form.taux_fiscalite_locale} onChange={e => set('taux_fiscalite_locale', e.target.value)} placeholder="Ex : 15" /></div>
|
||||
)}
|
||||
<div>
|
||||
<label>Type produit fiscal</label>
|
||||
<select value={form.type_produit_fiscal} onChange={e => set('type_produit_fiscal', e.target.value)}>
|
||||
<option value="2TT">2TT — Intérêts</option>
|
||||
<option value="2TR">2TR — Revenus assimilés</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Catégorie de plateforme</label>
|
||||
<select value={form.categorie_plateforme || 'psfp'} onChange={e => set('categorie_plateforme', e.target.value)}>
|
||||
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
||||
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<select value={form.methode_remboursement} onChange={e => set('methode_remboursement', e.target.value)}>
|
||||
<option value="portefeuille">Porte-monnaie</option>
|
||||
<option value="compte_courant">Compte courant</option>
|
||||
<option value="choix_investisseur">Choix investisseur</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Type de prêt (défaut)</label>
|
||||
<select value={form.type_pret_defaut} onChange={e => {
|
||||
const t = e.target.value;
|
||||
set('type_pret_defaut', t);
|
||||
if (t === 'differe') set('freq_interets_defaut', 'in_fine');
|
||||
else if (form.freq_interets_defaut === 'in_fine') set('freq_interets_defaut', 'mensuel');
|
||||
}}>
|
||||
<option value="">— Non défini —</option>
|
||||
<option value="in_fine">In fine</option>
|
||||
<option value="amortissable">Amortissable</option>
|
||||
<option value="differe">Différé</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Périodicité (défaut)</label>
|
||||
<select value={form.type_pret_defaut === 'differe' ? 'in_fine' : form.freq_interets_defaut}
|
||||
disabled={form.type_pret_defaut === 'differe'}
|
||||
onChange={e => set('freq_interets_defaut', e.target.value)}>
|
||||
{form.type_pret_defaut === 'differe'
|
||||
? <option value="in_fine">In fine (unique)</option>
|
||||
: <>
|
||||
<option value="">— Non définie —</option>
|
||||
<option value="mensuel">Mensuelle</option>
|
||||
<option value="trimestriel">Trimestrielle</option>
|
||||
</>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div><label>Description</label>
|
||||
<textarea value={form.description} onChange={e => set('description', e.target.value)}
|
||||
style={{ minHeight: 60, resize: 'vertical' }} placeholder="Informations générales sur la plateforme…" /></div>
|
||||
<div>
|
||||
<label>Catégories d'investissement</label>
|
||||
<InvSelect
|
||||
items={catsInv}
|
||||
selected={form.categories_inv_ids || []}
|
||||
onChange={ids => set('categories_inv_ids', ids)}
|
||||
emptyLabel="Aucune catégorie d'investissement"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Secteurs d'investissement</label>
|
||||
<InvSelect
|
||||
items={secteursInv}
|
||||
selected={form.secteurs_inv_ids || []}
|
||||
onChange={ids => set('secteurs_inv_ids', ids)}
|
||||
emptyLabel="Aucun secteur d'investissement"
|
||||
/>
|
||||
</div>
|
||||
const step1 = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div><label>Nom *</label><input value={form.nom} onChange={e => set('nom', e.target.value)} placeholder="Ex : October, Lendosphere…" /></div>
|
||||
<div><label>URL</label><input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" /></div>
|
||||
<div>
|
||||
<label>Domiciliation</label>
|
||||
<CountrySelect
|
||||
value={form.domiciliation}
|
||||
onChange={code => set('domiciliation', code)}
|
||||
showCode
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Catégorie de plateforme</label>
|
||||
<select value={form.categorie_plateforme || 'psfp'} onChange={e => set('categorie_plateforme', e.target.value)}>
|
||||
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
||||
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div><label>Description</label>
|
||||
<textarea value={form.description} onChange={e => set('description', e.target.value)}
|
||||
style={{ minHeight: 180, resize: 'vertical' }} placeholder="Informations générales sur la plateforme…" /></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const step2 = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<label>Fiscalité</label>
|
||||
{form.domiciliation === 'FR'
|
||||
? <input value="Flat tax (automatique)" disabled />
|
||||
: <select value={form.fiscalite} onChange={e => set('fiscalite', e.target.value)}>
|
||||
<option value="sans_fiscalite_locale">Sans fiscalité locale</option>
|
||||
<option value="avec_fiscalite_locale">Avec fiscalité locale</option>
|
||||
</select>}
|
||||
</div>
|
||||
{fiscalite === 'avec_fiscalite_locale' && (
|
||||
<div><label>Taux fiscalité locale (%)</label>
|
||||
<input type="number" min="0" max="100" step="0.1" value={form.taux_fiscalite_locale} onChange={e => set('taux_fiscalite_locale', e.target.value)} placeholder="Ex : 15" /></div>
|
||||
)}
|
||||
<div>
|
||||
<label>Type produit fiscal</label>
|
||||
<select value={form.type_produit_fiscal} onChange={e => set('type_produit_fiscal', e.target.value)}>
|
||||
<option value="2TT">2TT — Intérêts</option>
|
||||
<option value="2TR">2TR — Revenus assimilés</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const step3 = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<select value={form.methode_remboursement} onChange={e => set('methode_remboursement', e.target.value)}>
|
||||
<option value="portefeuille">Porte-monnaie</option>
|
||||
<option value="compte_courant">Compte courant</option>
|
||||
<option value="choix_investisseur">Choix investisseur</option>
|
||||
</select>
|
||||
</div>
|
||||
{err && <div className="error" style={{ marginTop: 12 }}>{err}</div>}
|
||||
</form>
|
||||
</Modal>
|
||||
<div>
|
||||
<label>Type de prêt (défaut)</label>
|
||||
<select value={form.type_pret_defaut} onChange={e => {
|
||||
const t = e.target.value;
|
||||
set('type_pret_defaut', t);
|
||||
if (t === 'differe') set('freq_interets_defaut', 'in_fine');
|
||||
else if (form.freq_interets_defaut === 'in_fine') set('freq_interets_defaut', 'mensuel');
|
||||
}}>
|
||||
<option value="">— Non défini —</option>
|
||||
<option value="in_fine">In fine</option>
|
||||
<option value="amortissable">Amortissable</option>
|
||||
<option value="differe">Différé</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Périodicité (défaut)</label>
|
||||
<select value={form.type_pret_defaut === 'differe' ? 'in_fine' : form.freq_interets_defaut}
|
||||
disabled={form.type_pret_defaut === 'differe'}
|
||||
onChange={e => set('freq_interets_defaut', e.target.value)}>
|
||||
{form.type_pret_defaut === 'differe'
|
||||
? <option value="in_fine">In fine (unique)</option>
|
||||
: <>
|
||||
<option value="">— Non définie —</option>
|
||||
<option value="mensuel">Mensuelle</option>
|
||||
<option value="trimestriel">Trimestrielle</option>
|
||||
</>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Frais liés à la plateforme</label>
|
||||
<select value={form.frais_plateforme || 'aucun'} onChange={e => set('frais_plateforme', e.target.value)}>
|
||||
<option value="aucun">{FRAIS_PLATEFORME_LABELS.aucun}</option>
|
||||
<option value="entree_gestion_directe">{FRAIS_PLATEFORME_LABELS.entree_gestion_directe}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const step4 = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<label>Catégories</label>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{catsInv.map(c => {
|
||||
const checked = (form.categories_inv_ids || []).includes(c.id);
|
||||
return (
|
||||
<label key={c.id} style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={checked} style={{ width: 'auto' }}
|
||||
onChange={e => set('categories_inv_ids', e.target.checked
|
||||
? [...(form.categories_inv_ids || []), c.id]
|
||||
: (form.categories_inv_ids || []).filter(id => id !== c.id))} />
|
||||
<span className="chip-cat" style={{ fontSize: 11 }}>{c.nom}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Secteurs</label>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{secteursInv.map(s => {
|
||||
const checked = (form.secteurs_inv_ids || []).includes(s.id);
|
||||
return (
|
||||
<label key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={checked} style={{ width: 'auto' }}
|
||||
onChange={e => set('secteurs_inv_ids', e.target.checked
|
||||
? [...(form.secteurs_inv_ids || []), s.id]
|
||||
: (form.secteurs_inv_ids || []).filter(id => id !== s.id))} />
|
||||
<span className="chip-sect" style={{ fontSize: 11 }}>{s.nom}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const step5 = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div className="text-muted" style={{ fontSize: 13 }}>
|
||||
Le logo et les autres informations de profil (régulateur, statistiques publiques, réseaux sociaux…) se gèrent depuis la fiche détaillée de la plateforme, après création.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<PlateformeFormModal
|
||||
title={isNew ? 'Ajouter au référentiel' : `Modifier — ${row.nom}`}
|
||||
onClose={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
saving={saving}
|
||||
err={err}
|
||||
submitLabel={isNew ? 'Créer' : 'Enregistrer'}
|
||||
width={640}
|
||||
step1={step1} step2={step2} step3={step3} step4={step4} step5={step5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user