"use client"; import React, { useState, useRef, useEffect } from "react"; import SignatureCanvas from "react-signature-canvas"; import Link from "next/link"; import ProtectedRoute from "@/components/ProtectedRoute"; import { useAuth } from "@/hooks/useAuth"; import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, type Paciente, buscarRelatorioPorId } from "@/lib/api"; import { useReports } from "@/hooks/useReports"; import { CreateReportData } from "@/types/report-types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { SimpleThemeToggle } from "@/components/simple-theme-toggle"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, 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, FileCheck, Upload, Download, Eye, History, Stethoscope, Pill, Activity, Search } from "lucide-react" import { Calendar as CalendarIcon, FileText, Settings } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import dynamic from "next/dynamic"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import ptBrLocale from "@fullcalendar/core/locales/pt-br"; const FullCalendar = dynamic(() => import("@fullcalendar/react"), { ssr: false, }); const pacientes = [ { nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, statusLaudo: "Finalizado" }, { nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, statusLaudo: "Pendente" }, { nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, statusLaudo: "Rascunho" }, ]; const medico = { nome: "Dr. Carlos Andrade", identificacao: "CRM 000000 • Cardiologia e Dermatologia", fotoUrl: "", } const colorsByType = { Rotina: "#4dabf7", Cardiologia: "#f76c6c", Otorrino: "#f7b84d", Pediatria: "#6cf78b", Dermatologia: "#9b59b6", Oftalmologia: "#2ecc71" }; // Helpers para normalizar dados de paciente (suporta schema antigo e novo) const getPatientName = (p: any) => p?.full_name ?? p?.nome ?? ''; const getPatientCpf = (p: any) => p?.cpf ?? ''; const getPatientSex = (p: any) => p?.sex ?? p?.sexo ?? ''; const getPatientId = (p: any) => p?.id ?? ''; const getPatientAge = (p: any) => { if (!p) return ''; // Prefer birth_date (ISO) to calcular idade const bd = p?.birth_date ?? p?.data_nascimento ?? p?.birthDate; if (bd) { const d = new Date(bd); if (!isNaN(d.getTime())) { const age = Math.floor((Date.now() - d.getTime()) / (1000 * 60 * 60 * 24 * 365.25)); return `${age}`; } } // Fallback para campo idade/idade_anterior return p?.idade ?? p?.age ?? ''; }; // Helpers para normalizar campos do laudo/relatório const getReportPatientName = (r: any) => r?.paciente?.full_name ?? r?.paciente?.nome ?? r?.patient?.full_name ?? r?.patient?.nome ?? r?.patient_name ?? r?.patient_full_name ?? ''; const getReportPatientId = (r: any) => r?.paciente?.id ?? r?.patient?.id ?? r?.patient_id ?? r?.patientId ?? r?.patient_id_raw ?? r?.patient_id ?? r?.id ?? ''; const getReportPatientCpf = (r: any) => r?.paciente?.cpf ?? r?.patient?.cpf ?? r?.patient_cpf ?? ''; const getReportExecutor = (r: any) => r?.executante ?? r?.requested_by ?? r?.requestedBy ?? r?.created_by ?? r?.createdBy ?? r?.requested_by_name ?? r?.executor ?? ''; const getReportExam = (r: any) => r?.exame ?? r?.exam ?? r?.especialidade ?? r?.cid_code ?? r?.report_type ?? '-'; const getReportDate = (r: any) => r?.data ?? r?.created_at ?? r?.due_at ?? r?.report_date ?? ''; const formatReportDate = (raw?: string) => { if (!raw) return '-'; try { const d = new Date(raw); if (isNaN(d.getTime())) return raw; return d.toLocaleDateString('pt-BR'); } catch (e) { return raw; } }; const ProfissionalPage = () => { const { logout, user } = useAuth(); const [activeSection, setActiveSection] = useState('calendario'); const [pacienteSelecionado, setPacienteSelecionado] = useState(null); // Estados para edição de laudo const [isEditingLaudoForPatient, setIsEditingLaudoForPatient] = useState(false); const [patientForLaudo, setPatientForLaudo] = useState(null); // Estados para o perfil do médico const [isEditingProfile, setIsEditingProfile] = useState(false); const [profileData, setProfileData] = useState({ nome: "Dr. Carlos Andrade", 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 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 campos principais da consulta const [consultaAtual, setConsultaAtual] = useState({ patient_id: "", order_number: "", exam: "", diagnosis: "", conclusion: "", cid_code: "", content_html: "", content_json: {}, status: "draft", requested_by: "", due_at: new Date().toISOString(), hide_date: true, hide_signature: true }); const [events, setEvents] = useState([ { id: 1, title: "Ana Souza", type: "Cardiologia", time: "09:00", date: new Date().toISOString().split('T')[0], pacienteId: "123.456.789-00", color: colorsByType.Cardiologia }, { id: 2, title: "Bruno Lima", type: "Cardiologia", time: "10:30", date: new Date().toISOString().split('T')[0], pacienteId: "987.654.321-00", color: colorsByType.Cardiologia }, { id: 3, title: "Carla Menezes", type: "Dermatologia", time: "14:00", date: new Date().toISOString().split('T')[0], pacienteId: "111.222.333-44", color: colorsByType.Dermatologia } ]); const [editingEvent, setEditingEvent] = useState(null); const [showPopup, setShowPopup] = useState(false); const [showActionModal, setShowActionModal] = useState(false); const [step, setStep] = useState(1); const [newEvent, setNewEvent] = useState({ title: "", type: "", time: "", pacienteId: "" }); const [selectedDate, setSelectedDate] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null); const [currentCalendarDate, setCurrentCalendarDate] = useState(new Date()); const handleSave = (event: React.MouseEvent) => { event.preventDefault(); console.log("Laudo salvo!"); window.scrollTo({ top: 0, behavior: "smooth" }); }; const handleEditarLaudo = (paciente: any) => { setPatientForLaudo(paciente); setIsEditingLaudoForPatient(true); setActiveSection('laudos'); }; const navigateDate = (direction: 'prev' | 'next') => { const newDate = new Date(currentCalendarDate); newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1)); setCurrentCalendarDate(newDate); }; const goToToday = () => { setCurrentCalendarDate(new Date()); }; const formatDate = (date: Date) => { return date.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); }; // Filtrar eventos do dia atual const getTodayEvents = () => { const today = currentCalendarDate.toISOString().split('T')[0]; return events .filter(event => event.date === today) .sort((a, b) => a.time.localeCompare(b.time)); }; const getStatusColor = (type: string) => { return colorsByType[type as keyof typeof colorsByType] || "#4dabf7"; }; // Funções para o perfil const handleProfileChange = (field: string, value: string) => { setProfileData(prev => ({ ...prev, [field]: value })); }; const handleSaveProfile = () => { setIsEditingProfile(false); alert('Perfil atualizado com sucesso!'); }; const handleCancelEdit = () => { setIsEditingProfile(false); }; const handleDateClick = (arg: any) => { setSelectedDate(arg.dateStr); setNewEvent({ title: "", type: "", time: "", pacienteId: "" }); setStep(1); setEditingEvent(null); setShowPopup(true); }; const handleAddEvent = () => { const paciente = pacientes.find(p => p.nome === newEvent.title); const eventToAdd = { id: Date.now(), title: newEvent.title, type: newEvent.type, time: newEvent.time, date: selectedDate || currentCalendarDate.toISOString().split('T')[0], pacienteId: paciente ? paciente.cpf : "", color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7" }; setEvents((prev) => [...prev, eventToAdd]); setShowPopup(false); }; const handleEditEvent = () => { setEvents((prevEvents) => prevEvents.map((ev) => ev.id.toString() === editingEvent.id.toString() ? { ...ev, title: newEvent.title, type: newEvent.type, time: newEvent.time, color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7" } : ev ) ); setEditingEvent(null); setShowPopup(false); setShowActionModal(false); }; const handleNextStep = () => { if (step < 3) setStep(step + 1); else editingEvent ? handleEditEvent() : handleAddEvent(); }; const handleEventClick = (clickInfo: any) => { setSelectedEvent(clickInfo.event); setShowActionModal(true); }; const handleDeleteEvent = () => { if (!selectedEvent) return; setEvents((prevEvents) => prevEvents.filter((ev: any) => ev.id.toString() !== selectedEvent.id.toString()) ); setShowActionModal(false); }; const handleStartEdit = () => { if (!selectedEvent) return; setEditingEvent(selectedEvent); setNewEvent({ title: selectedEvent.title, type: selectedEvent.extendedProps.type, time: selectedEvent.extendedProps.time, pacienteId: selectedEvent.extendedProps.pacienteId || "" }); setStep(1); setShowActionModal(false); setShowPopup(true); }; const renderEventContent = (eventInfo: any) => { const bg = eventInfo.event.backgroundColor || eventInfo.event.extendedProps?.color || "#4dabf7"; return (
{eventInfo.event.title} {eventInfo.event.extendedProps.type} {eventInfo.event.extendedProps.time}
); }; const renderCalendarioSection = () => { const todayEvents = getTodayEvents(); return (

Agenda do Dia

{/* Navegação de Data */}

{formatDate(currentCalendarDate)}

{todayEvents.length} consulta{todayEvents.length !== 1 ? 's' : ''} agendada{todayEvents.length !== 1 ? 's' : ''}
{/* Lista de Pacientes do Dia */}
{todayEvents.length === 0 ? (

Nenhuma consulta agendada para este dia

Agenda livre para este dia

) : ( todayEvents.map((appointment) => { const paciente = pacientes.find(p => p.nome === appointment.title); return (
{appointment.title}
{paciente && (
CPF: {getPatientCpf(paciente)} • {getPatientAge(paciente)} anos
)}
{appointment.time}
{appointment.type}
Ver informações do paciente
); }) )}
); }; const renderLaudosSection = () => (
{ setIsEditingLaudoForPatient(false); setPatientForLaudo(null); }} />
); // --- NOVO SISTEMA DE LAUDOS COMPLETO --- function LaudoManager({ isEditingForPatient, selectedPatientForLaudo, onClosePatientEditor }: { isEditingForPatient?: boolean; selectedPatientForLaudo?: any; onClosePatientEditor?: () => void }) { const [pacientesDisponiveis] = useState([ { id: "95170038", nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, sexo: "Feminino" }, { id: "93203056", nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, sexo: "Masculino" }, { id: "92953542", nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, sexo: "Feminino" }, ]); const { reports, loadReports, loading: reportsLoading, createNewReport, updateExistingReport } = useReports(); const [laudos, setLaudos] = useState([]); const [selectedRange, setSelectedRange] = useState<'todos'|'semana'|'mes'|'custom'>('mes'); const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); // helper to check if a date string is in range const isInRange = (dateStr: string | undefined, range: 'todos'|'semana'|'mes'|'custom') => { if (range === 'todos') return true; if (!dateStr) return false; const d = new Date(dateStr); if (isNaN(d.getTime())) return false; const now = new Date(); if (range === 'semana') { const start = new Date(now); start.setDate(now.getDate() - now.getDay()); // sunday start const end = new Date(start); end.setDate(start.getDate() + 6); return d >= start && d <= end; } // mes return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth(); }; // When selectedRange changes (and isn't custom), compute start/end dates useEffect(() => { const now = new Date(); if (selectedRange === 'todos') { setStartDate(null); setEndDate(null); return; } if (selectedRange === 'semana') { const start = new Date(now); start.setDate(now.getDate() - now.getDay()); // sunday const end = new Date(start); end.setDate(start.getDate() + 6); setStartDate(start.toISOString().slice(0,10)); setEndDate(end.toISOString().slice(0,10)); return; } if (selectedRange === 'mes') { const start = new Date(now.getFullYear(), now.getMonth(), 1); const end = new Date(now.getFullYear(), now.getMonth() + 1, 0); setStartDate(start.toISOString().slice(0,10)); setEndDate(end.toISOString().slice(0,10)); return; } // custom: leave startDate/endDate as-is }, [selectedRange]); const filteredLaudos = (laudos || []).filter(l => { // If a specific start/end date is set, use that range if (startDate && endDate) { const ds = getReportDate(l); if (!ds) return false; const d = new Date(ds); if (isNaN(d.getTime())) return false; const start = new Date(startDate + 'T00:00:00'); const end = new Date(endDate + 'T23:59:59'); return d >= start && d <= end; } // Fallback to selectedRange heuristics if (!selectedRange) return true; const ds = getReportDate(l); return isInRange(ds, selectedRange); }); function DateRangeButtons() { return ( <> ); } // SearchBox inserido aqui para acessar reports, setLaudos e loadReports function SearchBox() { const [searchTerm, setSearchTerm] = useState(''); const [searching, setSearching] = useState(false); const { token } = useAuth(); const isMaybeId = (s: string) => { const t = s.trim(); if (!t) return false; if (t.includes('-') && t.length > 10) return true; if (t.toUpperCase().startsWith('REL-')) return true; const digits = t.replace(/\D/g, ''); if (digits.length >= 8) return true; return false; }; const doSearch = async () => { const term = searchTerm.trim(); if (!term) return; setSearching(true); try { if (isMaybeId(term)) { try { const r = await buscarRelatorioPorId(term); if (r) { // If token exists, attempt batch enrichment like useReports const enriched: any = { ...r }; // Collect possible patient/doctor ids from payload const pidCandidates: string[] = []; const didCandidates: string[] = []; const pid = (r as any).patient_id ?? (r as any).patient ?? (r as any).paciente ?? null; if (pid) pidCandidates.push(String(pid)); const possiblePatientName = (r as any).patient_name ?? (r as any).patient_full_name ?? (r as any).paciente?.full_name ?? (r as any).paciente?.nome ?? null; if (possiblePatientName) { enriched.paciente = enriched.paciente ?? {}; enriched.paciente.full_name = possiblePatientName; } const did = (r as any).requested_by ?? (r as any).created_by ?? (r as any).executante ?? null; if (did) didCandidates.push(String(did)); // If token available, perform batch fetch to get full patient/doctor objects if (token) { try { if (pidCandidates.length) { const patients = await buscarPacientesPorIds(pidCandidates); if (patients && patients.length) { const p = patients[0]; enriched.paciente = enriched.paciente ?? {}; enriched.paciente.full_name = enriched.paciente.full_name || p.full_name || (p as any).nome; enriched.paciente.id = enriched.paciente.id || p.id; enriched.paciente.cpf = enriched.paciente.cpf || p.cpf; } } if (didCandidates.length) { const doctors = await buscarMedicosPorIds(didCandidates); if (doctors && doctors.length) { const d = doctors[0]; enriched.executante = enriched.executante || d.full_name || (d as any).nome; } } } catch (e) { // fallback: continue with payload-only enrichment console.warn('[SearchBox] batch enrichment failed, falling back to payload-only enrichment', e); } } // Final payload-only fallbacks (ensure id/cpf/order_number are populated) const possiblePatientId = (r as any).paciente?.id ?? (r as any).patient?.id ?? (r as any).patient_id ?? (r as any).patientId ?? (r as any).id ?? undefined; if (possiblePatientId && !enriched.paciente?.id) { enriched.paciente = enriched.paciente ?? {}; enriched.paciente.id = possiblePatientId; } const possibleCpf = (r as any).patient_cpf ?? (r as any).paciente?.cpf ?? (r as any).patient?.cpf ?? null; if (possibleCpf) { enriched.paciente = enriched.paciente ?? {}; enriched.paciente.cpf = possibleCpf; } const execName = (r as any).requested_by_name ?? (r as any).requester_name ?? (r as any).requestedByName ?? (r as any).executante_name ?? (r as any).created_by_name ?? (r as any).createdByName ?? (r as any).executante ?? (r as any).requested_by ?? (r as any).created_by ?? ''; if (execName) enriched.executante = enriched.executante || execName; if ((r as any).order_number) enriched.order_number = (r as any).order_number; setLaudos([enriched]); return; } } catch (err: any) { console.warn('Relatório não encontrado por ID:', err); } } const lower = term.toLowerCase(); const filtered = (reports || []).filter((x: any) => { const name = (x.paciente?.full_name || x.patient_name || x.patient_full_name || x.order_number || x.exame || x.exam || '').toString().toLowerCase(); return name.includes(lower); }); if (filtered.length) setLaudos(filtered); else setLaudos([]); } finally { setSearching(false); } }; const handleKey = (e: React.KeyboardEvent) => { if (e.key === 'Enter') doSearch(); }; const handleClear = async () => { setSearchTerm(''); await loadReports(); setLaudos(reports || []); }; return (
setSearchTerm(e.target.value)} onKeyDown={handleKey} />
); } // carregar laudos ao montar - somente dos pacientes atribuídos ao médico logado useEffect(() => { let mounted = true; (async () => { try { // obter assignments para o usuário logado const assignments = await import('@/lib/assignment').then(m => m.listAssignmentsForUser(user?.id || '')); const patientIds = Array.isArray(assignments) ? assignments.map(a => String(a.patient_id)).filter(Boolean) : []; if (patientIds.length === 0) { if (mounted) setLaudos([]); return; } // carregar relatórios para cada paciente encontrado (useReports não tem batch by multiple ids, então carregamos por paciente) const allReports: any[] = []; for (const pid of patientIds) { try { const rels = await import('@/lib/reports').then(m => m.listarRelatoriosPorPaciente(pid)); if (Array.isArray(rels)) allReports.push(...rels); } catch (err) { console.warn('[LaudoManager] falha ao carregar relatórios para paciente', pid, err); } } if (mounted) { setLaudos(allReports); } } catch (e) { console.warn('[LaudoManager] erro ao carregar laudos para pacientes atribuídos:', e); if (mounted) setLaudos(reports || []); } })(); return () => { mounted = false; }; }, [user?.id]); // sincroniza quando reports mudarem no hook (fallback) useEffect(() => { if (!laudos || laudos.length === 0) setLaudos(reports || []); }, [reports]); const [activeTab, setActiveTab] = useState("descobrir"); const [laudoSelecionado, setLaudoSelecionado] = useState(null); const [isViewing, setIsViewing] = useState(false); const [isCreatingNew, setIsCreatingNew] = useState(false); return (
{/* Header */}

Gerenciamento de Laudo

Nesta seção você pode gerenciar todos os laudos gerados.

{/* Tabs */}
{/* Filtros */}
{/* Search input integrado com busca por ID */}
{ setStartDate(e.target.value); setSelectedRange('custom'); }} className="p-1 text-sm" /> - { setEndDate(e.target.value); setSelectedRange('custom'); }} className="p-1 text-sm" />
{/* date range buttons: Semana / Mês */}
{/* Filtros e pesquisa removidos por solicitação */}
{/* Tabela para desktop e cards empilháveis para mobile */}
{/* Desktop / tablet (md+) - tabela com scroll horizontal */}
Pedido Data Prazo Paciente Executante/Solicitante Exame/Classificação Ação {filteredLaudos.map((laudo) => (
{laudo.urgente && (
)} {getReportPatientName(laudo) || laudo.order_number || getShortId(laudo.id)}
{formatReportDate(getReportDate(laudo))}
{laudo?.hora || new Date(laudo?.data || laudo?.created_at || laudo?.due_at || Date.now()).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{laudo?.prazo ?? laudo?.due_at ? formatReportDate(laudo?.due_at ?? laudo?.prazo) : '-'}
{laudo?.prazo_hora ?? laudo?.due_time ?? '-'}
{getReportPatientName(laudo) || '—'}
{getReportPatientCpf(laudo) ? `CPF: ${getReportPatientCpf(laudo)}` : ''}
{getReportExecutor(laudo) || '-'} {getReportExam(laudo) || "-"}
))}
{/* Mobile - cards empilháveis */}
{filteredLaudos.map((laudo) => (
{getReportExam(laudo) || '-'}
{formatReportDate(getReportDate(laudo))} {laudo?.hora ? `• ${laudo.hora}` : ''}
{getReportPatientName(laudo) ? getShortId(laudo.id) : ''}
{getReportPatientName(laudo) || '—'}
{getReportPatientCpf(laudo) ? `CPF: ${getReportPatientCpf(laudo)}` : ''}
{getReportExecutor(laudo) || '-'}
))}
{/* Visualizador de Laudo */} {isViewing && laudoSelecionado && ( setIsViewing(false)} /> )} {/* Editor para Novo Laudo */} {isCreatingNew && ( setIsCreatingNew(false)} isNewLaudo={true} createNewReport={createNewReport} updateExistingReport={updateExistingReport} reloadReports={loadReports} onSaved={(r:any) => { setLaudoSelecionado(r); setIsViewing(true); }} /> )} {/* Editor para Paciente Específico */} {isEditingForPatient && selectedPatientForLaudo && ( {})} isNewLaudo={!selectedPatientForLaudo?.id} preSelectedPatient={selectedPatientForLaudo.paciente || selectedPatientForLaudo} createNewReport={createNewReport} updateExistingReport={updateExistingReport} reloadReports={loadReports} onSaved={(r:any) => { setLaudoSelecionado(r); setIsViewing(true); }} /> )}
); } // Visualizador de Laudo (somente leitura) function LaudoViewer({ laudo, onClose }: { laudo: any; onClose: () => void }) { return (
{/* Header */}

Visualizar Laudo

Paciente: {getPatientName(laudo?.paciente) || getPatientName(laudo) || '—'} | CPF: {getReportPatientCpf(laudo) ?? laudo?.patient_cpf ?? '-'} | {laudo?.especialidade ?? laudo?.exame ?? '-'}

{/* Content */}
{/* Header do Laudo */}

LAUDO MÉDICO - {(laudo.especialidade ?? laudo.exame ?? '').toString().toUpperCase()}

Data: {formatReportDate(getReportDate(laudo))}

{/* Dados do Paciente */}

Dados do Paciente:

Nome: {getPatientName(laudo?.paciente) || getPatientName(laudo) || '-'}

ID: {getPatientId(laudo?.paciente) ?? getPatientId(laudo) ?? '-'}

CPF: {getPatientCpf(laudo?.paciente) ?? laudo?.patient_cpf ?? '-'}

Idade: {getPatientAge(laudo?.paciente) ? `${getPatientAge(laudo?.paciente)} anos` : (getPatientAge(laudo) ? `${getPatientAge(laudo)} anos` : '-')}

Sexo: {getPatientSex(laudo?.paciente) ?? getPatientSex(laudo) ?? '-'}

CID: {laudo?.cid ?? laudo?.cid_code ?? '-'}

{/* Conteúdo do Laudo */}
') }} />
{/* Exame */} {((laudo.exame ?? laudo.exam ?? laudo.especialidade ?? laudo.report_type) || '').toString().length > 0 && (

Exame / Especialidade:

{laudo.exame ?? laudo.exam ?? laudo.especialidade ?? laudo.report_type}

)} {/* Diagnóstico */} {((laudo.diagnostico ?? laudo.diagnosis) || '').toString().length > 0 && (

Diagnóstico:

{laudo.diagnostico ?? laudo.diagnosis}

)} {/* Conclusão */} {((laudo.conclusao ?? laudo.conclusion) || '').toString().length > 0 && (

Conclusão:

{laudo.conclusao ?? laudo.conclusion}

)} {/* Diagnóstico e Conclusão */} {laudo.diagnostico && (

Diagnóstico:

{laudo.diagnostico}

)} {laudo.conclusao && (

Conclusão:

{laudo.conclusao}

)} {/* Assinatura */}
{(() => { const signatureName = laudo?.created_by_name ?? laudo?.createdByName ?? ((laudo?.created_by && user?.id && laudo.created_by === user.id) ? 'Squad-20' : medico.nome ?? 'Squad-20'); return ( <>

{signatureName}

CRM 000000 - {laudo.especialidade}

Data: {formatReportDate(getReportDate(laudo))}

); })()}
{/* Footer */}
Status: {laudo.status} | Executante: {laudo.executante}
); } // Editor de Laudo Avançado (para novos laudos) function LaudoEditor({ pacientes, laudo, onClose, isNewLaudo, preSelectedPatient, createNewReport, updateExistingReport, reloadReports, onSaved }: { pacientes?: any[]; laudo?: any; onClose: () => void; isNewLaudo?: boolean; preSelectedPatient?: any; createNewReport?: (data: any) => Promise; updateExistingReport?: (id: string, data: any) => Promise; reloadReports?: () => Promise; onSaved?: (r:any) => void }) { // Import useToast at the top level of the component const { toast } = require('@/hooks/use-toast').useToast(); const [activeTab, setActiveTab] = useState("editor"); const [content, setContent] = useState(laudo?.conteudo || ""); const [showPreview, setShowPreview] = useState(false); const [pacienteSelecionado, setPacienteSelecionado] = useState(preSelectedPatient || null); const [listaPacientes, setListaPacientes] = useState([]); // Novo: campos para solicitante e prazo const [solicitante, setSolicitante] = useState(user?.id || ""); const [prazoDate, setPrazoDate] = useState(""); const [prazoTime, setPrazoTime] = useState(""); // Pega token do usuário logado (passado explicitamente para listarPacientes) const { token } = useAuth(); // Carregar pacientes reais do Supabase ao abrir o modal ou quando o token mudar useEffect(() => { async function fetchPacientes() { try { if (!token) { setListaPacientes([]); return; } const pacientes = await listarPacientes(); setListaPacientes(pacientes || []); } catch (err) { console.warn('Erro ao carregar pacientes:', err); setListaPacientes([]); } } fetchPacientes(); }, [token]); const [campos, setCampos] = useState({ cid: laudo?.cid || "", diagnostico: laudo?.diagnostico || "", conclusao: laudo?.conclusao || "", exame: laudo?.exame || "", especialidade: laudo?.especialidade || "", mostrarData: true, mostrarAssinatura: true }); const [imagens, setImagens] = useState([]); const [templates] = useState([ "Exame normal, sem alterações significativas", "Paciente em acompanhamento ambulatorial", "Recomenda-se retorno em 30 dias", "Alterações compatíveis com processo inflamatório", "Resultado dentro dos parâmetros de normalidade", "Recomendo seguimento com especialista" ]); const sigCanvasRef = useRef(null); // Estado para imagem da assinatura const [assinaturaImg, setAssinaturaImg] = useState(null); useEffect(() => { if (!sigCanvasRef.current) return; const handleEnd = () => { const url = sigCanvasRef.current.getTrimmedCanvas().toDataURL('image/png'); setAssinaturaImg(url); }; const canvas = sigCanvasRef.current; if (canvas && canvas.canvas) { canvas.canvas.addEventListener('mouseup', handleEnd); canvas.canvas.addEventListener('touchend', handleEnd); } return () => { if (canvas && canvas.canvas) { canvas.canvas.removeEventListener('mouseup', handleEnd); canvas.canvas.removeEventListener('touchend', handleEnd); } }; }, [sigCanvasRef]); const handleClearSignature = () => { if (sigCanvasRef.current) { sigCanvasRef.current.clear(); } setAssinaturaImg(null); }; // Carregar dados do laudo existente quando disponível (mais robusto: suporta vários nomes de campo) useEffect(() => { if (laudo && !isNewLaudo) { // Conteúdo: aceita 'conteudo', 'content_html', 'contentHtml', 'content' const contentValue = laudo.conteudo ?? laudo.content_html ?? laudo.contentHtml ?? laudo.content ?? ""; setContent(contentValue); // Campos: use vários fallbacks const cidValue = laudo.cid ?? laudo.cid_code ?? ''; const diagnosticoValue = laudo.diagnostico ?? laudo.diagnosis ?? ''; const conclusaoValue = laudo.conclusao ?? laudo.conclusion ?? ''; const exameValue = laudo.exame ?? laudo.exam ?? laudo.especialidade ?? ''; const especialidadeValue = laudo.especialidade ?? laudo.exame ?? laudo.exam ?? ''; const mostrarDataValue = typeof laudo.hide_date === 'boolean' ? !laudo.hide_date : true; const mostrarAssinaturaValue = typeof laudo.hide_signature === 'boolean' ? !laudo.hide_signature : true; setCampos({ cid: cidValue, diagnostico: diagnosticoValue, conclusao: conclusaoValue, exame: exameValue, especialidade: especialidadeValue, mostrarData: mostrarDataValue, mostrarAssinatura: mostrarAssinaturaValue }); // Paciente: não sobrescrever se já existe preSelectedPatient ou pacienteSelecionado if (!pacienteSelecionado) { const pacienteFromLaudo = laudo.paciente ?? laudo.patient ?? null; if (pacienteFromLaudo) { setPacienteSelecionado(pacienteFromLaudo); } else if (laudo.patient_id && listaPacientes && listaPacientes.length) { const found = listaPacientes.find(p => String(p.id) === String(laudo.patient_id)); if (found) setPacienteSelecionado(found); } } // preencher solicitante/prazo quando existe laudo (edição) const possibleName = laudo.requested_by_name ?? laudo.requester_name ?? laudo.requestedByName ?? laudo.executante_name ?? laudo.executante?.nome ?? laudo.requested_by ?? laudo.created_by_name ?? user?.id ?? ""; setSolicitante(possibleName); const dueRaw = laudo.due_at ?? laudo.prazo ?? laudo.dueDate ?? laudo.data ?? null; if (dueRaw) { try { const d = new Date(dueRaw); if (!isNaN(d.getTime())) { setPrazoDate(d.toISOString().slice(0,10)); setPrazoTime(d.toTimeString().slice(0,5)); } } catch (e) { // ignore invalid date } } // assinatura: aceitar vários campos possíveis const sig = laudo.assinaturaImg ?? laudo.signature_image ?? laudo.signature ?? laudo.sign_image ?? null; if (sig) setAssinaturaImg(sig); } }, [laudo, isNewLaudo, pacienteSelecionado, listaPacientes, user]); // Histórico para desfazer/refazer const [history, setHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); // Atualiza histórico ao digitar useEffect(() => { if (history[historyIndex] !== content) { const newHistory = history.slice(0, historyIndex + 1); setHistory([...newHistory, content]); setHistoryIndex(newHistory.length); } // eslint-disable-next-line }, [content]); const handleUndo = () => { if (historyIndex > 0) { setContent(history[historyIndex - 1]); setHistoryIndex(historyIndex - 1); } }; const handleRedo = () => { if (historyIndex < history.length - 1) { setContent(history[historyIndex + 1]); setHistoryIndex(historyIndex + 1); } }; // Formatação avançada const formatText = (type: string, value?: any) => { const textarea = document.querySelector('textarea') as HTMLTextAreaElement; if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const selectedText = textarea.value.substring(start, end); let formattedText = ""; switch(type) { case "bold": formattedText = selectedText ? `**${selectedText}**` : "**texto em negrito**"; break; case "italic": formattedText = selectedText ? `*${selectedText}*` : "*texto em itálico*"; break; case "underline": formattedText = selectedText ? `__${selectedText}__` : "__texto sublinhado__"; break; case "list-ul": formattedText = selectedText ? selectedText.split('\n').map(l => `• ${l}`).join('\n') : "• item da lista"; break; case "list-ol": formattedText = selectedText ? selectedText.split('\n').map((l,i) => `${i+1}. ${l}`).join('\n') : "1. item da lista"; break; case "indent": formattedText = selectedText ? selectedText.split('\n').map(l => ` ${l}`).join('\n') : " "; break; case "outdent": formattedText = selectedText ? selectedText.split('\n').map(l => l.replace(/^\s{1,4}/, "")).join('\n') : ""; break; case "align-left": formattedText = selectedText ? `[left]${selectedText}[/left]` : "[left]Texto à esquerda[/left]"; break; case "align-center": formattedText = selectedText ? `[center]${selectedText}[/center]` : "[center]Texto centralizado[/center]"; break; case "align-right": formattedText = selectedText ? `[right]${selectedText}[/right]` : "[right]Texto à direita[/right]"; break; case "align-justify": formattedText = selectedText ? `[justify]${selectedText}[/justify]` : "[justify]Texto justificado[/justify]"; break; case "font-size": formattedText = selectedText ? `[size=${value}]${selectedText}[/size]` : `[size=${value}]Texto tamanho ${value}[/size]`; break; case "font-family": formattedText = selectedText ? `[font=${value}]${selectedText}[/font]` : `[font=${value}]${value}[/font]`; break; case "font-color": formattedText = selectedText ? `[color=${value}]${selectedText}[/color]` : `[color=${value}]${value}[/color]`; break; default: return; } const newText = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end); setContent(newText); }; const insertTemplate = (template: string) => { setContent((prev: string) => prev ? `${prev}\n\n${template}` : template); }; const handleImageUpload = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); files.forEach(file => { const reader = new FileReader(); reader.onload = (e) => { setImagens(prev => [...prev, { id: Date.now() + Math.random(), name: file.name, url: e.target?.result, type: file.type }]); }; reader.readAsDataURL(file); }); }; const processContent = (content: string) => { return content .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/__(.*?)__/g, '$1') .replace(/\[left\]([\s\S]*?)\[\/left\]/g, '
$1
') .replace(/\[center\]([\s\S]*?)\[\/center\]/g, '
$1
') .replace(/\[right\]([\s\S]*?)\[\/right\]/g, '
$1
') .replace(/\[justify\]([\s\S]*?)\[\/justify\]/g, '
$1
') .replace(/\[size=(\d+)\]([\s\S]*?)\[\/size\]/g, '$2') .replace(/\[font=([^\]]+)\]([\s\S]*?)\[\/font\]/g, '$2') .replace(/\[color=([^\]]+)\]([\s\S]*?)\[\/color\]/g, '$2') .replace(/{{sexo_paciente}}/g, pacienteSelecionado?.sexo || laudo?.paciente?.sexo || '[SEXO]') .replace(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]') .replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]') .replace(/\n/g, '
'); }; return (
{/* Header */}

{isNewLaudo ? "Novo Laudo Médico" : "Editar Laudo Existente"}

{isNewLaudo ? (

Crie um novo laudo selecionando um paciente

) : (

Paciente: {getPatientName(pacienteSelecionado) || getPatientName(laudo?.paciente) || getPatientName(laudo) || '-'} | CPF: {getReportPatientCpf(laudo) ?? laudo?.patient_cpf ?? '-'} | {laudo?.especialidade}

)}
{/* Seleção de Paciente (apenas para novos laudos) */} {isNewLaudo && (
{!pacienteSelecionado ? (
) : (
{getPatientName(pacienteSelecionado)}
{getPatientCpf(pacienteSelecionado) ? `CPF: ${getPatientCpf(pacienteSelecionado)} | ` : ''} {pacienteSelecionado?.birth_date ? `Nascimento: ${pacienteSelecionado.birth_date}` : (getPatientAge(pacienteSelecionado) ? `Idade: ${getPatientAge(pacienteSelecionado)} anos` : '')} {getPatientSex(pacienteSelecionado) ? ` | Sexo: ${getPatientSex(pacienteSelecionado)}` : ''}
{!preSelectedPatient && ( )}
)} {/* Novos campos: Solicitante e Prazo */}
setSolicitante(e.target.value)} placeholder="Nome ou ID do solicitante (opcional)" />

Se vazio, o usuário logado será usado como solicitante.

setPrazoDate(e.target.value)} /> setPrazoTime(e.target.value)} />

Defina a data e hora do prazo (opcional).

)}
{/* Tabs */}
{/* Informações tab removed - only Editor/Imagens/Campos/Pré-visualização remain */}
{/* Content */}
{/* Left Panel */}
{/* 'Informações' section removed to keep editor-only experience */} {activeTab === "editor" && (
{/* Toolbar */}
{/* Tamanho da fonte */} formatText('font-size', e.target.value)} className="w-14 border rounded px-1 py-0.5 text-xs mr-2" title="Tamanho da fonte" /> {/* Família da fonte */} {/* Cor da fonte */} formatText('font-color', e.target.value)} className="w-6 h-6 border rounded mr-2" title="Cor da fonte" /> {/* Alinhamento */} {/* Listas */} {/* Recuo */} {/* Desfazer/Refazer */} {/* Negrito, itálico, sublinhado */}
{/* Templates */}

Frases rápidas:

{templates.map((template, idx) => ( ))}
{/* Editor */}