chore(ops): migrate clube67 to newwhats.clube67.com directory
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)

This commit is contained in:
VPS 4 Deploy Agent
2026-05-18 03:26:41 +02:00
parent 298b1c64b0
commit 5ec6bd6354
576 changed files with 29016 additions and 235673 deletions
+146
View File
@@ -0,0 +1,146 @@
import React, { useEffect, useState } from 'react';
import {
Users, Calendar, DollarSign, Activity,
ArrowUpRight, ArrowDownRight, Zap,
TrendingUp, AlertCircle, ArrowRight
} from 'lucide-react';
import { MedicoBackend } from './services/medicoBackend.ts';
import { MedicoNotif } from './services/medicoNotif.ts';
import { MedPaciente, MedAgendamento, MedNotificacao } from './types.ts';
export const MedDashboard: React.FC = () => {
const [pacientes, setPacientes] = useState<MedPaciente[]>([]);
const [agendamentos, setAgendamentos] = useState<MedAgendamento[]>([]);
const [notificacoes, setNotificacoes] = useState<MedNotificacao[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
await MedicoNotif.generateIntelligentAlerts();
const [p, a, n] = await Promise.all([
MedicoBackend.getPacientes(),
MedicoBackend.getAgendamentos(),
MedicoBackend.getNotificacoes()
]);
setPacientes(p);
setAgendamentos(a);
setNotificacoes(n.filter(notif => !notif.lida));
setLoading(false);
};
loadData();
}, []);
const stats = [
{ label: 'Pacientes Ativos', value: pacientes.length.toString(), change: '+12%', positive: true, icon: Users, color: 'cyan' },
{ label: 'Consultas Hoje', value: agendamentos.filter(a => a.start.includes(new Date().toISOString().split('T')[0])).length.toString(), change: '+4', positive: true, icon: Calendar, color: 'blue' },
{ label: 'Receita Est. (MRR)', value: 'R$ 84.200', change: '+8%', positive: true, icon: DollarSign, color: 'emerald' },
{ label: 'Alertas Ativos', value: notificacoes.length.toString(), change: '', positive: true, icon: Activity, color: 'purple' },
];
if (loading) {
return <div className="p-20 text-center text-cyan-500 font-black uppercase tracking-[0.5em] animate-pulse">Iniciando Motor Médico...</div>;
}
return (
<div className="space-y-10 animate-in fade-in duration-700">
{/* Header */}
<div>
<h1 className="text-4xl font-black tracking-tighter text-white mb-2 uppercase italic">Painel <span className="text-cyan-400">Estratégico</span></h1>
<p className="text-slate-500 font-bold uppercase tracking-widest text-xs">Visão geral do ecossistema médico e fidelidade</p>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat, i) => {
const Icon = stat.icon;
return (
<div key={i} className="bg-white/5 border border-white/10 p-8 rounded-[2rem] hover:bg-white/[0.08] transition-all group">
<div className="flex justify-between items-start mb-6">
<div className={`p-4 rounded-2xl bg-${stat.color}-500/10 text-${stat.color}-400 group-hover:scale-110 transition-transform`}>
<Icon size={24} />
</div>
<div className={`flex items-center gap-1 text-[10px] font-black uppercase tracking-widest ${stat.positive ? 'text-emerald-400' : 'text-rose-400'}`}>
{stat.change && (stat.positive ? <ArrowUpRight size={14} /> : <ArrowDownRight size={14} />)}
{stat.change}
</div>
</div>
<div className="space-y-1">
<span className="text-slate-500 text-[10px] font-black uppercase tracking-[0.2em]">{stat.label}</span>
<div className="text-3xl font-black text-white tracking-tighter">{stat.value}</div>
</div>
</div>
);
})}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Upcoming Appointments */}
<div className="lg:col-span-2 bg-white/5 border border-white/10 rounded-[2.5rem] overflow-hidden">
<div className="p-8 border-b border-white/5 flex justify-between items-center">
<h3 className="text-sm font-black uppercase tracking-[0.3em] text-white">Próximos Atendimentos</h3>
<button className="text-[10px] font-black text-cyan-400 hover:text-cyan-300 uppercase tracking-widest transition-colors">Ver Agenda Completa</button>
</div>
<div className="p-4">
{agendamentos.length > 0 ? agendamentos.slice(0, 5).map((item, i) => (
<div key={i} className="flex items-center justify-between p-4 hover:bg-white/5 rounded-2xl transition-all cursor-pointer group">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-slate-800 flex flex-col items-center justify-center border border-white/5">
<span className="text-[10px] font-black text-slate-500 leading-none">HRS</span>
<span className="text-sm font-black text-white">{new Date(item.start).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
<div>
<div className="text-sm font-black text-white uppercase tracking-tight group-hover:text-cyan-400 transition-colors">{item.pacienteNome}</div>
<div className="text-[10px] text-slate-500 font-bold uppercase tracking-widest">{item.tipo}</div>
</div>
</div>
<div className="flex items-center gap-4">
<span className={`px-3 py-1 rounded-full text-[9px] font-black uppercase tracking-widest ${item.status === 'Confirmado' ? 'bg-emerald-500/10 text-emerald-400' : 'bg-slate-800 text-slate-400'
}`}>
{item.status}
</span>
<button className="p-2 text-slate-600 hover:text-white transition-colors">
<ArrowUpRight size={18} />
</button>
</div>
</div>
)) : (
<div className="p-10 text-center text-slate-500 font-bold uppercase tracking-widest text-xs">Nenhum agendamento para hoje</div>
)}
</div>
</div>
{/* Intelligent Insights */}
<div className="bg-gradient-to-b from-cyan-600/20 to-transparent border border-cyan-500/20 rounded-[2.5rem] p-8 relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-cyan-500/20 blur-[60px]"></div>
<div className="relative">
<div className="w-12 h-12 bg-cyan-500 rounded-2xl flex items-center justify-center text-white mb-8 shadow-lg shadow-cyan-500/20">
<Zap size={24} />
</div>
<h3 className="text-xl font-black text-white tracking-tighter mb-4 italic uppercase">Inteligência <br />Médica</h3>
<div className="space-y-6">
{notificacoes.length > 0 ? notificacoes.slice(0, 2).map((notif, i) => (
<div key={i} className="p-5 bg-black/20 rounded-2xl border border-white/5">
<div className="flex items-center gap-2 mb-2">
<AlertCircle size={14} className={notif.priority === 'high' ? 'text-rose-400' : 'text-amber-400'} />
<span className={`text-[10px] font-black uppercase tracking-widest ${notif.priority === 'high' ? 'text-rose-400' : 'text-amber-400'}`}>
{notif.titulo}
</span>
</div>
<p className="text-xs text-slate-300 leading-relaxed font-medium">{notif.mensagem}</p>
</div>
)) : (
<div className="p-5 bg-black/20 rounded-2xl border border-white/5 text-center">
<p className="text-[10px] text-slate-500 font-black uppercase tracking-widest">Sem alertas pendentes</p>
</div>
)}
</div>
<button className="w-full mt-10 py-5 bg-white text-slate-900 rounded-2xl font-black uppercase tracking-widest hover:bg-cyan-400 transition-all flex items-center justify-center gap-3">
Ver Insights <ArrowRight size={18} />
</button>
</div>
</div>
</div>
</div>
);
};