feat: improve learning and platform operations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Loader2, RefreshCw, ShieldCheck, UserCheck, UserX, Users } from 'lucide-react';
|
||||
import { adminApi, ManagedUser, ManagedUserDetail, PlatformAnalytics } from '../services/api';
|
||||
import { Download, Loader2, RefreshCw, ScrollText, ShieldCheck, UserCheck, UserX, Users } from 'lucide-react';
|
||||
import { adminApi, AuditEntry, ManagedUser, ManagedUserDetail, PlatformAnalytics } from '../services/api';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useToast } from '../context/ToastContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -25,14 +25,15 @@ export const SuperAdmin: React.FC = () => {
|
||||
const [accessLink, setAccessLink] = useState('');
|
||||
const [selectedUser, setSelectedUser] = useState<ManagedUserDetail | null>(null);
|
||||
const [loadingDetailId, setLoadingDetailId] = useState<string | null>(null);
|
||||
const [auditEntries, setAuditEntries] = useState<AuditEntry[]>([]);
|
||||
|
||||
const loadUsers = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await adminApi.listUsers();
|
||||
const [response, dashboard, auditLog] = await Promise.all([adminApi.listUsers(), adminApi.dashboard(), adminApi.auditLog()]);
|
||||
setUsers(response.data);
|
||||
const dashboard = await adminApi.dashboard();
|
||||
setAnalytics(dashboard.data);
|
||||
setAuditEntries(auditLog.data);
|
||||
} catch {
|
||||
showToast('Não foi possível carregar os usuários.', 'error');
|
||||
} finally {
|
||||
@@ -89,6 +90,12 @@ export const SuperAdmin: React.FC = () => {
|
||||
setLoadingDetailId(null);
|
||||
}
|
||||
};
|
||||
const exportUsers = () => {
|
||||
const csv = ['Nome,E-mail,Perfil,Status,Cadastro', ...filteredUsers.map((account) => [account.name, account.email, roleLabel[account.role], account.isActive ? 'Ativo' : 'Desativado', new Intl.DateTimeFormat('pt-BR').format(new Date(account.createdAt))].map((value) => `"${value.replaceAll('"', '""')}"`).join(','))].join('\n');
|
||||
const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }));
|
||||
const link = document.createElement('a'); link.href = url; link.download = 'usuarios-compor-hub.csv'; link.click(); URL.revokeObjectURL(url);
|
||||
};
|
||||
const auditLabel: Record<string, string> = { 'invitation.created': 'Convite criado', 'password_reset.created': 'Redefinição de senha criada', 'user.updated': 'Usuário atualizado', 'course.published': 'Curso publicado', 'course.drafted': 'Rascunho salvo', 'course.archived': 'Curso arquivado', 'comment.replied': 'Comentário respondido', 'comment.deleted': 'Comentário removido' };
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-28 pb-20 px-6 md:px-12 max-w-[1440px] mx-auto">
|
||||
@@ -113,6 +120,7 @@ export const SuperAdmin: React.FC = () => {
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" /> Atualizar
|
||||
</button>
|
||||
<button onClick={exportUsers} className="inline-flex items-center justify-center gap-2 rounded-full px-5 py-3 text-sm font-semibold bg-white/10 hover:bg-white/15 text-white transition-colors"><Download className="w-4 h-4" /> Exportar CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -226,6 +234,11 @@ export const SuperAdmin: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-white/10 bg-zinc-950/80 overflow-hidden mt-8">
|
||||
<div className="p-5 border-b border-white/10 flex items-center gap-2"><ScrollText className="w-5 h-5 text-orange-400" /><h2 className="text-lg font-bold text-white">Registro de atividades</h2></div>
|
||||
{auditEntries.length === 0 ? <p className="px-5 py-10 text-sm text-gray-500">Nenhuma atividade administrativa registrada ainda.</p> : <div className="divide-y divide-white/5">{auditEntries.map((entry) => <div key={entry.id} className="p-4 flex flex-col sm:flex-row sm:items-center gap-1 sm:justify-between text-sm"><div><span className="font-semibold text-white">{auditLabel[entry.action] || entry.action}</span><span className="text-gray-500"> · {entry.actorName}</span></div><time className="text-xs text-gray-500">{new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(entry.createdAt))}</time></div>)}</div>}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user