Files
riseup_squad_03/src/pages/ReportsPage.jsx
2026-04-28 10:34:05 -03:00

924 lines
34 KiB
JavaScript

import { useEffect, useMemo, useState } from 'react'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { reportRepository } from '../repositories/reportRepository.js'
import { patientRepository } from '../repositories/patientRepository.js'
const statusConfig = {
rascunho: {
label: 'Rascunho',
pill: 'bg-amber-500/20 text-amber-400',
stat: 'text-amber-400',
},
finalizado: {
label: 'Finalizado',
pill: 'bg-emerald-500/20 text-emerald-400',
stat: 'text-emerald-400',
},
enviado: {
label: 'Enviado',
pill: 'bg-blue-500/20 text-blue-400',
stat: 'text-blue-400',
},
}
const adminUsers = reportRepository.getAdminUsers()
const currentUser = reportRepository.getCurrentUser()
const doctors = reportRepository.getDoctors()
const reportTypes = reportRepository.getReportTypes()
const templates = reportRepository.getTemplates()
const emptyEditor = {
id: null,
type: reportTypes[0],
patient: '',
doctor: doctors[0],
content: '',
showDate: true,
signDigital: true,
}
const inputClass =
'h-10 w-full rounded-lg border border-[#404040] bg-[#1a1a1a] px-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]'
const labelClass = 'mb-1.5 block text-xs font-medium text-[#e5e5e5]'
const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
export function ReportsPage() {
const [reports, setReports] = useState([])
const [patients, setPatients] = useState([])
useEffect(() => {
reportRepository.getInitialReports().then(setReports).catch(console.error)
patientRepository.getAll().then(setPatients).catch(console.error)
}, [])
const [search, setSearch] = useState('')
const [filterStatus, setFilterStatus] = useState('')
const [openMenuId, setOpenMenuId] = useState(null)
const [editorOpen, setEditorOpen] = useState(false)
const [templatesOpen, setTemplatesOpen] = useState(false)
const [historyReport, setHistoryReport] = useState(null)
const [confirmRelease, setConfirmRelease] = useState(null)
const [deliveryReport, setDeliveryReport] = useState(null)
const [confirmDelete, setConfirmDelete] = useState(null)
const [deleteConfirmText, setDeleteConfirmText] = useState('')
const [preview, setPreview] = useState(false)
const [editor, setEditor] = useState(emptyEditor)
const filteredReports = useMemo(() => {
return reports.filter((report) => {
const matchesSearch = [report.patient, report.type]
.join(' ')
.toLowerCase()
.includes(search.toLowerCase())
const matchesStatus = !filterStatus || report.status === filterStatus
return matchesSearch && matchesStatus
})
}, [filterStatus, reports, search])
const stats = [
{ label: 'Rascunhos', status: 'rascunho' },
{ label: 'Finalizados', status: 'finalizado' },
{ label: 'Enviados', status: 'enviado' },
].map((stat) => ({
...stat,
value: reports.filter((report) => report.status === stat.status).length,
}))
function openNew(template = null) {
setEditor({
...emptyEditor,
type: template?.type || emptyEditor.type,
content: template?.content || '',
})
setPreview(false)
setTemplatesOpen(false)
setEditorOpen(true)
}
function openEdit(report) {
setEditor({
id: report.id,
type: report.type,
patient: report.patient,
doctor: report.doctor,
content: report.content,
showDate: report.showDate,
signDigital: report.signDigital,
})
setOpenMenuId(null)
setPreview(false)
setEditorOpen(true)
}
async function saveReport(status) {
if (!editor.patient.trim() || !editor.content.trim()) return
try {
const selectedPatient = patients.find(p => p.name === editor.patient || p.full_name === editor.patient)
const patientId = selectedPatient?.id || null
if (editor.id) {
const updated = await reportRepository.update(editor.id, {
type: editor.type,
content: editor.content,
patientId: patientId,
status,
})
setReports(curr => curr.map(r => r.id == updated.id ? { ...updated, status } : r))
} else {
const created = await reportRepository.create({
type: editor.type,
content: editor.content,
patientId: patientId,
status,
})
setReports(curr => [{ ...created, status }, ...curr])
}
setEditorOpen(false)
} catch(e) {
alert(e.message || 'Erro ao persistir na Base de Dados')
}
}
function releaseReport(reportId) {
setReports((currentReports) =>
currentReports.map((report) =>
report.id === reportId
? {
...report,
status: 'finalizado',
versions: [
...report.versions,
{ version: report.versions.length + 1, action: 'Liberado', user: currentUser, summary: 'Laudo liberado' },
],
}
: report,
),
)
setConfirmRelease(null)
}
function sendReport(reportId) {
setReports((currentReports) =>
currentReports.map((report) =>
report.id === reportId
? {
...report,
status: 'enviado',
versions: [
...report.versions,
{ version: report.versions.length + 1, action: 'Enviado', user: currentUser, summary: 'Laudo enviado ao paciente' },
],
}
: report,
),
)
setOpenMenuId(null)
}
function deleteReport(reportId) {
setReports((currentReports) => currentReports.filter((report) => report.id !== reportId))
setConfirmDelete(null)
setDeleteConfirmText('')
}
return (
<div className="mx-auto max-w-7xl space-y-6 text-[#e5e5e5]">
<FeatureCallout
description="Listagem e salvar laudo usam API. Templates, protocolo de entrega e parte do fluxo editorial ainda são locais."
status="partial"
title="Laudos com integração parcial"
/>
<div className="flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
<div>
<h1 className="text-2xl font-bold tracking-tight text-[#e5e5e5]">Gestão de Laudos</h1>
</div>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
<button
className={`inline-flex h-10 items-center justify-center gap-2 rounded-lg border px-4 text-sm font-medium text-[#e5e5e5] transition hover:bg-[#2a2a2a] ${featurePanelClass('mock')}`}
onClick={() => setTemplatesOpen(true)}
type="button"
>
<ReportIcon className="size-4 text-[#3b82f6]" name="template" />
Templates
<FeatureBadge status="mock" />
</button>
<button
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg bg-[#3b82f6] px-4 text-sm font-medium text-white transition hover:bg-[#2563eb]"
onClick={() => openNew()}
type="button"
>
<ReportIcon name="plus" />
Novo Laudo
</button>
</div>
</div>
<section className="grid gap-4 md:grid-cols-3">
{stats.map((stat) => (
<div className={`${cardClass} p-4`} key={stat.status}>
<p className="text-xs font-semibold text-[#a3a3a3]">{stat.label}</p>
<p className={`mt-1 text-2xl font-bold ${statusConfig[stat.status].stat}`}>{stat.value}</p>
</div>
))}
</section>
<section className={`${cardClass} ${featurePanelClass('live')} p-6`}>
<div className="mb-6 flex flex-col gap-4 md:flex-row">
<div className="relative flex-1">
<ReportIcon className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#a3a3a3]" name="search" />
<input
className="h-10 w-full rounded-none border border-[#404040] bg-[#1a1a1a] py-2 pl-10 pr-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]"
onChange={(event) => setSearch(event.target.value)}
placeholder="Buscar por paciente ou tipo..."
value={search}
/>
</div>
<select
className="h-10 rounded-none border border-[#404040] bg-[#1a1a1a] px-3 text-sm font-semibold text-[#e5e5e5] outline-none transition focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]"
onChange={(event) => setFilterStatus(event.target.value)}
value={filterStatus}
>
<option value="">Todos os Status</option>
<option value="rascunho">Rascunho</option>
<option value="finalizado">Finalizado</option>
<option value="enviado">Enviado</option>
</select>
</div>
<div className="overflow-x-auto rounded-none border border-[#404040]">
<table className="w-full whitespace-nowrap text-left text-sm">
<thead className="bg-[#171717] text-xs font-semibold uppercase text-[#a3a3a3]">
<tr>
<th className="px-4 py-3">Tipo</th>
<th className="px-4 py-3">Paciente</th>
<th className="px-4 py-3">Médico</th>
<th className="px-4 py-3">Data</th>
<th className="px-4 py-3">Status</th>
<th className="px-4 py-3">Versões</th>
<th className="px-4 py-3 text-right">Ações</th>
</tr>
</thead>
<tbody className="divide-y divide-[#404040] bg-[#262626]">
{filteredReports.length ? (
filteredReports.map((report) => (
<ReportRow
key={report.id}
onDelete={() => {
setConfirmDelete({ report })
setDeleteConfirmText('')
setOpenMenuId(null)
}}
onDelivery={() => {
setDeliveryReport(report)
setOpenMenuId(null)
}}
onEdit={() => openEdit(report)}
onHistory={() => {
setHistoryReport(report)
setOpenMenuId(null)
}}
onPrint={() => {
window.print()
setOpenMenuId(null)
}}
onRelease={() => {
setConfirmRelease(report)
setOpenMenuId(null)
}}
onSend={() => sendReport(report.id)}
open={openMenuId === report.id}
report={report}
setOpenMenuId={setOpenMenuId}
/>
))
) : (
<tr>
<td className="px-4 py-8 text-center text-sm text-[#a3a3a3]" colSpan={7}>
Nenhum laudo encontrado.
</td>
</tr>
)}
</tbody>
</table>
</div>
</section>
{templatesOpen ? <TemplatesModal onClose={() => setTemplatesOpen(false)} onUseTemplate={openNew} /> : null}
{historyReport ? <HistoryModal onClose={() => setHistoryReport(null)} report={historyReport} /> : null}
{deliveryReport ? <DeliveryModal onClose={() => setDeliveryReport(null)} report={deliveryReport} /> : null}
{confirmRelease ? (
<ConfirmReleaseModal
onClose={() => setConfirmRelease(null)}
onConfirm={() => releaseReport(confirmRelease.id)}
report={confirmRelease}
/>
) : null}
{confirmDelete ? (
<DeleteModal
confirmText={deleteConfirmText}
onClose={() => setConfirmDelete(null)}
onConfirm={() => deleteReport(confirmDelete.report.id)}
report={confirmDelete.report}
setConfirmText={setDeleteConfirmText}
/>
) : null}
{editorOpen ? (
<ReportEditorModal
editor={editor}
onClose={() => setEditorOpen(false)}
onSave={saveReport}
preview={preview}
setEditor={setEditor}
setPreview={setPreview}
/>
) : null}
</div>
)
}
function ReportRow({
onDelete,
onDelivery,
onEdit,
onHistory,
onPrint,
onRelease,
onSend,
open,
report,
setOpenMenuId,
}) {
return (
<tr className="transition hover:bg-[#303030]">
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<ReportIcon className="size-4 text-[#3b82f6]" name="file" />
<span className="font-medium text-[#e5e5e5]">{report.type}</span>
</div>
</td>
<td className="px-4 py-3 text-[#e5e5e5]">{report.patient}</td>
<td className="px-4 py-3 text-[#a3a3a3]">{report.doctor}</td>
<td className="px-4 py-3 text-[#a3a3a3]">{report.date}</td>
<td className="px-4 py-3">
<span className={`rounded px-2 py-1 text-[10px] font-bold ${statusConfig[report.status].pill}`}>
{statusConfig[report.status].label}
</span>
</td>
<td className="px-4 py-3">
<button
className="inline-flex items-center gap-1.5 rounded-md bg-[#2a2a2a] px-2 py-1 text-xs font-medium text-[#a3a3a3] transition hover:bg-[#3b82f6]/10 hover:text-[#3b82f6]"
onClick={onHistory}
title="Ver histórico de versões"
type="button"
>
<ReportIcon className="size-3.5" name="history" />
v{report.versions ? report.versions.length : 1}
</button>
</td>
<td className="relative px-4 py-3 text-right">
<button
aria-label={`Ações de ${report.type} de ${report.patient}`}
className="rounded p-1 text-[#a3a3a3] transition hover:bg-[#2a2a2a] hover:text-[#e5e5e5]"
onClick={() => setOpenMenuId(open ? null : report.id)}
type="button"
>
<ReportIcon className="size-5" name="more" />
</button>
{open ? (
<>
<button
aria-label="Fechar menu"
className="fixed inset-0 z-10 cursor-default"
onClick={() => setOpenMenuId(null)}
type="button"
/>
<div className="absolute right-8 top-10 z-20 w-56 rounded-lg border border-[#404040] bg-[#303030] py-1 text-left shadow-lg">
<MenuItem icon="edit" label="Editar" onClick={onEdit} />
<MenuItem icon="history" label="Histórico de Versões" onClick={onHistory} />
<MenuItem icon="printer" label="Imprimir" onClick={onPrint} />
{(report.status === 'finalizado' || report.status === 'enviado') ? (
<MenuItem icon="clipboard" label="Protocolo de Entrega" onClick={onDelivery} />
) : null}
<div className="my-1 border-t border-[#404040]" />
{report.status === 'rascunho' ? <MenuItem icon="check" label="Liberar Laudo" onClick={onRelease} tone="green" /> : null}
{report.status === 'finalizado' ? <MenuItem icon="send" label="Enviar ao Paciente" onClick={onSend} tone="blue" /> : null}
<div className="my-1 border-t border-[#404040]" />
{canDelete(report) ? (
<MenuItem icon="trash" label="Excluir" onClick={onDelete} tone="danger" />
) : (
<div className="flex w-full cursor-not-allowed items-center gap-2 px-4 py-2 text-sm text-[#737373]">
<ReportIcon className="size-4" name="shield-off" />
Excluir
<span className="ml-auto rounded bg-[#2a2a2a] px-1.5 py-0.5 text-[10px]">Sem permissão</span>
</div>
)}
</div>
</>
) : null}
</td>
</tr>
)
}
function ReportEditorModal({ editor, onClose, onSave, preview, setEditor, setPreview }) {
const isValid = editor.patient.trim() && editor.content.trim()
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div
className="flex max-h-[92vh] w-full max-w-4xl flex-col rounded-2xl border border-[#404040] bg-[#262626] shadow-xl"
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-center justify-between border-b border-[#404040] px-6 py-4">
<h2 className="text-lg font-bold text-[#e5e5e5]">{editor.id ? 'Editar Laudo' : 'Novo Laudo'}</h2>
<div className="flex items-center gap-2">
<button
className={`inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm transition ${
preview
? 'border-[#3b82f6] bg-[#3b82f6]/10 text-[#3b82f6]'
: 'border-[#404040] text-[#a3a3a3] hover:bg-[#2a2a2a]'
}`}
onClick={() => setPreview((current) => !current)}
type="button"
>
<ReportIcon className="size-3.5" name="eye" />
{preview ? 'Editar' : 'Pré-visualizar'}
</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">
{preview ? (
<div className="min-h-[400px] rounded-xl bg-white p-8 text-gray-900 shadow-inner">
<div className="mb-6 border-b border-gray-200 pb-4 text-center">
<h3 className="text-xl font-bold">{editor.type}</h3>
{editor.showDate ? <p className="mt-1 text-sm text-gray-500">{new Date().toLocaleDateString('pt-BR')}</p> : null}
</div>
<p className="text-sm"><strong>Paciente:</strong> {editor.patient || '-'}</p>
<p className="mt-1 text-sm"><strong>Médico(a):</strong> {editor.doctor}</p>
<p className="mt-6 whitespace-pre-wrap text-sm leading-6">
{editor.content || 'Nenhum conteúdo inserido.'}
</p>
{editor.signDigital ? (
<div className="mt-12 border-t border-gray-200 pt-6 text-center">
<p className="text-sm font-medium text-gray-700">{editor.doctor}</p>
<p className="mt-1 text-xs text-gray-400">Assinatura Digital - MediConnect</p>
</div>
) : null}
</div>
) : (
<div className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
<DarkField label="Tipo de Laudo *">
<select className={inputClass} onChange={(event) => setEditorValue(setEditor, 'type', event.target.value)} value={editor.type}>
{reportTypes.map((type) => (
<option key={type}>{type}</option>
))}
</select>
</DarkField>
<DarkField label="Paciente *">
<input
className={inputClass}
onChange={(event) => setEditorValue(setEditor, 'patient', event.target.value)}
placeholder="Digite o nome do paciente..."
value={editor.patient}
/>
</DarkField>
</div>
<DarkField label="Médico Responsável">
<select className={inputClass} onChange={(event) => setEditorValue(setEditor, 'doctor', event.target.value)} value={editor.doctor}>
{doctors.map((doctor) => (
<option key={doctor}>{doctor}</option>
))}
</select>
</DarkField>
<DarkField label="Conteúdo *">
<textarea
className={`${inputClass} min-h-72 py-3 leading-6`}
onChange={(event) => setEditorValue(setEditor, 'content', event.target.value)}
placeholder="Digite o conteúdo do laudo aqui..."
value={editor.content}
/>
</DarkField>
<div className="flex flex-wrap items-center gap-6">
<label className="flex cursor-pointer items-center gap-2 text-sm text-[#e5e5e5]">
<input
checked={editor.showDate}
className="size-4 accent-[#3b82f6]"
onChange={(event) => setEditorValue(setEditor, 'showDate', event.target.checked)}
type="checkbox"
/>
Exibir data no laudo
</label>
<label className="flex cursor-pointer items-center gap-2 text-sm text-[#e5e5e5]">
<input
checked={editor.signDigital}
className="size-4 accent-[#3b82f6]"
onChange={(event) => setEditorValue(setEditor, 'signDigital', event.target.checked)}
type="checkbox"
/>
Incluir assinatura digital
</label>
</div>
{!isValid ? <p className="text-xs text-amber-400">* Preencha o paciente e o conteúdo do laudo para salvar.</p> : null}
</div>
)}
</div>
<div className="flex items-center justify-between border-t border-[#404040] px-6 py-4">
<button
className="rounded-lg border border-[#404040] bg-[#262626] px-4 py-2 text-sm font-medium text-[#e5e5e5] transition hover:bg-[#2a2a2a]"
onClick={onClose}
type="button"
>
Cancelar
</button>
<div className="flex gap-3">
<button
className="rounded-lg border border-[#404040] bg-[#2a2a2a] px-4 py-2 text-sm font-medium text-[#e5e5e5] transition hover:bg-[#333333] disabled:cursor-not-allowed disabled:opacity-40"
disabled={!isValid}
onClick={() => onSave('rascunho')}
type="button"
>
Salvar Rascunho
</button>
<button
className="inline-flex items-center gap-2 rounded-lg bg-[#3b82f6] px-4 py-2 text-sm font-medium text-white transition hover:bg-[#2563eb] disabled:cursor-not-allowed disabled:opacity-40"
disabled={!isValid}
onClick={() => onSave('finalizado')}
type="button"
>
<ReportIcon className="size-3.5" name="lock" />
Liberar Laudo
</button>
</div>
</div>
</div>
</div>
)
}
function TemplatesModal({ onClose, onUseTemplate }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div
className="w-full max-w-3xl rounded-2xl border border-[#404040] bg-[#262626] p-6 shadow-xl"
onClick={(event) => event.stopPropagation()}
>
<div className="mb-5 flex items-center justify-between">
<div>
<h2 className="text-lg font-bold text-[#e5e5e5]">Templates de Laudo</h2>
<p className="mt-1 text-xs text-[#a3a3a3]">Modelos locais para acelerar a escrita do laudo.</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>
<div className="grid gap-3 md:grid-cols-3">
{templates.map((template) => (
<button
className="rounded-xl border border-[#404040] bg-[#1a1a1a] p-4 text-left transition hover:border-[#3b82f6]/50 hover:bg-[#2a2a2a]"
key={template.id}
onClick={() => onUseTemplate(template)}
type="button"
>
<p className="text-sm font-bold text-[#e5e5e5]">{template.name}</p>
<p className="mt-1 text-xs font-medium text-[#3b82f6]">{template.type}</p>
<p className="mt-3 text-xs leading-5 text-[#a3a3a3]">{template.description}</p>
</button>
))}
</div>
</div>
</div>
)
}
function HistoryModal({ onClose, report }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div
className="w-full max-w-2xl rounded-2xl border border-[#404040] bg-[#262626] p-6 shadow-xl"
onClick={(event) => event.stopPropagation()}
>
<div className="mb-5 flex items-center justify-between">
<div>
<h2 className="text-lg font-bold text-[#e5e5e5]">Histórico de Versões</h2>
<p className="mt-1 text-xs text-[#a3a3a3]">{report.type} - {report.patient}</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>
<div className="space-y-3">
{[...report.versions].reverse().map((version) => (
<div className="rounded-xl border border-[#404040] bg-[#1a1a1a] p-4" key={version.version}>
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-bold text-[#e5e5e5]">v{version.version} - {version.action}</p>
<p className="text-xs text-[#a3a3a3]">{version.user}</p>
</div>
<p className="mt-2 text-xs text-[#a3a3a3]">{version.summary}</p>
</div>
))}
</div>
</div>
</div>
)
}
function ConfirmReleaseModal({ onClose, onConfirm, report }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div className="w-full max-w-sm rounded-2xl border border-[#404040] bg-[#262626] p-6 shadow-xl" onClick={(event) => event.stopPropagation()}>
<div className="mb-4 flex items-center gap-3">
<div className="rounded-lg bg-emerald-500/10 p-2 text-emerald-400">
<ReportIcon className="size-5" name="check" />
</div>
<div>
<h3 className="text-sm font-bold text-[#e5e5e5]">Liberar Laudo?</h3>
<p className="mt-0.5 text-xs text-[#a3a3a3]">{report.type} - {report.patient}</p>
</div>
</div>
<p className="mb-5 text-sm leading-6 text-[#a3a3a3]">
Ao liberar, o laudo ficará com status <strong className="text-emerald-400">Finalizado</strong> e poderá ser impresso ou enviado.
</p>
<div className="flex justify-end gap-3">
<button className="rounded-lg border border-[#404040] px-4 py-2 text-sm text-[#e5e5e5] transition hover:bg-[#2a2a2a]" onClick={onClose} type="button">
Cancelar
</button>
<button className="rounded-lg bg-emerald-500 px-4 py-2 text-sm font-medium text-white transition hover:bg-emerald-600" onClick={onConfirm} type="button">
Confirmar Liberação
</button>
</div>
</div>
</div>
)
}
function DeliveryModal({ onClose, report }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div className="w-full max-w-md rounded-2xl border border-[#404040] bg-[#262626] p-6 shadow-xl" onClick={(event) => event.stopPropagation()}>
<h2 className="text-lg font-bold text-[#e5e5e5]">Protocolo de Entrega</h2>
<p className="mt-2 text-sm leading-6 text-[#a3a3a3]">
Entrega mockada para {report.patient}, referente a {report.type} de {report.date}.
</p>
<div className="mt-5 grid gap-3">
<DarkField label="Canal">
<select className={inputClass} defaultValue="Portal do paciente">
<option>Portal do paciente</option>
<option>E-mail</option>
<option>Impresso</option>
</select>
</DarkField>
<DarkField label="Observação">
<textarea className={`${inputClass} min-h-20 py-2`} placeholder="Observação local do protocolo..." />
</DarkField>
</div>
<div className="mt-6 flex justify-end gap-3">
<button className="rounded-lg border border-[#404040] px-4 py-2 text-sm text-[#e5e5e5] transition hover:bg-[#2a2a2a]" onClick={onClose} type="button">
Cancelar
</button>
<button className="rounded-lg bg-[#3b82f6] px-4 py-2 text-sm font-medium text-white transition hover:bg-[#2563eb]" onClick={onClose} type="button">
Registrar Protocolo
</button>
</div>
</div>
</div>
)
}
function DeleteModal({ confirmText, onClose, onConfirm, report, setConfirmText }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={onClose}>
<div className="w-full max-w-sm rounded-2xl border border-[#404040] bg-[#262626] p-6 shadow-xl" onClick={(event) => event.stopPropagation()}>
<div className="mb-4 flex items-center gap-3">
<div className="rounded-lg bg-[#ef4444]/10 p-2 text-[#ef4444]">
<ReportIcon className="size-5" name="trash" />
</div>
<div>
<h3 className="text-sm font-bold text-[#e5e5e5]">Excluir laudo?</h3>
<p className="mt-0.5 text-xs text-[#a3a3a3]">{report.type} - {report.patient}</p>
</div>
</div>
<p className="mb-3 text-sm leading-6 text-[#a3a3a3]">Para confirmar, digite EXCLUIR no campo abaixo.</p>
<input
autoFocus
className="mb-4 h-10 w-full rounded-lg border border-[#ef4444]/40 bg-[#1a1a1a] px-3 text-sm text-[#e5e5e5] outline-none focus:border-[#ef4444]"
onChange={(event) => setConfirmText(event.target.value)}
placeholder="Digite EXCLUIR"
value={confirmText}
/>
<div className="flex justify-end gap-3">
<button className="rounded-lg border border-[#404040] px-4 py-2 text-sm text-[#e5e5e5] transition hover:bg-[#2a2a2a]" onClick={onClose} type="button">
Cancelar
</button>
<button
className="rounded-lg bg-[#ef4444] px-4 py-2 text-sm font-medium text-white transition hover:bg-[#dc2626] disabled:cursor-not-allowed disabled:opacity-40"
disabled={confirmText !== 'EXCLUIR'}
onClick={onConfirm}
type="button"
>
Excluir Permanentemente
</button>
</div>
</div>
</div>
)
}
function MenuItem({ icon, label, onClick, tone = 'default' }) {
const colors = {
blue: 'text-blue-400 hover:bg-blue-500/10',
danger: 'text-[#ef4444] hover:bg-[#ef4444]/10',
default: 'text-[#e5e5e5] hover:bg-[#2a2a2a]',
green: 'text-emerald-400 hover:bg-emerald-500/10',
}
return (
<button className={`flex w-full items-center gap-2 px-4 py-2 text-sm transition ${colors[tone]}`} onClick={onClick} type="button">
<ReportIcon className="size-4" name={icon} />
{label}
</button>
)
}
function DarkField({ children, label }) {
return (
<label className="block">
<span className={labelClass}>{label}</span>
{children}
</label>
)
}
function setEditorValue(setEditor, key, value) {
setEditor((currentEditor) => ({ ...currentEditor, [key]: value }))
}
function canDelete(report) {
return adminUsers.includes(currentUser) || (report.status === 'rascunho' && report.doctor === currentUser)
}
function ReportIcon({ className = 'size-4', name }) {
const common = {
className,
fill: 'none',
stroke: 'currentColor',
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeWidth: 1.8,
viewBox: '0 0 24 24',
}
if (name === 'search') {
return (
<svg {...common}>
<path d="m21 21-4.3-4.3" />
<circle cx="11" cy="11" r="7" />
</svg>
)
}
if (name === 'plus') {
return (
<svg {...common}>
<path d="M12 5v14M5 12h14" />
</svg>
)
}
if (name === 'file') {
return (
<svg {...common}>
<path d="M7 3h7l4 4v14H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z" />
<path d="M14 3v5h5M9 13h6M9 17h6" />
</svg>
)
}
if (name === 'template') {
return (
<svg {...common}>
<path d="M4 5h16M4 12h7M13 12h7M4 19h16" />
<path d="M4 5v14M20 5v14M11 12v7M13 12V5" />
</svg>
)
}
if (name === 'history') {
return (
<svg {...common}>
<path d="M3 12a9 9 0 1 0 3-6.7L3 8" />
<path d="M3 4v4h4M12 7v5l3 2" />
</svg>
)
}
if (name === 'more') {
return (
<svg {...common}>
<path d="M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM19 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM5 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" />
</svg>
)
}
if (name === 'edit') {
return (
<svg {...common}>
<path d="m16 3 5 5L8 21H3v-5L16 3Z" />
</svg>
)
}
if (name === 'printer') {
return (
<svg {...common}>
<path d="M7 8V3h10v5M7 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 === 'clipboard') {
return (
<svg {...common}>
<path d="M9 5h6M9 5a3 3 0 0 1 6 0M8 6H6a1 1 0 0 0-1 1v13a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1h-2M8 13h8M8 17h5" />
</svg>
)
}
if (name === 'check') {
return (
<svg {...common}>
<path d="m5 12 4 4L19 6" />
</svg>
)
}
if (name === 'send') {
return (
<svg {...common}>
<path d="m22 2-7 20-4-9-9-4 20-7Z" />
<path d="M22 2 11 13" />
</svg>
)
}
if (name === 'trash') {
return (
<svg {...common}>
<path d="M3 6h18M8 6V4h8v2M6 6l1 15h10l1-15M10 11v6M14 11v6" />
</svg>
)
}
if (name === 'shield-off') {
return (
<svg {...common}>
<path d="M12 3 5 6v5c0 4.5 3 8.5 7 10 1.1-.4 2.1-1 3-1.7M19 13.5V6l-7-3-4.2 1.8M3 3l18 18" />
</svg>
)
}
if (name === 'eye') {
return (
<svg {...common}>
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z" />
<circle cx="12" cy="12" r="3" />
</svg>
)
}
if (name === 'x') {
return (
<svg {...common}>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
)
}
if (name === 'lock') {
return (
<svg {...common}>
<rect height="10" rx="2" width="16" x="4" y="11" />
<path d="M8 11V8a4 4 0 1 1 8 0v3" />
</svg>
)
}
return (
<svg {...common}>
<path d="m6 9 6 6 6-6" />
</svg>
)
}