From 653b21e2d2dd69a49c971e8ea35a624dd5f96e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:29:26 -0300 Subject: [PATCH 1/4] fix-doctor-and-patient --- .../app/(main-routes)/doutores/page.tsx | 38 +++++- .../forms/doctor-registration-form.tsx | 112 +++++++++++++----- susconecta/lib/api.ts | 24 ++++ 3 files changed, 142 insertions(+), 32 deletions(-) diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 8400a1f..1acbb83 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -19,14 +19,48 @@ import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, buscarP import { listAssignmentsForUser } from '@/lib/assignment'; function normalizeMedico(m: any): Medico { + const normalizeSex = (v: any) => { + if (v === null || typeof v === 'undefined') return null; + const s = String(v || '').trim().toLowerCase(); + if (!s) return null; + const male = new Set(['m','masc','male','masculino','homem','h','1','mas']); + const female = new Set(['f','fem','female','feminino','mulher','mul','2','fem']); + const other = new Set(['o','outro','other','3','nb','nonbinary','nao binario','não binário']); + if (male.has(s)) return 'masculino'; + if (female.has(s)) return 'feminino'; + if (other.has(s)) return 'outro'; + if (['masculino','feminino','outro'].includes(s)) return s; + return null; + }; + + const formatBirth = (v: any) => { + if (!v && typeof v !== 'string') return null; + const s = String(v || '').trim(); + if (!s) return null; + const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/); + if (iso) { + const [, y, mth, d] = iso; + return `${d.padStart(2,'0')}/${mth.padStart(2,'0')}/${y}`; + } + const ddmmyyyy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); + if (ddmmyyyy) return s; + const parsed = new Date(s); + if (!isNaN(parsed.getTime())) { + const d = String(parsed.getDate()).padStart(2,'0'); + const mth = String(parsed.getMonth() + 1).padStart(2,'0'); + const y = String(parsed.getFullYear()); + return `${d}/${mth}/${y}`; + } + return null; + }; return { id: String(m.id ?? m.uuid ?? ""), 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, - sexo: m.sexo ?? m.sex ?? null, - data_nascimento: m.data_nascimento ?? m.birth_date ?? null, + sexo: normalizeSex(m.sexo ?? m.sex ?? m.sexualidade ?? null), + data_nascimento: formatBirth(m.data_nascimento ?? m.birth_date ?? m.birthDate ?? null), telefone: m.telefone ?? m.phone_mobile ?? "", celular: m.celular ?? m.phone2 ?? null, contato_emergencia: m.contato_emergencia ?? null, diff --git a/susconecta/components/forms/doctor-registration-form.tsx b/susconecta/components/forms/doctor-registration-form.tsx index 63f49f3..7c29f30 100644 --- a/susconecta/components/forms/doctor-registration-form.tsx +++ b/susconecta/components/forms/doctor-registration-form.tsx @@ -202,37 +202,78 @@ export function DoctorRegistrationForm({ ""; console.log('🎯 Especialidade encontrada:', especialidade); + const m: any = medico as any; + + const normalizeSex = (v: any): string | null => { + if (v === null || typeof v === 'undefined') return null; + const s = String(v).trim().toLowerCase(); + if (!s) return null; + const male = new Set(['m','masc','male','masculino','homem','h','1','mas']); + const female = new Set(['f','fem','female','feminino','mulher','mul','2','fem']); + const other = new Set(['o','outro','other','3','nb','nonbinary','nao binario','não binário']); + if (male.has(s)) return 'masculino'; + if (female.has(s)) return 'feminino'; + if (other.has(s)) return 'outro'; + // Already canonical? + if (['masculino','feminino','outro'].includes(s)) return s; + return null; + }; + + const formatBirth = (v: any) => { + if (!v && typeof v !== 'string') return ''; + const s = String(v).trim(); + if (!s) return ''; + // Accept ISO YYYY-MM-DD or full ISO datetime + const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})/); + if (isoMatch) { + const [, y, mth, d] = isoMatch; + return `${d.padStart(2,'0')}/${mth.padStart(2,'0')}/${y}`; + } + // If already dd/mm/yyyy, return as-is + const ddmmyyyy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); + if (ddmmyyyy) return s; + // Try parsing other common formats + const maybe = new Date(s); + if (!isNaN(maybe.getTime())) { + const d = String(maybe.getDate()).padStart(2,'0'); + const mm = String(maybe.getMonth() + 1).padStart(2,'0'); + const y = String(maybe.getFullYear()); + return `${d}/${mm}/${y}`; + } + return ''; + }; + const formData = { photo: null, - full_name: String(medico.full_name || ""), - nome_social: String(medico.nome_social || ""), - crm: String(medico.crm || ""), - estado_crm: String(medico.estado_crm || ""), - rqe: String(medico.rqe || ""), - formacao_academica: Array.isArray(medico.formacao_academica) ? medico.formacao_academica : [], + full_name: String(m.full_name || m.nome || ""), + nome_social: String(m.nome_social || m.social_name || ""), + crm: String(m.crm || ""), + estado_crm: String(m.estado_crm || m.crm_uf || m.crm_state || ""), + rqe: String(m.rqe || ""), + formacao_academica: Array.isArray(m.formacao_academica) ? m.formacao_academica : [], curriculo: null, especialidade: String(especialidade), - cpf: String(medico.cpf || ""), - rg: String(medico.rg || ""), - sexo: String(medico.sexo || ""), - data_nascimento: String(medico.data_nascimento || ""), - email: String(medico.email || ""), - telefone: String(medico.telefone || ""), - celular: String(medico.celular || ""), - contato_emergencia: String(medico.contato_emergencia || ""), - cep: String(medico.cep || ""), - logradouro: String(medico.street || ""), - numero: String(medico.number || ""), - complemento: String(medico.complement || ""), - bairro: String(medico.neighborhood || ""), - cidade: String(medico.city || ""), - estado: String(medico.state || ""), - observacoes: String(medico.observacoes || ""), + cpf: String(m.cpf || ""), + rg: String(m.rg || m.document_number || ""), + sexo: normalizeSex(m.sexo || m.sex || m.sexualidade || null) ?? "", + data_nascimento: String(formatBirth(m.data_nascimento || m.birth_date || m.birthDate || "")), + email: String(m.email || ""), + telefone: String(m.telefone || m.phone_mobile || m.phone || m.mobile || ""), + celular: String(m.celular || m.phone2 || ""), + contato_emergencia: String(m.contato_emergencia || ""), + cep: String(m.cep || ""), + logradouro: String(m.street || m.logradouro || ""), + numero: String(m.number || m.numero || ""), + complemento: String(m.complement || m.complemento || ""), + bairro: String(m.neighborhood || m.bairro || ""), + cidade: String(m.city || m.cidade || ""), + estado: String(m.state || m.estado || ""), + observacoes: String(m.observacoes || m.notes || ""), anexos: [], - tipo_vinculo: String(medico.tipo_vinculo || ""), - dados_bancarios: medico.dados_bancarios || { banco: "", agencia: "", conta: "", tipo_conta: "" }, - agenda_horario: String(medico.agenda_horario || ""), - valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "", + tipo_vinculo: String(m.tipo_vinculo || ""), + dados_bancarios: m.dados_bancarios || { banco: "", agencia: "", conta: "", tipo_conta: "" }, + agenda_horario: String(m.agenda_horario || ""), + valor_consulta: m.valor_consulta ? String(m.valor_consulta) : "", }; console.log("[DoctorForm] Dados do formulário preparados:", formData); @@ -355,9 +396,12 @@ function setField(k: T, v: FormData[T]) { if (!form.cpf.trim()) e.cpf = "CPF é obrigatório"; if (!form.crm.trim()) e.crm = "CRM é obrigatório"; if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória"; - if (!form.cep.trim()) e.cep = "CEP é obrigatório"; // Verifique se o CEP está preenchido - if (!form.bairro.trim()) e.bairro = "Bairro é obrigatório"; // Verifique se o bairro está preenchido - if (!form.cidade.trim()) e.cidade = "Cidade é obrigatória"; // Verifique se a cidade está preenchida + // During edit, avoid forcing address fields. They are required on create only. + if (mode !== 'edit') { + if (!form.cep.trim()) e.cep = "CEP é obrigatório"; // Verifique se o CEP está preenchido + if (!form.bairro.trim()) e.bairro = "Bairro é obrigatório"; // Verifique se o bairro está preenchido + if (!form.cidade.trim()) e.cidade = "Cidade é obrigatória"; // Verifique se a cidade está preenchida + } setErrors(e); return Object.keys(e).length === 0; @@ -426,7 +470,15 @@ function toPayload(): MedicoInput { async function handleSubmit(ev: React.FormEvent) { ev.preventDefault(); - if (!validateLocal()) return; + console.debug('[DoctorForm] handleSubmit invoked. mode=', mode, 'doctorId=', doctorId); + if (!validateLocal()) { + try { + const { toast } = require('@/hooks/use-toast').useToast(); + const msgs = Object.entries(errors).map(([k,v]) => v).filter(Boolean).join('\n') || 'Preencha os campos obrigatórios'; + toast({ title: 'Erro de validação', description: msgs, variant: 'destructive' }); + } catch {} + return; + } setSubmitting(true); setErrors({}); diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index 703ccd9..6fc60c0 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -2086,17 +2086,41 @@ export async function atualizarMedico(id: string | number, input: MedicoInput): // Criar um payload limpo apenas com campos básicos que sabemos que existem const cleanPayload = { + // Basic identification / contact full_name: input.full_name, + nome_social: (input as any).nome_social || undefined, crm: input.crm, + crm_uf: (input as any).crm_uf || (input as any).crmUf || undefined, + rqe: (input as any).rqe || undefined, specialty: input.specialty, email: input.email, phone_mobile: input.phone_mobile, + phone2: (input as any).phone2 ?? (input as any).telefone ?? undefined, cpf: input.cpf, + rg: (input as any).rg ?? undefined, + + // Address cep: input.cep, street: input.street, number: input.number, + complement: (input as any).complement ?? undefined, + neighborhood: (input as any).neighborhood ?? (input as any).bairro ?? undefined, city: input.city, state: input.state, + + // Personal / professional + birth_date: (input as any).birth_date ?? (input as any).data_nascimento ?? undefined, + sexo: (input as any).sexo ?? undefined, + formacao_academica: (input as any).formacao_academica ?? undefined, + observacoes: (input as any).observacoes ?? undefined, + + // Administrative / financial + tipo_vinculo: (input as any).tipo_vinculo ?? undefined, + dados_bancarios: (input as any).dados_bancarios ?? undefined, + valor_consulta: (input as any).valor_consulta ?? undefined, + agenda_horario: (input as any).agenda_horario ?? undefined, + + // Flags active: input.active ?? true }; From f67ff8df8c925652a62a882b3a8e21d19a8737e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 23 Oct 2025 02:50:57 -0300 Subject: [PATCH 2/4] fixpatient-page --- susconecta/app/paciente/page.tsx | 453 ++++++++++++++++++------------- susconecta/lib/api.ts | 55 ++++ 2 files changed, 316 insertions(+), 192 deletions(-) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 8bd8e24..52f2714 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1,7 +1,7 @@ 'use client' // import { useAuth } from '@/hooks/useAuth' // removido duplicado -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' @@ -16,6 +16,8 @@ import Link from 'next/link' import ProtectedRoute from '@/components/ProtectedRoute' import { useAuth } from '@/hooks/useAuth' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarMensagensPorPaciente } from '@/lib/api' +import { useReports } from '@/hooks/useReports' // Simulação de internacionalização básica const strings = { dashboard: 'Dashboard', @@ -57,8 +59,6 @@ export default function PacientePage() { 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('') @@ -74,14 +74,161 @@ export default function PacientePage() { // Estado para edição do perfil const [isEditingProfile, setIsEditingProfile] = useState(false) const [profileData, setProfileData] = useState({ - nome: "Maria Silva Santos", - email: user?.email || "paciente@example.com", - telefone: "(11) 99999-9999", - endereco: "Rua das Flores, 123", - cidade: "São Paulo", - cep: "01234-567", - biografia: "Paciente desde 2020. Histórico de consultas e exames regulares.", + nome: '', + email: user?.email || '', + telefone: '', + endereco: '', + cidade: '', + cep: '', + biografia: '', }) + const [patientId, setPatientId] = useState(null) + + // Load authoritative patient row for the logged-in user (prefer user_id lookup) + useEffect(() => { + let mounted = true + const uid = user?.id ?? null + const uemail = user?.email ?? null + if (!uid && !uemail) return + + async function loadProfile() { + try { + setLoading(true) + setError('') + + // 1) exact lookup by user_id on patients table + let paciente: any = null + if (uid) paciente = await buscarPacientePorUserId(uid) + + // 2) fallback: search patients by email and prefer a row that has user_id equal to auth id + if (!paciente && uemail) { + try { + const results = await buscarPacientes(uemail) + if (results && results.length) { + paciente = results.find((r: any) => String(r.user_id) === String(uid)) || results[0] + } + } catch (e) { + console.warn('[PacientePage] buscarPacientes falhou', e) + } + } + + // 3) fallback: use getUserInfo() (auth profile) if available + if (!paciente) { + try { + const info = await getUserInfo().catch(() => null) + const p = info?.profile ?? null + if (p) { + // map auth profile to our local shape (best-effort) + paciente = { + full_name: p.full_name ?? undefined, + email: p.email ?? undefined, + phone_mobile: p.phone ?? undefined, + } + } + } catch (e) { + // ignore + } + } + + if (paciente && mounted) { + try { if ((paciente as any).id) setPatientId(String((paciente as any).id)) } catch {} + const getFirst = (obj: any, keys: string[]) => { + if (!obj) return undefined + for (const k of keys) { + const v = obj[k] + if (v !== undefined && v !== null && String(v).trim() !== '') return String(v) + } + return undefined + } + + const nome = getFirst(paciente, ['full_name','fullName','name','nome','social_name']) || '' + const telefone = getFirst(paciente, ['phone_mobile','phone','telefone','mobile']) || '' + const rua = getFirst(paciente, ['street','logradouro','endereco','address']) + const numero = getFirst(paciente, ['number','numero']) + const bairro = getFirst(paciente, ['neighborhood','bairro']) + const endereco = rua ? (numero ? `${rua}, ${numero}` : rua) + (bairro ? ` - ${bairro}` : '') : '' + const cidade = getFirst(paciente, ['city','cidade','localidade']) || '' + const cep = getFirst(paciente, ['cep','postal_code','zip']) || '' + const biografia = getFirst(paciente, ['biography','bio','notes']) || '' + const emailFromRow = getFirst(paciente, ['email']) || uemail || '' + + if (process.env.NODE_ENV !== 'production') console.debug('[PacientePage] paciente row', paciente) + + setProfileData({ nome, email: emailFromRow, telefone, endereco, cidade, cep, biografia }) + } + } catch (err) { + console.warn('[PacientePage] erro ao carregar paciente', err) + } finally { + if (mounted) setLoading(false) + } + } + + loadProfile() + return () => { mounted = false } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user?.id, user?.email]) + + // Load authoritative patient row for the logged-in user (prefer user_id lookup) + useEffect(() => { + let mounted = true + const uid = user?.id ?? null + const uemail = user?.email ?? null + if (!uid && !uemail) return + + async function loadProfile() { + try { + setLoading(true) + setError('') + + let paciente: any = null + if (uid) paciente = await buscarPacientePorUserId(uid) + + if (!paciente && uemail) { + try { + const res = await buscarPacientes(uemail) + if (res && res.length) paciente = res.find((r:any) => String((r as any).user_id) === String(uid)) || res[0] + } catch (e) { + console.warn('[PacientePage] busca por email falhou', e) + } + } + + if (paciente && mounted) { + try { if ((paciente as any).id) setPatientId(String((paciente as any).id)) } catch {} + const getFirst = (obj: any, keys: string[]) => { + if (!obj) return undefined + for (const k of keys) { + const v = obj[k] + if (v !== undefined && v !== null && String(v).trim() !== '') return String(v) + } + return undefined + } + + const nome = getFirst(paciente, ['full_name','fullName','name','nome','social_name']) || profileData.nome + const telefone = getFirst(paciente, ['phone_mobile','phone','telefone','mobile']) || profileData.telefone + const rua = getFirst(paciente, ['street','logradouro','endereco','address']) + const numero = getFirst(paciente, ['number','numero']) + const bairro = getFirst(paciente, ['neighborhood','bairro']) + const endereco = rua ? (numero ? `${rua}, ${numero}` : rua) + (bairro ? ` - ${bairro}` : '') : profileData.endereco + const cidade = getFirst(paciente, ['city','cidade','localidade']) || profileData.cidade + const cep = getFirst(paciente, ['cep','postal_code','zip']) || profileData.cep + const biografia = getFirst(paciente, ['biography','bio','notes']) || profileData.biografia || '' + const emailFromRow = getFirst(paciente, ['email']) || user?.email || profileData.email + + if (process.env.NODE_ENV !== 'production') console.debug('[PacientePage] paciente row', paciente) + + setProfileData(prev => ({ ...prev, nome, email: emailFromRow, telefone, endereco, cidade, cep, biografia })) + } + } catch (err) { + console.warn('[PacientePage] erro ao carregar paciente', err) + } finally { + if (mounted) setLoading(false) + } + } + + loadProfile() + return () => { mounted = false } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user?.id, user?.email]) const handleProfileChange = (field: string, value: string) => { setProfileData(prev => ({ ...prev, [field]: value })) @@ -399,136 +546,58 @@ export default function PacientePage() { ) } - // Exames e laudos fictícios - const examesFicticios = [ - { - id: 1, - nome: "Hemograma Completo", - data: "2025-09-20", - status: "Disponível", - 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.", - }, - { - id: 3, - nome: "Eletrocardiograma", - data: "2025-07-05", - status: "Disponível", - prontuario: "Ritmo sinusal, sem arritmias. Exame dentro da normalidade.", - }, - ]; - - const laudosFicticios = [ - { - id: 1, - 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.", - }, - { - 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.", - }, - { - id: 3, - nome: "Laudo Eletrocardiograma", - data: "2025-07-06", - status: "Assinado", - laudo: "ECG normal. Não há sinais de isquemia ou sobrecarga.", - }, - ]; - - const [exameSelecionado, setExameSelecionado] = useState(null) - const [laudoSelecionado, setLaudoSelecionado] = useState(null) + // Reports (laudos) hook + const { reports, loadReportsByPatient, loading: reportsLoading } = useReports() + const [selectedReport, setSelectedReport] = useState(null) function ExamesLaudos() { + useEffect(() => { + if (!patientId) return + // load laudos for this patient + loadReportsByPatient(patientId).catch(() => {}) + }, [patientId]) + return (
-

Exames

-
-

Meus Exames

-
- {examesFicticios.map(exame => ( -
-
-
{exame.nome}
-
Data: {new Date(exame.data).toLocaleDateString('pt-BR')}
-
-
- - -
-
- ))} -
-

Laudos

-
-

Meus Laudos

+ + {reportsLoading ? ( +
Carregando laudos...
+ ) : (!reports || reports.length === 0) ? ( +
Nenhum laudo salvo.
+ ) : (
- {laudosFicticios.map(laudo => ( -
+ {reports.map((r: any) => ( +
-
{laudo.nome}
-
Data: {new Date(laudo.data).toLocaleDateString('pt-BR')}
+
{r.title || r.report_type || r.exame || r.name || 'Laudo'}
+
Data: {new Date(r.report_date || r.data || r.created_at || Date.now()).toLocaleDateString('pt-BR')}
- - + +
))}
-
+ )} - {/* Modal Prontuário Exame */} - !open && setExameSelecionado(null)}> - - - Prontuário do Exame - - {exameSelecionado && ( - <> -
{exameSelecionado.nome}
-
Data: {new Date(exameSelecionado.data).toLocaleDateString('pt-BR')}
-
{exameSelecionado.prontuario}
- - )} -
-
- - - -
-
- - {/* Modal Visualizar Laudo */} - !open && setLaudoSelecionado(null)}> + !open && setSelectedReport(null)}> Laudo Médico - {laudoSelecionado && ( + {selectedReport && ( <> -
{laudoSelecionado.nome}
-
Data: {new Date(laudoSelecionado.data).toLocaleDateString('pt-BR')}
-
{laudoSelecionado.laudo}
+
{selectedReport.title || selectedReport.report_type || selectedReport.exame || 'Laudo'}
+
Data: {new Date(selectedReport.report_date || selectedReport.data || selectedReport.created_at || Date.now()).toLocaleDateString('pt-BR')}
+
{selectedReport.content || selectedReport.laudo || selectedReport.body || JSON.stringify(selectedReport, null, 2)}
)}
- +
@@ -536,54 +605,51 @@ export default function PacientePage() { ) } - // Mensagens fictícias recebidas do médico - const mensagensFicticias = [ - { - 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 - }, - { - 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 - }, - { - 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 - }, - ]; - function Mensagens() { + const [msgs, setMsgs] = useState([]) + const [loadingMsgs, setLoadingMsgs] = useState(false) + const [msgsError, setMsgsError] = useState(null) + + useEffect(() => { + let mounted = true + if (!patientId) return + setLoadingMsgs(true) + setMsgsError(null) + listarMensagensPorPaciente(String(patientId)) + .then(res => { + if (!mounted) return + setMsgs(Array.isArray(res) ? res : []) + }) + .catch(err => { + console.warn('[Mensagens] erro ao carregar mensagens', err) + if (!mounted) return + setMsgsError('Falha ao carregar mensagens.') + }) + .finally(() => { if (mounted) setLoadingMsgs(false) }) + + return () => { mounted = false } + }, [patientId]) + return (

Mensagens Recebidas

- {mensagensFicticias.length === 0 ? ( -
- -

Nenhuma mensagem recebida

-

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

-
+ {loadingMsgs ? ( +
Carregando mensagens...
+ ) : msgsError ? ( +
{msgsError}
+ ) : (!msgs || msgs.length === 0) ? ( +
Nenhuma mensagem encontrada.
) : ( - mensagensFicticias.map(msg => ( -
-
-
- - {msg.medico} - {!msg.lida && Nova} -
-
{new Date(msg.data).toLocaleString('pt-BR')}
-
{msg.conteudo}
+ msgs.map((msg: any) => ( +
+
+ + {msg.sender_name || msg.from || msg.doctor_name || 'Remetente'} + {!msg.read && Nova}
+
{new Date(msg.created_at || msg.data || Date.now()).toLocaleString('pt-BR')}
+
{msg.body || msg.content || msg.text || JSON.stringify(msg)}
)) )} @@ -593,6 +659,7 @@ export default function PacientePage() { } function Perfil() { + const hasAddress = Boolean(profileData.endereco || profileData.cidade || profileData.cep || profileData.biografia) return (
@@ -634,42 +701,44 @@ export default function PacientePage() { )}
- {/* Endereço e Contato */} -
-

Endereço

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

{profileData.endereco}

- )} + {/* Endereço e Contato (render apenas se existir algum dado) */} + {hasAddress && ( +
+

Endereço

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

{profileData.endereco}

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

{profileData.cidade}

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

{profileData.cep}

+ )} +
+
+ + {isEditingProfile ? ( +