Files
anatomia/frontend/src/routes/AtlasPage.tsx
T

113 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { useSystemStructures } from "../hooks/useSystemStructures.js";
import { useAtlasStore } from "../store/useAtlasStore.js";
import { Scene } from "../components/viewer/Scene.js";
import { LayerToggle } from "../components/LayerToggle.js";
import { ClippingControls } from "../components/ClippingControls.js";
import { SearchNav } from "../components/SearchNav.js";
import { StructureInfoPanel } from "../components/StructureInfoPanel.js";
import { QuickRegionNav } from "../components/QuickRegionNav.js";
import { SkinDiagram } from "../components/SkinDiagram.js";
import { trackVisit } from "../lib/analytics.js";
// Em telas pequenas não há espaço para os 3 painéis lado a lado — os
// laterais viram gavetas (drawers) que deslizam por cima do visualizador
// 3D, que fica sempre em tela cheia (é o que importa pra estudar).
export default function AtlasPage() {
const visibleSystems = useAtlasStore((s) => s.visibleSystems);
const { bySlug } = useSystemStructures(visibleSystems);
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
const [leftOpen, setLeftOpen] = useState(false);
const [rightOpen, setRightOpen] = useState(false);
useEffect(() => {
if (selectedStructureId) {
setRightOpen(true);
setLeftOpen(false);
}
}, [selectedStructureId]);
// Registra qual(is) sistema(s) do atlas o visitante ligou — é o que
// alimenta o ranking de "sistemas mais acessados" no painel de
// estatísticas. Só manda algo se o visitante aceitou o aviso de cookies.
useEffect(() => {
for (const slug of visibleSystems) trackVisit("/", slug);
}, [visibleSystems]);
return (
<div className="h-full relative md:grid md:grid-cols-[280px_1fr_320px]">
<aside
className={`fixed md:static inset-y-0 left-0 z-30 w-[86vw] max-w-[300px] md:w-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 self-end text-slate-400 hover:text-white text-sm mb-2 px-2 py-1"
aria-label="Fechar painel"
>
✕ Fechar
</button>
<LayerToggle />
<ClippingControls />
<div className="flex-1 min-h-0 mt-4">
<SearchNav />
</div>
</aside>
{/* No mobile o painel de detalhes é uma bottom sheet (ver <aside> mais
abaixo) — o fundo escurecido só faz sentido pro drawer de camadas,
que ainda cobre a tela toda; a sheet já deixa o modelo 3D visível
por cima dela, então não precisa (e não deveria) escurecer a cena. */}
{leftOpen && (
<div
className="fixed inset-0 bg-black/50 z-20 md:hidden"
onClick={() => setLeftOpen(false)}
/>
)}
<div className="relative h-full min-h-0">
<Scene bySlug={bySlug} />
<QuickRegionNav />
<SkinDiagram bySlug={bySlug} />
<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"
>
☰ Camadas
</button>
{!rightOpen && (
<button
onClick={() => setRightOpen(true)}
className="md:hidden absolute top-3 right-3 z-10 bg-panel/90 border border-white/10 rounded-lg px-3 py-2 text-xs shadow-lg"
>
ℹ️ Detalhes
</button>
)}
</div>
{/* Desktop: gaveta lateral fixa, igual à de camadas. Mobile: bottom
sheet que sobe de baixo pra cima e some — a parte anatômica
selecionada (destacada no modelo 3D, atrás dela) continua visível
por cima, em vez de ser tampada pela tela inteira do painel. */}
<aside
className={`fixed md:static inset-x-0 bottom-0 md:inset-auto z-30
max-h-[65vh] md:max-h-none md:h-full w-full md:w-auto
border-t md:border-t-0 md:border-l border-white/10 bg-panel
rounded-t-2xl md:rounded-none p-4 pt-2 md:pt-4 overflow-y-auto
shadow-[0_-8px_24px_rgba(0,0,0,0.4)] md:shadow-none
transition-transform duration-200 md:!translate-y-0
${rightOpen ? "translate-y-0" : "translate-y-full"}`}
>
{/* Alça de arrastar — só decorativa, mas é o sinal visual padrão de
bottom sheet ("puxe/deslize pra baixo pra fechar"). */}
<div className="md:hidden mx-auto mb-2 h-1.5 w-10 rounded-full bg-white/15" />
<StructureInfoPanel bySlug={bySlug} onClose={() => setRightOpen(false)} />
</aside>
</div>
);
}