| 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> |
| ); |
| } |
|
|