forked from RiseUP/riseup_squad_03
modo-claro
modified: src/hooks/useAgenda.js modified: src/index.css modified: src/main.jsx modified: src/mappers/reportMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/SettingsPage.jsx modified: src/repositories/analyticsRepository.js modified: src/repositories/authRepository.js modified: src/repositories/patientRepository.js modified: src/repositories/reportRepository.js modified: src/repositories/repositoryUtils.js new file: src/utils/theme.js new file: vercel.json
This commit is contained in:
@@ -37,6 +37,7 @@ const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
|
||||
|
||||
const emptyEditor = {
|
||||
id: null,
|
||||
orderNumber: '',
|
||||
patientId: '',
|
||||
status: 'draft',
|
||||
exam: '',
|
||||
@@ -242,6 +243,7 @@ export function ReportsPage({ role }) {
|
||||
function openEdit(report) {
|
||||
setEditor({
|
||||
id: report.id,
|
||||
orderNumber: report.orderNumber,
|
||||
patientId: String(report.patientId || ''),
|
||||
status: report.status,
|
||||
exam: report.exam,
|
||||
@@ -264,6 +266,7 @@ export function ReportsPage({ role }) {
|
||||
setSaving(true)
|
||||
|
||||
const payload = {
|
||||
orderNumber: editor.id ? editor.orderNumber : `REL-${Date.now()}`,
|
||||
patientId: editor.patientId,
|
||||
status: editor.status,
|
||||
exam: editor.exam,
|
||||
@@ -276,6 +279,8 @@ export function ReportsPage({ role }) {
|
||||
hideDate: editor.hideDate,
|
||||
hideSignature: editor.hideSignature,
|
||||
dueAt: editor.dueAt ? new Date(editor.dueAt).toISOString() : '',
|
||||
createdBy: editor.id ? undefined : viewerProfile?.id || currentProfessional?.userId || currentProfessional?.id || undefined,
|
||||
updatedBy: viewerProfile?.id || currentProfessional?.userId || currentProfessional?.id || undefined,
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -519,7 +524,11 @@ function ReportRow({ onEdit, onView, report }) {
|
||||
}
|
||||
|
||||
function ReportEditorModal({ editor, onChange, onClose, onSave, patientOptions, professionalOptions, saving }) {
|
||||
const [requesterSearch, setRequesterSearch] = useState(editor.requestedBy || '')
|
||||
const isValid = Boolean(editor.patientId)
|
||||
const filteredRequesterOptions = professionalOptions
|
||||
.filter((professional) => normalizeSearch(professional.name).includes(normalizeSearch(requesterSearch)))
|
||||
.slice(0, 6)
|
||||
|
||||
function updateField(field, value) {
|
||||
onChange((current) => ({ ...current, [field]: value }))
|
||||
@@ -573,19 +582,39 @@ function ReportEditorModal({ editor, onChange, onClose, onSave, patientOptions,
|
||||
</DarkField>
|
||||
|
||||
<DarkField label="Solicitante">
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
className={inputClass}
|
||||
list="report-requested-by-suggestions"
|
||||
onChange={(event) => updateField('requestedBy', event.target.value)}
|
||||
placeholder="Nome do solicitante"
|
||||
value={editor.requestedBy}
|
||||
onChange={(event) => {
|
||||
setRequesterSearch(event.target.value)
|
||||
updateField('requestedBy', event.target.value)
|
||||
}}
|
||||
placeholder="Pesquisar médico"
|
||||
type="search"
|
||||
value={requesterSearch}
|
||||
/>
|
||||
<datalist id="report-requested-by-suggestions">
|
||||
{professionalOptions.map((professional) => (
|
||||
<option key={professional.id} value={professional.name} />
|
||||
))}
|
||||
</datalist>
|
||||
<div className="max-h-36 overflow-y-auto rounded-md border border-[#404040] bg-[#1a1a1a] p-1">
|
||||
{filteredRequesterOptions.length ? (
|
||||
filteredRequesterOptions.map((professional) => (
|
||||
<button
|
||||
className={`flex w-full items-center justify-between gap-3 rounded-sm px-3 py-2 text-left text-sm font-medium transition hover:bg-[#303030] ${
|
||||
editor.requestedBy === professional.name ? 'text-[#51a2ff]' : 'text-[#e5e5e5]'
|
||||
}`}
|
||||
key={professional.id || professional.createdByValue || professional.name}
|
||||
onClick={() => {
|
||||
setRequesterSearch(professional.name)
|
||||
updateField('requestedBy', professional.name)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span className="truncate">{professional.name}</span>
|
||||
{editor.requestedBy === professional.name ? <ReportIcon className="size-3.5" name="check" /> : null}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<p className="px-3 py-2 text-sm text-[#a3a3a3]">Nenhum médico encontrado.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DarkField>
|
||||
</div>
|
||||
@@ -632,7 +661,6 @@ function ReportEditorModal({ editor, onChange, onClose, onSave, patientOptions,
|
||||
<textarea
|
||||
className={`${textareaClass} min-h-72`}
|
||||
onChange={(event) => updateField('contentHtml', event.target.value)}
|
||||
placeholder="Complemento em texto simples"
|
||||
value={editor.contentHtml}
|
||||
/>
|
||||
</DarkField>
|
||||
@@ -698,9 +726,19 @@ function ReportViewModal({ onClose, report }) {
|
||||
<h2 className="text-lg font-bold text-[#e5e5e5]">Relatório</h2>
|
||||
<p className="mt-1 text-xs text-[#a3a3a3]">{report.orderNumber || 'Sem número'} </p>
|
||||
</div>
|
||||
<button className="rounded-lg p-1.5 transition hover:bg-[#2a2a2a]" onClick={onClose} type="button">
|
||||
<ReportIcon className="size-4 text-[#a3a3a3]" name="x" />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg border border-[#404040] bg-[#1a1a1a] px-3 text-xs font-semibold text-[#e5e5e5] transition hover:bg-[#2a2a2a]"
|
||||
onClick={() => printReportAsPdf(report, currentStatus)}
|
||||
type="button"
|
||||
>
|
||||
<ReportIcon className="size-4" name="print" />
|
||||
Imprimir PDF
|
||||
</button>
|
||||
<button className="rounded-lg p-1.5 transition hover:bg-[#2a2a2a]" onClick={onClose} type="button">
|
||||
<ReportIcon className="size-4 text-[#a3a3a3]" name="x" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
@@ -846,6 +884,94 @@ function uniqueValues(values) {
|
||||
return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))]
|
||||
}
|
||||
|
||||
function normalizeSearch(value) {
|
||||
return String(value || '')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function printReportAsPdf(report, status) {
|
||||
const printWindow = window.open('', '_blank', 'noopener,noreferrer,width=900,height=1100')
|
||||
|
||||
if (!printWindow) {
|
||||
window.print()
|
||||
return
|
||||
}
|
||||
|
||||
printWindow.document.write(`
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Relatório ${escapeHtml(report.orderNumber || '')}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { color: #171717; font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { font-size: 24px; margin: 0 0 4px; }
|
||||
.muted { color: #525252; font-size: 12px; }
|
||||
.grid { display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 24px; }
|
||||
.box { border: 1px solid #d4d4d4; border-radius: 8px; padding: 12px; }
|
||||
.label { color: #525252; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
||||
.value { font-size: 13px; margin-top: 6px; white-space: pre-wrap; }
|
||||
.section { margin-top: 20px; }
|
||||
@media print { body { margin: 24mm; } button { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Relatório</h1>
|
||||
<p class="muted">${escapeHtml(report.orderNumber || 'Sem número')}</p>
|
||||
<div class="grid">
|
||||
${printDetail('Paciente', report.patientName)}
|
||||
${printDetail('Solicitante', report.requestedBy || '-')}
|
||||
${printDetail('Criado em', formatDate(report.createdAt))}
|
||||
${printDetail('Criado por', report.createdByName)}
|
||||
${printDetail('Status', status.label)}
|
||||
${printDetail('Prazo', formatDateTime(report.dueAt))}
|
||||
</div>
|
||||
<div class="grid">
|
||||
${printDetail('Exame', report.exam || '-')}
|
||||
${printDetail('CID-10', report.cidCode || '-')}
|
||||
</div>
|
||||
<div class="section box">
|
||||
<p class="label">Diagnóstico</p>
|
||||
<p class="value">${escapeHtml(report.diagnosis || '-')}</p>
|
||||
</div>
|
||||
<div class="section box">
|
||||
<p class="label">Conclusão</p>
|
||||
<p class="value">${escapeHtml(report.conclusion || '-')}</p>
|
||||
</div>
|
||||
<div class="section box">
|
||||
<p class="label">Complemento</p>
|
||||
<p class="value">${escapeHtml(report.contentHtml || 'Nenhum complemento informado.')}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
printWindow.document.close()
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
}
|
||||
|
||||
function printDetail(label, value) {
|
||||
return `
|
||||
<div class="box">
|
||||
<p class="label">${escapeHtml(label)}</p>
|
||||
<p class="value">${escapeHtml(value || '-')}</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
function ReportIcon({ className = 'size-4', name }) {
|
||||
const common = {
|
||||
className,
|
||||
@@ -924,6 +1050,24 @@ function ReportIcon({ className = 'size-4', name }) {
|
||||
)
|
||||
}
|
||||
|
||||
if (name === 'print') {
|
||||
return (
|
||||
<svg {...common}>
|
||||
<path d="M7 8V4h10v4" />
|
||||
<path d="M7 17H5a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2" />
|
||||
<path d="M7 14h10v7H7zM17 12h.01" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
if (name === 'check') {
|
||||
return (
|
||||
<svg {...common}>
|
||||
<path d="m5 12 4 4L19 6" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<svg {...common}>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
|
||||
Reference in New Issue
Block a user