develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
6 changed files with 123 additions and 48 deletions
Showing only changes of commit f6fad55ff3 - Show all commits

View File

@ -309,7 +309,7 @@ export default function AgendamentoPage() {
</div> </div>
{/* legenda dinâmica: mostra as cores presentes nos agendamentos do dia atual */} {/* legenda dinâmica: mostra as cores presentes nos agendamentos do dia atual */}
<div className="sm:absolute sm:top-2 sm:right-2 mt-2 sm:mt-0 z-40"> <div className="sm:absolute sm:top-2 sm:right-2 mt-2 sm:mt-0 z-10">
<DynamicLegend events={managerEvents} /> <DynamicLegend events={managerEvents} />
</div> </div>
</div> </div>

View File

@ -178,6 +178,33 @@ export default function LaudosEditorPage() {
} }
}, []); }, []);
// Auto-salvar no localStorage sempre que houver mudanças (com debounce)
useEffect(() => {
const timeoutId = setTimeout(() => {
// Capturar conteúdo atual do editor antes de salvar
const currentContent = editorRef.current?.innerHTML || content;
const draft = {
pacienteSelecionado,
content: currentContent,
campos,
solicitanteId,
solicitanteNome,
prazoDate,
prazoTime,
imagens,
lastSaved: new Date().toISOString(),
};
// Só salvar se houver conteúdo ou dados preenchidos
if (currentContent || pacienteSelecionado || campos.exame || campos.diagnostico || imagens.length > 0) {
localStorage.setItem('laudoDraft', JSON.stringify(draft));
}
}, 1000); // Aguarda 1 segundo após última mudança
return () => clearTimeout(timeoutId);
}, [pacienteSelecionado, content, campos, solicitanteId, solicitanteNome, prazoDate, prazoTime, imagens]);
// Tentar obter o registro de médico correspondente ao usuário autenticado // Tentar obter o registro de médico correspondente ao usuário autenticado
useEffect(() => { useEffect(() => {
let mounted = true; let mounted = true;
@ -247,6 +274,23 @@ export default function LaudosEditorPage() {
} }
}, [content]); }, [content]);
// Função para trocar de aba salvando conteúdo antes
const handleTabChange = (newTab: string) => {
// Salvar conteúdo do editor antes de trocar
if (editorRef.current) {
const editorContent = editorRef.current.innerHTML;
setContent(editorContent);
}
setActiveTab(newTab);
};
// Restaurar conteúdo do editor quando voltar para a aba editor
useEffect(() => {
if (activeTab === 'editor' && editorRef.current && content) {
editorRef.current.innerHTML = content;
}
}, [activeTab]);
// Desfazer // Desfazer
const handleUndo = () => { const handleUndo = () => {
if (historyIndex > 0) { if (historyIndex > 0) {
@ -321,11 +365,15 @@ export default function LaudosEditorPage() {
// Salvar rascunho no localStorage // Salvar rascunho no localStorage
const saveDraft = () => { const saveDraft = () => {
// Capturar conteúdo atual do editor antes de salvar
const currentContent = editorRef.current?.innerHTML || content;
const draft = { const draft = {
pacienteSelecionado, pacienteSelecionado,
content, content: currentContent,
campos, campos,
solicitanteId, solicitanteId,
solicitanteNome,
prazoDate, prazoDate,
prazoTime, prazoTime,
imagens, imagens,
@ -389,6 +437,9 @@ export default function LaudosEditorPage() {
return; return;
} }
// Capturar conteúdo atual do editor antes de salvar
const currentContent = editorRef.current?.innerHTML || content;
const userId = user?.id || '00000000-0000-0000-0000-000000000001'; const userId = user?.id || '00000000-0000-0000-0000-000000000001';
let composedDueAt = undefined; let composedDueAt = undefined;
@ -404,7 +455,7 @@ export default function LaudosEditorPage() {
diagnosis: campos.diagnostico || '', diagnosis: campos.diagnostico || '',
conclusion: campos.conclusao || '', conclusion: campos.conclusao || '',
cid_code: campos.cid || '', cid_code: campos.cid || '',
content_html: content, content_html: currentContent,
content_json: {}, content_json: {},
requested_by: solicitanteId || userId, requested_by: solicitanteId || userId,
due_at: composedDueAt ?? new Date().toISOString(), due_at: composedDueAt ?? new Date().toISOString(),
@ -414,6 +465,10 @@ export default function LaudosEditorPage() {
if (createNewReport) { if (createNewReport) {
await createNewReport(payload as any); await createNewReport(payload as any);
// Limpar rascunho salvo após sucesso
localStorage.removeItem('laudoDraft');
toast({ toast({
title: 'Laudo criado com sucesso!', title: 'Laudo criado com sucesso!',
description: 'O laudo foi liberado e salvo.', description: 'O laudo foi liberado e salvo.',
@ -536,7 +591,7 @@ export default function LaudosEditorPage() {
{/* Tabs */} {/* Tabs */}
<div className="flex border-b border-border bg-card overflow-x-auto flex-shrink-0"> <div className="flex border-b border-border bg-card overflow-x-auto flex-shrink-0">
<button <button
onClick={() => setActiveTab('editor')} onClick={() => handleTabChange('editor')}
className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${ className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === 'editor' activeTab === 'editor'
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'
@ -547,7 +602,7 @@ export default function LaudosEditorPage() {
Editor Editor
</button> </button>
<button <button
onClick={() => setActiveTab('imagens')} onClick={() => handleTabChange('imagens')}
className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${ className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === 'imagens' activeTab === 'imagens'
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'
@ -558,7 +613,7 @@ export default function LaudosEditorPage() {
Imagens ({imagens.length}) Imagens ({imagens.length})
</button> </button>
<button <button
onClick={() => setActiveTab('campos')} onClick={() => handleTabChange('campos')}
className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${ className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === 'campos' activeTab === 'campos'
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'

View File

@ -69,32 +69,47 @@ export default function EditarLaudoPage() {
// Estado para rastrear alinhamento ativo // Estado para rastrear alinhamento ativo
const [activeAlignment, setActiveAlignment] = useState('left'); const [activeAlignment, setActiveAlignment] = useState('left');
// Salvar conteúdo no localStorage sempre que muda // Salvar conteúdo no localStorage sempre que muda (com debounce)
useEffect(() => { useEffect(() => {
if (content && laudoId) { const timeoutId = setTimeout(() => {
localStorage.setItem(`laudo-draft-${laudoId}`, content); if (laudoId) {
} // Capturar conteúdo atual do editor antes de salvar
}, [content, laudoId]); const currentContent = editorRef.current?.innerHTML || content;
const draft = {
content: currentContent,
campos,
lastSaved: new Date().toISOString(),
};
localStorage.setItem(`laudo-draft-${laudoId}`, JSON.stringify(draft));
}
}, 1000); // Aguarda 1 segundo após última mudança
return () => clearTimeout(timeoutId);
}, [content, campos, laudoId]);
// Sincronizar conteúdo com o editor // Sincronizar conteúdo com o editor
useEffect(() => { useEffect(() => {
if (editorRef.current && content) { if (editorRef.current && content) {
if (editorRef.current.innerHTML !== content) { editorRef.current.innerHTML = content;
editorRef.current.innerHTML = content;
}
} }
}, [content]); }, [content]);
// Função para trocar de aba salvando conteúdo antes
const handleTabChange = (newTab: string) => {
// Salvar conteúdo do editor antes de trocar
if (editorRef.current) {
const editorContent = editorRef.current.innerHTML;
setContent(editorContent);
}
setActiveTab(newTab);
};
// Restaurar conteúdo quando volta para a aba editor // Restaurar conteúdo quando volta para a aba editor
useEffect(() => { useEffect(() => {
if (activeTab === 'editor' && editorRef.current && content) { if (activeTab === 'editor' && editorRef.current && content) {
editorRef.current.focus(); editorRef.current.innerHTML = content;
const range = document.createRange();
const sel = window.getSelection();
range.setStart(editorRef.current, editorRef.current.childNodes.length);
range.collapse(true);
sel?.removeAllRanges();
sel?.addRange(range);
} }
}, [activeTab]); }, [activeTab]);
@ -166,20 +181,36 @@ export default function EditarLaudoPage() {
const contentHtml = r.content_html || r.conteudo_html || ''; const contentHtml = r.content_html || r.conteudo_html || '';
// Verificar se existe rascunho salvo no localStorage // Verificar se existe rascunho salvo no localStorage
const draftContent = typeof window !== 'undefined' ? localStorage.getItem(`laudo-draft-${laudoId}`) : null; let finalContent = contentHtml;
const finalContent = draftContent || contentHtml; let finalCampos = {
cid: r.cid_code || r.cid || '',
diagnostico: r.diagnosis || r.diagnostico || '',
conclusao: r.conclusion || r.conclusao || '',
exame: r.exam || r.exame || '',
especialidade: r.especialidade || '',
mostrarData: !r.hide_date,
mostrarAssinatura: !r.hide_signature,
};
if (typeof window !== 'undefined') {
const draftData = localStorage.getItem(`laudo-draft-${laudoId}`);
if (draftData) {
try {
const draft = JSON.parse(draftData);
if (draft.content) finalContent = draft.content;
if (draft.campos) finalCampos = { ...finalCampos, ...draft.campos };
} catch (err) {
// Se falhar parse, tentar como string simples (formato antigo)
finalContent = draftData;
}
}
}
setCampos(finalCampos);
setContent(finalContent); setContent(finalContent);
if (editorRef.current) { if (editorRef.current) {
editorRef.current.innerHTML = finalContent; editorRef.current.innerHTML = finalContent;
// Colocar cursor no final do texto
editorRef.current.focus();
const range = document.createRange();
const sel = window.getSelection();
range.setStart(editorRef.current, editorRef.current.childNodes.length);
range.collapse(true);
sel?.removeAllRanges();
sel?.addRange(range);
} }
} catch (err) { } catch (err) {
console.warn('Erro ao carregar laudo:', err); console.warn('Erro ao carregar laudo:', err);
@ -357,7 +388,7 @@ export default function EditarLaudoPage() {
{/* Tabs */} {/* Tabs */}
<div className="flex border-b border-border bg-card overflow-x-auto flex-shrink-0"> <div className="flex border-b border-border bg-card overflow-x-auto flex-shrink-0">
<button <button
onClick={() => setActiveTab('editor')} onClick={() => handleTabChange('editor')}
className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${ className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === 'editor' activeTab === 'editor'
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'
@ -368,7 +399,7 @@ export default function EditarLaudoPage() {
Editor Editor
</button> </button>
<button <button
onClick={() => setActiveTab('campos')} onClick={() => handleTabChange('campos')}
className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${ className={`px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === 'campos' activeTab === 'campos'
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'

View File

@ -876,17 +876,6 @@ export default function ResultadosClient() {
</Select> </Select>
</div> </div>
{/* Mais filtros / Voltar */}
<div className="sm:col-span-4">
<Button
variant="outline"
className="h-10 w-full rounded-full border border-primary/30 bg-primary/5 text-primary hover:bg-primary hover:text-primary-foreground"
>
<Filter className="mr-2 h-4 w-4" />
Mais filtros
</Button>
</div>
{/* Voltar */} {/* Voltar */}
<div className="sm:col-span-12"> <div className="sm:col-span-12">
<Button <Button

View File

@ -1396,13 +1396,13 @@ const ProfissionalPage = () => {
{/* Filtros */} {/* Filtros */}
<div className="p-4 border-b border-border"> <div className="p-4 border-b border-border">
<div className="flex flex-wrap items-center gap-4"> <div className="flex flex-wrap items-start gap-4">
<div className="relative flex-1 min-w-[200px]"> <div className="relative flex-1 min-w-[200px]">
{/* Search input integrado com busca por ID */} {/* Search input integrado com busca por ID */}
<SearchBox /> <SearchBox />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 mt-0">
<div className="flex items-center gap-1 text-sm"> <div className="flex items-center gap-1 text-sm">
<CalendarIcon className="w-4 h-4" /> <CalendarIcon className="w-4 h-4" />
<Input type="date" value={startDate ?? ''} onChange={(e) => { setStartDate(e.target.value); setSelectedRange('custom'); }} className="p-1 text-sm h-10" /> <Input type="date" value={startDate ?? ''} onChange={(e) => { setStartDate(e.target.value); setSelectedRange('custom'); }} className="p-1 text-sm h-10" />
@ -1411,7 +1411,7 @@ const ProfissionalPage = () => {
</div> </div>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center mt-0">
{/* date range buttons: Semana / Mês */} {/* date range buttons: Semana / Mês */}
<DateRangeButtons /> <DateRangeButtons />
</div> </div>

View File

@ -79,7 +79,7 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub
</Avatar> </Avatar>
</Button> </Button>
{dropdownOpen && ( {dropdownOpen && (
<div className="absolute right-0 mt-2 w-64 sm:w-80 bg-popover border border-border rounded-md shadow-lg z-50 text-popover-foreground animate-in fade-in slide-in-from-top-2"> <div className="absolute right-0 mt-2 w-64 sm:w-80 bg-popover border border-border rounded-md shadow-lg z-[100] text-popover-foreground animate-in fade-in slide-in-from-top-2">
<div className="p-3 sm:p-4 border-b border-border"> <div className="p-3 sm:p-4 border-b border-border">
<div className="flex flex-col space-y-1"> <div className="flex flex-col space-y-1">
<p className="text-xs sm:text-sm font-semibold leading-none"> <p className="text-xs sm:text-sm font-semibold leading-none">