develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
2 changed files with 133 additions and 47 deletions
Showing only changes of commit 0a7d3f3ae4 - Show all commits

View File

@ -528,6 +528,11 @@ export default function PacientePage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<User className="h-6 w-6 text-primary" aria-hidden /> <User className="h-6 w-6 text-primary" aria-hidden />
<span className="font-bold">Portal do Paciente</span> <span className="font-bold">Portal do Paciente</span>
<Button asChild variant="outline" className="ml-4">
<Link href="/">
<Home className="h-4 w-4 mr-1" /> Início
</Link>
</Button>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<SimpleThemeToggle /> <SimpleThemeToggle />

View File

@ -2033,14 +2033,40 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
} }
}, [laudo, isNewLaudo]); }, [laudo, isNewLaudo]);
const formatText = (type: string) => { // Histórico para desfazer/refazer
const [history, setHistory] = useState<string[]>([]);
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; const textarea = document.querySelector('textarea') as HTMLTextAreaElement;
if (!textarea) return; if (!textarea) return;
const start = textarea.selectionStart; const start = textarea.selectionStart;
const end = textarea.selectionEnd; const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end); const selectedText = textarea.value.substring(start, end);
let formattedText = ""; let formattedText = "";
switch(type) { switch(type) {
case "bold": case "bold":
@ -2050,13 +2076,44 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
formattedText = selectedText ? `*${selectedText}*` : "*texto em itálico*"; formattedText = selectedText ? `*${selectedText}*` : "*texto em itálico*";
break; break;
case "underline": case "underline":
formattedText = selectedText ? `<u>${selectedText}</u>` : "<u>texto sublinhado</u>"; formattedText = selectedText ? `__${selectedText}__` : "__texto sublinhado__";
break; break;
case "list": case "list-ul":
formattedText = selectedText ? `${selectedText}` : "• item da lista"; formattedText = selectedText ? selectedText.split('\n').map(l => `${l}`).join('\n') : "• item da lista";
break; 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); const newText = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end);
setContent(newText); setContent(newText);
}; };
@ -2085,7 +2142,14 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
return content return content
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>') .replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>') .replace(/__(.*?)__/g, '<u>$1</u>')
.replace(/\[left\](.*?)\[\/left\]/gs, '<div style="text-align:left">$1</div>')
.replace(/\[center\](.*?)\[\/center\]/gs, '<div style="text-align:center">$1</div>')
.replace(/\[right\](.*?)\[\/right\]/gs, '<div style="text-align:right">$1</div>')
.replace(/\[justify\](.*?)\[\/justify\]/gs, '<div style="text-align:justify">$1</div>')
.replace(/\[size=(\d+)\](.*?)\[\/size\]/gs, '<span style="font-size:$1px">$2</span>')
.replace(/\[font=([^\]]+)\](.*?)\[\/font\]/gs, '<span style="font-family:$1">$2</span>')
.replace(/\[color=([^\]]+)\](.*?)\[\/color\]/gs, '<span style="color:$1">$2</span>')
.replace(/{{sexo_paciente}}/g, pacienteSelecionado?.sexo || laudo?.paciente?.sexo || '[SEXO]') .replace(/{{sexo_paciente}}/g, pacienteSelecionado?.sexo || laudo?.paciente?.sexo || '[SEXO]')
.replace(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]') .replace(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]')
.replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]') .replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]')
@ -2384,44 +2448,60 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
{/* Toolbar */} {/* Toolbar */}
<div className="p-3 border-b border-border"> <div className="p-3 border-b border-border">
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2 items-center">
<Button {/* Tamanho da fonte */}
variant="outline" <label className="text-xs mr-1">Tamanho</label>
size="sm" <input
onClick={() => formatText("bold")} type="number"
title="Negrito" min={8}
className="hover:bg-blue-50 dark:hover:bg-accent" max={32}
defaultValue={14}
onBlur={e => 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 */}
<label className="text-xs mr-1">Fonte</label>
<select
defaultValue={'Arial'}
onBlur={e => formatText('font-family', e.target.value)}
className="border rounded px-1 py-0.5 text-xs mr-2"
title="Família da fonte"
> >
<strong>B</strong> <option value="Arial">Arial</option>
</Button> <option value="Helvetica">Helvetica</option>
<Button <option value="Times New Roman">Times New Roman</option>
variant="outline" <option value="Courier New">Courier New</option>
size="sm" <option value="Verdana">Verdana</option>
onClick={() => formatText("italic")} <option value="Georgia">Georgia</option>
title="Itálico" </select>
className="hover:bg-blue-50 dark:hover:bg-accent" {/* Cor da fonte */}
> <label className="text-xs mr-1">Cor</label>
<em>I</em> <input
</Button> type="color"
<Button defaultValue="#222222"
variant="outline" onBlur={e => formatText('font-color', e.target.value)}
size="sm" className="w-6 h-6 border rounded mr-2"
onClick={() => formatText("underline")} title="Cor da fonte"
title="Sublinhado" />
className="hover:bg-blue-50 dark:hover:bg-accent" {/* Alinhamento */}
> <Button variant="outline" size="sm" onClick={() => formatText('align-left')} title="Alinhar à esquerda" className="px-1"><svg width="16" height="16" fill="none"><rect x="2" y="4" width="12" height="2" rx="1" fill="currentColor"/><rect x="2" y="7" width="8" height="2" rx="1" fill="currentColor"/><rect x="2" y="10" width="10" height="2" rx="1" fill="currentColor"/></svg></Button>
<u>U</u> <Button variant="outline" size="sm" onClick={() => formatText('align-center')} title="Centralizar" className="px-1"><svg width="16" height="16" fill="none"><rect x="4" y="4" width="8" height="2" rx="1" fill="currentColor"/><rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor"/><rect x="3" y="10" width="10" height="2" rx="1" fill="currentColor"/></svg></Button>
</Button> <Button variant="outline" size="sm" onClick={() => formatText('align-right')} title="Alinhar à direita" className="px-1"><svg width="16" height="16" fill="none"><rect x="6" y="4" width="8" height="2" rx="1" fill="currentColor"/><rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor"/><rect x="4" y="10" width="10" height="2" rx="1" fill="currentColor"/></svg></Button>
<Button <Button variant="outline" size="sm" onClick={() => formatText('align-justify')} title="Justificar" className="px-1"><svg width="16" height="16" fill="none"><rect x="2" y="4" width="12" height="2" rx="1" fill="currentColor"/><rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor"/><rect x="2" y="10" width="12" height="2" rx="1" fill="currentColor"/></svg></Button>
variant="outline" {/* Listas */}
size="sm" <Button variant="outline" size="sm" onClick={() => formatText('list-ol')} title="Lista numerada" className="px-1">1.</Button>
onClick={() => formatText("list")} <Button variant="outline" size="sm" onClick={() => formatText('list-ul')} title="Lista com marcadores" className="px-1"></Button>
title="Lista" {/* Recuo */}
className="hover:bg-blue-50 dark:hover:bg-accent" <Button variant="outline" size="sm" onClick={() => formatText('indent')} title="Aumentar recuo" className="px-1"></Button>
> <Button variant="outline" size="sm" onClick={() => formatText('outdent')} title="Diminuir recuo" className="px-1"></Button>
{/* Desfazer/Refazer */}
</Button> <Button variant="outline" size="sm" onClick={handleUndo} title="Desfazer" className="px-1"></Button>
<Button variant="outline" size="sm" onClick={handleRedo} title="Refazer" className="px-1"></Button>
{/* Negrito, itálico, sublinhado */}
<Button variant="outline" size="sm" onClick={() => formatText("bold") } title="Negrito" className="hover:bg-blue-50 dark:hover:bg-accent"><strong>B</strong></Button>
<Button variant="outline" size="sm" onClick={() => formatText("italic") } title="Itálico" className="hover:bg-blue-50 dark:hover:bg-accent"><em>I</em></Button>
<Button variant="outline" size="sm" onClick={() => formatText("underline") } title="Sublinhado" className="hover:bg-blue-50 dark:hover:bg-accent"><u>U</u></Button>
</div> </div>
{/* Templates */} {/* Templates */}
@ -2444,12 +2524,13 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
</div> </div>
{/* Editor */} {/* Editor */}
<div className="flex-1 p-4"> <div className="flex-1 p-4 overflow-auto max-h-[500px]">
<Textarea <Textarea
value={content} value={content}
onChange={(e) => setContent(e.target.value)} onChange={(e) => setContent(e.target.value)}
placeholder="Digite o conteúdo do laudo aqui. Use ** para negrito, * para itálico, <u></u> para sublinhado." placeholder="Digite o conteúdo do laudo aqui. Use ** para negrito, * para itálico, <u></u> para sublinhado."
className="h-full min-h-[400px] resize-none" className="h-full min-h-[400px] resize-none scrollbar-thin scrollbar-thumb-blue-400 scrollbar-track-blue-100"
style={{ maxHeight: 400, overflow: 'auto' }}
/> />
</div> </div>
</div> </div>