diff --git a/susconecta/app/(main-routes)/calendar/page.tsx b/susconecta/app/(main-routes)/calendar/page.tsx index b5a7947..a6a4bfd 100644 --- a/susconecta/app/(main-routes)/calendar/page.tsx +++ b/susconecta/app/(main-routes)/calendar/page.tsx @@ -26,7 +26,7 @@ import { const ListaEspera = dynamic( () => import("@/components/agendamento/ListaEspera"), - { ssr: false } + { ssr: false }, ); export default function AgendamentoPage() { @@ -48,17 +48,19 @@ export default function AgendamentoPage() { useEffect(() => { let events: EventInput[] = []; - appointments.forEach((obj) => { + appointments.forEach((object) => { const event: EventInput = { - title: `${obj.patient}: ${obj.type}`, - start: new Date(obj.time), - end: new Date(new Date(obj.time).getTime() + obj.duration * 60 * 1000), + title: `${object.patient}: ${object.type}`, + start: new Date(object.time), + end: new Date( + new Date(object.time).getTime() + object.duration * 60 * 1000, + ), color: - obj.status === "confirmed" + object.status === "confirmed" ? "#68d68a" - : obj.status === "pending" - ? "#ffe55f" - : "#ff5f5fff", + : object.status === "pending" + ? "#ffe55f" + : "#ff5f5fff", }; events.push(event); }); @@ -68,15 +70,15 @@ export default function AgendamentoPage() { // mantive para caso a lógica de salvar consulta passe a funcionar const handleSaveAppointment = (appointment: any) => { if (appointment.id) { - setAppointments((prev) => - prev.map((a) => (a.id === appointment.id ? appointment : a)) + setAppointments((previous) => + previous.map((a) => (a.id === appointment.id ? appointment : a)), ); } else { const newAppointment = { ...appointment, id: Date.now().toString(), }; - setAppointments((prev) => [...prev, newAppointment]); + setAppointments((previous) => [...previous, newAppointment]); } }; @@ -90,7 +92,9 @@ export default function AgendamentoPage() {
-

{activeTab === "calendar" ? "Calendário" : "Lista de Espera"}

+

+ {activeTab === "calendar" ? "Calendário" : "Lista de Espera"} +

Navegue através dos atalhos: Calendário (C) ou Fila de espera (F). diff --git a/susconecta/app/(main-routes)/consultas/page.tsx b/susconecta/app/(main-routes)/consultas/page.tsx index 47365d8..59b81d2 100644 --- a/susconecta/app/(main-routes)/consultas/page.tsx +++ b/susconecta/app/(main-routes)/consultas/page.tsx @@ -53,10 +53,12 @@ import { SelectValue, } from "@/components/ui/select"; -import { mockAppointments, mockProfessionals } from "@/lib/mocks/appointment-mocks"; +import { + mockAppointments, + mockProfessionals, +} from "@/lib/mocks/appointment-mocks"; import { CalendarRegistrationForm } from "@/components/forms/calendar-registration-form"; - const formatDate = (date: string | Date) => { if (!date) return ""; return new Date(date).toLocaleDateString("pt-BR", { @@ -69,43 +71,56 @@ const formatDate = (date: string | Date) => { }; const capitalize = (s: string) => { - if (typeof s !== 'string' || s.length === 0) return ''; - return s.charAt(0).toUpperCase() + s.slice(1); + if (typeof s !== "string" || s.length === 0) return ""; + return s.charAt(0).toUpperCase() + s.slice(1); }; export default function ConsultasPage() { const [appointments, setAppointments] = useState(mockAppointments); const [showForm, setShowForm] = useState(false); - const [editingAppointment, setEditingAppointment] = useState(null); - const [viewingAppointment, setViewingAppointment] = useState(null); + const [editingAppointment, setEditingAppointment] = useState( + null, + ); + const [viewingAppointment, setViewingAppointment] = useState( + null, + ); const mapAppointmentToFormData = (appointment: any) => { - const professional = mockProfessionals.find(p => p.id === appointment.professional); + const professional = mockProfessionals.find( + (p) => p.id === appointment.professional, + ); const appointmentDate = new Date(appointment.time); - + return { - id: appointment.id, - patientName: appointment.patient, - professionalName: professional ? professional.name : '', - appointmentDate: appointmentDate.toISOString().split('T')[0], - startTime: appointmentDate.toTimeString().split(' ')[0].substring(0, 5), - endTime: new Date(appointmentDate.getTime() + appointment.duration * 60000).toTimeString().split(' ')[0].substring(0, 5), - status: appointment.status, - appointmentType: appointment.type, - notes: appointment.notes, - cpf: '', - rg: '', - birthDate: '', - phoneCode: '+55', - phoneNumber: '', - email: '', - unit: 'nei', + id: appointment.id, + patientName: appointment.patient, + professionalName: professional ? professional.name : "", + appointmentDate: appointmentDate.toISOString().split("T")[0], + startTime: appointmentDate.toTimeString().split(" ")[0].substring(0, 5), + endTime: new Date( + appointmentDate.getTime() + appointment.duration * 60000, + ) + .toTimeString() + .split(" ")[0] + .substring(0, 5), + status: appointment.status, + appointmentType: appointment.type, + notes: appointment.notes, + cpf: "", + rg: "", + birthDate: "", + phoneCode: "+55", + phoneNumber: "", + email: "", + unit: "nei", }; }; const handleDelete = (appointmentId: string) => { if (window.confirm("Tem certeza que deseja excluir esta consulta?")) { - setAppointments((prev) => prev.filter((a) => a.id !== appointmentId)); + setAppointments((previous) => + previous.filter((a) => a.id !== appointmentId), + ); } }; @@ -114,7 +129,7 @@ export default function ConsultasPage() { setEditingAppointment(formData); setShowForm(true); }; - + const handleView = (appointment: any) => { setViewingAppointment(appointment); }; @@ -125,40 +140,49 @@ export default function ConsultasPage() { }; const handleSave = (formData: any) => { - const updatedAppointment = { - id: formData.id, - patient: formData.patientName, - time: new Date(`${formData.appointmentDate}T${formData.startTime}`).toISOString(), - duration: 30, - type: formData.appointmentType as any, - status: formData.status as any, - professional: appointments.find(a => a.id === formData.id)?.professional || '', - notes: formData.notes, + id: formData.id, + patient: formData.patientName, + time: new Date( + `${formData.appointmentDate}T${formData.startTime}`, + ).toISOString(), + duration: 30, + type: formData.appointmentType as any, + status: formData.status as any, + professional: + appointments.find((a) => a.id === formData.id)?.professional || "", + notes: formData.notes, }; - setAppointments(prev => - prev.map(a => a.id === updatedAppointment.id ? updatedAppointment : a) + setAppointments((previous) => + previous.map((a) => + a.id === updatedAppointment.id ? updatedAppointment : a, + ), ); - handleCancel(); + handleCancel(); }; if (showForm && editingAppointment) { return ( -

-
- -

Editar Consulta

-
- +
+
+ +

Editar Consulta

- ) + +
+ ); } return ( @@ -166,7 +190,9 @@ export default function ConsultasPage() {

Gerenciamento de Consultas

-

Visualize, filtre e gerencie todas as consultas da clínica.

+

+ Visualize, filtre e gerencie todas as consultas da clínica. +

@@ -223,7 +249,7 @@ export default function ConsultasPage() { {appointments.map((appointment) => { const professional = mockProfessionals.find( - (p) => p.id === appointment.professional + (p) => p.id === appointment.professional, ); return ( @@ -239,11 +265,13 @@ export default function ConsultasPage() { appointment.status === "confirmed" ? "default" : appointment.status === "pending" - ? "secondary" - : "destructive" + ? "secondary" + : "destructive" } className={ - appointment.status === "confirmed" ? "bg-green-600" : "" + appointment.status === "confirmed" + ? "bg-green-600" + : "" } > {capitalize(appointment.status)} @@ -265,7 +293,9 @@ export default function ConsultasPage() { Ver - handleEdit(appointment)}> + handleEdit(appointment)} + > Editar @@ -288,12 +318,16 @@ export default function ConsultasPage() { {viewingAppointment && ( - setViewingAppointment(null)}> + setViewingAppointment(null)} + > Detalhes da Consulta - Informações detalhadas da consulta de {viewingAppointment?.patient}. + Informações detalhadas da consulta de{" "} + {viewingAppointment?.patient}.
@@ -301,62 +335,68 @@ export default function ConsultasPage() { - {viewingAppointment?.patient} -
-
- - {mockProfessionals.find(p => p.id === viewingAppointment?.professional)?.name || "Não encontrado"} + {viewingAppointment?.patient}
- - {viewingAppointment?.time ? formatDate(viewingAppointment.time) : ''} -
-
- + - - {capitalize(viewingAppointment?.status || '')} - + {mockProfessionals.find( + (p) => p.id === viewingAppointment?.professional, + )?.name || "Não encontrado"}
-
- - {capitalize(viewingAppointment?.type || '')} +
+ + + {viewingAppointment?.time + ? formatDate(viewingAppointment.time) + : ""} +
-
- - {viewingAppointment?.notes || "Nenhuma"} +
+ + + + {capitalize(viewingAppointment?.status || "")} + + +
+
+ + + {capitalize(viewingAppointment?.type || "")} + +
+
+ + + {viewingAppointment?.notes || "Nenhuma"} +
- +
)}
); -} \ No newline at end of file +} diff --git a/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx b/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx index 473cbde..614dcab 100644 --- a/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx +++ b/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx @@ -1,18 +1,62 @@ - "use client"; import { Button } from "@/components/ui/button"; -import { FileDown, BarChart2, Users, DollarSign, TrendingUp, UserCheck, CalendarCheck, ThumbsUp, User, Briefcase } from "lucide-react"; +import { + FileDown, + BarChart2, + Users, + DollarSign, + TrendingUp, + UserCheck, + CalendarCheck, + ThumbsUp, + User, + Briefcase, +} from "lucide-react"; import jsPDF from "jspdf"; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell } from "recharts"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + LineChart, + Line, + PieChart, + Pie, + Cell, +} from "recharts"; // Dados fictícios para demonstração const metricas = [ - { label: "Atendimentos", value: 1240, icon: }, - { label: "Absenteísmo", value: "7,2%", icon: }, - { label: "Satisfação", value: "92%", icon: }, - { label: "Faturamento (Mês)", value: "R$ 45.000", icon: }, - { label: "No-show", value: "5,1%", icon: }, + { + label: "Atendimentos", + value: 1240, + icon: , + }, + { + label: "Absenteísmo", + value: "7,2%", + icon: , + }, + { + label: "Satisfação", + value: "92%", + icon: , + }, + { + label: "Faturamento (Mês)", + value: "R$ 45.000", + icon: , + }, + { + label: "No-show", + value: "5,1%", + icon: , + }, ]; const consultasPorPeriodo = [ @@ -74,24 +118,33 @@ const performancePorMedico = [ const COLORS = ["#10b981", "#6366f1", "#f59e42", "#ef4444"]; function exportPDF(title: string, content: string) { - const doc = new jsPDF(); - doc.text(title, 10, 10); - doc.text(content, 10, 20); - doc.save(`${title.toLowerCase().replace(/ /g, '-')}.pdf`); + const document_ = new jsPDF(); + document_.text(title, 10, 10); + document_.text(content, 10, 20); + document_.save(`${title.toLowerCase().replace(/ /g, "-")}.pdf`); } export default function RelatoriosPage() { return (
-

Dashboard Executivo de Relatórios

+

+ Dashboard Executivo de Relatórios +

{/* Métricas principais */}
{metricas.map((m) => ( -
+
{m.icon} - {m.value} - {m.label} + + {m.value} + + + {m.label} +
))}
@@ -101,8 +154,22 @@ export default function RelatoriosPage() { {/* Consultas realizadas por período */}
-

Consultas por Período

- +

+ Consultas por Período +

+
@@ -118,8 +185,19 @@ export default function RelatoriosPage() { {/* Faturamento mensal/anual */}
-

Faturamento Mensal

- +

+ Faturamento Mensal +

+
@@ -127,7 +205,13 @@ export default function RelatoriosPage() { - +
@@ -137,8 +221,19 @@ export default function RelatoriosPage() { {/* Taxa de no-show */}
-

Taxa de No-show

- +

+ Taxa de No-show +

+
@@ -146,7 +241,13 @@ export default function RelatoriosPage() { - +
@@ -154,12 +255,28 @@ export default function RelatoriosPage() { {/* Indicadores de satisfação */}
-

Satisfação dos Pacientes

- +

+ Satisfação dos Pacientes +

+
92% - Índice de satisfação geral + + Índice de satisfação geral +
@@ -168,8 +285,22 @@ export default function RelatoriosPage() { {/* Pacientes mais atendidos */}
-

Pacientes Mais Atendidos

- +

+ Pacientes Mais Atendidos +

+
@@ -192,8 +323,22 @@ export default function RelatoriosPage() { {/* Médicos mais produtivos */}
-

Médicos Mais Produtivos

- +

+ Médicos Mais Produtivos +

+
@@ -218,14 +363,39 @@ export default function RelatoriosPage() { {/* Análise de convênios */}
-

Análise de Convênios

- +

+ Análise de Convênios +

+
- + {convenios.map((entry, index) => ( - + ))} @@ -237,8 +407,22 @@ export default function RelatoriosPage() { {/* Performance por médico */}
-

Performance por Médico

- +

+ Performance por Médico +

+
diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 64fcf5e..57275aa 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -3,21 +3,64 @@ import { useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; -import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye } from "lucide-react"; +import { + MoreHorizontal, + Plus, + Search, + Edit, + Trash2, + ArrowLeft, + Eye, + ShieldCheck, +} from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; - -import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, Medico } from "@/lib/api"; +import { + listarMedicos, + excluirMedico, + buscarMedicos, + buscarMedicoPorId, + Medico, + listarAutorizacoesUsuario, + atualizarAutorizacoesUsuario, + buscarUsuarioPorEmail, + criarUsuarioMedico, + type AuthorizationRole, +} from "@/lib/api"; +import { + UpdateAuthorizationsDialog, + type AuthorizationState, +} from "@/components/dialogs/update-authorizations-dialog"; +import { useToast } from "@/hooks/use-toast"; function normalizeMedico(m: any): Medico { return { id: String(m.id ?? m.uuid ?? ""), - full_name: m.full_name ?? m.nome ?? "", // 👈 Correção: usar full_name como padrão + full_name: m.full_name ?? m.nome ?? "", // 👈 Correção: usar full_name como padrão nome_social: m.nome_social ?? m.social_name ?? null, cpf: m.cpf ?? "", rg: m.rg ?? m.document_number ?? null, @@ -56,7 +99,6 @@ function normalizeMedico(m: any): Medico { }; } - export default function DoutoresPage() { const [doctors, setDoctors] = useState([]); const [loading, setLoading] = useState(false); @@ -66,65 +108,78 @@ export default function DoutoresPage() { const [viewingDoctor, setViewingDoctor] = useState(null); const [searchResults, setSearchResults] = useState([]); const [searchMode, setSearchMode] = useState(false); - const [searchTimeout, setSearchTimeout] = useState(null); + const [searchTimeout, setSearchTimeout] = useState( + null, + ); + const [authDialogOpen, setAuthDialogOpen] = useState(false); + const [authTargetDoctor, setAuthTargetDoctor] = useState(null); + const [authInitialRoles, setAuthInitialRoles] = + useState(null); + const [authorizationsLoading, setAuthorizationsLoading] = useState(false); + const [authorizationsError, setAuthorizationsError] = useState( + null, + ); + const [authorizationsSubmitDisabled, setAuthorizationsSubmitDisabled] = + useState(false); + + const { toast } = useToast(); - async function load() { setLoading(true); try { - const list = await listarMedicos({ limit: 50 }); - const normalized = (list ?? []).map(normalizeMedico); - console.log('🏥 Médicos carregados:', normalized); - setDoctors(normalized); - + const list = await listarMedicos({ limit: 50 }); + const normalized = (list ?? []).map(normalizeMedico); + console.log("🏥 Médicos carregados:", normalized); + setDoctors(normalized); } finally { setLoading(false); } } // Função para detectar se é um UUID válido - function isValidUUID(str: string): boolean { - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - return uuidRegex.test(str); + function isValidUUID(string_: string): boolean { + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return uuidRegex.test(string_); } // Função para buscar médicos no servidor async function handleBuscarServidor(termoBusca?: string) { const termo = (termoBusca || search).trim(); - + if (!termo) { setSearchMode(false); setSearchResults([]); return; } - console.log('🔍 Buscando médico por:', termo); - + console.log("🔍 Buscando médico por:", termo); + setLoading(true); try { // Se parece com UUID, tenta busca direta por ID if (isValidUUID(termo)) { - console.log('📋 Detectado UUID, buscando por ID...'); + console.log("📋 Detectado UUID, buscando por ID..."); try { const medico = await buscarMedicoPorId(termo); const normalizado = normalizeMedico(medico); - console.log('✅ Médico encontrado por ID:', normalizado); + console.log("✅ Médico encontrado por ID:", normalizado); setSearchResults([normalizado]); setSearchMode(true); return; } catch (error) { - console.log('❌ Não encontrado por ID, tentando busca geral...'); + console.log("❌ Não encontrado por ID, tentando busca geral..."); } } // Busca geral const resultados = await buscarMedicos(termo); const normalizados = resultados.map(normalizeMedico); - console.log('📋 Resultados da busca geral:', normalizados); - + console.log("📋 Resultados da busca geral:", normalizados); + setSearchResults(normalizados); setSearchMode(true); } catch (error) { - console.error('❌ Erro na busca:', error); + console.error("❌ Erro na busca:", error); setSearchResults([]); setSearchMode(true); } finally { @@ -136,31 +191,31 @@ export default function DoutoresPage() { function handleSearchChange(e: React.ChangeEvent) { const valor = e.target.value; setSearch(valor); - + // Limpa o timeout anterior se existir if (searchTimeout) { clearTimeout(searchTimeout); } - + // Se limpar a busca, volta ao modo normal if (!valor.trim()) { setSearchMode(false); setSearchResults([]); return; } - + // Busca automática com debounce ajustável // Para IDs (UUID) longos, faz busca no servidor // Para busca parcial, usa apenas filtro local - const isLikeUUID = valor.includes('-') && valor.length > 10; + const isLikeUUID = valor.includes("-") && valor.length > 10; const shouldSearchServer = isLikeUUID || valor.length >= 3; - + if (shouldSearchServer) { const debounceTime = isLikeUUID ? 300 : 500; const newTimeout = setTimeout(() => { handleBuscarServidor(valor); }, debounceTime); - + setSearchTimeout(newTimeout); } else { // Para termos curtos, apenas usa filtro local @@ -171,7 +226,7 @@ export default function DoutoresPage() { // Handler para Enter no campo de busca function handleSearchKeyDown(e: React.KeyboardEvent) { - if (e.key === 'Enter') { + if (e.key === "Enter") { e.preventDefault(); handleBuscarServidor(); } @@ -197,43 +252,65 @@ export default function DoutoresPage() { // Lista de médicos a exibir (busca ou filtro local) const displayedDoctors = useMemo(() => { - console.log('🔍 Filtro - search:', search, 'searchMode:', searchMode, 'doctors:', doctors.length, 'searchResults:', searchResults.length); - + console.log( + "🔍 Filtro - search:", + search, + "searchMode:", + searchMode, + "doctors:", + doctors.length, + "searchResults:", + searchResults.length, + ); + // Se não tem busca, mostra todos os médicos if (!search.trim()) return doctors; - + const q = search.toLowerCase().trim(); const qDigits = q.replace(/\D/g, ""); - + // Se estamos em modo de busca (servidor), filtra os resultados da busca const sourceList = searchMode ? searchResults : doctors; - console.log('🔍 Usando sourceList:', searchMode ? 'searchResults' : 'doctors', '- tamanho:', sourceList.length); - + console.log( + "🔍 Usando sourceList:", + searchMode ? "searchResults" : "doctors", + "- tamanho:", + sourceList.length, + ); + const filtered = sourceList.filter((d) => { // Busca por nome const byName = (d.full_name || "").toLowerCase().includes(q); - + // Busca por CRM (remove formatação se necessário) - const byCrm = qDigits.length >= 3 && (d.crm || "").replace(/\D/g, "").includes(qDigits); - + const byCrm = + qDigits.length >= 3 && + (d.crm || "").replace(/\D/g, "").includes(qDigits); + // Busca por ID (UUID completo ou parcial) const byId = (d.id || "").toLowerCase().includes(q); - + // Busca por email const byEmail = (d.email || "").toLowerCase().includes(q); - + // Busca por especialidade const byEspecialidade = (d.especialidade || "").toLowerCase().includes(q); - + const match = byName || byCrm || byId || byEmail || byEspecialidade; if (match) { - console.log('✅ Match encontrado:', d.full_name, d.id, 'por:', { byName, byCrm, byId, byEmail, byEspecialidade }); + console.log("✅ Match encontrado:", d.full_name, d.id, "por:", { + byName, + byCrm, + byId, + byEmail, + byEspecialidade, + }); } - + return match; }); - - console.log('🔍 Resultados filtrados:', filtered.length); + + console.log("🔍 Resultados filtrados:", filtered.length); return filtered; }, [doctors, search, searchMode, searchResults]); @@ -242,8 +319,6 @@ export default function DoutoresPage() { setShowForm(true); } - - function handleEdit(id: string) { setEditingId(id); setShowForm(true); @@ -253,46 +328,178 @@ export default function DoutoresPage() { setViewingDoctor(doctor); } - + async function handleOpenAuthorizations(doctor: Medico) { + setAuthTargetDoctor(doctor); + setAuthDialogOpen(true); + setAuthorizationsLoading(!!doctor.user_id); + setAuthorizationsError(null); + setAuthInitialRoles(null); + setAuthorizationsSubmitDisabled(false); + + if (!doctor.user_id) { + setAuthorizationsError( + "Este profissional ainda não possui um usuário vinculado. Cadastre ou vincule um usuário para gerenciar autorizações.", + ); + setAuthInitialRoles({ paciente: false, medico: true }); + setAuthorizationsSubmitDisabled(true); + return; + } + + try { + const roles = await listarAutorizacoesUsuario(doctor.user_id); + if (!roles.length) { + setAuthInitialRoles({ paciente: false, medico: true }); + } else { + setAuthInitialRoles({ + paciente: roles.includes("paciente"), + medico: roles.includes("medico"), + }); + } + } catch (error: any) { + setAuthorizationsError( + error?.message || "Erro ao carregar autorizações.", + ); + } finally { + setAuthorizationsLoading(false); + } + } + + function handleAuthDialogOpenChange(open: boolean) { + if (!open) { + setAuthDialogOpen(false); + setAuthTargetDoctor(null); + setAuthInitialRoles(null); + setAuthorizationsError(null); + setAuthorizationsLoading(false); + setAuthorizationsSubmitDisabled(false); + } + } + + async function handleConfirmAuthorizations(selection: AuthorizationState) { + console.log("[Auth] handleConfirmAuthorizations CHAMADA!", selection, "authTargetDoctor=", authTargetDoctor); + + // Verifica se o médico tem email + if (!authTargetDoctor?.email) { + toast({ + title: "Email obrigatório", + description: "O médico precisa ter um email cadastrado para receber autorizações.", + variant: "destructive", + }); + return; + } + + setAuthorizationsLoading(true); + setAuthorizationsError(null); + + try { + // PASSO 1: Buscar ou criar usuário no sistema de autenticação + console.log("[Auth] Buscando user_id para email:", authTargetDoctor.email); + + let userId = await buscarUsuarioPorEmail(authTargetDoctor.email); + + // Se não encontrou, cria um novo usuário + if (!userId) { + console.log("[Auth] Usuário não existe. Criando novo usuário..."); + + const newUserResponse = await criarUsuarioMedico({ + email: authTargetDoctor.email, + full_name: authTargetDoctor.full_name, + phone_mobile: authTargetDoctor.telefone || authTargetDoctor.celular || "", + }); + + userId = newUserResponse.user.id; + console.log("[Auth] Novo usuário criado! user_id:", userId); + + // Mostra credenciais ao admin + toast({ + title: "Usuário criado com sucesso!", + description: `Email: ${newUserResponse.email}\nSenha: ${newUserResponse.password}`, + duration: 10000, + }); + } else { + console.log("[Auth] Usuário já existe. user_id:", userId); + } + + // PASSO 2: Atualizar autorizações via patient_assignments + const selectedRoles: AuthorizationRole[] = []; + if (selection.paciente) selectedRoles.push("paciente"); + if (selection.medico) selectedRoles.push("medico"); + + console.log("[Auth] Atualizando roles:", selectedRoles, "para user_id:", userId, "doctor_id:", authTargetDoctor.id); + const result = await atualizarAutorizacoesUsuario( + userId, + authTargetDoctor.id, // doctor_id (usamos como patient_id na tabela) + selectedRoles + ); + console.log("[Auth] Resultado:", result); + + toast({ + title: "Autorizações atualizadas", + description: "As permissões deste profissional foram atualizadas com sucesso.", + }); + + setAuthDialogOpen(false); + setAuthTargetDoctor(null); + setAuthInitialRoles(null); + await load(); + + } catch (error: any) { + console.error("[Auth] Erro:", error); + toast({ + title: "Erro ao atualizar autorizações", + description: error?.message || "Não foi possível atualizar as autorizações.", + variant: "destructive", + }); + } finally { + setAuthorizationsLoading(false); + } + } + async function handleDelete(id: string) { if (!confirm("Excluir este médico?")) return; await excluirMedico(id); await load(); } - function handleSaved(savedDoctor?: Medico) { - setShowForm(false); + setShowForm(false); - if (savedDoctor) { - const normalized = normalizeMedico(savedDoctor); - setDoctors((prev) => { - const i = prev.findIndex((d) => String(d.id) === String(normalized.id)); - if (i < 0) { - // Novo médico → adiciona no topo - return [normalized, ...prev]; - } else { - // Médico editado → substitui na lista - const clone = [...prev]; - clone[i] = normalized; - return clone; - } - }); - } else { - // fallback → recarrega tudo - load(); + if (savedDoctor) { + const normalized = normalizeMedico(savedDoctor); + setDoctors((previous) => { + const index = previous.findIndex( + (d) => String(d.id) === String(normalized.id), + ); + if (index < 0) { + // Novo médico → adiciona no topo + return [normalized, ...previous]; + } else { + // Médico editado → substitui na lista + const clone = [...previous]; + clone[index] = normalized; + return clone; + } + }); + } else { + // fallback → recarrega tudo + load(); + } } -} - if (showForm) { return (
- -

{editingId ? "Editar Médico" : "Novo Médico"}

+

+ {editingId ? "Editar Médico" : "Novo Médico"} +

Médicos

-

Gerencie os médicos da sua clínica

+

+ Gerencie os médicos da sua clínica +

@@ -328,15 +537,15 @@ export default function DoutoresPage() {
{searchMode && ( -
{viewingDoctor && ( - setViewingDoctor(null)}> + setViewingDoctor(null)} + > Detalhes do Médico @@ -435,12 +668,16 @@ export default function DoutoresPage() {
- {viewingDoctor?.full_name} + + {viewingDoctor?.full_name} +
- {viewingDoctor?.especialidade} + + {viewingDoctor?.especialidade} +
@@ -464,8 +701,21 @@ export default function DoutoresPage() { )}
- Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`} + Mostrando {displayedDoctors.length}{" "} + {searchMode ? "resultado(s) da busca" : `de ${doctors.length}`}
+ +
); } diff --git a/susconecta/app/(main-routes)/layout.tsx b/susconecta/app/(main-routes)/layout.tsx index 6269b21..568c8a9 100644 --- a/susconecta/app/(main-routes)/layout.tsx +++ b/susconecta/app/(main-routes)/layout.tsx @@ -9,8 +9,8 @@ export default function MainRoutesLayout({ }: { children: React.ReactNode; }) { - console.log('[MAIN-ROUTES-LAYOUT] Layout do administrador carregado') - + console.log("[MAIN-ROUTES-LAYOUT] Layout do administrador carregado"); + return (
diff --git a/susconecta/app/(main-routes)/pacientes/loading.tsx b/susconecta/app/(main-routes)/pacientes/loading.tsx index f15322a..4349ac3 100644 --- a/susconecta/app/(main-routes)/pacientes/loading.tsx +++ b/susconecta/app/(main-routes)/pacientes/loading.tsx @@ -1,3 +1,3 @@ export default function Loading() { - return null + return null; } diff --git a/susconecta/app/(main-routes)/pacientes/page.tsx b/susconecta/app/(main-routes)/pacientes/page.tsx index 1506dc1..520f066 100644 --- a/susconecta/app/(main-routes)/pacientes/page.tsx +++ b/susconecta/app/(main-routes)/pacientes/page.tsx @@ -17,6 +17,8 @@ import { excluirPaciente, listarAutorizacoesUsuario, atualizarAutorizacoesUsuario, + buscarUsuarioPorEmail, + criarUsuarioPaciente, type AuthorizationRole, } from "@/lib/api"; import { PatientRegistrationForm } from "@/components/forms/patient-registration-form"; @@ -75,8 +77,13 @@ export default function PacientesPage() { setLoading(true); const data = await listarPacientes({ page: 1, limit: 20 }); + console.log("[loadAll] Dados brutos da API:", data); + if (Array.isArray(data)) { - setPatients(data.map(normalizePaciente)); + const normalized = data.map(normalizePaciente); + console.log("[loadAll] Pacientes normalizados:", normalized); + console.log("[loadAll] user_ids dos pacientes:", normalized.map(p => ({ nome: p.full_name, user_id: p.user_id }))); + setPatients(normalized); } else { setPatients([]); } @@ -175,43 +182,78 @@ export default function PacientesPage() { } async function handleConfirmAuthorizations(selection: AuthorizationState) { - if (!authTargetPatient?.user_id) { + console.log("[Auth] handleConfirmAuthorizations CHAMADA!", selection, "authTargetPatient=", authTargetPatient); + + // Verifica se o paciente tem email + if (!authTargetPatient?.email) { toast({ - title: "Usuário não vinculado", - description: "Não foi possível atualizar as autorizações porque o usuário não está vinculado.", + title: "Email obrigatório", + description: "O paciente precisa ter um email cadastrado para receber autorizações.", variant: "destructive", }); - setAuthorizationsError( - "Vincule este paciente a um usuário antes de ajustar as autorizações.", - ); - setAuthorizationsSubmitDisabled(true); return; } - - console.log("[Auth] Confirm clicked", selection, "targetUserId=", authTargetPatient?.user_id); - setAuthorizationsLoading(true); + + setAuthorizationsLoading(true); setAuthorizationsError(null); - - const selectedRoles: AuthorizationRole[] = []; - if (selection.paciente) selectedRoles.push("paciente"); - if (selection.medico) selectedRoles.push("medico"); - + try { - console.log("[Auth] Updating roles to server:", selectedRoles); - const result = await atualizarAutorizacoesUsuario(authTargetPatient.user_id, selectedRoles); - console.log("[Auth] Update result:", result); + // PASSO 1: Buscar ou criar usuário no sistema de autenticação + console.log("[Auth] Buscando user_id para email:", authTargetPatient.email); + + let userId = await buscarUsuarioPorEmail(authTargetPatient.email); + + // Se não encontrou, cria um novo usuário + if (!userId) { + console.log("[Auth] Usuário não existe. Criando novo usuário..."); + + const newUserResponse = await criarUsuarioPaciente({ + email: authTargetPatient.email, + full_name: authTargetPatient.full_name, + phone_mobile: authTargetPatient.phone_mobile || "", + }); + + userId = newUserResponse.user.id; + console.log("[Auth] Novo usuário criado! user_id:", userId); + + // Mostra credenciais ao admin + toast({ + title: "Usuário criado com sucesso!", + description: `Email: ${newUserResponse.email}\nSenha: ${newUserResponse.password}`, + duration: 10000, + }); + } else { + console.log("[Auth] Usuário já existe. user_id:", userId); + } + + // PASSO 2: Atualizar autorizações via patient_assignments + const selectedRoles: AuthorizationRole[] = []; + if (selection.paciente) selectedRoles.push("paciente"); + if (selection.medico) selectedRoles.push("medico"); + + console.log("[Auth] Atualizando roles:", selectedRoles, "para user_id:", userId, "patient_id:", authTargetPatient.id); + const result = await atualizarAutorizacoesUsuario( + userId, + authTargetPatient.id, // patient_id é obrigatório! + selectedRoles + ); + console.log("[Auth] Resultado:", result); + toast({ title: "Autorizações atualizadas", description: "As permissões deste paciente foram atualizadas com sucesso.", }); + setAuthDialogOpen(false); setAuthTargetPatient(null); setAuthInitialRoles(null); await loadAll(); - } catch (e: any) { + + } catch (error: any) { + console.error("[Auth] Erro:", error); toast({ title: "Erro ao atualizar autorizações", - description: e?.message || "Não foi possível atualizar as autorizações.", + description: error?.message || "Não foi possível atualizar as autorizações.", variant: "destructive", }); } finally { @@ -231,20 +273,12 @@ export default function PacientesPage() { async function handleSaved(p: Paciente) { // Normaliza e atualiza localmente - let saved = normalizePaciente(p); - // Vincula o registro de paciente ao usuário autenticado - try { - const user = await getCurrentUser(); - // Preparar payload apenas com campos de PacienteInput e user_id - const { id: _id, user_id: _oldUserId, ...rest } = saved; - const payload = { ...rest, user_id: user.id }; - const linked = await atualizarPaciente(saved.id, payload); - saved = normalizePaciente(linked); - } catch (e) { - // Se falhar, mantém saved original - console.warn("Falha ao vincular usuário ao paciente:", e); - } - // Atualiza lista com o registro vinculado + const saved = normalizePaciente(p); + + console.log("[handleSaved] Paciente salvo:", saved); + console.log("[handleSaved] user_id do paciente:", saved.user_id); + + // Atualiza lista com o registro salvo (que já deve ter user_id do formulário) setPatients((prev) => { const i = prev.findIndex((x) => String(x.id) === String(saved.id)); if (i < 0) return [saved, ...prev]; diff --git a/susconecta/app/agenda/page.tsx b/susconecta/app/agenda/page.tsx index fff1a81..24fb1b9 100644 --- a/susconecta/app/agenda/page.tsx +++ b/susconecta/app/agenda/page.tsx @@ -39,7 +39,7 @@ export default function NovoAgendamentoPage() { const handleSave = () => { console.log("Salvando novo agendamento...", formData); alert("Novo agendamento salvo (simulado)!"); - router.push("/consultas"); + router.push("/consultas"); }; const handleCancel = () => { @@ -50,12 +50,12 @@ export default function NovoAgendamentoPage() {
-
); -} \ No newline at end of file +} diff --git a/susconecta/app/financeiro/page.tsx b/susconecta/app/financeiro/page.tsx index 7981f4b..a14d7e8 100644 --- a/susconecta/app/financeiro/page.tsx +++ b/susconecta/app/financeiro/page.tsx @@ -57,7 +57,9 @@ export default function FinanceiroPage() {
- +
- +
- +
- +
Subtotal: - R$ 0,00 + + R$ 0,00 +
Desconto: - - R$ 0,00 + + - R$ 0,00 +
- Total: - R$ 0,00 + + Total: + + + R$ 0,00 +
@@ -154,4 +170,4 @@ export default function FinanceiroPage() {
); -} \ No newline at end of file +} diff --git a/susconecta/app/layout.tsx b/susconecta/app/layout.tsx index b7b71f1..eae18ad 100644 --- a/susconecta/app/layout.tsx +++ b/susconecta/app/layout.tsx @@ -1,31 +1,33 @@ -import type React from "react" -import type { Metadata } from "next" -import { AuthProvider } from "@/hooks/useAuth" -import { ThemeProvider } from "@/components/theme-provider" -import "./globals.css" +import type React from "react"; +import type { Metadata } from "next"; +import { AuthProvider } from "@/hooks/useAuth"; +import { ThemeProvider } from "@/components/theme-provider"; +import "./globals.css"; export const metadata: Metadata = { title: "MediConnect - Conectando Pacientes e Profissionais de Saúde", description: "Plataforma inovadora que conecta pacientes, clínicas, e médicos de forma prática, segura e humanizada. Experimente o futuro dos agendamentos médicos.", keywords: "saúde, médicos, pacientes, agendamento, telemedicina, SUS", - generator: 'v0.app' -} + generator: "v0.app", +}; export default function RootLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }) { return ( - - - {children} - + + {children} - ) + ); } diff --git a/susconecta/app/login-admin/page.tsx b/susconecta/app/login-admin/page.tsx index 958c714..8cc4910 100644 --- a/susconecta/app/login-admin/page.tsx +++ b/susconecta/app/login-admin/page.tsx @@ -1,48 +1,52 @@ -'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' +"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 LoginAdminPage() { - const [credentials, setCredentials] = useState({ email: '', password: '' }) - const [error, setError] = useState('') - const [loading, setLoading] = useState(false) - const router = useRouter() - const { login } = useAuth() + 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('') + e.preventDefault(); + setLoading(true); + setError(""); try { // Tentar fazer login usando o contexto com tipo administrador - const success = await login(credentials.email, credentials.password, 'administrador') - + const success = await login( + credentials.email, + credentials.password, + "administrador", + ); + if (success) { - console.log('[LOGIN-ADMIN] Login bem-sucedido, redirecionando...') - + console.log("[LOGIN-ADMIN] Login bem-sucedido, redirecionando..."); + // Redirecionamento direto - solução que funcionou - window.location.href = '/dashboard' + window.location.href = "/dashboard"; } - } catch (err) { - console.error('[LOGIN-ADMIN] Erro no login:', err) - - if (err instanceof AuthenticationError) { - setError(err.message) + } catch (error_) { + console.error("[LOGIN-ADMIN] Erro no login:", error_); + + if (error_ instanceof AuthenticationError) { + setError(error_.message); } else { - setError('Erro inesperado. Tente novamente.') + setError("Erro inesperado. Tente novamente."); } } finally { - setLoading(false) + setLoading(false); } - } + }; return (
@@ -55,7 +59,7 @@ export default function LoginAdminPage() { Entre com suas credenciais para acessar o sistema administrativo

- + Acesso Administrativo @@ -63,7 +67,10 @@ export default function LoginAdminPage() {
-
- +
-
- ) -} \ No newline at end of file + ); +} diff --git a/susconecta/app/login-paciente/page.tsx b/susconecta/app/login-paciente/page.tsx index 75e4b2e..63a2715 100644 --- a/susconecta/app/login-paciente/page.tsx +++ b/susconecta/app/login-paciente/page.tsx @@ -1,55 +1,63 @@ -'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' +"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 [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('') + e.preventDefault(); + setLoading(true); + setError(""); try { // Tentar fazer login usando o contexto com tipo paciente - const success = await login(credentials.email, credentials.password, 'paciente') - + const success = await login( + credentials.email, + credentials.password, + "paciente", + ); + if (success) { // Redirecionar para a página do paciente - router.push('/paciente') + router.push("/paciente"); } - } catch (err) { - console.error('[LOGIN-PACIENTE] Erro no login:', err) - - if (err instanceof AuthenticationError) { + } catch (error_) { + console.error("[LOGIN-PACIENTE] Erro no login:", error_); + + if (error_ instanceof AuthenticationError) { // Verificar se é erro de credenciais inválidas (pode ser email não confirmado) - if (err.code === '400' || err.details?.error_code === 'invalid_credentials') { + if ( + error_.code === "400" || + error_.details?.error_code === "invalid_credentials" + ) { setError( - '⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, ' + - 'verifique sua caixa de entrada e clique no link de confirmação ' + - 'que foi enviado para ' + credentials.email - ) + "⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, " + + "verifique sua caixa de entrada e clique no link de confirmação " + + "que foi enviado para " + + credentials.email, + ); } else { - setError(err.message) + setError(error_.message); } } else { - setError('Erro inesperado. Tente novamente.') + setError("Erro inesperado. Tente novamente."); } } finally { - setLoading(false) + setLoading(false); } - } + }; return (
@@ -62,7 +70,7 @@ export default function LoginPacientePage() { Acesse sua área pessoal e gerencie suas consultas

- + Entrar como Paciente @@ -70,7 +78,10 @@ export default function LoginPacientePage() {
-
- +
-
- ) -} \ No newline at end of file + ); +} diff --git a/susconecta/app/login/page.tsx b/susconecta/app/login/page.tsx index f39edb6..0037e30 100644 --- a/susconecta/app/login/page.tsx +++ b/susconecta/app/login/page.tsx @@ -1,57 +1,67 @@ -'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' +"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 LoginPage() { - const [credentials, setCredentials] = useState({ email: '', password: '' }) - const [error, setError] = useState('') - const [loading, setLoading] = useState(false) - const router = useRouter() - const { login } = useAuth() + 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('') + e.preventDefault(); + setLoading(true); + setError(""); try { // Tentar fazer login usando o contexto com tipo profissional - const success = await login(credentials.email, credentials.password, 'profissional') - + const success = await login( + credentials.email, + credentials.password, + "profissional", + ); + if (success) { - console.log('[LOGIN-PROFISSIONAL] Login bem-sucedido, redirecionando...') - + console.log( + "[LOGIN-PROFISSIONAL] Login bem-sucedido, redirecionando...", + ); + // Redirecionamento direto - solução que funcionou - window.location.href = '/profissional' + window.location.href = "/profissional"; } - } catch (err) { - console.error('[LOGIN-PROFISSIONAL] Erro no login:', err) - - if (err instanceof AuthenticationError) { + } catch (error_) { + console.error("[LOGIN-PROFISSIONAL] Erro no login:", error_); + + if (error_ instanceof AuthenticationError) { // Verificar se é erro de credenciais inválidas (pode ser email não confirmado) - if (err.code === '400' || err.details?.error_code === 'invalid_credentials') { + if ( + error_.code === "400" || + error_.details?.error_code === "invalid_credentials" + ) { setError( - '⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, ' + - 'verifique sua caixa de entrada e clique no link de confirmação ' + - 'que foi enviado para ' + credentials.email - ) + "⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, " + + "verifique sua caixa de entrada e clique no link de confirmação " + + "que foi enviado para " + + credentials.email, + ); } else { - setError(err.message) + setError(error_.message); } } else { - setError('Erro inesperado. Tente novamente.') + setError("Erro inesperado. Tente novamente."); } } finally { - setLoading(false) + setLoading(false); } - } + }; return (
@@ -64,7 +74,7 @@ export default function LoginPage() { Entre com suas credenciais para acessar o sistema

- + Acesso ao Sistema @@ -72,7 +82,10 @@ export default function LoginPage() {
-
- +
-
- ) -} \ No newline at end of file + ); +} diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index f76067b..e8712ce 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1,76 +1,102 @@ -'use client' +"use client"; // import { useAuth } from '@/hooks/useAuth' // removido duplicado -import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' -import { User, LogOut, Calendar, FileText, MessageCircle, UserCog, Home, Clock, FolderOpen, ChevronLeft, ChevronRight, MapPin, Stethoscope } from 'lucide-react' -import { SimpleThemeToggle } from '@/components/simple-theme-toggle' -import Link from 'next/link' -import ProtectedRoute from '@/components/ProtectedRoute' -import { useAuth } from '@/hooks/useAuth' +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { + User, + LogOut, + Calendar, + FileText, + MessageCircle, + UserCog, + Home, + Clock, + FolderOpen, + ChevronLeft, + ChevronRight, + MapPin, + Stethoscope, +} from "lucide-react"; +import { SimpleThemeToggle } from "@/components/simple-theme-toggle"; +import Link from "next/link"; +import ProtectedRoute from "@/components/ProtectedRoute"; +import { useAuth } from "@/hooks/useAuth"; // Simulação de internacionalização básica const strings = { - dashboard: 'Dashboard', - consultas: 'Consultas', - exames: 'Exames & Laudos', - mensagens: 'Mensagens', - perfil: 'Perfil', - sair: 'Sair', - proximaConsulta: 'Próxima Consulta', - ultimosExames: 'Últimos Exames', - mensagensNaoLidas: 'Mensagens Não Lidas', - agendar: 'Agendar', - reagendar: 'Reagendar', - cancelar: 'Cancelar', - detalhes: 'Detalhes', - adicionarCalendario: 'Adicionar ao calendário', - visualizarLaudo: 'Visualizar Laudo', - download: 'Download', - compartilhar: 'Compartilhar', - inbox: 'Caixa de Entrada', - enviarMensagem: 'Enviar Mensagem', - salvar: 'Salvar', - editarPerfil: 'Editar Perfil', - consentimentos: 'Consentimentos', - notificacoes: 'Preferências de Notificação', - vazio: 'Nenhum dado encontrado.', - erro: 'Ocorreu um erro. Tente novamente.', - carregando: 'Carregando...', - sucesso: 'Salvo com sucesso!', - erroSalvar: 'Erro ao salvar.', -} + dashboard: "Dashboard", + consultas: "Consultas", + exames: "Exames & Laudos", + mensagens: "Mensagens", + perfil: "Perfil", + sair: "Sair", + proximaConsulta: "Próxima Consulta", + ultimosExames: "Últimos Exames", + mensagensNaoLidas: "Mensagens Não Lidas", + agendar: "Agendar", + reagendar: "Reagendar", + cancelar: "Cancelar", + detalhes: "Detalhes", + adicionarCalendario: "Adicionar ao calendário", + visualizarLaudo: "Visualizar Laudo", + download: "Download", + compartilhar: "Compartilhar", + inbox: "Caixa de Entrada", + enviarMensagem: "Enviar Mensagem", + salvar: "Salvar", + editarPerfil: "Editar Perfil", + consentimentos: "Consentimentos", + notificacoes: "Preferências de Notificação", + vazio: "Nenhum dado encontrado.", + erro: "Ocorreu um erro. Tente novamente.", + carregando: "Carregando...", + sucesso: "Salvo com sucesso!", + erroSalvar: "Erro ao salvar.", +}; export default function PacientePage() { - const { logout, user } = useAuth() - const [tab, setTab] = useState<'dashboard'|'consultas'|'exames'|'mensagens'|'perfil'>('dashboard') + const { logout, user } = useAuth(); + const [tab, setTab] = useState< + "dashboard" | "consultas" | "exames" | "mensagens" | "perfil" + >("dashboard"); // Simulação de loaders, empty states e erro - const [loading, setLoading] = useState(false) - const [error, setError] = useState('') - const [toast, setToast] = useState<{type: 'success'|'error', msg: string}|null>(null) + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [toast, setToast] = useState<{ + type: "success" | "error"; + msg: string; + } | null>(null); // Acessibilidade: foco visível e ordem de tabulação garantidos por padrão nos botões e inputs const handleLogout = async () => { - setLoading(true) - setError('') + setLoading(true); + setError(""); try { - await logout() + await logout(); } catch { - setError(strings.erro) + setError(strings.erro); } finally { - setLoading(false) + setLoading(false); } - } + }; // Estado para edição do perfil - const [isEditingProfile, setIsEditingProfile] = useState(false) + const [isEditingProfile, setIsEditingProfile] = useState(false); const [profileData, setProfileData] = useState({ nome: "Maria Silva Santos", email: user?.email || "paciente@example.com", @@ -78,19 +104,20 @@ export default function PacientePage() { endereco: "Rua das Flores, 123", cidade: "São Paulo", cep: "01234-567", - biografia: "Paciente desde 2020. Histórico de consultas e exames regulares.", - }) + biografia: + "Paciente desde 2020. Histórico de consultas e exames regulares.", + }); const handleProfileChange = (field: string, value: string) => { - setProfileData(prev => ({ ...prev, [field]: value })) - } + setProfileData((previous) => ({ ...previous, [field]: value })); + }; const handleSaveProfile = () => { - setIsEditingProfile(false) - setToast({ type: 'success', msg: strings.sucesso }) - } + setIsEditingProfile(false); + setToast({ type: "success", msg: strings.sucesso }); + }; const handleCancelEdit = () => { - setIsEditingProfile(false) - } + setIsEditingProfile(false); + }; function DashboardCards() { return (
@@ -109,58 +136,69 @@ export default function PacientePage() { {strings.mensagensNaoLidas} 1 -
- ) +
+ ); } // Consultas fictícias - const [currentDate, setCurrentDate] = useState(new Date()) + const [currentDate, setCurrentDate] = useState(new Date()); const consultasFicticias = [ { id: 1, medico: "Dr. Carlos Andrade", especialidade: "Cardiologia", local: "Clínica Coração Feliz", - data: new Date().toISOString().split('T')[0], + data: new Date().toISOString().split("T")[0], hora: "09:00", - status: "Confirmada" + status: "Confirmada", }, { id: 2, medico: "Dra. Fernanda Lima", especialidade: "Dermatologia", local: "Clínica Pele Viva", - data: new Date().toISOString().split('T')[0], + data: new Date().toISOString().split("T")[0], hora: "14:30", - status: "Pendente" + status: "Pendente", }, { id: 3, medico: "Dr. João Silva", especialidade: "Ortopedia", local: "Hospital Ortopédico", - data: (() => { let d = new Date(); d.setDate(d.getDate()+1); return d.toISOString().split('T')[0] })(), + data: (() => { + let d = new Date(); + d.setDate(d.getDate() + 1); + return d.toISOString().split("T")[0]; + })(), hora: "11:00", - status: "Cancelada" + status: "Cancelada", }, ]; - function formatDatePt(dateStr: string) { - const date = new Date(dateStr); - return date.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); + function formatDatePt(dateString: string) { + const date = new Date(dateString); + return date.toLocaleDateString("pt-BR", { + weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + }); } - function navigateDate(direction: 'prev' | 'next') { + function navigateDate(direction: "prev" | "next") { const newDate = new Date(currentDate); - newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1)); + newDate.setDate(newDate.getDate() + (direction === "next" ? 1 : -1)); setCurrentDate(newDate); } function goToToday() { setCurrentDate(new Date()); } - const todayStr = currentDate.toISOString().split('T')[0]; - const consultasDoDia = consultasFicticias.filter(c => c.data === todayStr); + const todayString = currentDate.toISOString().split("T")[0]; + const consultasDoDia = consultasFicticias.filter( + (c) => c.data === todayString, + ); function Consultas() { return ( @@ -171,13 +209,38 @@ export default function PacientePage() { {/* Navegação de Data */}
- -

{formatDatePt(todayStr)}

- - + +

+ {formatDatePt(todayString)} +

+ +
- {consultasDoDia.length} consulta{consultasDoDia.length !== 1 ? 's' : ''} agendada{consultasDoDia.length !== 1 ? 's' : ''} + {consultasDoDia.length} consulta + {consultasDoDia.length !== 1 ? "s" : ""} agendada + {consultasDoDia.length !== 1 ? "s" : ""}
{/* Lista de Consultas do Dia */} @@ -185,16 +248,33 @@ export default function PacientePage() { {consultasDoDia.length === 0 ? (
-

Nenhuma consulta agendada para este dia

+

+ Nenhuma consulta agendada para este dia +

Você pode agendar uma nova consulta

- +
) : ( - consultasDoDia.map(consulta => ( -
+ consultasDoDia.map((consulta) => ( +
-
+
@@ -210,12 +290,26 @@ export default function PacientePage() { {consulta.hora}
-
{consulta.status}
+
+ {consulta.status} +
- - {consulta.status !== 'Cancelada' && } - {consulta.status !== 'Cancelada' && } + + {consulta.status !== "Cancelada" && ( + + )} + {consulta.status !== "Cancelada" && ( + + )}
@@ -223,7 +317,7 @@ export default function PacientePage() { )}
- ) + ); } // Exames e laudos fictícios @@ -233,14 +327,16 @@ export default function PacientePage() { nome: "Hemograma Completo", data: "2025-09-20", status: "Disponível", - prontuario: "Paciente apresenta hemograma dentro dos padrões de normalidade. Sem alterações significativas.", + prontuario: + "Paciente apresenta hemograma dentro dos padrões de normalidade. Sem alterações significativas.", }, { id: 2, nome: "Raio-X de Tórax", data: "2025-08-10", status: "Disponível", - prontuario: "Exame radiológico sem evidências de lesões pulmonares. Estruturas cardíacas normais.", + prontuario: + "Exame radiológico sem evidências de lesões pulmonares. Estruturas cardíacas normais.", }, { id: 3, @@ -257,14 +353,16 @@ export default function PacientePage() { nome: "Laudo Hemograma Completo", data: "2025-09-21", status: "Assinado", - laudo: "Hemoglobina, hematócrito, leucócitos e plaquetas dentro dos valores de referência. Sem anemias ou infecções detectadas.", + laudo: + "Hemoglobina, hematócrito, leucócitos e plaquetas dentro dos valores de referência. Sem anemias ou infecções detectadas.", }, { id: 2, nome: "Laudo Raio-X de Tórax", data: "2025-08-11", status: "Assinado", - laudo: "Radiografia sem alterações. Parênquima pulmonar preservado. Ausência de derrame pleural.", + laudo: + "Radiografia sem alterações. Parênquima pulmonar preservado. Ausência de derrame pleural.", }, { id: 3, @@ -275,8 +373,12 @@ export default function PacientePage() { }, ]; - const [exameSelecionado, setExameSelecionado] = useState(null) - const [laudoSelecionado, setLaudoSelecionado] = useState(null) + const [exameSelecionado, setExameSelecionado] = useState< + null | (typeof examesFicticios)[0] + >(null); + const [laudoSelecionado, setLaudoSelecionado] = useState< + null | (typeof laudosFicticios)[0] + >(null); function ExamesLaudos() { return ( @@ -285,14 +387,26 @@ export default function PacientePage() {

Meus Exames

- {examesFicticios.map(exame => ( -
+ {examesFicticios.map((exame) => ( +
-
{exame.nome}
-
Data: {new Date(exame.data).toLocaleDateString('pt-BR')}
+
+ {exame.nome} +
+
+ Data: {new Date(exame.data).toLocaleDateString("pt-BR")} +
- +
@@ -303,14 +417,26 @@ export default function PacientePage() {

Meus Laudos

- {laudosFicticios.map(laudo => ( -
+ {laudosFicticios.map((laudo) => ( +
-
{laudo.nome}
-
Data: {new Date(laudo.data).toLocaleDateString('pt-BR')}
+
+ {laudo.nome} +
+
+ Data: {new Date(laudo.data).toLocaleDateString("pt-BR")} +
- +
@@ -319,48 +445,82 @@ export default function PacientePage() {
{/* Modal Prontuário Exame */} - !open && setExameSelecionado(null)}> + !open && setExameSelecionado(null)} + > Prontuário do Exame {exameSelecionado && ( <> -
{exameSelecionado.nome}
-
Data: {new Date(exameSelecionado.data).toLocaleDateString('pt-BR')}
-
{exameSelecionado.prontuario}
+
+ {exameSelecionado.nome} +
+
+ Data:{" "} + {new Date(exameSelecionado.data).toLocaleDateString( + "pt-BR", + )} +
+
+ {exameSelecionado.prontuario} +
)}
- +
{/* Modal Visualizar Laudo */} - !open && setLaudoSelecionado(null)}> + !open && setLaudoSelecionado(null)} + > Laudo Médico {laudoSelecionado && ( <> -
{laudoSelecionado.nome}
-
Data: {new Date(laudoSelecionado.data).toLocaleDateString('pt-BR')}
-
{laudoSelecionado.laudo}
+
+ {laudoSelecionado.nome} +
+
+ Data:{" "} + {new Date(laudoSelecionado.data).toLocaleDateString( + "pt-BR", + )} +
+
+ {laudoSelecionado.laudo} +
)}
- +
- ) + ); } // Mensagens fictícias recebidas do médico @@ -369,22 +529,25 @@ export default function PacientePage() { id: 1, medico: "Dr. Carlos Andrade", data: "2025-10-06T15:30:00", - conteudo: "Olá Maria, seu exame de hemograma está normal. Parabéns por manter seus exames em dia!", - lida: false + conteudo: + "Olá Maria, seu exame de hemograma está normal. Parabéns por manter seus exames em dia!", + lida: false, }, { id: 2, medico: "Dra. Fernanda Lima", data: "2025-09-21T10:15:00", - conteudo: "Maria, seu laudo de Raio-X já está disponível no sistema. Qualquer dúvida, estou à disposição.", - lida: true + conteudo: + "Maria, seu laudo de Raio-X já está disponível no sistema. Qualquer dúvida, estou à disposição.", + lida: true, }, { id: 3, medico: "Dr. João Silva", data: "2025-08-12T09:00:00", - conteudo: "Bom dia! Lembre-se de agendar seu retorno para acompanhamento da ortopedia.", - lida: true + conteudo: + "Bom dia! Lembre-se de agendar seu retorno para acompanhamento da ortopedia.", + lida: true, }, ]; @@ -397,26 +560,39 @@ export default function PacientePage() {

Nenhuma mensagem recebida

-

Você ainda não recebeu mensagens dos seus médicos.

+

+ Você ainda não recebeu mensagens dos seus médicos. +

) : ( - mensagensFicticias.map(msg => ( -
+ mensagensFicticias.map((message) => ( +
- {msg.medico} - {!msg.lida && Nova} + {message.medico} + {!message.lida && ( + + Nova + + )} +
+
+ {new Date(message.data).toLocaleString("pt-BR")} +
+
+ {message.conteudo}
-
{new Date(msg.data).toLocaleString('pt-BR')}
-
{msg.conteudo}
)) )}
- ) + ); } function Perfil() { @@ -425,98 +601,173 @@ export default function PacientePage() {

Meu Perfil

{!isEditingProfile ? ( - ) : (
- - + +
)}
{/* Informações Pessoais */}
-

Informações Pessoais

+

+ Informações Pessoais +

-

{profileData.nome}

- Este campo não pode ser alterado +

+ {profileData.nome} +

+ + Este campo não pode ser alterado +
{isEditingProfile ? ( - handleProfileChange('email', e.target.value)} /> + handleProfileChange("email", e.target.value)} + /> ) : ( -

{profileData.email}

+

+ {profileData.email} +

)}
{isEditingProfile ? ( - handleProfileChange('telefone', e.target.value)} /> + + handleProfileChange("telefone", e.target.value) + } + /> ) : ( -

{profileData.telefone}

+

+ {profileData.telefone} +

)}
{/* Endereço e Contato */}
-

Endereço

+

+ Endereço +

{isEditingProfile ? ( - handleProfileChange('endereco', e.target.value)} /> + + handleProfileChange("endereco", e.target.value) + } + /> ) : ( -

{profileData.endereco}

+

+ {profileData.endereco} +

)}
{isEditingProfile ? ( - handleProfileChange('cidade', e.target.value)} /> + + handleProfileChange("cidade", e.target.value) + } + /> ) : ( -

{profileData.cidade}

+

+ {profileData.cidade} +

)}
{isEditingProfile ? ( - handleProfileChange('cep', e.target.value)} /> + handleProfileChange("cep", e.target.value)} + /> ) : ( -

{profileData.cep}

+

+ {profileData.cep} +

)}
{isEditingProfile ? ( -