406 lines
23 KiB
React
406 lines
23 KiB
React
import Modal from './Modal.jsx';
|
|
import InvestissementSelect from './InvestissementSelect.jsx';
|
|
import { fmtEUR } from '../utils/format.js';
|
|
|
|
/* ── Helpers purs (dupliqués volontairement, pas d'état) ── */
|
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
const fmtRate = (n) => n != null ? n.toFixed(1).replace('.', ',') + ' %' : '…';
|
|
|
|
// Le frais ne réduit interets_nets que lorsqu'il est retenu SUR ce remboursement (mode
|
|
// 'remboursement', par défaut) — un frais réglé à part ('portefeuille'/'compte_courant',
|
|
// Étape 5 bis, 01/09/26 — cas Baltis) n'a aucun impact ici, cf. computeChamps() côté backend.
|
|
const fraisReduitBase = (f) => !f.frais_mode_reglement || f.frais_mode_reglement === 'remboursement';
|
|
|
|
const computeInteretsNets = (f) =>
|
|
round2(Number(f.interets_bruts || 0) - (fraisReduitBase(f) ? Number(f.frais_ttc || 0) : 0) - Number(f.prelev_sociaux || 0) - Number(f.prelev_forfaitaire || 0));
|
|
|
|
const computeNet = (f) =>
|
|
round2(Number(f.capital || 0) + Number(f.cashback || 0) + computeInteretsNets(f));
|
|
|
|
const getRatesForYear = (dateStr, pfuRates) => {
|
|
if (!dateStr || !pfuRates?.length) return null;
|
|
const year = parseInt(dateStr.slice(0, 4), 10);
|
|
return pfuRates.find(r => r.annee === year)
|
|
?? pfuRates.reduce((best, r) => r.annee > best.annee ? r : best, pfuRates[0]);
|
|
};
|
|
|
|
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
|
const INTERETS_PLATEFORME_VALUE = 'INTERETS_PLATEFORME';
|
|
|
|
/**
|
|
* RembFormModal — formulaire partagé de saisie/édition d'un remboursement.
|
|
*
|
|
* Utilisé par Remboursements.jsx (mode complet, sélecteur d'investissement + bonus),
|
|
* InvestissementDetail.jsx et Plateformes.jsx (mode `locked` : investissement déjà déterminé
|
|
* par le contexte, pas de sélecteur ni de bonus).
|
|
*
|
|
* Le composant est volontairement "bête" côté état : le parent garde la propriété du form
|
|
* (state, effets de bord au changement de champ comme le chargement des comptes du détenteur,
|
|
* héritage de la méthode de remboursement, calcul de la fiscalité locale…). Ce composant se
|
|
* contente de dériver les valeurs d'affichage (labels PFU, fiscalité indicative, montant versé…)
|
|
* et de restituer le JSX.
|
|
*
|
|
* Props :
|
|
* open, onClose — état d'ouverture
|
|
* editingId — id en édition, null si création
|
|
* title — titre optionnel (sinon déduit de editingId/locked)
|
|
* form — objet form courant (voir `empty` dans les pages appelantes)
|
|
* onFieldChange(key, value) — pour les champs à effets de bord (déléqué au setField du parent)
|
|
* onFormPatch(partialObj) — pour les mises à jour directes sans effet de bord (setForm({...}))
|
|
* err — message d'erreur à afficher
|
|
* onSubmit — handler de soumission du formulaire
|
|
* onDelete — handler de suppression (optionnel, affiche le bouton si fourni)
|
|
* pfuRates — taux PFU par année
|
|
* comptesInvestisseur — comptes du détenteur courant (pour le select compte de réception)
|
|
* investissements — liste des investissements (pour résoudre l'investissement courant
|
|
* à partir de form.investissement_id). En mode locked, passer [investissement].
|
|
* plateformes — liste des plateformes (fiscalité locale, méthode héritée, dropdown bonus)
|
|
* locked — bool : masque le sélecteur d'investissement et les champs bonus
|
|
* investissementOptions — liste pour le <select> investissement (mode non-locked uniquement)
|
|
* multiDetenteur — bool, affiche le détenteur dans le dropdown bonus (mode non-locked)
|
|
* confirmingDelete, setConfirmingDelete — état de confirmation de suppression (géré par le parent,
|
|
* remis à false dans son propre onClose)
|
|
*/
|
|
export default function RembFormModal({
|
|
open, onClose, editingId, title,
|
|
form, onFieldChange, onFormPatch,
|
|
err, onSubmit, onDelete,
|
|
pfuRates, comptesInvestisseur,
|
|
investissements = [], plateformes = [],
|
|
locked = false,
|
|
investissementOptions = [],
|
|
multiDetenteur = false,
|
|
confirmingDelete, setConfirmingDelete,
|
|
submitting = false,
|
|
// Libellé du sélecteur du haut (17/09/26, cf. RemboursementsSimple.jsx — pages PE,
|
|
// qui n'ont pas de notion d'"investissement" mais réutilisent ce même sélecteur pour
|
|
// choisir entre les 3 types bonus_parrainage/bonus_plateforme/interets_plateforme).
|
|
investissementLabel = 'Investissement *',
|
|
// Deals PE éligibles à la plateforme sélectionnée (17/09/26, cf. RemboursementsSimple.jsx) :
|
|
// permet de rattacher une distribution bonus/intérêts à un deal précis pour son TVPI.
|
|
// Vide par défaut (aucun impact pour les pages crowdlending qui ne passent pas cette prop).
|
|
dealOptions = [],
|
|
// Force l'affichage de la section Frais même quand la plateforme n'a pas de
|
|
// frais_plateforme configuré (17/09/26, cf. RemboursementsSimple.jsx : référentiel
|
|
// crowdlending-only qu'un deal PE n'a aucune raison d'avoir renseigné — sans ce
|
|
// forçage, le champ "Frais TTC" disparaissait pour toute nouvelle distribution PE).
|
|
forceShowFrais = false,
|
|
interetsPlateformeLabel = '🏦 Intérêts plateforme',
|
|
}) {
|
|
const isBonus = !locked && BONUS_VALUES.includes(form.investissement_id);
|
|
const isInteretsPlateforme = !locked && form.investissement_id === INTERETS_PLATEFORME_VALUE;
|
|
// Modes "plateforme" (pas d'investissement précis) : bonus classique ou intérêts plateforme
|
|
const isPlateformeMode = isBonus || isInteretsPlateforme;
|
|
|
|
const rates = getRatesForYear(form.date_remb, pfuRates);
|
|
const labelPS = `Prélèvements sociaux${rates ? ` (${fmtRate(rates.prelev_sociaux)})` : ''} (€)`;
|
|
const labelIR = `Impôt sur le revenu${rates ? ` (${fmtRate(rates.impot_revenu)})` : ''} (€)`;
|
|
const interetsNets = computeInteretsNets(form);
|
|
|
|
const currentInv = locked
|
|
? investissements[0]
|
|
: (!isPlateformeMode ? investissements.find(i => i.id === Number(form.investissement_id)) : null);
|
|
const currentPlat = isInteretsPlateforme
|
|
? plateformes.find(p => String(p.id) === String(form.bonus_plateforme_id))
|
|
: (currentInv ? plateformes.find(p => p.id === currentInv.plateforme_id) : null);
|
|
const hasLocalTax = !isBonus && currentPlat?.fiscalite === 'avec_fiscalite_locale' && currentPlat?.taux_fiscalite_locale;
|
|
const isExonere = !isBonus && currentInv?.fiscalite_override === 'exonere';
|
|
const isIndicatif = Boolean(isExonere || (!isBonus && currentPlat && currentPlat.fiscalite !== 'flat_tax'));
|
|
// Cas indicatif (plateforme étrangère ou exonérée) : le montant versé est net de frais mais
|
|
// pas des prélèvements (indicatifs) — même formule que net_recu côté backend (computeChamps,
|
|
// branche isTaxIndicatif) — cf. Étape 4, 31/08/26.
|
|
const montantVerse = isIndicatif
|
|
? round2(Number(form.capital || 0) + Number(form.cashback || 0) + Number(form.interets_bruts || 0) - (fraisReduitBase(form) ? Number(form.frais_ttc || 0) : 0))
|
|
: computeNet(form);
|
|
|
|
// Section "Frais" (frais de distribution, Étape 4) : visible si la plateforme facture des
|
|
// frais (référentiel frais_plateforme ≠ 'aucun'), ou si un montant est déjà saisi (garde
|
|
// l'accès même si la config de la plateforme change ensuite) — même logique que le bloc
|
|
// "Frais d'opération" de la fiche investissement, cf. project_frais_operation.md.
|
|
const showFraisSection = !isBonus
|
|
&& (forceShowFrais || (currentPlat?.frais_plateforme && currentPlat.frais_plateforme !== 'aucun') || Number(form.frais_ttc) > 0);
|
|
|
|
const modalTitle = title ?? (editingId ? 'Modifier le remboursement' : (locked ? 'Saisir le remboursement' : 'Nouveau remboursement'));
|
|
|
|
const handleMethodeChange = (m) => {
|
|
const def = m === 'compte_courant' ? (comptesInvestisseur?.find(c => c.type === 'compte_courant') ?? comptesInvestisseur?.[0]) : null;
|
|
onFormPatch({ methode_remboursement: m, compte_id: def ? String(def.id) : '' });
|
|
};
|
|
|
|
// Prélèvement du frais (Étape 5 bis, 01/09/26) : passe par onFieldChange pour déclencher le
|
|
// recalcul des prélèvements (comme un changement de frais_ttc, cf. setField/setRembField dans
|
|
// les 3 pages appelantes), puis pré-sélectionne un compte par défaut si on bascule sur
|
|
// 'compte_courant' — même pattern que handleFraisReglementChange côté investissement.
|
|
const handleFraisModeChange = (mode) => {
|
|
onFieldChange('frais_mode_reglement', mode);
|
|
const def = mode === 'compte_courant' ? (comptesInvestisseur?.find(c => c.type === 'compte_courant') ?? comptesInvestisseur?.[0]) : null;
|
|
onFormPatch({ frais_compte_id: def ? String(def.id) : '' });
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
title={modalTitle}
|
|
onClose={onClose}
|
|
width={900}
|
|
busy={submitting}
|
|
footer={
|
|
<div style={{ display: 'flex', alignItems: 'center', width: '100%', gap: 8 }}>
|
|
<div style={{ flex: 1 }}>
|
|
{editingId && onDelete && (
|
|
!confirmingDelete ? (
|
|
<button className="danger" disabled={submitting} 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 disabled={submitting} onClick={() => setConfirmingDelete(false)}>Non</button>
|
|
<button className="danger" disabled={submitting} onClick={onDelete}>{submitting ? 'Suppression…' : 'Oui, supprimer'}</button>
|
|
</span>
|
|
)
|
|
)}
|
|
</div>
|
|
<button disabled={submitting} onClick={onClose}>Annuler</button>
|
|
<button className="primary" disabled={submitting} onClick={onSubmit}>
|
|
{submitting ? (editingId ? 'Enregistrement…' : 'Création…') : (editingId ? 'Enregistrer' : 'Créer')}
|
|
</button>
|
|
</div>
|
|
}
|
|
>
|
|
<form onSubmit={onSubmit}>
|
|
{err && <div className="error">{err}</div>}
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px 16px' }}>
|
|
|
|
{/* ── Sélecteur d'investissement (mode non verrouillé uniquement) ── */}
|
|
{!locked && (
|
|
<div style={{ gridColumn: isPlateformeMode ? 'span 1' : 'span 3' }}>
|
|
<label>{investissementLabel}</label>
|
|
<InvestissementSelect
|
|
value={form.investissement_id}
|
|
onChange={v => onFieldChange('investissement_id', v)}
|
|
options={investissementOptions}
|
|
interetsPlateformeLabel={interetsPlateformeLabel}
|
|
/>
|
|
</div>
|
|
)}
|
|
{!locked && isPlateformeMode && (
|
|
<div>
|
|
<label>Plateforme associée</label>
|
|
<select value={form.bonus_plateforme_id} onChange={e => {
|
|
const plat = plateformes.find(p => String(p.id) === e.target.value);
|
|
onFormPatch({ bonus_plateforme_id: e.target.value, bonus_investisseur_id: plat?.investisseur_id ? String(plat.investisseur_id) : '' });
|
|
}}>
|
|
<option value="">— Aucune —</option>
|
|
{plateformes.map(p => <option key={p.id} value={p.id}>{p.nom}{multiDetenteur && p.investisseur_nom ? ` — ${p.investisseur_nom}` : ''}</option>)}
|
|
</select>
|
|
</div>
|
|
)}
|
|
{!locked && isPlateformeMode && dealOptions.length > 0 && (
|
|
<div>
|
|
<label>Investissement PE (optionnel)</label>
|
|
<select value={form.investissement_pe_id || ''} onChange={e => onFormPatch({ investissement_pe_id: e.target.value })}>
|
|
<option value="">— Aucun (non rattaché à un deal) —</option>
|
|
{dealOptions.map(d => <option key={d.id} value={d.id}>{d.nom_deal}</option>)}
|
|
</select>
|
|
</div>
|
|
)}
|
|
{!locked && isBonus && (
|
|
<div>
|
|
<label>Date *</label>
|
|
<input type="date" required value={form.date_remb || ''}
|
|
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
|
</div>
|
|
)}
|
|
|
|
{/* ── Champs remboursement normal / intérêts plateforme ── */}
|
|
{!isBonus && (<>
|
|
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Remboursement</span>
|
|
</div>
|
|
{hasLocalTax && (
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Intérêts bruts avant retenue locale ({currentPlat.taux_fiscalite_locale} %) (€)</label>
|
|
<input type="number" step="0.01" value={form.interets_bruts_avant_local ?? 0}
|
|
onChange={e => onFieldChange('interets_bruts_avant_local', e.target.value)} />
|
|
</div>
|
|
)}
|
|
{hasLocalTax && (
|
|
<div>
|
|
<label>Retenue à la source locale (€)</label>
|
|
<input type="number" step="0.01" value={form.taxe_locale ?? 0}
|
|
onChange={e => onFieldChange('taxe_locale', e.target.value)} />
|
|
</div>
|
|
)}
|
|
{!isInteretsPlateforme && (
|
|
<div>
|
|
<label>Capital (€)</label>
|
|
<input type="number" step="0.01" value={form.capital ?? 0}
|
|
onChange={e => onFieldChange('capital', e.target.value)} />
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label>{hasLocalTax ? 'Intérêts bruts après retenue locale (€)' : 'Intérêts bruts (€)'}</label>
|
|
<input type="number" step="0.01" value={form.interets_bruts ?? 0}
|
|
readOnly={!!hasLocalTax}
|
|
style={hasLocalTax ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
|
onChange={e => !hasLocalTax && onFieldChange('interets_bruts', e.target.value)} />
|
|
</div>
|
|
{!isInteretsPlateforme && (
|
|
<div>
|
|
<label>Cashback (€)</label>
|
|
<input type="number" step="0.01" value={form.cashback ?? 0}
|
|
onChange={e => onFieldChange('cashback', e.target.value)} />
|
|
</div>
|
|
)}
|
|
{showFraisSection && (<>
|
|
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Frais</span>
|
|
</div>
|
|
<div>
|
|
<label>Montant des frais (€ TTC)</label>
|
|
<input type="number" step="0.01" min="0" value={form.frais_ttc ?? 0}
|
|
onChange={e => onFieldChange('frais_ttc', e.target.value)} />
|
|
</div>
|
|
{!isInteretsPlateforme ? (<>
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Prélèvement</label>
|
|
<select value={form.frais_mode_reglement || 'remboursement'} onChange={e => handleFraisModeChange(e.target.value)}>
|
|
<option value="remboursement">Retenu sur ce remboursement (réduit la base imposable et le montant versé)</option>
|
|
<option value="portefeuille">Versement complémentaire — Porte-monnaie de la plateforme</option>
|
|
<option value="compte_courant">Versement complémentaire — Compte courant du détenteur</option>
|
|
</select>
|
|
</div>
|
|
{form.frais_mode_reglement === 'compte_courant' && (
|
|
<div style={{ gridColumn: 'span 3' }}>
|
|
<label>Compte de règlement des frais *</label>
|
|
{comptesInvestisseur?.length > 0 ? (
|
|
<select required value={form.frais_compte_id || ''} onChange={e => onFieldChange('frais_compte_id', 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. <a href="/settings?section=comptes" target="_blank" rel="noreferrer">Créer un compte →</a>
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div style={{ gridColumn: 'span 3', display: 'flex', alignItems: 'flex-end', fontSize: 12, color: 'var(--text-muted)', paddingBottom: 6 }}>
|
|
{form.frais_mode_reglement === 'compte_courant' || form.frais_mode_reglement === 'portefeuille'
|
|
? "Frais réglé à part (comme pour la souscription) — aucun impact sur l'imposition ni le montant versé de ce remboursement ; compté séparément dans le Frais payés et le XIRR net."
|
|
: "Frais d'entrée/de gestion prélevés par la plateforme sur cette distribution (ex. avis d'opéré) — vient réduire la base imposable des intérêts bruts avant calcul des prélèvements."}
|
|
</div>
|
|
</>) : (
|
|
<div style={{ gridColumn: 'span 2', display: 'flex', alignItems: 'flex-end', fontSize: 12, color: 'var(--text-muted)', paddingBottom: 6 }}>
|
|
Frais d'entrée/de gestion prélevés par la plateforme sur cette distribution (ex. avis d'opéré) —
|
|
vient réduire la base imposable des intérêts bruts avant calcul des prélèvements.
|
|
</div>
|
|
)}
|
|
</>)}
|
|
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Imposition{isIndicatif ? ' — indicatif' : ''}</span>
|
|
</div>
|
|
<div>
|
|
<label>{labelPS}</label>
|
|
<input type="number" step="0.01" value={form.prelev_sociaux ?? 0}
|
|
readOnly={isIndicatif}
|
|
style={isIndicatif ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
|
onChange={e => !isIndicatif && onFormPatch({ prelev_sociaux: Number(e.target.value) })} />
|
|
</div>
|
|
<div>
|
|
<label>{labelIR}</label>
|
|
<input type="number" step="0.01" value={form.prelev_forfaitaire ?? 0}
|
|
readOnly={isIndicatif}
|
|
style={isIndicatif ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
|
onChange={e => !isIndicatif && onFormPatch({ prelev_forfaitaire: Number(e.target.value) })} />
|
|
</div>
|
|
<div>
|
|
<label>Total prélèvements (€)</label>
|
|
<input type="number" step="0.01"
|
|
value={round2((Number(form.prelev_sociaux) || 0) + (Number(form.prelev_forfaitaire) || 0))}
|
|
readOnly
|
|
style={{ background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' }} />
|
|
</div>
|
|
<div style={{ gridColumn: 'span 3', display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
|
|
<span style={{ color: 'var(--text-muted)' }}>Montant des intérêts après imposition</span>
|
|
<span style={{ fontWeight: 600, color: 'var(--text)' }}>{fmtEUR(interetsNets)}</span>
|
|
</div>
|
|
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Versement</span>
|
|
</div>
|
|
<div>
|
|
<label>Date *</label>
|
|
<input type="date" required value={form.date_remb || ''}
|
|
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
|
</div>
|
|
{!isInteretsPlateforme && (
|
|
<div>
|
|
<label>Méthode de remboursement</label>
|
|
<select value={form.methode_remboursement || 'portefeuille'}
|
|
onChange={e => handleMethodeChange(e.target.value)}>
|
|
<option value="portefeuille">Porte-monnaie de la plateforme</option>
|
|
<option value="compte_courant">Compte courant de l'investisseur</option>
|
|
</select>
|
|
</div>
|
|
)}
|
|
<div>
|
|
{!isInteretsPlateforme && form.methode_remboursement === 'compte_courant' && (
|
|
<>
|
|
<label>Compte de réception</label>
|
|
{comptesInvestisseur?.length > 0 ? (
|
|
<select value={form.compte_id || ''}
|
|
onChange={e => onFormPatch({ compte_id: 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. <a href="/settings?section=comptes" target="_blank" rel="noreferrer">Créer un compte →</a>
|
|
</p>
|
|
)}
|
|
</>
|
|
)}
|
|
<label>Montant versé (€)</label>
|
|
<input type="number" step="0.01" value={montantVerse} readOnly
|
|
style={{ background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' }} />
|
|
</div>
|
|
<div style={{ gridColumn: 'span 3', display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
|
|
<span style={{ color: 'var(--text-muted)' }}>
|
|
{isIndicatif
|
|
? "L'imposition n'est pas déduite du montant versé (fiscalité appliquée à titre indicatif)."
|
|
: "L'imposition est déduite du montant versé (capital + cashback + intérêts nets)."}
|
|
</span>
|
|
</div>
|
|
</>)}
|
|
|
|
{/* ── Bonus (mode non verrouillé uniquement) ── */}
|
|
{!locked && isBonus && (
|
|
<div>
|
|
<label>Montant (€)</label>
|
|
<input type="number" step="0.01" value={form.cashback ?? 0}
|
|
onChange={e => onFieldChange('cashback', e.target.value)} />
|
|
</div>
|
|
)}
|
|
{!locked && isPlateformeMode && (
|
|
<div style={{ gridColumn: isBonus ? 'span 2' : 'span 3' }}>
|
|
<label>Projet / Commentaire</label>
|
|
<input type="text"
|
|
placeholder={isBonus ? 'Nom du projet sollicité par le filleul…' : 'Commentaire…'}
|
|
value={form.notes || ''}
|
|
onChange={e => onFieldChange('notes', e.target.value)} />
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</form>
|
|
</Modal>
|
|
);
|
|
}
|