'use client'; import React, { useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import ProtectedRoute from '@/components/shared/ProtectedRoute'; import { useAuth } from '@/hooks/useAuth'; import { useToast } from '@/hooks/use-toast'; import { listarPacientes, buscarMedicos } from '@/lib/api'; import { useReports } from '@/hooks/useReports'; 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 { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { FileText, Upload, Settings, Eye, ArrowLeft, BookOpen } from 'lucide-react'; // Helpers para normalizar dados const getPatientName = (p: any) => p?.full_name ?? p?.nome ?? ''; const getPatientCpf = (p: any) => p?.cpf ?? ''; const getPatientSex = (p: any) => p?.sex ?? p?.sexo ?? ''; const getPatientAge = (p: any) => { if (!p) return ''; 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}`; } } return p?.idade ?? p?.age ?? ''; }; export default function LaudosEditorPage() { const router = useRouter(); const { user, token } = useAuth(); const { toast } = useToast(); const { createNewReport } = useReports(); // Estados principais const [pacienteSelecionado, setPacienteSelecionado] = useState(null); const [listaPacientes, setListaPacientes] = useState([]); const [content, setContent] = useState(''); const [activeTab, setActiveTab] = useState('editor'); const [showPreview, setShowPreview] = useState(false); // Estados para solicitante e prazo const [solicitanteId, setSolicitanteId] = useState(user?.id || ''); // Nome exibido do solicitante (preferir nome do médico vindo da API) const [solicitanteNome, setSolicitanteNome] = useState(user?.name || ''); const [prazoDate, setPrazoDate] = useState(''); const [prazoTime, setPrazoTime] = useState(''); // Campos do laudo const [campos, setCampos] = useState({ cid: '', diagnostico: '', conclusao: '', exame: '', especialidade: '', mostrarData: true, mostrarAssinatura: true, }); // Imagens 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', ]); // Frases prontas const [frasesProntas] = useState([ 'Paciente apresenta bom estado geral.', 'Recomenda-se seguimento clínico periódico.', 'Encaminhar para especialista.', 'Realizar novos exames em 30 dias.', 'Retorno em 15 dias para reavaliação.', 'Suspender medicamento em caso de efeitos colaterais.', 'Manter repouso relativo por 7 dias.', 'Seguir orientações prescritas rigorosamente.', 'Compatível com os achados clínicos.', 'Sem alterações significativas detectadas.', ]); // Histórico const [history, setHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); // Editor ref const editorRef = useRef(null); // Estado para rastrear formatações ativas const [activeFormats, setActiveFormats] = useState({ bold: false, italic: false, underline: false, strikethrough: false, }); // Estado para controlar modal de confirmação de rascunho const [showDraftConfirm, setShowDraftConfirm] = useState(false); // Atualizar formatações ativas ao mudar seleção useEffect(() => { const updateFormats = () => { setActiveFormats({ bold: document.queryCommandState('bold'), italic: document.queryCommandState('italic'), underline: document.queryCommandState('underline'), strikethrough: document.queryCommandState('strikeThrough'), }); }; editorRef.current?.addEventListener('mouseup', updateFormats); editorRef.current?.addEventListener('keyup', updateFormats); return () => { editorRef.current?.removeEventListener('mouseup', updateFormats); editorRef.current?.removeEventListener('keyup', updateFormats); }; }, []); // Carregar pacientes ao montar 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(); // Carregar rascunho salvo ao montar const savedDraft = localStorage.getItem('laudoDraft'); if (savedDraft) { try { const draft = JSON.parse(savedDraft); setPacienteSelecionado(draft.pacienteSelecionado); setContent(draft.content); setCampos(draft.campos); setSolicitanteId(draft.solicitanteId); setPrazoDate(draft.prazoDate); setPrazoTime(draft.prazoTime); setImagens(draft.imagens || []); // Sincronizar editor com conteúdo carregado if (editorRef.current) { editorRef.current.innerHTML = draft.content; } } catch (err) { console.warn('Erro ao carregar rascunho:', err); } } }, [token]); // Import Quill CSS on client side only useEffect(() => { // No CSS needed for native contenteditable }, []); // Sincronizar conteúdo inicial com editor ao montar useEffect(() => { if (editorRef.current && !editorRef.current.innerHTML) { editorRef.current.innerHTML = content; } }, []); // Tentar obter o registro de médico correspondente ao usuário autenticado useEffect(() => { let mounted = true; async function fetchDoctorName() { try { // Se já temos um nome razoável, não sobrescrever if (solicitanteNome && solicitanteNome.trim().length > 1) return; if (!user) return; // Buscar médicos por email (buscarMedicos aceita termos com @ e faz a busca por email) if (user.email && user.email.includes('@')) { const docs = await buscarMedicos(user.email).catch(() => []); if (!mounted) return; if (Array.isArray(docs) && docs.length > 0) { const d = docs[0]; // Preferir full_name do médico quando disponível if (d && (d.full_name || (d as any).nome)) { setSolicitanteNome((d.full_name as string) || ((d as any).nome as string) || user.name || user.email || ''); return; } } } // Fallbacks: usar user.name se existir; caso contrário, email completo setSolicitanteNome(user.name || user.email || ''); } catch (err) { // em caso de erro, manter o fallback setSolicitanteNome(user?.name || user?.email || ''); } } fetchDoctorName(); return () => { mounted = false; }; }, [user]); // Atualizar histórico useEffect(() => { if (history[historyIndex] !== content) { const newHistory = history.slice(0, historyIndex + 1); setHistory([...newHistory, content]); setHistoryIndex(newHistory.length); } }, [content]); // Desfazer const handleUndo = () => { if (historyIndex > 0) { const newIndex = historyIndex - 1; setContent(history[newIndex]); setHistoryIndex(newIndex); // Atualizar editor com conteúdo anterior setTimeout(() => { if (editorRef.current) { editorRef.current.innerHTML = history[newIndex]; editorRef.current.focus(); } }, 0); } }; // Formatação com contenteditable (document.execCommand) const applyFormat = (command: string, value?: string) => { document.execCommand(command, false, value || undefined); editorRef.current?.focus(); }; const makeBold = () => applyFormat('bold'); const makeItalic = () => applyFormat('italic'); const makeUnderline = () => applyFormat('underline'); const makeStrikethrough = () => applyFormat('strikeThrough'); const insertUnorderedList = () => { document.execCommand('insertUnorderedList', false); editorRef.current?.focus(); }; const insertOrderedList = () => { document.execCommand('insertOrderedList', false); editorRef.current?.focus(); }; const alignLeft = () => applyFormat('justifyLeft'); const alignCenter = () => applyFormat('justifyCenter'); const alignRight = () => applyFormat('justifyRight'); const alignJustify = () => applyFormat('justifyFull'); const insertTemplate = (template: string) => { setContent((prev: string) => (prev ? `${prev}\n\n${template}` : template)); }; const insertFraseProta = (frase: string) => { editorRef.current?.focus(); document.execCommand('insertText', false, frase + ' '); setContent(editorRef.current?.innerHTML || ''); }; 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); }); }; // Salvar rascunho no localStorage const saveDraft = () => { const draft = { pacienteSelecionado, content, campos, solicitanteId, prazoDate, prazoTime, imagens, }; localStorage.setItem('laudoDraft', JSON.stringify(draft)); toast({ title: 'Rascunho salvo!', description: 'As informações do laudo foram salvas. Você pode continuar depois.', variant: 'default', }); // Redirecionar para profissional após 1 segundo setTimeout(() => { router.push('/profissional'); }, 1000); }; // Descartar rascunho const discardDraft = () => { localStorage.removeItem('laudoDraft'); router.push('/profissional'); }; // Processar cancelamento com confirmação const handleCancel = () => { // Verificar se há dados para salvar const hasData = content || campos.cid || campos.diagnostico || campos.conclusao || campos.exame || imagens.length > 0; if (hasData) { setShowDraftConfirm(true); } else { router.push('/profissional'); } }; 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(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]') .replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]') .replace(/\n/g, '
'); }; const handleSave = async () => { try { if (!pacienteSelecionado?.id) { toast({ title: 'Erro', description: 'Selecione um paciente para continuar.', variant: 'destructive', }); return; } const userId = user?.id || '00000000-0000-0000-0000-000000000001'; let composedDueAt = undefined; if (prazoDate) { const t = prazoTime || '23:59'; composedDueAt = new Date(`${prazoDate}T${t}:00`).toISOString(); } const payload = { patient_id: pacienteSelecionado?.id, order_number: '', exam: campos.exame || '', diagnosis: campos.diagnostico || '', conclusion: campos.conclusao || '', cid_code: campos.cid || '', content_html: content, content_json: {}, requested_by: solicitanteId || userId, due_at: composedDueAt ?? new Date().toISOString(), hide_date: !campos.mostrarData, hide_signature: !campos.mostrarAssinatura, }; if (createNewReport) { await createNewReport(payload as any); toast({ title: 'Laudo criado com sucesso!', description: 'O laudo foi liberado e salvo.', variant: 'default', }); // Redirecionar para profissional router.push('/profissional'); } } catch (err) { toast({ title: 'Erro ao criar laudo', description: (err && typeof err === 'object' && 'message' in err) ? (err as any).message : String(err) || 'Tente novamente.', variant: 'destructive', }); } }; return (
{/* Header */}

Novo Laudo Médico

Crie um novo laudo selecionando um paciente

{/* Main Content */}
{/* Seleção de Paciente */}
{!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)}` : ''}
)} {/* Solicitante e Prazo */} {pacienteSelecionado && (
setPrazoDate(e.target.value)} className="text-xs sm:text-sm h-8 sm:h-10 flex-1" /> setPrazoTime(e.target.value)} className="text-xs sm:text-sm h-8 sm:h-10 flex-1" />

Defina a data e hora (opcional).

)}
{/* Tabs */}
{/* Content */}
{/* Left Panel */}
{/* Editor Tab */} {activeTab === 'editor' && (
{/* Toolbar */}
{/* Font Family */} {/* Font Size */}
{frasesProntas.map((frase, index) => ( insertFraseProta(frase)} className="text-xs cursor-pointer" > {frase} ))}
{/* Editor contenteditable */}
setContent(e.currentTarget.innerHTML)} onPaste={(e) => { e.preventDefault(); const text = e.clipboardData.getData('text/plain'); document.execCommand('insertText', false, text); }} className="w-full h-full overflow-auto p-3 text-sm border border-border rounded bg-background text-foreground outline-none empty:before:content-['Digite_aqui...'] empty:before:text-muted-foreground" style={{ caretColor: 'currentColor' }} suppressContentEditableWarning />
)} {/* Imagens Tab */} {activeTab === 'imagens' && (
{imagens.map((img) => (
{img.type.startsWith('image/') ? ( {img.name} ) : (
)}

{img.name}

))}
)} {/* Campos Tab */} {activeTab === 'campos' && (
setCampos((prev) => ({ ...prev, cid: e.target.value }))} placeholder="Ex: M25.5, I10, etc." className="text-xs sm:text-sm mt-1 h-8 sm:h-10" />
setCampos((prev) => ({ ...prev, exame: e.target.value }))} placeholder="Exame realizado" className="text-xs sm:text-sm mt-1 h-8 sm:h-10" />