76b50fec84
Named volume mounted root-owned over the app user's chown, so every PDF (orçamento, fatura, download) failed with 500. Entrypoint now fixes ownership on container start and drops to the app user. feat(dashboard): decouple pipeline state from period filter Status counts and recent OTs no longer disappear when an open order was created before the selected period window. feat(workorders): add vehicle/total columns, deadline urgency colors, on-time delivery saldo and efficiency metric, plate search Expose completed_at from wo_status_log so the frontend can compare actual completion date against the deadline. Add "Eficiência de Entrega" metric to Dashboard and Reports. feat(reports): add dedicated vehicle service history report New page with per-vehicle summary (OT count, total spent, last visit) and full itemized service history with print output. chore: relabel OT status "Concluir OT" -> "Concluída" and "Iniciar Trabalho" -> "Em Progresso" (action buttons unchanged) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
98 lines
4.0 KiB
TypeScript
98 lines
4.0 KiB
TypeScript
import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router'
|
|
import { QueryClientProvider } from '@tanstack/react-query'
|
|
import { queryClient } from '@/lib/queryClient'
|
|
import { useAuthStore } from '@/store/authStore'
|
|
import type { UserRole } from '@/store/authStore'
|
|
import LoginPage from '@/pages/auth/LoginPage'
|
|
import AppDashboardPage from '@/pages/app/DashboardPage'
|
|
import ClientsPage from '@/pages/app/ClientsPage'
|
|
import CatalogPage from '@/pages/app/CatalogPage'
|
|
import WorkOrdersPage from '@/pages/app/WorkOrdersPage'
|
|
import WorkOrderDetailPage from '@/pages/app/WorkOrderDetailPage'
|
|
import StaffPage from '@/pages/app/StaffPage'
|
|
import ExpensesPage from '@/pages/app/ExpensesPage'
|
|
import SettingsPage from '@/pages/app/SettingsPage'
|
|
import InvoicesPage from '@/pages/app/InvoicesPage'
|
|
import ReportsPage from '@/pages/app/ReportsPage'
|
|
import VehicleReportsPage from '@/pages/app/VehicleReportsPage'
|
|
import ExpenseReportsPage from '@/pages/app/ExpenseReportsPage'
|
|
import TechnicianReportsPage from '@/pages/app/TechnicianReportsPage'
|
|
import HelpPage from '@/pages/app/HelpPage'
|
|
import AdminDashboardPage from '@/pages/admin/DashboardPage'
|
|
import TenantsPage from '@/pages/admin/TenantsPage'
|
|
import AdminSettingsPage from '@/pages/admin/SettingsPage'
|
|
import InviteRedeemPage from '@/pages/public/InviteRedeemPage'
|
|
import AppLayout from '@/components/layout/AppLayout'
|
|
import AdminLayout from '@/components/layout/AdminLayout'
|
|
|
|
function RequireAuth({
|
|
children,
|
|
allowedRoles,
|
|
}: {
|
|
children: React.ReactNode
|
|
allowedRoles: UserRole[]
|
|
}) {
|
|
const { isAuthenticated, user } = useAuthStore()
|
|
const location = useLocation()
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />
|
|
// Allow super_admin carrying an impersonation payload through to /app —
|
|
// AppLayout applies the role switch on mount via useLayoutEffect.
|
|
const hasImpersonation = !!location.state?.impersonation
|
|
if (user && !allowedRoles.includes(user.role) && !hasImpersonation) {
|
|
return <Navigate to="/login" replace />
|
|
}
|
|
return <>{children}</>
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<BrowserRouter>
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route path="/invite/:token" element={<InviteRedeemPage />} />
|
|
|
|
<Route
|
|
path="/admin"
|
|
element={
|
|
<RequireAuth allowedRoles={['super_admin']}>
|
|
<AdminLayout />
|
|
</RequireAuth>
|
|
}
|
|
>
|
|
<Route index element={<AdminDashboardPage />} />
|
|
<Route path="tenants" element={<TenantsPage />} />
|
|
<Route path="settings" element={<AdminSettingsPage />} />
|
|
</Route>
|
|
|
|
<Route
|
|
path="/app"
|
|
element={
|
|
<RequireAuth allowedRoles={['tenant_admin', 'manager', 'technician']}>
|
|
<AppLayout />
|
|
</RequireAuth>
|
|
}
|
|
>
|
|
<Route index element={<AppDashboardPage />} />
|
|
<Route path="clients" element={<ClientsPage />} />
|
|
<Route path="catalog" element={<CatalogPage />} />
|
|
<Route path="work-orders" element={<WorkOrdersPage />} />
|
|
<Route path="work-orders/:id" element={<WorkOrderDetailPage />} />
|
|
<Route path="staff" element={<StaffPage />} />
|
|
<Route path="expenses" element={<ExpensesPage />} />
|
|
<Route path="invoices" element={<InvoicesPage />} />
|
|
<Route path="reports" element={<ReportsPage />} />
|
|
<Route path="reports/vehicles" element={<VehicleReportsPage />} />
|
|
<Route path="reports/expenses" element={<ExpenseReportsPage />} />
|
|
<Route path="reports/technicians" element={<TechnicianReportsPage />} />
|
|
<Route path="settings" element={<SettingsPage />} />
|
|
<Route path="help" element={<HelpPage />} />
|
|
</Route>
|
|
|
|
<Route path="/" element={<Navigate to="/app" replace />} />
|
|
</Routes>
|
|
</BrowserRouter>
|
|
</QueryClientProvider>
|
|
)
|
|
}
|