Amélioration des frais

This commit is contained in:
ocroguennec committed 2026-09-04 08:38:32 +02:00
1 parent 981ddf5830
commit 3e8508d48b
12 files changed
+1083 -206

No files matched your search

+147
View File
@@ -0,0 +1,147 @@
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>
);
}