function PatientsView({ onOpenPatient, onNewPatient, podeEditar = true }) { const [cases, setCases] = React.useState([]); const [loading, setLoading] = React.useState(true); const [filter, setFilter] = React.useState("all"); const [q, setQ] = React.useState(""); const [lifecycle, setLifecycle] = React.useState(null); const [acting, setActing] = React.useState(false); const [actionError, setActionError] = React.useState(null); const requestId = React.useRef(0); const loadCases = () => { const currentRequest = ++requestId.current; setLoading(true); window.API.listCases({ limit: 100, ...(filter !== "all" ? { status: filter } : {}), ...(q.trim() ? { q: q.trim() } : {}), }).then(data => { if (currentRequest !== requestId.current) return; const items = data.items || []; setCases(filter === "all" ? items.filter(item => item.status !== "archived") : items); setLoading(false); }).catch(() => { if (currentRequest !== requestId.current) return; setCases([]); setLoading(false); }); }; React.useEffect(loadCases, [filter, q]); const openLifecycle = async (patient, action) => { setActionError(null); let warnings = []; if (action === "publish") { setActing(true); try { const check = await window.API.validateCasePublication(patient.id); warnings = check.warnings; } catch (error) { setActing(false); if (error.issues && error.issues.length) { onOpenPatient(patient.id, { publicationIssues: error.issues }); return; } alert(userErrorMessage(error)); return; } setActing(false); } setLifecycle({ patient, action, warnings }); }; const closeLifecycle = () => { if (acting) return; setLifecycle(null); setActionError(null); }; const confirmLifecycle = () => { if (!lifecycle) return; const method = lifecycle.action + "Case"; setActing(true); setActionError(null); window.API[method](lifecycle.patient.id).then(() => { setActing(false); setLifecycle(null); loadCases(); }).catch(error => { setActing(false); setActionError(error); }); }; const lifecycleCopy = lifecycle && { publish: ["Publicar caso clínico", "Publicar", "checkCircle", "Após a publicação, o conteúdo ficará somente leitura até ser despublicado."], unpublish: ["Despublicar caso clínico", "Despublicar", "edit", "O caso voltará a ser um rascunho editável e sairá do acervo disponível para simulação."], archive: ["Arquivar caso clínico", "Arquivar", "archive", "O caso sairá do acervo ativo e poderá ser restaurado depois."], restore: ["Restaurar caso clínico", "Restaurar", "restore", "O caso voltará ao acervo como rascunho."], }[lifecycle.action]; return (
Acervo clínico

Casos

Casos clínicos configuráveis usados diretamente nas simulações para o Meta Quest.

{/* RF-USUARIOS-02: criar caso é de `administrador` e `editor`. Esconder o botão é cortesia com o operador, não a garantia — a recusa vale no backend (routers/cases.py), que é onde ela não pode ser contornada. */} {podeEditar && Novo caso}
{[["all", "Todos"], ["draft", "Rascunhos"], ["published", "Publicados"], ["archived", "Arquivados"]].map(([value, label]) => ( ))}
setQ(event.target.value)} />
{loading ? (
Carregando...
) : ( {cases.map(patient => { const pessoais = (patient.data || {}).pessoais || {}; const diagnostico = (patient.data || {}).diagnostico || {}; const tags = diagnostico.queixaClassificada && diagnostico.queixaClassificada !== "naoSeAplica" ? [diagnostico.queixaClassificada.toUpperCase()] : []; const updated = patient.updated_at ? new Date(patient.updated_at) : null; const updatedLabel = updated ? (updated.toLocaleDateString("pt-BR") === new Date().toLocaleDateString("pt-BR") ? "hoje" : updated.toLocaleDateString("pt-BR", { day: "numeric", month: "short" })) : "—"; return ( onOpenPatient(patient.id)}> ); })}
CasoPerfilQueixa principalAtualizadoStatusAções
{(patient.nome || "?").split(" ").map(word => word[0]).slice(0, 2).join("")}
{patient.nome}
{patient.ref}
{pessoais.idade ? pessoais.idade + " anos" : "—"} · {pessoais.profissao || "—"} {tags.map(tag => {tag})}{tags.length === 0 && } {updatedLabel} event.stopPropagation()}>
{patient.status === "draft" && <> openLifecycle(patient, "publish")} /> onOpenPatient(patient.id)} /> openLifecycle(patient, "archive")} />} {patient.status === "published" && <> onOpenPatient(patient.id)} /> openLifecycle(patient, "unpublish")} /> openLifecycle(patient, "archive")} />} {patient.status === "archived" && <> onOpenPatient(patient.id)} /> openLifecycle(patient, "restore")} />}
)}
{cases.length === 0 && !loading &&
Nenhum caso encontrado.
}
{lifecycle && lifecycleCopy &&

{lifecycle.patient.nome}
{lifecycleCopy[3]}

} {lifecycle && }
); } // ActionIcon foi para `ui-portal.jsx` em 2026-07-30: a tela de usuários usa os // mesmos botões de ação de linha. window.PatientsView = PatientsView;