feat(frontend): Relatórios > Chamadas + Gravações (proxy de áudio autenticado)
Relatórios > Chamadas: lista das últimas 500 chamadas dos últimos 30 dias, nomes de fila/agente/campanha/disposição resolvidos client-side, busca por telefone. Só o filtro de telefone nesta primeira versão. Gravações: lista + player + download. Achado de arquitetura resolvido antes de codar: <audio src>/<a download> não mandam Authorization Bearer (só cookie), e a API nunca expõe o storage por URL direta — criado um proxy autenticado (Route Handler /api/recordings/[id]/audio) que lê o cookie de sessão, chama a API real com o access token do lado do servidor, e reencaminha o stream com Content-Disposition: inline (a API manda attachment). Mesmo princípio de apiFetch: token nunca chega em JS legível. Testado ponta a ponta contra a API real (estados vazios honestos, proxy confirmado 401 sem sessão). Smoke test de regressão nas 15 telas anteriores do tenant + platform, todas 200. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -37,6 +37,16 @@ export function formatDate(iso: string): string {
|
||||
return new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "short", year: "numeric" }).format(new Date(iso));
|
||||
}
|
||||
|
||||
export function formatDateTime(iso: string): string {
|
||||
return new Intl.DateTimeFormat("pt-BR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(new Date(iso));
|
||||
}
|
||||
|
||||
/** agente.md secao 128 — catálogo de preço, mesma nomenclatura do
|
||||
* `PriceItemType` do backend (packages/database/prisma/schema.prisma). */
|
||||
export const PRICE_ITEM_TYPE_LABELS: Record<string, string> = {
|
||||
|
||||
11
apps/frontend/src/lib/recording-types.ts
Normal file
11
apps/frontend/src/lib/recording-types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface Recording {
|
||||
id: string;
|
||||
callId: string;
|
||||
format: string;
|
||||
durationSeconds: number | null;
|
||||
channels: number;
|
||||
sizeBytes: number | null;
|
||||
recordedAt: string;
|
||||
retentionUntil: string | null;
|
||||
status: string;
|
||||
}
|
||||
@@ -43,6 +43,29 @@ export interface CampaignReport {
|
||||
iaValor: number | null;
|
||||
}
|
||||
|
||||
export interface Call {
|
||||
id: string;
|
||||
direction: "INBOUND" | "OUTBOUND" | "INTERNAL";
|
||||
callerNumber: string | null;
|
||||
calledNumber: string | null;
|
||||
queueId: string | null;
|
||||
agentId: string | null;
|
||||
campaignId: string | null;
|
||||
hangupCause: string | null;
|
||||
dispositionId: string | null;
|
||||
waitTime: number | null;
|
||||
talkTime: number | null;
|
||||
durationSeconds: number | null;
|
||||
createdAt: string;
|
||||
endAt: string | null;
|
||||
}
|
||||
|
||||
export const CALL_DIRECTION_LABELS: Record<string, string> = {
|
||||
INBOUND: "Entrada",
|
||||
OUTBOUND: "Saída",
|
||||
INTERNAL: "Interna",
|
||||
};
|
||||
|
||||
export interface AIDashboardReport {
|
||||
callsAnalyzed: number;
|
||||
avgQualityScore: number | null;
|
||||
|
||||
Reference in New Issue
Block a user