148 lines
7.5 KiB
React
148 lines
7.5 KiB
React
import Modal from './Modal.jsx';
|
|
|
|
/**
|
|
* FraisFormModal — formulaire partagé de saisie/édition d'un frais d'opération
|
|
* (frais_operations, catégories gestion/distribution/autre — cf. project_frais_operation.md).
|
|
*
|
|
* Extrait de InvestissementDetail.jsx (Étape 5, page dédiée Frais, 02/09/26) sur le modèle de
|
|
* RembFormModal.jsx : composant 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). Utilisé par InvestissementDetail.jsx (mode `locked` : investissement déjà
|
|
* déterminé par le contexte, pas de sélecteur) et par la page Frais.jsx (mode complet, avec
|
|
* sélecteur d'investissement).
|
|
*
|
|
* La catégorie "souscription" n'apparaît jamais dans le sélecteur de ce formulaire : elle est
|
|
* unique par investissement et se saisit/modifie uniquement depuis l'étape "Frais" du formulaire
|
|
* "Modifier l'investissement" (décision Olivier, 31/08/26) — ce composant ne gère jamais cette
|
|
* catégorie, y compris depuis la page dédiée Frais.
|
|
*
|
|
* Props :
|
|
* open, onClose — état d'ouverture
|
|
* editingId — id en édition, null si création
|
|
* form — { investissement_id?, montant, mode_reglement, compte_id,
|
|
* date_operation, categorie, notes }
|
|
* onFieldChange(key, value) — pour les champs à effets de bord (ex. investissement_id,
|
|
* qui déclenche côté parent le rechargement des comptes du
|
|
* détenteur ; mode_reglement, qui réinitialise compte_id)
|
|
* err — message d'erreur à afficher
|
|
* onSubmit — handler de soumission du formulaire
|
|
* onDelete — handler de suppression (optionnel, affiche le bouton si fourni)
|
|
* comptesInvestisseur — comptes du détenteur de l'investissement courant (select
|
|
* "Compte de règlement" quand mode_reglement === 'compte_courant')
|
|
* locked — bool : masque le sélecteur d'investissement (mode
|
|
* InvestissementDetail.jsx, investissement déjà connu du contexte)
|
|
* investissementOptions — liste pour le <select> investissement (mode non-locked
|
|
* uniquement) — { id, plateforme_nom, nom_projet }[]
|
|
* confirmingDelete, setConfirmingDelete — état de confirmation de suppression (géré par le
|
|
* parent, remis à false dans son propre onClose)
|
|
*/
|
|
export default function FraisFormModal({
|
|
open, onClose, editingId,
|
|
form, onFieldChange,
|
|
err, onSubmit, onDelete,
|
|
comptesInvestisseur = [],
|
|
locked = false,
|
|
investissementOptions = [],
|
|
confirmingDelete, setConfirmingDelete,
|
|
}) {
|
|
const modalTitle = editingId ? 'Modifier le frais' : 'Ajouter un frais';
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
title={modalTitle}
|
|
onClose={onClose}
|
|
width={locked ? 480 : 560}
|
|
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>
|
|
<button className="ghost" onClick={onClose}>Annuler</button>
|
|
<button className="primary" onClick={onSubmit}>{editingId ? 'Enregistrer' : 'Ajouter'}</button>
|
|
</div>
|
|
}
|
|
>
|
|
<form onSubmit={onSubmit}>
|
|
{err && <div className="error">{err}</div>}
|
|
<div className="form-grid">
|
|
{!locked && (
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Investissement *</label>
|
|
<select required value={form.investissement_id || ''}
|
|
onChange={e => onFieldChange('investissement_id', e.target.value)}>
|
|
<option value="">— Choisir un investissement —</option>
|
|
{investissementOptions.map(i => (
|
|
<option key={i.id} value={i.id}>{i.plateforme_nom} — {i.nom_projet}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label>Montant TTC (€) *</label>
|
|
<input type="number" step="0.01" min="0" required value={form.montant}
|
|
onChange={e => onFieldChange('montant', e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label>Date de l'opération *</label>
|
|
<input type="date" required value={form.date_operation}
|
|
onChange={e => onFieldChange('date_operation', e.target.value)} />
|
|
</div>
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Catégorie</label>
|
|
<select value={form.categorie} onChange={e => onFieldChange('categorie', e.target.value)}>
|
|
<option value="gestion">Gestion</option>
|
|
<option value="distribution">Distribution</option>
|
|
<option value="autre">Autre</option>
|
|
</select>
|
|
</div>
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Prélèvement</label>
|
|
<select value={form.mode_reglement}
|
|
onChange={e => onFieldChange('mode_reglement', e.target.value)}>
|
|
<option value="source">À la source (sans mouvement complémentaire)</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.mode_reglement === 'compte_courant' && (
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Compte de règlement *</label>
|
|
{comptesInvestisseur.length > 0 ? (
|
|
<select required value={form.compte_id} onChange={e => onFieldChange('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 pour ce détenteur. <a href="/settings?section=comptes" target="_blank" rel="noreferrer">Créer un compte →</a>
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div style={{ gridColumn: 'span 2' }}>
|
|
<label>Notes</label>
|
|
<input value={form.notes} onChange={e => onFieldChange('notes', e.target.value)} placeholder="Précisions optionnelles…" />
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Modal>
|
|
);
|
|
}
|