Initial commit
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user