modified: src/App.jsx

modified:   src/components/AppShell.jsx
new file:   src/config/permissions.js
new file:   src/hooks/useAuth.js
new file:   src/pages/UsersPage.jsx
new file:   src/repositories/userRepository.js
This commit is contained in:
2026-05-05 22:49:20 -03:00
parent 06acf8cc61
commit bb5200664a
6 changed files with 717 additions and 22 deletions

View File

@@ -1,9 +1,9 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { authRepository } from './repositories/authRepository.js'
import './App.css'
import { AppShell } from './components/AppShell.jsx'
import { canAccess } from './config/permissions.js'
import { useAuth } from './hooks/useAuth.js'
import { AgendaPage } from './pages/AgendaPage.jsx'
import { AnalyticsPage } from './pages/AnalyticsPage.jsx'
import { ForgotPasswordPage, LoginPage, RegisterPage } from './pages/AuthPages.jsx'
@@ -16,11 +16,13 @@ import { ProfilePage } from './pages/ProfilePage.jsx'
import { ReportsPage } from './pages/ReportsPage.jsx'
import { SettingsPage } from './pages/SettingsPage.jsx'
import { TeamPage } from './pages/TeamPage.jsx'
import { UsersPage } from './pages/UsersPage.jsx'
import { VisitsPage } from './pages/VisitsPage.jsx'
import { patientRepository } from './repositories/patientRepository.js'
function App() {
const [location, setLocation] = useState(() => readLocation())
const { isAuthenticated, role, loading: authLoading } = useAuth()
const navigate = useCallback((to, options = {}) => {
if (options.replace) {
@@ -49,25 +51,47 @@ function App() {
return () => window.removeEventListener('popstate', handlePopState)
}, [])
const route = useMemo(() => resolveRoute(location.pathname, navigate), [location.pathname, navigate])
const isAuthenticated = authRepository.isAuthenticated()
const route = useMemo(
() => resolveRoute(location.pathname, navigate, role),
[location.pathname, navigate, role],
)
// Tela de carregamento enquanto busca o role do usuário
if (authLoading) {
return (
<div className="flex min-h-screen items-center justify-center bg-[#0a0a0a]">
<p className="text-sm text-[#a3a3a3]">Carregando...</p>
</div>
)
}
// Rotas públicas (sem shell)
if (!route.withShell) {
return route.element
}
// Usuário não autenticado
if (!isAuthenticated) {
return <LoginPage navigate={navigate} />
}
// Usuário autenticado mas sem permissão para a rota
if (role && !canAccess(role, location.pathname)) {
return (
<AppShell currentPath={location.pathname} navigate={navigate} role={role} routeTitle="Sem acesso">
<UnauthorizedPage navigate={navigate} />
</AppShell>
)
}
return (
<AppShell currentPath={location.pathname} navigate={navigate} routeTitle={route.title}>
<AppShell currentPath={location.pathname} navigate={navigate} role={role} routeTitle={route.title}>
{route.element}
</AppShell>
)
}
function resolveRoute(pathname, navigate) {
function resolveRoute(pathname, navigate, role) {
if (pathname === '/' || pathname === '/login') {
return {
element: <LoginPage navigate={navigate} />,
@@ -102,7 +126,7 @@ function resolveRoute(pathname, navigate) {
if (pathname === '/agenda') {
return {
element: <AgendaPage navigate={navigate} />,
element: <AgendaPage navigate={navigate} role={role} />,
title: 'Agenda',
withShell: true,
}
@@ -110,7 +134,7 @@ function resolveRoute(pathname, navigate) {
if (pathname === '/pacientes') {
return {
element: <PatientsPage navigate={navigate} />,
element: <PatientsPage navigate={navigate} role={role} />,
title: 'Pacientes',
withShell: true,
}
@@ -126,7 +150,6 @@ function resolveRoute(pathname, navigate) {
if (pathname.startsWith('/pacientes/')) {
const patientId = pathname.split('/')[2]
return {
element: <PatientDetailRoute navigate={navigate} patientId={patientId} />,
title: 'Paciente',
@@ -145,7 +168,7 @@ function resolveRoute(pathname, navigate) {
if (pathname === '/laudos') {
return {
element: <ReportsPage navigate={navigate} />,
title: 'Relatorios medicos',
title: 'Relatórios médicos',
withShell: true,
}
}
@@ -174,6 +197,14 @@ function resolveRoute(pathname, navigate) {
}
}
if (pathname === '/usuarios') {
return {
element: <UsersPage role={role} />,
title: 'Usuários',
withShell: true,
}
}
if (pathname === '/perfil') {
return {
element: <ProfilePage navigate={navigate} />,
@@ -192,7 +223,7 @@ function resolveRoute(pathname, navigate) {
return {
element: <NotFoundPage navigate={navigate} />,
title: 'Tela nao encontrada',
title: 'Página não encontrada',
withShell: true,
}
}
@@ -204,7 +235,8 @@ function PatientDetailRoute({ navigate, patientId }) {
useEffect(() => {
let active = true
patientRepository.getById(patientId)
patientRepository
.getById(patientId)
.then((data) => {
if (active) setPatient(data)
})
@@ -221,7 +253,30 @@ function PatientDetailRoute({ navigate, patientId }) {
return <div className="pt-10 text-sm text-[#a3a3a3]">Carregando paciente...</div>
}
return patient ? <PatientDetailPage navigate={navigate} patient={patient} /> : <NotFoundPage navigate={navigate} />
return patient ? (
<PatientDetailPage navigate={navigate} patient={patient} />
) : (
<NotFoundPage navigate={navigate} />
)
}
function UnauthorizedPage({ navigate }) {
return (
<div className="flex flex-col items-center justify-center py-20 text-center">
<p className="text-5xl">🔒</p>
<h1 className="mt-4 text-2xl font-bold text-[#e5e5e5]">Acesso não permitido</h1>
<p className="mt-2 text-sm text-[#a3a3a3]">
Você não tem permissão para acessar esta página.
</p>
<button
className="mt-6 rounded-lg bg-[#3b82f6] px-5 py-2.5 text-sm font-medium text-white transition hover:bg-[#2563eb]"
onClick={() => navigate('/inicio')}
type="button"
>
Voltar ao painel
</button>
</div>
)
}
function readLocation() {