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:
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user