O que o supervisor encontra agora:
Na fila de manutenção (:3001), novo botão "Relatório de turno" no header.
Página /maintenance/report com:
Atalhos Manhã / Tarde / Noite / Hoje + seletor de dia + Personalizado (date-time livre)
Label sempre visível com a janela ativa ("Turno da Manhã — 30/05 06:00 → 14:00")
6 cartões de métricas: pedidos, resolvidos, em aberto, tempo médio de resposta, tempo médio de resolução, pior resposta
Tabela por posto e resumo por área
Lista "Em aberto à hora do relatório" (ou "Nada em aberto. ✓")
Botão Imprimir → PDF via browser; CSS @media print limpa botões/nav
Verificações verdes:
report-smoke.ts — 17/17 (totals, responseMs, resolutionMs, byWorkstation, byArea, stillOpen, window edge cases)
E2E MAI CALL happy-path — 1/1 (dados de seed extra não interferem)
TypeScript — limpo nos pacotes tocados (@repo/api, @repo/admin-web)
Seed cria 6 pedidos de exemplo: relatório "Hoje" nunca começa vazio
+
Resumo da revisão do v0.3
Conformidade com o plano: alta. Shape de output exato, ctx.db (tenant-scoped), requireRole, helper de turnos com o caso da noite, seed com 6 pedidos, UI completa + impressão. Tudo no sítio.
Dois defeitos reais que escaparam ao typecheck e ao E2E — corrigidos:
# Problema Correção
🔴 1 Fetch storm no modo "Hoje" (default): computeWindow recalculava to = new Date() a cada render → nova query key → loop de fetch contínuo. useMemo([windowState]) estabiliza a janela em report-view.tsx:101. Reclicar "Hoje" refresca. Também limpei estado morto (customFrom/customTo).
🔴 2 Smoke não cumpria o AC: re-implementava a agregação à mão em vez de chamar a procedure, e não testava to <= from → BAD_REQUEST (exigido pelo AC do Passo 1). Reescrito report-smoke.ts no padrão createCallerFactory — agora exercita a procedure real: agregação, BAD_REQUEST (to≤from e >31d), janela futura vazia, e FORBIDDEN para operador.
Verificações finais (todas verdes):
tsc --noEmit admin-web — limpo
report-smoke.ts — 22/22 (agora contra a procedure real)
E2E MAI CALL — 1 passed
26 lines
992 B
TypeScript
26 lines
992 B
TypeScript
export type ShiftKey = 'manha' | 'tarde' | 'noite';
|
|
|
|
export const SHIFTS: Record<ShiftKey, { label: string; startHour: number; endHour: number }> = {
|
|
manha: { label: 'Manhã', startHour: 6, endHour: 14 },
|
|
tarde: { label: 'Tarde', startHour: 14, endHour: 22 },
|
|
noite: { label: 'Noite', startHour: 22, endHour: 6 },
|
|
};
|
|
|
|
/** Given a shift and a day (Date at local midnight), returns [from, to). */
|
|
export function shiftWindow(shift: ShiftKey, day: Date): { from: Date; to: Date } {
|
|
const s = SHIFTS[shift];
|
|
const from = new Date(day);
|
|
from.setHours(s.startHour, 0, 0, 0);
|
|
const to = new Date(day);
|
|
if (s.endHour <= s.startHour) to.setDate(to.getDate() + 1); // noite crosses midnight
|
|
to.setHours(s.endHour, 0, 0, 0);
|
|
return { from, to };
|
|
}
|
|
|
|
/** "Hoje" = start of local day up to now. `now` injected for testability. */
|
|
export function todayWindow(now: Date): { from: Date; to: Date } {
|
|
const from = new Date(now);
|
|
from.setHours(0, 0, 0, 0);
|
|
return { from, to: now };
|
|
}
|