From a6ae27876e044bde5f9c34a2d50a694b12f9b0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Sun, 28 Sep 2025 04:10:40 -0300 Subject: [PATCH] add-login-and-logout-endpoints --- susconecta/app/(main-routes)/layout.tsx | 4 +- susconecta/app/login-admin/page.tsx | 43 +- susconecta/app/login-paciente/page.tsx | 122 ++ susconecta/app/login/page.tsx | 43 +- susconecta/app/paciente/page.tsx | 95 ++ susconecta/app/profissional/page.tsx | 1385 +++++++++++++++++++- susconecta/app/test-supabase/page.tsx | 83 ++ susconecta/components/ProtectedRoute.tsx | 130 +- susconecta/components/dashboard/header.tsx | 21 +- susconecta/components/header.tsx | 6 +- susconecta/components/hero-section.tsx | 31 +- susconecta/hooks/useAuth.tsx | 299 +++-- susconecta/lib/api.ts | 18 +- susconecta/lib/auth.ts | 388 ++++++ susconecta/lib/config.ts | 22 + susconecta/lib/debug-utils.ts | 34 + susconecta/lib/env-config.ts | 83 ++ susconecta/lib/http.ts | 260 ++++ susconecta/lib/jwt.ts | 133 ++ susconecta/lib/simple-auth.ts | 84 ++ susconecta/package-lock.json | 406 +++--- susconecta/types/auth.ts | 90 ++ 22 files changed, 3328 insertions(+), 452 deletions(-) create mode 100644 susconecta/app/login-paciente/page.tsx create mode 100644 susconecta/app/paciente/page.tsx create mode 100644 susconecta/app/test-supabase/page.tsx create mode 100644 susconecta/lib/auth.ts create mode 100644 susconecta/lib/config.ts create mode 100644 susconecta/lib/debug-utils.ts create mode 100644 susconecta/lib/env-config.ts create mode 100644 susconecta/lib/http.ts create mode 100644 susconecta/lib/jwt.ts create mode 100644 susconecta/lib/simple-auth.ts create mode 100644 susconecta/types/auth.ts diff --git a/susconecta/app/(main-routes)/layout.tsx b/susconecta/app/(main-routes)/layout.tsx index d0e759f..6269b21 100644 --- a/susconecta/app/(main-routes)/layout.tsx +++ b/susconecta/app/(main-routes)/layout.tsx @@ -9,8 +9,10 @@ export default function MainRoutesLayout({ }: { children: React.ReactNode; }) { + console.log('[MAIN-ROUTES-LAYOUT] Layout do administrador carregado') + return ( - +
diff --git a/susconecta/app/login-admin/page.tsx b/susconecta/app/login-admin/page.tsx index 8570b02..fe3e41f 100644 --- a/susconecta/app/login-admin/page.tsx +++ b/susconecta/app/login-admin/page.tsx @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Alert, AlertDescription } from '@/components/ui/alert' +import { AuthenticationError } from '@/lib/auth' export default function LoginAdminPage() { const [credentials, setCredentials] = useState({ email: '', password: '' }) @@ -20,29 +21,27 @@ export default function LoginAdminPage() { setLoading(true) setError('') - // Simular delay de autenticação - await new Promise(resolve => setTimeout(resolve, 1000)) - - // Tentar fazer login usando o contexto com tipo administrador - const success = login(credentials.email, credentials.password, 'administrador') - - if (success) { - // Redirecionar para o dashboard do administrador - setTimeout(() => { - router.push('/dashboard') + try { + // Tentar fazer login usando o contexto com tipo administrador + const success = await login(credentials.email, credentials.password, 'administrador') + + if (success) { + console.log('[LOGIN-ADMIN] Login bem-sucedido, redirecionando...') - // Fallback: usar window.location se router.push não funcionar - setTimeout(() => { - if (window.location.pathname === '/login-admin') { - window.location.href = '/dashboard' - } - }, 100) - }, 100) - } else { - setError('Email ou senha incorretos') + // Redirecionamento direto - solução que funcionou + window.location.href = '/dashboard' + } + } catch (err) { + console.error('[LOGIN-ADMIN] Erro no login:', err) + + if (err instanceof AuthenticationError) { + setError(err.message) + } else { + setError('Erro inesperado. Tente novamente.') + } + } finally { + setLoading(false) } - - setLoading(false) } return ( @@ -75,6 +74,7 @@ export default function LoginAdminPage() { onChange={(e) => setCredentials({...credentials, email: e.target.value})} required className="mt-1" + disabled={loading} />
@@ -90,6 +90,7 @@ export default function LoginAdminPage() { onChange={(e) => setCredentials({...credentials, password: e.target.value})} required className="mt-1" + disabled={loading} /> diff --git a/susconecta/app/login-paciente/page.tsx b/susconecta/app/login-paciente/page.tsx new file mode 100644 index 0000000..6468241 --- /dev/null +++ b/susconecta/app/login-paciente/page.tsx @@ -0,0 +1,122 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import Link from 'next/link' +import { useAuth } from '@/hooks/useAuth' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { AuthenticationError } from '@/lib/auth' + +export default function LoginPacientePage() { + const [credentials, setCredentials] = useState({ email: '', password: '' }) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + const router = useRouter() + const { login } = useAuth() + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError('') + + try { + // Tentar fazer login usando o contexto com tipo paciente + const success = await login(credentials.email, credentials.password, 'paciente') + + if (success) { + // Redirecionar para a página do paciente + router.push('/paciente') + } + } catch (err) { + console.error('[LOGIN-PACIENTE] Erro no login:', err) + + if (err instanceof AuthenticationError) { + setError(err.message) + } else { + setError('Erro inesperado. Tente novamente.') + } + } finally { + setLoading(false) + } + } + + return ( +
+
+
+

+ Portal do Paciente +

+

+ Acesse sua área pessoal e gerencie suas consultas +

+
+ + + + Entrar como Paciente + + +
+
+ + setCredentials({...credentials, email: e.target.value})} + required + className="mt-1" + disabled={loading} + /> +
+ +
+ + setCredentials({...credentials, password: e.target.value})} + required + className="mt-1" + disabled={loading} + /> +
+ + {error && ( + + {error} + + )} + + +
+ +
+ +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/susconecta/app/login/page.tsx b/susconecta/app/login/page.tsx index b308b85..bbf1826 100644 --- a/susconecta/app/login/page.tsx +++ b/susconecta/app/login/page.tsx @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Alert, AlertDescription } from '@/components/ui/alert' +import { AuthenticationError } from '@/lib/auth' export default function LoginPage() { const [credentials, setCredentials] = useState({ email: '', password: '' }) @@ -20,29 +21,27 @@ export default function LoginPage() { setLoading(true) setError('') - // Simular delay de autenticação - await new Promise(resolve => setTimeout(resolve, 1000)) - - // Tentar fazer login usando o contexto com tipo profissional - const success = login(credentials.email, credentials.password, 'profissional') - - if (success) { - // Redirecionar para a página do profissional - setTimeout(() => { - router.push('/profissional') + try { + // Tentar fazer login usando o contexto com tipo profissional + const success = await login(credentials.email, credentials.password, 'profissional') + + if (success) { + console.log('[LOGIN-PROFISSIONAL] Login bem-sucedido, redirecionando...') - // Fallback: usar window.location se router.push não funcionar - setTimeout(() => { - if (window.location.pathname === '/login') { - window.location.href = '/profissional' - } - }, 100) - }, 100) - } else { - setError('Email ou senha incorretos') + // Redirecionamento direto - solução que funcionou + window.location.href = '/profissional' + } + } catch (err) { + console.error('[LOGIN-PROFISSIONAL] Erro no login:', err) + + if (err instanceof AuthenticationError) { + setError(err.message) + } else { + setError('Erro inesperado. Tente novamente.') + } + } finally { + setLoading(false) } - - setLoading(false) } return ( @@ -75,6 +74,7 @@ export default function LoginPage() { onChange={(e) => setCredentials({...credentials, email: e.target.value})} required className="mt-1" + disabled={loading} /> @@ -90,6 +90,7 @@ export default function LoginPage() { onChange={(e) => setCredentials({...credentials, password: e.target.value})} required className="mt-1" + disabled={loading} /> diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx new file mode 100644 index 0000000..44449d3 --- /dev/null +++ b/susconecta/app/paciente/page.tsx @@ -0,0 +1,95 @@ +'use client' +import { useAuth } from '@/hooks/useAuth' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { User, LogOut, Home } from 'lucide-react' +import Link from 'next/link' +import ProtectedRoute from '@/components/ProtectedRoute' + +export default function PacientePage() { + const { logout, user } = useAuth() + + const handleLogout = async () => { + console.log('[PACIENTE] Iniciando logout...') + await logout() + } + + return ( + +
+ + +
+ +
+ + Portal do Paciente + +

+ Bem-vindo ao seu espaço pessoal +

+
+ + + {/* Informações do Paciente */} +
+

+ Maria Silva Santos +

+

+ CPF: 123.456.789-00 +

+

+ Idade: 35 anos +

+
+ + {/* Informações do Login */} +
+
+

+ Conectado como: +

+

+ {user?.email || 'paciente@example.com'} +

+

+ Tipo de usuário: Paciente +

+
+
+ + {/* Botão Voltar ao Início */} + + + {/* Botão de Logout */} + + + {/* Informação adicional */} +
+

+ Em breve, mais funcionalidades estarão disponíveis +

+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 1625bce..d2135fb 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -27,7 +27,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" -import { User, FolderOpen, X, Users, MessageSquare, ClipboardList, Plus, Edit, Trash2, ChevronLeft, ChevronRight, Clock } from "lucide-react" +import { User, FolderOpen, X, Users, MessageSquare, ClipboardList, Plus, Edit, Trash2, ChevronLeft, ChevronRight, Clock, FileCheck, Upload, Download, Eye, History, Stethoscope, Pill, Activity } from "lucide-react" import { Calendar as CalendarIcon, FileText, Settings } from "lucide-react"; import { Tooltip, @@ -55,7 +55,7 @@ const pacientes = [ const medico = { nome: "Dr. Carlos Andrade", - identificacao: "CRM 000000 • Cardiologia", + identificacao: "CRM 000000 • Cardiologia e Dermatologia", fotoUrl: "", } @@ -70,7 +70,7 @@ const colorsByType = { }; const ProfissionalPage = () => { - const { logout, userEmail } = useAuth(); + const { logout, user } = useAuth(); const [activeSection, setActiveSection] = useState('calendario'); const [pacienteSelecionado, setPacienteSelecionado] = useState(null); @@ -78,14 +78,57 @@ const ProfissionalPage = () => { const [isEditingProfile, setIsEditingProfile] = useState(false); const [profileData, setProfileData] = useState({ nome: "Dr. Carlos Andrade", - email: userEmail || "carlos.andrade@hospital.com", + email: user?.email || "carlos.andrade@hospital.com", telefone: "(11) 99999-9999", endereco: "Rua das Flores, 123 - Centro", cidade: "São Paulo", cep: "01234-567", crm: "CRM 000000", - especialidade: "Cardiologia", - biografia: "Médico cardiologista com mais de 15 anos de experiência em cirurgias cardíacas e tratamentos preventivos." + especialidade: "Cardiologia e Dermatologia", + biografia: "Médico especialista em cardiologia e dermatologia com mais de 15 anos de experiência em tratamentos clínicos e cirúrgicos." + }); + + // Estados para relatórios médicos + const [relatorioMedico, setRelatorioMedico] = useState({ + pacienteNome: "", + pacienteCpf: "", + pacienteIdade: "", + profissionalNome: medico.nome, + profissionalCrm: medico.identificacao, + motivoRelatorio: "", + historicoClinico: "", + sinaisSintomas: "", + examesRealizados: "", + resultadosExames: "", + diagnosticos: "", + prognostico: "", + tratamentosRealizados: "", + recomendacoes: "", + dataRelatorio: new Date().toISOString().split('T')[0] + }); + const [relatoriosMedicos, setRelatoriosMedicos] = useState([]); + const [editandoRelatorio, setEditandoRelatorio] = useState(null); + + // Estados para funcionalidades do prontuário + const [consultasRegistradas, setConsultasRegistradas] = useState([]); + const [historicoMedico, setHistoricoMedico] = useState([]); + const [prescricoesMedicas, setPrescricoesMedicas] = useState([]); + const [examesSolicitados, setExamesSolicitados] = useState([]); + const [diagnosticos, setDiagnosticos] = useState([]); + const [evolucaoQuadro, setEvolucaoQuadro] = useState([]); + const [anexos, setAnexos] = useState([]); + const [abaProntuarioAtiva, setAbaProntuarioAtiva] = useState('nova-consulta'); + + // Estados para campos principais da consulta + const [consultaAtual, setConsultaAtual] = useState({ + dataConsulta: new Date().toISOString().split('T')[0], + anamnese: "", + exameFisico: "", + hipotesesDiagnosticas: "", + condutaMedica: "", + prescricoes: "", + retornoAgendado: "", + cid10: "" }); const [events, setEvents] = useState([ @@ -102,11 +145,11 @@ const ProfissionalPage = () => { { id: 2, title: "Bruno Lima", - type: "Rotina", + type: "Cardiologia", time: "10:30", date: new Date().toISOString().split('T')[0], pacienteId: "987.654.321-00", - color: colorsByType.Rotina + color: colorsByType.Cardiologia }, { id: 3, @@ -206,6 +249,90 @@ const ProfissionalPage = () => { setIsEditingProfile(false); }; + // Funções para relatórios médicos + const handleRelatorioChange = (field: string, value: string) => { + setRelatorioMedico(prev => ({ + ...prev, + [field]: value + })); + }; + + const handleSalvarRelatorio = () => { + if (!relatorioMedico.pacienteNome || !relatorioMedico.motivoRelatorio) { + alert('Por favor, preencha pelo menos o nome do paciente e o motivo do relatório.'); + return; + } + + const novoRelatorio = { + ...relatorioMedico, + id: Date.now(), + dataGeracao: new Date().toLocaleString() + }; + + if (editandoRelatorio) { + setRelatoriosMedicos(prev => + prev.map(rel => rel.id === editandoRelatorio.id ? novoRelatorio : rel) + ); + setEditandoRelatorio(null); + alert('Relatório médico atualizado com sucesso!'); + } else { + setRelatoriosMedicos(prev => [novoRelatorio, ...prev]); + alert('Relatório médico salvo com sucesso!'); + } + + // Limpar formulário + setRelatorioMedico({ + pacienteNome: "", + pacienteCpf: "", + pacienteIdade: "", + profissionalNome: medico.nome, + profissionalCrm: medico.identificacao, + motivoRelatorio: "", + historicoClinico: "", + sinaisSintomas: "", + examesRealizados: "", + resultadosExames: "", + diagnosticos: "", + prognostico: "", + tratamentosRealizados: "", + recomendacoes: "", + dataRelatorio: new Date().toISOString().split('T')[0] + }); + }; + + const handleEditarRelatorio = (relatorio: any) => { + setRelatorioMedico(relatorio); + setEditandoRelatorio(relatorio); + }; + + const handleExcluirRelatorio = (id: number) => { + if (confirm('Tem certeza que deseja excluir este relatório médico?')) { + setRelatoriosMedicos(prev => prev.filter(rel => rel.id !== id)); + alert('Relatório médico excluído com sucesso!'); + } + }; + + const handleCancelarEdicaoRelatorio = () => { + setEditandoRelatorio(null); + setRelatorioMedico({ + pacienteNome: "", + pacienteCpf: "", + pacienteIdade: "", + profissionalNome: medico.nome, + profissionalCrm: medico.identificacao, + motivoRelatorio: "", + historicoClinico: "", + sinaisSintomas: "", + examesRealizados: "", + resultadosExames: "", + diagnosticos: "", + prognostico: "", + tratamentosRealizados: "", + recomendacoes: "", + dataRelatorio: new Date().toISOString().split('T')[0] + }); + }; + const handleDateClick = (arg: any) => { setSelectedDate(arg.dateStr); @@ -489,76 +616,891 @@ const ProfissionalPage = () => { const renderProntuarioSection = () => ( -
-

Prontuário do Paciente

- {pacienteSelecionado && ( -
-
-

Dados do Paciente

- +
+
+

Prontuário do Paciente

+ + {/* Informações do Paciente Selecionado */} + {pacienteSelecionado && ( +
+
+

Dados do Paciente

+
+ + +
+
+
+
+ Nome: +

{pacienteSelecionado.nome}

+
+
+ CPF: +

{pacienteSelecionado.cpf}

+
+
+ Idade: +

{pacienteSelecionado.idade} anos

+
+
-
+ )} + + {/* Seletor de Paciente */} + {!pacienteSelecionado && ( +
+
+
+ +

Selecionar Paciente

+

Escolha um paciente para visualizar o prontuário completo

+
+ +
+ + +
+
+ + {/* Cards de pacientes para seleção rápida */}
- Nome: -

{pacienteSelecionado.nome}

+

Ou selecione rapidamente:

+
+ {pacientes.map((paciente) => ( +
setPacienteSelecionado(paciente)} + className="border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-primary transition-all cursor-pointer group" + > +
+
+ +
+
+

{paciente.nome}

+

CPF: {paciente.cpf}

+

{paciente.idade} anos

+
+
+
+ + {paciente.statusLaudo} + + +
+
+ ))} +
+
+
+ )} + + {/* Tabs de Navegação do Prontuário */} + {pacienteSelecionado && ( +
+ +
+ )} + + {/* Conteúdo das Abas */} + {pacienteSelecionado && ( +
+ {abaProntuarioAtiva === 'nova-consulta' && renderNovaConsultaTab()} + {abaProntuarioAtiva === 'consultas' && renderConsultasTab()} + {abaProntuarioAtiva === 'historico' && renderHistoricoTab()} + {abaProntuarioAtiva === 'prescricoes' && renderPrescricoesTab()} + {abaProntuarioAtiva === 'exames' && renderExamesTab()} + {abaProntuarioAtiva === 'diagnosticos' && renderDiagnosticosTab()} + {abaProntuarioAtiva === 'evolucao' && renderEvolucaoTab()} + {abaProntuarioAtiva === 'anexos' && renderAnexosTab()} +
+ )} +
+
+ ); + + // Função para alterar campos da consulta atual + const handleConsultaChange = (field: string, value: string) => { + setConsultaAtual(prev => ({ + ...prev, + [field]: value + })); + }; + + // Função para salvar a consulta + const handleSalvarConsulta = () => { + if (!consultaAtual.anamnese || !consultaAtual.exameFisico) { + alert('Por favor, preencha os campos que são obrigatórios.'); + return; + } + + const novaConsulta = { + ...consultaAtual, + id: Date.now(), + paciente: pacienteSelecionado?.nome, + dataCriacao: new Date().toLocaleString(), + profissional: medico.nome + }; + + setConsultasRegistradas(prev => [novaConsulta, ...prev]); + + setConsultaAtual({ + dataConsulta: new Date().toISOString().split('T')[0], + anamnese: "", + exameFisico: "", + hipotesesDiagnosticas: "", + condutaMedica: "", + prescricoes: "", + retornoAgendado: "", + cid10: "" + }); + + alert('Consulta registrada com sucesso!'); + }; + + // Funções para renderizar cada aba do prontuário + const renderNovaConsultaTab = () => ( +
+
+

Registrar Nova Consulta

+
+ + +
+
+ +
+ {/* Data da Consulta */} +
+
+ + handleConsultaChange('dataConsulta', e.target.value)} + className="w-full" + /> +
+ +
+ + handleConsultaChange('cid10', e.target.value)} + placeholder="Ex: I10, E11, etc." + className="w-full" + /> +
+
+ + {/* Anamnese */} +
+ +