use corpo real exame fisico

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-10 20:07:34 -03:00
1 parent 939293ba60
commit 7e8963135b
4 files changed
+166 -108

No files matched your search

+45 -65
View File
@@ -1,16 +1,23 @@
import { useMemo, useState } from "react";
import { Canvas } from "@react-three/fiber";
import { CameraControls, Html } from "@react-three/drei";
import { Mannequin } from "../components/viewer/Mannequin.js";
import { useEffect, useMemo, useState } from "react";
import { useSystemStructures } from "../hooks/useSystemStructures.js";
import { useAtlasStore } from "../store/useAtlasStore.js";
import { Scene } from "../components/viewer/Scene.js";
import { ExamMarkers } from "../components/viewer/ExamMarkers.js";
import { LayerToggle } from "../components/LayerToggle.js";
import { QuickRegionNav } from "../components/QuickRegionNav.js";
import { EXAM_POINTS, TECHNIQUES, type ExamTechnique } from "../data/physicalExam.js";
// Módulo de estudo pro exame físico: o mesmo boneco esquemático do módulo
// de Planos e Eixos, agora com pontos clicáveis mostrando ONDE e COMO se
// faz cada manobra (inspeção/palpação/percussão/ausculta) — pensado pra
// quem está estudando propedêutica, não pra troca do atlas 3D real (que
// seria overkill pra um boneco de pontos de exame, que não muda por
// paciente).
// Módulo de estudo pro exame físico, sobre o corpo 3D REAL do Z-Anatomy
// (mesmo visualizador do Atlas — camadas, navegação por região, câmera
// tudo igual), com pontos clicáveis mostrando ONDE e COMO se faz cada
// manobra (inspeção/palpação/percussão/ausculta). Reaproveita o mesmo
// <Scene> do Atlas (via prop "children", ver Scene.tsx) em vez de um corpo
// geométrico à parte.
export default function PhysicalExamPage() {
const visibleSystems = useAtlasStore((s) => s.visibleSystems);
const { bySlug } = useSystemStructures(visibleSystems);
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
const [activeTechniques, setActiveTechniques] = useState<Set<ExamTechnique>>(
() => new Set(TECHNIQUES.map((t) => t.key)),
);
@@ -18,6 +25,13 @@ export default function PhysicalExamPage() {
const [leftOpen, setLeftOpen] = useState(false);
const [rightOpen, setRightOpen] = useState(false);
useEffect(() => {
if (selectedStructureId) {
setRightOpen(true);
setLeftOpen(false);
}
}, [selectedStructureId]);
function toggleTechnique(key: ExamTechnique) {
setActiveTechniques((prev) => {
const next = new Set(prev);
@@ -39,18 +53,21 @@ export default function PhysicalExamPage() {
<div className="h-full relative md:grid md:grid-cols-[260px_1fr_320px]">
<aside
className={`fixed md:static inset-y-0 left-0 z-30 w-[86vw] max-w-[280px] md:w-auto
border-r border-white/10 bg-panel p-4 space-y-4 overflow-y-auto
border-r border-white/10 bg-panel p-3 flex flex-col min-h-0 overflow-y-auto
transition-transform duration-200 md:translate-x-0
${leftOpen ? "translate-x-0" : "-translate-x-full"}`}
>
<button
onClick={() => setLeftOpen(false)}
className="md:hidden float-right text-slate-400 hover:text-white text-sm px-2 py-1"
className="md:hidden self-end text-slate-400 hover:text-white text-sm mb-2 px-2 py-1"
aria-label="Fechar painel"
>
✕ Fechar
</button>
<div>
<LayerToggle />
<div className="pt-3 mt-3 border-t border-white/10">
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Técnicas de Exame</h3>
<div className="space-y-1.5">
{TECHNIQUES.map((t) => (
@@ -68,24 +85,11 @@ export default function PhysicalExamPage() {
</div>
</div>
<div className="pt-3 border-t border-white/10">
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Como usar</h3>
<p className="text-xs text-slate-400 leading-relaxed">
Marque as técnicas que quer estudar e clique nos pontos coloridos
sobre o boneco. Cada ponto explica onde encostar, como fazer a
manobra e o que ela avalia — pensado como revisão rápida de
propedêutica, não substitui a prática supervisionada.
</p>
</div>
<div className="pt-3 border-t border-white/10 space-y-1">
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Sistemas cobertos</h3>
{[...new Set(EXAM_POINTS.map((p) => p.system))].map((sys) => (
<p key={sys} className="text-xs text-slate-400">
{sys} ({EXAM_POINTS.filter((p) => p.system === sys).length})
</p>
))}
</div>
<p className="text-[11px] text-slate-500 mt-4 leading-relaxed">
Ligue uma camada (ex: Esquelético) como referência, marque as
técnicas que quer estudar e clique nos pontos coloridos sobre o
corpo. Use o menu de regiões pra focar rápido numa parte do corpo.
</p>
</aside>
{(leftOpen || rightOpen) && (
@@ -99,6 +103,14 @@ export default function PhysicalExamPage() {
)}
<div className="relative h-full min-h-0">
<Scene bySlug={bySlug}>
<ExamMarkers points={visiblePoints} selectedId={selectedId} onSelect={(id) => {
setSelectedId(id);
setRightOpen(true);
}} />
</Scene>
<QuickRegionNav />
<button
onClick={() => setLeftOpen(true)}
className="md:hidden absolute top-3 left-3 z-10 bg-panel/90 border border-white/10 rounded-lg px-3 py-2 text-xs shadow-lg"
@@ -111,38 +123,6 @@ export default function PhysicalExamPage() {
>
ℹ️ Detalhes
</button>
<Canvas dpr={[1, 1.5]} camera={{ position: [1.6, 1.3, 2.0], fov: 45 }}>
<color attach="background" args={["#0b1220"]} />
<ambientLight intensity={0.65} />
<directionalLight position={[2, 3, 2]} intensity={1} />
<Mannequin />
{visiblePoints.map((p) => {
const isSelected = p.id === selectedId;
const color = techniqueColor(p.technique);
return (
<Html key={p.id} position={p.position} center distanceFactor={4} zIndexRange={[10, 0]}>
<button
onClick={() => {
setSelectedId(p.id);
setRightOpen(true);
}}
title={p.name}
className="w-4 h-4 rounded-full border-2 border-white/80 transition-transform hover:scale-125"
style={{
backgroundColor: color,
boxShadow: isSelected ? `0 0 0 5px ${color}55` : undefined,
transform: isSelected ? "scale(1.4)" : undefined,
}}
/>
</Html>
);
})}
<CameraControls makeDefault />
</Canvas>
</div>
<aside
@@ -153,7 +133,7 @@ export default function PhysicalExamPage() {
>
<button
onClick={() => setRightOpen(false)}
className="md:hidden text-slate-400 hover:text-white text-sm px-2 py-1"
className="md:hidden text-slate-400 hover:text-white text-sm mb-2 px-2 py-1"
aria-label="Fechar painel"
>
✕ Fechar
@@ -161,7 +141,7 @@ export default function PhysicalExamPage() {
{!selected ? (
<p className="text-sm text-slate-400">
Clique em um ponto colorido sobre o boneco pra ver a técnica, o
Clique em um ponto colorido sobre o corpo pra ver a técnica, o
local exato e o que ela avalia.
</p>
) : (