function AdminPanel({ user }) { const [tab, setTab] = React.useState('users'); const [users, setUsers] = React.useState([]); const [projects, setProjects] = React.useState([]); const [auditData, setAuditData] = React.useState({ items: [], total: 0 }); const [auditActions, setAuditActions] = React.useState([]); const [loading, setLoading] = React.useState(false); // Audit filters const [auditSearch, setAuditSearch] = React.useState(''); const [auditAction, setAuditAction] = React.useState(''); const [auditProject, setAuditProject] = React.useState(''); const [auditPage, setAuditPage] = React.useState(0); const pageSize = 50; // LDAP search const [ldapQuery, setLdapQuery] = React.useState(''); const [ldapResults, setLdapResults] = React.useState([]); const [ldapSearching, setLdapSearching] = React.useState(false); // Access management const [accessModal, setAccessModal] = React.useState(null); // { userId, username } const [accessProjects, setAccessProjects] = React.useState([]); const [selectedProject, setSelectedProject] = React.useState(''); const [selectedRole, setSelectedRole] = React.useState('editor'); const loadUsers = async () => { try { const data = await API.get('/api/admin/users'); setUsers(data); } catch (err) { Toasts.error(err.message); } }; const loadProjects = async () => { try { const data = await API.get('/api/projects'); setProjects(data); } catch (err) { Toasts.error(err.message); } }; const loadAudit = async () => { setLoading(true); try { const params = new URLSearchParams({ limit: pageSize, offset: auditPage * pageSize }); if (auditSearch) params.set('search', auditSearch); if (auditAction) params.set('action', auditAction); if (auditProject) params.set('project_id', auditProject); const data = await API.get(`/api/admin/audit?${params}`); setAuditData(data); } catch (err) { Toasts.error(err.message); } setLoading(false); }; const loadAuditActions = async () => { try { const data = await API.get('/api/admin/audit/actions'); setAuditActions(data); } catch (err) {} }; React.useEffect(() => { loadUsers(); loadProjects(); loadAuditActions(); }, []); React.useEffect(() => { if (tab === 'audit') loadAudit(); }, [tab, auditPage, auditSearch, auditAction, auditProject]); const handleLdapSearch = async () => { if (ldapQuery.length < 2) return; setLdapSearching(true); try { const data = await API.get(`/api/admin/ldap/search?q=${encodeURIComponent(ldapQuery)}`); setLdapResults(data); } catch (err) { Toasts.error(err.message); } setLdapSearching(false); }; const handleToggleRole = async (userId, currentRole, username) => { const newRole = currentRole === 'admin' ? 'user' : 'admin'; if (!confirm(`Changer le rôle de ${username} en ${newRole} ?`)) return; try { await API.put(`/api/admin/users/${userId}/role?role=${newRole}`); Toasts.success(`${username} est maintenant ${newRole}`); loadUsers(); } catch (err) { Toasts.error(err.message); } }; const handleAddLdapUser = async (username, ldapEmail) => { let email = ldapEmail; if (!email) { email = prompt(`Aucun email trouvé dans l'annuaire pour ${username}.\nSaisissez son adresse email :`, `${username}@aprogsys.com`); if (!email) return; } try { await API.post(`/api/admin/users/create-from-ldap?username=${encodeURIComponent(username)}&email=${encodeURIComponent(email)}`); Toasts.success(`Utilisateur ${username} ajouté`); loadUsers(); setLdapResults([]); setLdapQuery(''); } catch (err) { Toasts.error(err.message); } }; const handleDeleteUser = async (userId, username) => { if (!confirm(`Supprimer l'utilisateur "${username}" de l'application ?`)) return; try { await API.delete(`/api/admin/users/${userId}`); Toasts.success(`${username} supprimé`); loadUsers(); } catch (err) { Toasts.error(err.message); } }; const handleResendInvite = async (userId, username) => { try { const res = await API.post(`/api/admin/users/${userId}/resend-invite`); Toasts.success(res.message || `Invitation renvoyée à ${username}`); } catch (err) { Toasts.error(err.message); } }; const handleGrantAccess = async () => { if (!selectedProject || !accessModal) return; try { await API.post(`/api/admin/users/${accessModal.userId}/projects/${selectedProject}`, { role: selectedRole }); Toasts.success(`Accès accordé à ${accessModal.username}`); setAccessModal(null); loadUsers(); } catch (err) { Toasts.error(err.message); } }; const handleRevokeAccess = async (userId, projectId, username, projectName) => { if (!confirm(`Retirer l'accès de ${username} au projet ${projectName} ?`)) return; try { await API.delete(`/api/admin/users/${userId}/projects/${projectId}`); Toasts.success('Accès retiré'); loadUsers(); } catch (err) { Toasts.error(err.message); } }; const handleExportAudit = async () => { try { const params = new URLSearchParams(); if (auditProject) params.set('project_id', auditProject); const res = await API.fetch(`/api/admin/audit/export?${params}`); const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'audit_log.csv'; a.click(); URL.revokeObjectURL(url); Toasts.success('Export CSV téléchargé'); } catch (err) { Toasts.error(err.message); } }; const getActionClass = (action) => { if (action.includes('login')) return 'action-login'; if (action.includes('create') || action.includes('add') || action.includes('grant') || action.includes('upload')) return 'action-create'; if (action.includes('update') || action.includes('reorder') || action.includes('move')) return 'action-update'; if (action.includes('delete') || action.includes('revoke')) return 'action-delete'; return ''; }; return React.createElement('div', { className: 'admin-page' }, React.createElement('h2', { style: { marginBottom: '24px', fontSize: '24px', fontWeight: '700' } }, 'Administration'), React.createElement('div', { className: 'admin-tabs' }, ['users', 'audit'].map(t => React.createElement('button', { key: t, className: `admin-tab ${tab === t ? 'active' : ''}`, onClick: () => setTab(t) }, t === 'users' ? 'Utilisateurs & Accès' : 'Journal d\'audit') ) ), // USERS TAB tab === 'users' && React.createElement('div', null, // LDAP Search React.createElement('div', { style: { background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', padding: '20px', marginBottom: '24px' } }, React.createElement('h4', { style: { marginBottom: '12px', fontSize: '14px', fontWeight: '600' } }, 'Ajouter un utilisateur LDAP'), React.createElement('div', { style: { display: 'flex', gap: '8px' } }, React.createElement('input', { type: 'text', value: ldapQuery, placeholder: 'Rechercher dans l\'annuaire...', onChange: e => setLdapQuery(e.target.value), onKeyDown: e => { if (e.key === 'Enter') handleLdapSearch(); }, style: { flex: 1, padding: '8px 12px', background: 'var(--bg-input)', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', color: 'var(--text)', fontSize: '13px', outline: 'none' } }), React.createElement('button', { className: 'btn btn-primary btn-sm', onClick: handleLdapSearch, disabled: ldapQuery.length < 2 || ldapSearching }, ldapSearching ? 'Recherche...' : 'Rechercher') ), ldapResults.length > 0 && React.createElement('div', { style: { marginTop: '12px' } }, ldapResults.map((r, i) => React.createElement('div', { key: i, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: 'var(--bg-light)', borderRadius: 'var(--radius-sm)', marginBottom: '4px' } }, React.createElement('div', null, React.createElement('span', { style: { fontWeight: '600', fontSize: '13px' } }, r.display_name || r.username), React.createElement('span', { style: { color: 'var(--text-muted)', fontSize: '12px', marginLeft: '8px' } }, r.username), r.email && React.createElement('span', { style: { color: 'var(--text-muted)', fontSize: '12px', marginLeft: '8px' } }, r.email) ), React.createElement('button', { className: 'btn btn-success btn-sm', onClick: () => handleAddLdapUser(r.username, r.email) }, '+ Ajouter') ) ) ) ), // Users table React.createElement('div', { className: 'admin-table-wrapper' }, React.createElement('table', { className: 'admin-table' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, 'Utilisateur'), React.createElement('th', null, 'Nom'), React.createElement('th', null, 'Type'), React.createElement('th', null, 'Rôle'), React.createElement('th', null, 'Projets'), React.createElement('th', null, 'Dernière connexion'), React.createElement('th', null, 'Actions') ) ), React.createElement('tbody', null, users.map(u => React.createElement('tr', { key: u.id }, React.createElement('td', null, React.createElement('span', { style: { fontWeight: '600' } }, u.username) ), React.createElement('td', null, u.display_name || '—'), React.createElement('td', null, React.createElement('span', { className: `role-badge ${u.auth_type === 'local' ? 'role-admin' : 'role-editor'}` }, u.auth_type) ), React.createElement('td', null, React.createElement('span', { className: `role-badge role-${u.role}`, style: { cursor: 'pointer' }, title: 'Cliquer pour changer le rôle', onClick: () => handleToggleRole(u.id, u.role, u.username) }, u.role) ), React.createElement('td', { style: { fontSize: '12px', color: 'var(--text-muted)' } }, u.projects || '—'), React.createElement('td', { style: { fontSize: '12px' } }, u.last_login ? new Date(u.last_login).toLocaleString('fr') : '—'), React.createElement('td', null, React.createElement('button', { className: 'btn btn-ghost btn-sm', onClick: () => { setAccessModal({ userId: u.id, username: u.username }); setSelectedProject(''); setSelectedRole('editor'); } }, '\uD83D\uDD11 Accès'), u.email && React.createElement('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleResendInvite(u.id, u.username), title: `Renvoyer invitation à ${u.email}` }, '\u2709'), u.username !== 'admin' && React.createElement('button', { className: 'btn btn-ghost btn-sm', style: { color: 'var(--red)' }, onClick: () => handleDeleteUser(u.id, u.username), title: 'Supprimer cet utilisateur' }, '\u2715') ) ) ) ) ) ) ), // AUDIT TAB tab === 'audit' && React.createElement('div', null, React.createElement('div', { className: 'filters-bar' }, React.createElement('input', { type: 'text', placeholder: 'Rechercher...', value: auditSearch, onChange: e => { setAuditSearch(e.target.value); setAuditPage(0); } }), React.createElement('select', { value: auditAction, onChange: e => { setAuditAction(e.target.value); setAuditPage(0); } }, React.createElement('option', { value: '' }, 'Toutes les actions'), auditActions.map(a => React.createElement('option', { key: a, value: a }, a)) ), React.createElement('select', { value: auditProject, onChange: e => { setAuditProject(e.target.value); setAuditPage(0); } }, React.createElement('option', { value: '' }, 'Tous les projets'), projects.map(p => React.createElement('option', { key: p.id, value: p.id }, p.name)) ), React.createElement('button', { className: 'btn btn-ghost btn-sm', onClick: handleExportAudit }, '\uD83D\uDCE5 Export CSV'), React.createElement('span', { style: { color: 'var(--text-muted)', fontSize: '12px', marginLeft: 'auto' } }, `${auditData.total} entrées`) ), React.createElement('div', { className: 'admin-table-wrapper' }, React.createElement('table', { className: 'admin-table' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, 'Date'), React.createElement('th', null, 'Utilisateur'), React.createElement('th', null, 'Action'), React.createElement('th', null, 'Projet'), React.createElement('th', null, 'Détails'), React.createElement('th', null, 'IP') ) ), React.createElement('tbody', null, loading ? React.createElement('tr', null, React.createElement('td', { colSpan: 6, style: { textAlign: 'center', padding: '40px' } }, React.createElement('span', { className: 'loading-spinner' }) ) ) : auditData.items.map(a => React.createElement('tr', { key: a.id }, React.createElement('td', { style: { fontSize: '12px', whiteSpace: 'nowrap' } }, new Date(a.timestamp + 'Z').toLocaleString('fr') ), React.createElement('td', { style: { fontWeight: '600', fontSize: '13px' } }, a.username || '—'), React.createElement('td', null, React.createElement('span', { className: `action-badge ${getActionClass(a.action)}` }, a.action) ), React.createElement('td', { style: { fontSize: '12px' } }, a.project_name || '—'), React.createElement('td', { style: { fontSize: '11px', color: 'var(--text-muted)', maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, a.details || '' ), React.createElement('td', { style: { fontSize: '11px', color: 'var(--text-muted)' } }, a.ip_address || '') ) ) ) ) ), // Pagination auditData.total > pageSize && React.createElement('div', { style: { display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '16px' } }, React.createElement('button', { className: 'btn btn-ghost btn-sm', disabled: auditPage === 0, onClick: () => setAuditPage(auditPage - 1) }, '\u25C0 Précédent'), React.createElement('span', { style: { padding: '6px 12px', fontSize: '13px', color: 'var(--text-muted)' } }, `Page ${auditPage + 1} / ${Math.ceil(auditData.total / pageSize)}` ), React.createElement('button', { className: 'btn btn-ghost btn-sm', disabled: (auditPage + 1) * pageSize >= auditData.total, onClick: () => setAuditPage(auditPage + 1) }, 'Suivant \u25B6') ) ), // Access modal accessModal && React.createElement('div', { className: 'modal-overlay', onClick: e => { if (e.target === e.currentTarget) setAccessModal(null); } }, React.createElement('div', { className: 'modal-content' }, React.createElement('div', { className: 'modal-header' }, React.createElement('h3', null, `Gérer accès — ${accessModal.username}`), React.createElement('button', { className: 'modal-close', onClick: () => setAccessModal(null) }, '\u2715') ), React.createElement('div', { style: { marginBottom: '16px' } }, React.createElement('h4', { style: { fontSize: '13px', marginBottom: '8px', color: 'var(--text-muted)' } }, 'Accorder accès'), React.createElement('div', { style: { display: 'flex', gap: '8px' } }, React.createElement('select', { value: selectedProject, onChange: e => setSelectedProject(e.target.value), style: { flex: 1, padding: '8px', background: 'var(--bg-input)', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', color: 'var(--text)', fontSize: '13px' } }, React.createElement('option', { value: '' }, 'Sélectionner un projet...'), projects.map(p => React.createElement('option', { key: p.id, value: p.id }, p.name)) ), React.createElement('select', { value: selectedRole, onChange: e => setSelectedRole(e.target.value), style: { padding: '8px', background: 'var(--bg-input)', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', color: 'var(--text)', fontSize: '13px' } }, React.createElement('option', { value: 'viewer' }, 'Lecteur'), React.createElement('option', { value: 'editor' }, 'Éditeur'), React.createElement('option', { value: 'manager' }, 'Manager') ), React.createElement('button', { className: 'btn btn-primary btn-sm', disabled: !selectedProject, onClick: handleGrantAccess }, 'Accorder') ) ), // Current access React.createElement('div', null, React.createElement('h4', { style: { fontSize: '13px', marginBottom: '8px', color: 'var(--text-muted)' } }, 'Accès actuels'), (() => { const userInfo = users.find(u => u.id === accessModal.userId); const accessList = userInfo && userInfo.access ? userInfo.access : []; return accessList.length > 0 ? accessList.map((a) => React.createElement('div', { key: a.project_id, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: 'var(--bg-light)', borderRadius: 'var(--radius-sm)', marginBottom: '4px' } }, React.createElement('span', { style: { fontSize: '13px', fontWeight: '500' } }, a.project_name), React.createElement('div', { style: { display: 'flex', gap: '6px', alignItems: 'center' } }, React.createElement('select', { value: a.role, onChange: async (e) => { try { await API.post(`/api/admin/users/${accessModal.userId}/projects/${a.project_id}`, { role: e.target.value }); Toasts.success(`Rôle mis à jour`); loadUsers(); } catch (err) { Toasts.error(err.message); } }, style: { padding: '4px 8px', background: 'var(--bg-input)', border: '1px solid var(--border)', borderRadius: 'var(--radius-xs)', color: 'var(--text)', fontSize: '12px', cursor: 'pointer' } }, React.createElement('option', { value: 'viewer' }, 'Lecteur'), React.createElement('option', { value: 'editor' }, 'Éditeur'), React.createElement('option', { value: 'manager' }, 'Manager') ), React.createElement('button', { className: 'btn btn-ghost btn-sm', style: { color: 'var(--red)', padding: '2px 8px' }, onClick: () => handleRevokeAccess(accessModal.userId, a.project_id, accessModal.username, a.project_name) }, '\u2715') ) ) ) : React.createElement('p', { style: { fontSize: '13px', color: 'var(--text-muted)' } }, 'Aucun accès'); })() ) ) ) ); }