popremplissgeprofil

This commit is contained in:
2026-06-17 09:43:04 +02:00
parent 0329dbc93a
commit 3178e3bf40
3 changed files with 376 additions and 137 deletions
+38 -3
View File
@@ -5237,7 +5237,9 @@ app.post('/api/profil/documents/:type', authenticateToken, upload.single('file')
`);
// Notifier les Finance du même campus
const typeLabels = { rib: 'RIB', cartegrise: 'Carte grise', permis: 'Permis de conduire' };
// Notifier les Finance du même campus
const typeLabels = { rib: 'RIB', cartegrise: 'carte grise', permis: 'permis de conduire' };
const typeLabelsAvecArticle = { rib: 'son RIB', cartegrise: 'sa carte grise', permis: 'son permis de conduire' };
const campusNorm = normalizeCampus(campus);
const financeResult = await pool.request()
.input('campus', sql.NVarChar, campusNorm ? `%${campusNorm}%` : '%')
@@ -5257,7 +5259,7 @@ WHERE r.role IN ('Finance', 'VerificateurFinance') AND r.actif = 1
destinataireEmail: finance.email,
type: 'validationdoc',
titre: `Document à valider — ${prenom} ${nom}`,
message: `${prenom} ${nom} (${campus}) a soumis son ${typeLabels[type]} pour validation.`,
message: `${prenom} ${nom} (${campus}) a soumis ${typeLabelsAvecArticle[type]} pour validation.`,
noteId: null
});
} catch (e) { console.error('Notif BDD Finance doc', e.message); }
@@ -5271,7 +5273,7 @@ WHERE r.role IN ('Finance', 'VerificateurFinance') AND r.actif = 1
</div>
<div style="padding:24px;background:#f8fafc;border:1px solid #e2e8f0;border-top:none;border-radius:0 0 12px 12px">
<p>Bonjour <strong>${finance.prenom} ${finance.nom}</strong>,</p>
<p><strong>${prenom} ${nom}</strong> (${campus}) a soumis son <strong>${typeLabels[type]}</strong> en attente de votre validation.</p>
<p><strong>${prenom} ${nom}</strong> (${campus}) a soumis ${typeLabelsAvecArticle[type]} en attente de votre validation.</p>
<div style="text-align:center;margin-top:24px">
<a href="${frontendUrl}" style="background:#6366f1;color:white;padding:13px 28px;text-decoration:none;border-radius:8px;font-weight:700">
Valider les documents
@@ -5308,6 +5310,39 @@ app.delete('/api/profil/documents/:type', authenticateToken, async (req, res) =>
});
// GET /api/finance/documents-historique — Finance voit l'historique des docs traités (validés/refusés)
app.get('/api/finance/documents-historique', authenticateToken, async (req, res) => {
if (!hasAnyRole(req.user, 'Finance', 'VerificateurFinance', 'superUtilisateur'))
return res.status(403).json({ error: 'Accès réservé Finance' });
try {
const request = pool.request();
let campusFilter = '';
if (req.user.campus && !hasAnyRole(req.user, 'superUtilisateur')) {
const campusCode = normalizeCampus(req.user.campus);
if (campusCode) {
request.input('campus', sql.NVarChar, `%${campusCode}%`);
campusFilter = 'AND c.campus LIKE @campus';
}
}
const result = await request.query(`
SELECT d.id, d.collaborateurId, d.type, d.fileName, d.sharepointUrl,
d.dateUpload, d.DateModification, d.statut, d.commentaire,
d.dateValidation, d.validePar,
c.prenom, c.nom, c.email, c.campus, c.departement,
v.prenom + ' ' + v.nom AS validateurNom
FROM DocumentsCollaborateur d
JOIN CollaborateurAD c ON c.id = d.collaborateurId
LEFT JOIN CollaborateurAD v ON v.id = d.validePar
WHERE d.statut IN ('valide', 'refuse') ${campusFilter}
ORDER BY d.dateValidation DESC, d.DateModification DESC
`);
res.json(result.recordset);
} catch (error) {
console.error('GET /api/finance/documents-historique:', error.message);
res.status(500).json({ error: error.message });
}
});
// GET /api/finance/documents-a-valider — Finance voit les docs en_attente de son campus
app.get('/api/finance/documents-a-valider', authenticateToken, async (req, res) => {
if (!hasAnyRole(req.user, 'Finance', 'VerificateurFinance', 'superUtilisateur'))
+182 -9
View File
@@ -1742,6 +1742,15 @@ const Dashboard = (): JSX.Element => {
});
const [ancienneNoteComparaison, setAncienneNoteComparaison] = useState<Note | null>(null);
const [showProfilIncompletModal, setShowProfilIncompletModal] = useState(false);
const [profilCompletude, setProfilCompletude] = useState<{
adresseOk: boolean;
ibanOk: boolean;
bicOk: boolean;
} | null>(null);
const [profilCheckDone, setProfilCheckDone] = useState(false);
const [vehicule, setVehicule] = useState<{
chevaux: number;
marque: string;
@@ -1790,6 +1799,9 @@ const Dashboard = (): JSX.Element => {
const [profilDocsUploading, setProfilDocsUploading] = useState<string | null>(null);
const [docsAValider, setDocsAValider] = useState<any[]>([]);
const [docsAValiderLoading, setDocsAValiderLoading] = useState(false);
const [docsHistorique, setDocsHistorique] = useState<any[]>([]);
const [docsHistoriqueLoading, setDocsHistoriqueLoading] = useState(false);
const [vueDocsValider, setVueDocsValider] = useState<'avalider' | 'historique'>('avalider');
// ── Etats nouveaux rôles ────────────────────────────
const [notesAVerifier, setNotesAVerifier] = useState<Note[]>([]);
@@ -1907,14 +1919,31 @@ const Dashboard = (): JSX.Element => {
setTimeout(() => setToast(null), 3500);
};
const checkProfilMinimal = async (): Promise<boolean> => {
const adresseOk = !!(profile?.adresse_rue && profile?.adresse_cp && profile?.adresse_ville && profile?.adresse_pays);
let ibanOk = false, bicOk = false;
try {
const ibanCheck = await fetch(`${API}/api/profil/iban`, { headers: hdrs as HeadersInit }).then(r => r.ok ? r.json() : null);
ibanOk = !!ibanCheck?.ibanSaisi;
bicOk = !!ibanCheck?.bic;
} catch { /* ignore */ }
setProfilCompletude({ adresseOk, ibanOk, bicOk });
return adresseOk && ibanOk && bicOk;
};
// ── Navigation avec historique navigateur ────────────
const nav =(s: string) => {
if (s === 'nouvelle') {
checkProfilMinimal().then(complet => {
if (!complet) { setShowProfilIncompletModal(true); return; }
window.history.pushState({ section: s }, '', `#${s}`);
setSection(s);
});
return;
}
window.history.pushState({ section: s }, '', `#${s}`);
setSection(s);
};
useEffect(() => {
const hash = window.location.hash.replace('#', '');
if (hash && sectionTitles[hash]) {
@@ -2084,7 +2113,13 @@ const Dashboard = (): JSX.Element => {
fetch(`${API}/api/parametres/tva`, { headers: hdrs }).then(r => r.ok ? r.json() : []).then(d => Array.isArray(d) && setTauxTVADisponibles(d)).catch(() => { });
fetch(`${API}/api/profil/documents`, { headers: hdrs }).then(r => r.ok ? r.json() : { rib: null, carte_grise: null, permis: null }).then(d => setProfilDocs(d)).catch(() => { });
}, []);
useEffect(() => {
if (!profile || profilCheckDone) return;
checkProfilMinimal().then(complet => {
setProfilCheckDone(true);
if (!complet) setShowProfilIncompletModal(true);
});
}, [profile, profilCheckDone]);
useEffect(() => {
if ((section === 'mesnotes' || section === 'accueil') && !notesLoaded) {
fetch(`${API}/api/notes`, { headers: hdrs }).then(r => r.ok ? r.json() : []).then(d => { if (Array.isArray(d)) { setNotes(d); setNotesLoaded(true); } }).catch(() => { });
@@ -2145,6 +2180,8 @@ const Dashboard = (): JSX.Element => {
if (section === 'docsavalider' && (isFinance || isVerificateurFinance)) {
setDocsAValiderLoading(true);
fetch(`${API}/api/finance/documents-a-valider`, { headers: hdrs }).then(r => r.ok ? r.json() : []).then(d => Array.isArray(d) && setDocsAValider(d)).catch(() => { }).finally(() => setDocsAValiderLoading(false));
setDocsHistoriqueLoading(true);
fetch(`${API}/api/finance/documents-historique`, { headers: hdrs }).then(r => r.ok ? r.json() : []).then(d => Array.isArray(d) && setDocsHistorique(d)).catch(() => { }).finally(() => setDocsHistoriqueLoading(false));
}
if (section === 'historiquepaiements' && (isRHAdmin || isValidateurFinance)) {
const params = new URLSearchParams();
@@ -4622,7 +4659,7 @@ const Dashboard = (): JSX.Element => {
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Erreur');
showToast(
`${data.nbNotes} note(s) soumises au Président pour validation`,
`${data.nbNotes} note(s) envoyés au Président pour validation`,
'success'
);
setSelectedNoteIds([]);
@@ -5115,11 +5152,33 @@ const Dashboard = (): JSX.Element => {
{/* ════════════════════════════════════════ */}
{section === 'docsavalider' && (isFinance || isVerificateurFinance) && (
<div style={{ maxWidth: 900, margin: '0 auto' }}>
<div style={{ fontSize: 20, fontWeight: 800, color: 'var(--text-primary)', marginBottom: 24, display: 'flex', alignItems: 'center', gap: 10 }}>
<ShieldCheck size={22} /> Documents à valider
{docsAValider.length > 0 && <span style={{ background: '#6366f1', color: '#fff', fontSize: 12, fontWeight: 700, padding: '2px 10px', borderRadius: 20 }}>{docsAValider.length}</span>}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
<div style={{ fontSize: 20, fontWeight: 800, color: 'var(--text-primary)', display: 'flex', alignItems: 'center', gap: 10 }}>
<ShieldCheck size={22} /> Documents
{vueDocsValider === 'avalider' && docsAValider.length > 0 && (
<span style={{ background: '#6366f1', color: '#fff', fontSize: 12, fontWeight: 700, padding: '2px 10px', borderRadius: 20 }}>{docsAValider.length}</span>
)}
</div>
{docsAValiderLoading ? (
<div style={{ display: 'flex', gap: 6, background: 'var(--bg-input)', borderRadius: 10, padding: 4 }}>
<button onClick={() => setVueDocsValider('avalider')} style={{
padding: '7px 16px', borderRadius: 8, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 13, fontWeight: 700,
background: vueDocsValider === 'avalider' ? '#6366f1' : 'transparent',
color: vueDocsValider === 'avalider' ? '#fff' : 'var(--text-secondary)',
transition: 'all 0.15s',
}}>À valider</button>
<button onClick={() => setVueDocsValider('historique')} style={{
padding: '7px 16px', borderRadius: 8, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 13, fontWeight: 700,
background: vueDocsValider === 'historique' ? '#6366f1' : 'transparent',
color: vueDocsValider === 'historique' ? '#fff' : 'var(--text-secondary)',
transition: 'all 0.15s',
}}>Historique</button>
</div>
</div>
{vueDocsValider === 'avalider' ? (
docsAValiderLoading ? (
<div style={{ textAlign: 'center', padding: 60, color: 'var(--text-muted)' }}> Chargement...</div>
) : docsAValider.length === 0 ? (
<div style={{ textAlign: 'center', padding: 60, color: 'var(--text-muted)' }}> Aucun document en attente de validation</div>
@@ -5135,7 +5194,7 @@ const Dashboard = (): JSX.Element => {
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 3 }}>📍 {doc.campus} · {doc.departement}</div>
<div style={{ marginTop: 8, display: 'flex', gap: 8, alignItems: 'center' }}>
<span style={{ background: '#eef2ff', color: '#6366f1', padding: '3px 10px', borderRadius: 6, fontWeight: 700, fontSize: 12 }}>{tl[doc.type] || doc.type}</span>
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Soumis le {new Date(doc.DateModification).toLocaleDateString('fr-FR')}</span>
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Envoyé le {new Date(doc.DateModification).toLocaleDateString('fr-FR')}</span>
</div>
</div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
@@ -5158,6 +5217,53 @@ const Dashboard = (): JSX.Element => {
);
})}
</div>
)
) : (
docsHistoriqueLoading ? (
<div style={{ textAlign: 'center', padding: 60, color: 'var(--text-muted)' }}> Chargement...</div>
) : docsHistorique.length === 0 ? (
<div style={{ textAlign: 'center', padding: 60, color: 'var(--text-muted)' }}>📭 Aucun document traité pour le moment</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{docsHistorique.map((doc: any) => {
const tl: Record<string, string> = { rib: '🏦 RIB', carte_grise: '🚗 Carte grise', cartegrise: '🚗 Carte grise', permis: '📄 Permis de conduire' };
const isValide = doc.statut === 'valide';
return (
<div key={doc.id} style={{
...cardStyle, padding: '18px 22px',
borderLeft: `4px solid ${isValide ? '#15803d' : '#dc2626'}`,
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ fontWeight: 800, fontSize: 16, color: 'var(--text-primary)' }}>{doc.prenom} {doc.nom}</div>
<span style={{
fontSize: 11, fontWeight: 700, padding: '2px 9px', borderRadius: 20,
background: isValide ? '#dcfce7' : '#fee2e2',
color: isValide ? '#15803d' : '#dc2626',
}}>{isValide ? '✅ Validé' : '❌ Refusé'}</span>
</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 3 }}>📍 {doc.campus} · {doc.departement}</div>
<div style={{ marginTop: 8, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
<span style={{ background: '#eef2ff', color: '#6366f1', padding: '3px 10px', borderRadius: 6, fontWeight: 700, fontSize: 12 }}>{tl[doc.type] || doc.type}</span>
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
{isValide ? 'Validé' : 'Refusé'} le {doc.dateValidation ? new Date(doc.dateValidation).toLocaleDateString('fr-FR') : '—'}
{doc.validateurNom && ` par ${doc.validateurNom}`}
</span>
</div>
{!isValide && doc.commentaire && (
<div style={{ fontSize: 12, color: '#dc2626', marginTop: 6, background: '#fef2f2', padding: '6px 10px', borderRadius: 6 }}>
Motif : {doc.commentaire}
</div>
)}
</div>
<button onClick={() => setPreviewUrl({ url: proxyUrl(doc.sharepointUrl), name: doc.fileName })} style={{ padding: '8px 14px', background: '#eef2ff', color: '#6366f1', border: 'none', borderRadius: 8, cursor: 'pointer', fontSize: 13, fontWeight: 700, fontFamily: 'inherit', flexShrink: 0 }}>👁 Voir</button>
</div>
</div>
);
})}
</div>
)
)}
</div>
)}
@@ -6630,6 +6736,73 @@ const Dashboard = (): JSX.Element => {
</div>
</div>
)}
{showProfilIncompletModal && profilCompletude && (
<div style={{
position: 'fixed', inset: 0, zIndex: 10001,
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
}}>
<div style={{
background: 'var(--bg-card)', borderRadius: 16,
padding: 28, maxWidth: 440, width: '100%',
boxShadow: '0 24px 60px rgba(0,0,0,0.3)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20, paddingBottom: 16, borderBottom: '1px solid var(--border-divider)' }}>
<div style={{ width: 44, height: 44, borderRadius: 12, flexShrink: 0, background: 'linear-gradient(135deg,#f59e0b,#d97706)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22 }}></div>
<div>
<div style={{ fontSize: 16, fontWeight: 800, color: 'var(--text-primary)' }}>Profil incomplet</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
Complétez les informations ci-dessous avant de créer une note.
</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 22 }}>
{[
{ ok: profilCompletude.adresseOk, label: 'Adresse postale complète' },
{ ok: profilCompletude.ibanOk, label: 'IBAN renseigné' },
{ ok: profilCompletude.bicOk, label: 'BIC renseigné' },
].map(({ ok, label }) => (
<div key={label} style={{
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
background: ok ? '#f0fdf4' : '#fef2f2',
border: `1px solid ${ok ? '#86efac' : '#fecaca'}`,
borderRadius: 8,
}}>
<span style={{ fontSize: 16 }}>{ok ? '✅' : '❌'}</span>
<span style={{ fontSize: 13, fontWeight: 600, color: ok ? '#15803d' : '#dc2626' }}>{label}</span>
</div>
))}
</div>
<div style={{
fontSize: 11, color: 'var(--text-muted)', background: '#eef2ff',
border: '1px solid #c7d2fe', borderRadius: 8, padding: '8px 12px', marginBottom: 16,
}}>
💡 Pour les frais kilométriques, vous devrez aussi renseigner votre véhicule et faire valider votre carte grise / permis dans votre profil avant soumission.
</div>
<div style={{ display: 'flex', gap: 10 }}>
<button onClick={() => setShowProfilIncompletModal(false)} style={{
flex: 1, padding: '11px 16px', background: 'var(--bg-input)',
border: '1px solid var(--border-input)', borderRadius: 9, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: 'var(--text-secondary)',
}}>Annuler</button>
<button onClick={() => {
setShowProfilIncompletModal(false);
window.history.pushState({ section: 'profil' }, '', '#profil');
setSection('profil');
}} style={{
flex: 2, padding: '11px 16px',
background: 'linear-gradient(135deg,#f59e0b,#d97706)',
color: '#fff', border: 'none', borderRadius: 9, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 13, fontWeight: 700,
}}>👤 Compléter mon profil</button>
</div>
</div>
</div>
)}
<NDFChatbot />
+42 -11
View File
@@ -1883,6 +1883,8 @@ export default function NouvelleNote({
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isFirstRender = useRef(true);
const activeBrouillonIdRef = useRef<number | null>(initialBrouillonId);
const errorBannerRef = useRef<HTMLDivElement>(null);
const depenseRefs = useRef<Record<number, HTMLDivElement | null>>({});
const isSavingRef = useRef(false);
const needsResaveRef = useRef(false);
@@ -2197,35 +2199,57 @@ export default function NouvelleNote({
fetchBrouillons();
} catch { }
};
const scrollToError = (depenseId?: number) => {
requestAnimationFrame(() => {
const target = depenseId ? depenseRefs.current[depenseId] : errorBannerRef.current;
if (target) {
target.scrollIntoView({ behavior: "smooth", block: "center" });
} else {
errorBannerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
}
});
};
const handleSubmit = async () => {
setSubmitError("");
if (submitting) return;
if (!titre.trim()) { setSubmitError("Veuillez saisir un titre."); return; }
if (!titre.trim()) {
setSubmitError("Veuillez saisir un titre.");
scrollToError();
return;
}
if (dateDebut && isDateFutureMonth(dateDebut)) {
setSubmitError("La date de la note ne peut pas être dans un mois futur. Vous ne pouvez créer des notes que pour le mois en cours ou des mois passés.");
scrollToError();
return;
}
for (const d of depenses) {
if (!d.date || !d.libelle.trim()) {
setSubmitError(`La dépense "${d.libelle || "sans libellé"}" doit avoir une date et un libellé.`);
setExpandedId(d.id); return;
setExpandedId(d.id);
scrollToError(d.id);
return;
}
// ✅ Bloquer les dates dans un mois futur
if (isDateFutureMonth(d.date)) {
setSubmitError(`"${d.libelle}" — la date ne peut pas être dans un mois futur. Vous ne pouvez soumettre des frais que pour le mois en cours ou des mois passés.`);
setExpandedId(d.id); return;
setExpandedId(d.id);
scrollToError(d.id);
return;
}
const isKmLine = d.categorie.toLowerCase().includes("kilom");
if (isKmLine && (!d.km || parseFloat(d.km) <= 0)) {
setSubmitError(`"${d.libelle}" doit avoir un kilométrage.`);
setExpandedId(d.id); return;
setExpandedId(d.id);
scrollToError(d.id);
return;
}
if (!isKmLine) {
const hasFiles = d.files.length > 0 || (d.filesMeta ?? []).length > 0 || (d.qrFiles ?? []).length > 0;
if (!hasFiles) {
setSubmitError(`"${d.libelle}" — un justificatif est obligatoire pour cette catégorie.`);
setExpandedId(d.id); return;
setExpandedId(d.id);
scrollToError(d.id);
return;
}
}
if (d.categorie.toLowerCase().includes("repas")) {
@@ -2233,7 +2257,9 @@ export default function NouvelleNote({
const p = d.participants[pi];
if (!p.nom?.trim() || !p.prenom?.trim()) {
setSubmitError(`"${d.libelle}" — le nom et prénom du participant ${pi + 1} sont obligatoires.`);
setExpandedId(d.id); return;
setExpandedId(d.id);
scrollToError(d.id);
return;
}
}
// Pas de blocage sur le dépassement 25€ — c'est un warning uniquement
@@ -2242,7 +2268,6 @@ export default function NouvelleNote({
setSubmitting(true);
try {
const depensesBackend = depenses.map(d => {
// Fusionner : store (fichiers pas encore uploadés) + d.files (fichiers React)
const storedFiles = getStoredFiles(d.id);
const allFiles = storedFiles.length > 0
? [...d.files, ...storedFiles.filter(sf => !d.files.some(rf => rf.name === sf.name && rf.size === sf.size))]
@@ -2255,7 +2280,11 @@ export default function NouvelleNote({
};
});
await onSubmit(titre, dateDebut, dateFin, commentaire, depensesBackend);
} catch (e: any) { setSubmitError(e.message || "Erreur lors de la soumission"); setSubmitting(false); }
} catch (e: any) {
setSubmitError(e.message || "Erreur lors de la soumission");
setSubmitting(false);
scrollToError();
}
};
// ✅ Calcul des totaux globaux — gestion du cas MIXED
@@ -2420,7 +2449,7 @@ export default function NouvelleNote({
</div>
</div>
{submitError && <div className="nn-error"> {submitError}</div>}
{submitError && <div ref={errorBannerRef} className="nn-error"> {submitError}</div>}
<div className="nn-meta">
<div className="nn-meta-field">
@@ -2454,13 +2483,15 @@ export default function NouvelleNote({
</div>
{depenses.map((d, i) => (
<DepenseCard key={d.id} depense={d} index={i} total={depenses.length}
<div key={d.id} ref={el => { depenseRefs.current[d.id] = el; }}>
<DepenseCard depense={d} index={i} total={depenses.length}
expanded={expandedId === d.id}
onToggle={handleToggle} onUpdate={handleUpdateDepense}
onDelete={handleDeleteDepense} onGenerateQR={generateQRForDepense}
disabled={submitting} apiBaseUrl={apiBaseUrl} profilVehicule={profilVehicule}
onNavigateToProfil={onNavigateToProfil}
/>
</div>
))}
<button className="nn-btn-add-card" onClick={handleAddDepense} disabled={submitting}>