feat(superadmin): página "Secretária IA" com motivos de liga/desliga por sessão

Nova aba no super admin (/superadmin/secretaria): tabela com quem ligou/desligou a
Secretária de cada número, quando e o MOTIVO do desligamento. Endpoint
/api/superadmin/secretaria-desligamentos (superadminGuard) busca o log global no
motor (resolve um e-mail de dono pareado; log é global). Item no Sidebar + rotas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-06 20:02:39 +02:00
parent cfeac891fb
commit b67fa15c55
4 changed files with 118 additions and 6 deletions
+70 -2
View File
@@ -5,7 +5,7 @@ import {
CheckCircle2, XCircle, Clock, Search, ChevronLeft, ChevronRight,
TrendingUp, Stethoscope, FlaskConical, User, ToggleLeft, ToggleRight,
Save, AlertTriangle, Wifi, WifiOff, Briefcase, Trash2, Bell, Send,
MapPin, Phone, MoreVertical, X, ArrowLeft, UserX, Sparkles, DoorOpen, UserSearch
MapPin, Phone, MoreVertical, X, ArrowLeft, UserX, Sparkles, DoorOpen, UserSearch, Power
} from 'lucide-react';
const apiFetch = (url: string, opts: RequestInit = {}) => {
@@ -74,7 +74,7 @@ const KPI: React.FC<{ icon: React.ElementType; label: string; value: string | nu
);
};
type SuperAdminTab = 'overview' | 'users' | 'clinicas' | 'logs' | 'financeiro' | 'notificacoes';
type SuperAdminTab = 'overview' | 'users' | 'clinicas' | 'logs' | 'financeiro' | 'notificacoes' | 'secretaria';
/* ═══════════════════════════════════════════════════ MAIN VIEW */
export const SuperAdminView: React.FC<{ tab?: SuperAdminTab }> = ({ tab = 'overview' }) => {
@@ -86,6 +86,74 @@ export const SuperAdminView: React.FC<{ tab?: SuperAdminTab }> = ({ tab = 'overv
{tab === 'logs' && <TabLogs />}
{tab === 'financeiro' && <TabFinanceiro />}
{tab === 'notificacoes' && <TabNotificacoes />}
{tab === 'secretaria' && <TabSecretaria />}
</div>
);
};
/* ═══════════════════════════════════════════════════ TAB SECRETÁRIA (liga/desliga por sessão) */
const TabSecretaria: React.FC = () => {
const [log, setLog] = useState<any[]>([]);
const [configurado, setConfigurado] = useState(true);
const [loading, setLoading] = useState(true);
const load = useCallback(() => {
setLoading(true);
apiFetch('/api/superadmin/secretaria-desligamentos')
.then(r => r.json())
.then(d => { setLog(Array.isArray(d.log) ? d.log : []); setConfigurado(d.configurado !== false); })
.catch(() => {})
.finally(() => setLoading(false));
}, []);
useEffect(() => { load(); }, [load]);
const totalOff = log.filter(l => !l.enabled).length;
return (
<div className="space-y-4">
<div className="flex items-center justify-between gap-3 flex-wrap">
<div>
<h2 className="text-lg font-black text-gray-900 uppercase tracking-tight flex items-center gap-2"><Power size={18} className="text-red-600" /> Secretária IA liga/desliga por sessão</h2>
<p className="text-xs text-gray-400 font-medium">Quem ligou/desligou a Secretária de cada número, e o motivo do desligamento. {totalOff > 0 && `${totalOff} desligamento(s).`}</p>
</div>
<button onClick={load} className="p-2 rounded-xl border border-gray-200 text-gray-400 hover:text-gray-700"><RefreshCw size={16} /></button>
</div>
{!configurado ? (
<div className="bg-amber-50 text-amber-700 text-sm rounded-2xl p-4 flex items-center gap-2"><AlertTriangle size={16} /> Integração com o motor da Secretária não está configurada neste ambiente.</div>
) : loading ? (
<div className="text-center text-gray-400 py-16">Carregando</div>
) : log.length === 0 ? (
<div className="text-center text-gray-400 py-16">Nenhum registro de liga/desliga ainda.</div>
) : (
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-x-auto">
<table className="w-full text-sm min-w-[640px]">
<thead className="bg-gray-50 text-[10px] font-black text-gray-400 uppercase tracking-widest">
<tr>
<th className="text-left px-4 py-3">Ação</th>
<th className="text-left px-4 py-3">Número</th>
<th className="text-left px-4 py-3">Quem</th>
<th className="text-left px-4 py-3">Quando</th>
<th className="text-left px-4 py-3">Motivo</th>
</tr>
</thead>
<tbody>
{log.map((l, i) => (
<tr key={i} className={`border-t border-gray-50 ${!l.enabled ? 'bg-red-50/30' : ''}`}>
<td className="px-4 py-3">
{l.enabled
? <span className="inline-flex items-center gap-1 text-emerald-700 text-[11px] font-black uppercase"><ToggleRight size={14} /> Ligou</span>
: <span className="inline-flex items-center gap-1 text-red-600 text-[11px] font-black uppercase"><ToggleLeft size={14} /> Desligou</span>}
</td>
<td className="px-4 py-3 text-gray-700 font-bold whitespace-nowrap">{l.phone || l.name || l.instance_id}</td>
<td className="px-4 py-3 text-gray-600 whitespace-nowrap">{l.by || '—'}</td>
<td className="px-4 py-3 text-gray-500 whitespace-nowrap">{fmt(l.at)}</td>
<td className="px-4 py-3 text-gray-600">{l.reason || <span className="text-gray-300"></span>}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
};