sipracd / frontend /src /components /Layout.tsx
Guilherme Silberfarb Costa
Deploy current SIPRAC app snapshot
7fabf33
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react";
import { NavLink, useLocation } from "react-router-dom";
import type { PropsWithChildren, ReactNode } from "react";
const links = [
{
to: "/processos",
label: "Processos",
matches: (pathname: string) =>
pathname === "/processos" ||
pathname === "/backlog" ||
pathname.startsWith("/trabalho/"),
},
{ to: "/dashboard", label: "Fluxo de Processos", matches: (pathname: string) => pathname === "/dashboard" },
{
to: "/inscricoes",
label: "Inscrições",
matches: (pathname: string) => pathname === "/inscricoes" || pathname.startsWith("/inscricoes/"),
},
{ to: "/visao-gs", label: "Visão GS", matches: (pathname: string) => pathname === "/visao-gs" },
{
to: "/cronogramas",
label: "Cronogramas",
matches: (pathname: string) =>
pathname === "/cronogramas" ||
pathname === "/equipe" ||
pathname === "/metas" ||
pathname.startsWith("/avaliadores/"),
},
{ to: "/novo", label: "Novo processo", matches: (pathname: string) => pathname === "/novo" },
];
const PageHeaderActionContext = createContext<((action: ReactNode | null) => void) | null>(null);
export function usePageHeaderAction(action: ReactNode | null) {
const setAction = useContext(PageHeaderActionContext);
useLayoutEffect(() => {
if (!setAction) return;
setAction(action);
return () => setAction(null);
}, [action, setAction]);
}
export function Layout({ children }: PropsWithChildren) {
const location = useLocation();
const [headerAction, setHeaderAction] = useState<ReactNode | null>(null);
const [headerCollapsed, setHeaderCollapsed] = useState(false);
function pageTitle() {
const { pathname } = location;
if (pathname === "/processos" || pathname === "/backlog") return "Processos";
if (pathname === "/dashboard") return "Fluxo de Processos";
if (pathname === "/inscricoes") return "Inscrições";
if (pathname === "/visao-gs") return "Visão GS";
if (pathname === "/cronogramas" || pathname === "/equipe" || pathname === "/metas") return "Cronogramas";
if (pathname === "/novo") return "Novo processo";
if (pathname.startsWith("/avaliadores/")) return "Avaliador";
if (pathname.startsWith("/trabalho/") && pathname.endsWith("/editar")) return "Editar processo";
if (pathname.startsWith("/trabalho/")) return "Processo";
return "SIPRAC";
}
const headerContextValue = useMemo(() => setHeaderAction, []);
useEffect(() => {
function handleScroll() {
const currentScrollY = window.scrollY;
setHeaderCollapsed(currentScrollY > 96);
}
handleScroll();
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [location.pathname]);
return (
<PageHeaderActionContext.Provider value={headerContextValue}>
<div className={`app-shell${headerCollapsed ? " app-shell-header-collapsed" : ""}`}>
<header className="app-header">
<div className="app-header-inner">
<div className="sidebar-brand">
<h1>SIPRAC</h1>
<p className="sidebar-copy">Sistema de Processos, Acompanhamento e Cronogramas</p>
</div>
<nav className="nav">
{links.map((link) => (
<NavLink
key={link.to}
to={link.to}
className={() => `nav-link${link.matches(location.pathname) ? " active" : ""}`}
>
{link.label}
</NavLink>
))}
</nav>
</div>
</header>
<main className="main-content">
<header className="topbar">
<h2>{pageTitle()}</h2>
{headerAction ? <div className="topbar-action">{headerAction}</div> : null}
</header>
{children}
</main>
</div>
</PageHeaderActionContext.Provider>
);
}