diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index 5aee36d..673fbdd 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -12,7 +12,8 @@ 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 { FileText, Upload, Settings, Eye, ArrowLeft } from 'lucide-react'; +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 ?? ''; @@ -73,10 +74,58 @@ export default function LaudosEditorPage() { '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() { @@ -93,8 +142,42 @@ export default function LaudosEditorPage() { } } 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; @@ -143,73 +226,56 @@ export default function LaudosEditorPage() { // Desfazer const handleUndo = () => { if (historyIndex > 0) { - setContent(history[historyIndex - 1]); - setHistoryIndex(historyIndex - 1); + 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 de texto - 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); + // 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) => { @@ -229,6 +295,48 @@ export default function LaudosEditorPage() { }); }; + // 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') @@ -449,8 +557,7 @@ export default function LaudosEditorPage() { }`} > - {showPreview ? 'Ocultar' : 'Pré-visualização'} - {showPreview ? 'Ocultar' : 'Preview'} + {showPreview ? 'Ocultar' : 'Pré-visualização'} @@ -462,24 +569,14 @@ export default function LaudosEditorPage() { {activeTab === 'editor' && (
{/* Toolbar */} -
-
- - formatText('font-size', e.target.value)} - className="w-10 sm:w-12 border rounded px-1 py-0.5 text-xs" - title="Tamanho da fonte" - /> - +
+
+ {/* Font Family */} + - - formatText('font-color', e.target.value)} - className="w-7 sm:w-8 h-7 sm:h-8 border rounded hidden sm:block" - title="Cor da fonte" - /> - - - - - - -
- {templates.map((template, idx) => ( - + + + +
+ + + - ))} -
+ + + {frasesProntas.map((frase, index) => ( + insertFraseProta(frase)} + className="text-xs cursor-pointer" + > + {frase} + + ))} + +
- {/* Editor Textarea */} + {/* Editor contenteditable */}
-