feat(dashboard): painel Sandra — aprovações, pedidos do dia, inativos por rep (C8)

GET /dashboard/supervisor com fila de aprovações, KPIs do dia vs semana
anterior e top 3 reps com mais clientes inativos. SandraPainel com polling
30s. Rota / role-aware: rep → RafaelPainel, supervisor/manager → SandraPainel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:40:08 +00:00
parent 6028bf1ba9
commit 36103eaa87
6 changed files with 454 additions and 5 deletions

View File

@@ -1,5 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import { RepDashboardSchema, type RepDashboard } from '@sar/api-interface';
import {
RepDashboardSchema,
SupervisorDashboardSchema,
type RepDashboard,
type SupervisorDashboard,
} from '@sar/api-interface';
import { apiFetch } from '../api-client';
export function useRepDashboard() {
@@ -9,6 +14,18 @@ export function useRepDashboard() {
const raw = await apiFetch('/dashboard/rep');
return RepDashboardSchema.parse(raw);
},
staleTime: 5 * 60 * 1000, // 5 min
staleTime: 5 * 60 * 1000,
});
}
export function useSupervisorDashboard() {
return useQuery<SupervisorDashboard>({
queryKey: ['dashboard', 'supervisor'],
queryFn: async () => {
const raw = await apiFetch('/dashboard/supervisor');
return SupervisorDashboardSchema.parse(raw);
},
staleTime: 30 * 1000, // 30s — simula near-real-time até C6 (SSE)
refetchInterval: 30 * 1000,
});
}

View File

@@ -7,6 +7,24 @@ import { OrdersPage } from '../cockpits/rafael/OrdersPage';
import { OrderDetailPage } from '../cockpits/rafael/OrderDetailPage';
import { NewOrderPage } from '../cockpits/rafael/NewOrderPage';
import { ApprovalQueuePage } from '../cockpits/sandra/ApprovalQueuePage';
import { SandraPainel } from '../cockpits/sandra/SandraPainel';
import { authStore } from './auth-store';
function getRoleFromToken(): string {
const token = authStore.get();
if (!token) return 'rep';
try {
const payload = JSON.parse(atob(token.split('.')[1] ?? ''));
return (payload.role as string) ?? 'rep';
} catch {
return 'rep';
}
}
function HomeRoute() {
const role = getRoleFromToken();
return role === 'supervisor' || role === 'manager' ? <SandraPainel /> : <RafaelPainel />;
}
const rootRoute = createRootRoute({
component: () => (
@@ -19,7 +37,7 @@ const rootRoute = createRootRoute({
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: RafaelPainel,
component: HomeRoute,
});
const rafaelRoute = createRoute({