Files
crowdlending-app/frontend/src/pages/settings/SecteursInvSection.jsx
T
Olivier CROGUENNEC 48ed7fe65e Initial commit
2026-06-13 14:57:15 +02:00

197 lines
9.0 KiB
React

import { useState, useEffect } from 'react';
import { api } from '../../api.js';
export default function SecteursInvSection() {
const [secteursInv, setSecteursInv] = useState([]);
const [err, setErr] = useState(null);
const [selectedSectInv, setSelectedSectInv] = useState(null);
const [editingSectInv, setEditingSectInv] = useState(null);
const [editingNomSectInv, setEditingNomSectInv] = useState('');
const [newSectInvNom, setNewSectInvNom] = useState('');
const [showNewSectInv, setShowNewSectInv] = useState(false);
const [sectGlobalOpen, setSectGlobalOpen] = useState(false);
const [sectPrivateOpen, setSectPrivateOpen] = useState(true);
useEffect(() => {
api.get('/secteurs-inv').then(data => {
setSecteursInv(data.sort((a, b) => b.is_global - a.is_global || a.nom.localeCompare(b.nom)));
}).catch(() => {});
}, []);
const saveSectInv = async (nom) => {
if (!nom.trim()) return;
try {
const row = await api.post('/secteurs-inv', { nom: nom.trim() });
setSecteursInv(prev => [...prev, row].sort((a, b) =>
b.is_global - a.is_global || a.nom.localeCompare(b.nom)));
setShowNewSectInv(false); setNewSectInvNom(''); setErr(null);
} catch (e) { setErr(e.message || 'Erreur'); }
};
const renameSectInv = async (id, nom) => {
if (!nom.trim()) return;
try {
await api.put(`/secteurs-inv/${id}`, { nom: nom.trim() });
setSecteursInv(prev => prev.map(s => s.id === id ? { ...s, nom: nom.trim() } : s));
setEditingSectInv(null);
} catch (e) { setErr(e.message || 'Erreur'); }
};
const delSectInv = async (id) => {
try {
await api.del(`/secteurs-inv/${id}`);
setSecteursInv(prev => prev.filter(s => s.id !== id));
if (selectedSectInv?.id === id) setSelectedSectInv(null);
} catch (e) { setErr(e.message || 'Erreur'); }
};
const globalSects = secteursInv.filter(s => s.is_global);
const privateSects = secteursInv.filter(s => !s.is_global);
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<h3 style={{ margin: 0 }}>Mes secteurs d'investissement</h3>
</div>
{err && <div className="error" style={{ marginBottom: 12 }}>{err}</div>}
{/* Accordéon — Secteurs globalement définis */}
<div className="card" style={{ marginBottom: 10 }}>
<button type="button"
onClick={() => setSectGlobalOpen(o => !o)}
style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
background: 'none', border: 'none', cursor: 'pointer', padding: 0, color: 'var(--text)' }}>
<span style={{ fontWeight: 600, fontSize: 'var(--fs-base)' }}>
Secteurs globalement définis
<span style={{ marginLeft: 8, fontWeight: 400, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
({globalSects.length})
</span>
</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"
strokeLinecap="round" strokeLinejoin="round"
style={{ transform: sectGlobalOpen ? 'rotate(180deg)' : 'none', transition: 'transform .2s', flexShrink: 0 }}>
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
{sectGlobalOpen && (
<div style={{ marginTop: 14 }}>
<table>
<thead>
<tr>
<th>Nom</th>
<th className="num">Plateformes</th>
<th className="num">Investissements</th>
</tr>
</thead>
<tbody>
{globalSects.length === 0 && (
<tr><td colSpan={3} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>Aucun secteur global</td></tr>
)}
{globalSects.map(s => (
<tr key={s.id}>
<td><span style={{ fontWeight: 600 }}>{s.nom}</span></td>
<td className="num">{s.nb_plateformes > 0 ? s.nb_plateformes : <span className="text-muted">—</span>}</td>
<td className="num">{s.nb_investissements > 0 ? s.nb_investissements : <span className="text-muted">—</span>}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{/* Accordéon — Mes propres secteurs */}
<div className="card">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<button type="button"
onClick={() => setSectPrivateOpen(o => !o)}
style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8,
background: 'none', border: 'none', cursor: 'pointer', padding: 0, color: 'var(--text)', textAlign: 'left' }}>
<span style={{ fontWeight: 600, fontSize: 'var(--fs-base)' }}>
Mes propres secteurs
<span style={{ marginLeft: 8, fontWeight: 400, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
({privateSects.length})
</span>
</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"
strokeLinecap="round" strokeLinejoin="round"
style={{ transform: sectPrivateOpen ? 'rotate(180deg)' : 'none', transition: 'transform .2s', flexShrink: 0 }}>
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
{sectPrivateOpen && (
<button className="primary" type="button" style={{ marginLeft: 12, flexShrink: 0 }}
onClick={() => { setSectPrivateOpen(true); setShowNewSectInv(true); setNewSectInvNom(''); setErr(null); }}>
+ Ajouter
</button>
)}
</div>
{sectPrivateOpen && (
<div style={{ marginTop: 14 }}>
{showNewSectInv && (
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
<input autoFocus style={{ flex: 1 }} placeholder="Nom du secteur"
value={newSectInvNom} onChange={e => setNewSectInvNom(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') saveSectInv(newSectInvNom); if (e.key === 'Escape') setShowNewSectInv(false); }} />
<button className="primary" onClick={() => saveSectInv(newSectInvNom)}>Créer</button>
<button onClick={() => setShowNewSectInv(false)}>Annuler</button>
</div>
)}
<table>
<thead>
<tr>
<th>Nom</th>
<th className="num">Plateformes</th>
<th className="num">Investissements</th>
<th style={{ width: 80 }}></th>
</tr>
</thead>
<tbody>
{privateSects.length === 0 && (
<tr><td colSpan={4} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>Aucun secteur personnel</td></tr>
)}
{privateSects.map(s => (
<tr key={s.id}>
<td>
{editingSectInv === s.id ? (
<div style={{ display: 'flex', gap: 6 }}>
<input autoFocus style={{ flex: 1 }} value={editingNomSectInv}
onChange={e => setEditingNomSectInv(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') renameSectInv(s.id, editingNomSectInv); if (e.key === 'Escape') setEditingSectInv(null); }} />
<button className="primary" onClick={() => renameSectInv(s.id, editingNomSectInv)}>OK</button>
<button onClick={() => setEditingSectInv(null)}>✕</button>
</div>
) : (
<span style={{ fontWeight: 600 }}>{s.nom}</span>
)}
</td>
<td className="num">{s.nb_plateformes > 0 ? s.nb_plateformes : <span className="text-muted">—</span>}</td>
<td className="num">{s.nb_investissements > 0 ? s.nb_investissements : <span className="text-muted">—</span>}</td>
<td>
{editingSectInv !== s.id && (
<div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
<button style={{ fontSize: 12, padding: '2px 8px' }}
onClick={() => { setEditingSectInv(s.id); setEditingNomSectInv(s.nom); setErr(null); }}>
Renommer
</button>
<button style={{ fontSize: 12, padding: '2px 8px', color: 'var(--danger)', borderColor: 'var(--danger)' }}
onClick={() => setConfirmDelete({
title: 'Supprimer le secteur',
message: `Supprimer le secteur "${s.nom}" ? Il sera retiré de toutes les plateformes et investissements.`,
confirmLabel: 'Supprimer',
onConfirm: () => { delSectInv(s.id); setConfirmDelete(null); }
})}>
Supprimer
</button>
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);
}