From 15d11b6b12f5d1986dc42c6e2b37dabd53dec931 Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Fri, 14 Nov 2025 19:41:47 -0300 Subject: [PATCH] feat: improve reports filters and ticket flows --- convex/reports.ts | 9 +- src/app/api/reports/backlog.xlsx/route.ts | 9 +- .../reports/category-insights.xlsx/route.ts | 50 +++++ src/app/api/reports/csat.xlsx/route.ts | 9 +- .../api/reports/hours-by-client.xlsx/route.ts | 4 + .../reports/machine-category.xlsx/route.ts | 54 ++++++ src/app/api/reports/sla.xlsx/route.ts | 9 +- .../reports/tickets-by-channel.xlsx/route.ts | 4 + src/app/tickets/new/page.tsx | 14 -- .../agenda/agenda-calendar-view.tsx | 5 +- src/components/portal/portal-ticket-card.tsx | 21 +- .../portal/portal-ticket-detail.tsx | 3 - src/components/portal/portal-ticket-form.tsx | 18 +- src/components/reports/backlog-report.tsx | 13 +- src/components/reports/category-report.tsx | 10 + src/components/reports/csat-report.tsx | 13 +- src/components/reports/hours-report.tsx | 14 +- .../reports/machine-category-report.tsx | 12 ++ src/components/reports/sla-report.tsx | 13 +- .../tickets/close-ticket-dialog.tsx | 27 +-- src/components/tickets/my-tickets-panel.tsx | 1 - src/components/tickets/new-ticket-dialog.tsx | 23 +-- .../tickets/play-next-ticket-card.tsx | 1 - .../tickets/recent-tickets-panel.tsx | 1 - .../tickets/ticket-summary-header.tsx | 35 +--- src/components/tickets/tickets-table.tsx | 3 - src/components/ui/rich-text-editor.tsx | 12 +- src/lib/agenda-utils.ts | 10 +- src/server/report-exporters.ts | 180 ++++++++++++++++++ 29 files changed, 437 insertions(+), 140 deletions(-) create mode 100644 src/app/api/reports/category-insights.xlsx/route.ts create mode 100644 src/app/api/reports/machine-category.xlsx/route.ts diff --git a/convex/reports.ts b/convex/reports.ts index ac14039..a3b2efd 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -631,7 +631,14 @@ export async function slaOverviewHandler( } export const slaOverview = query({ - args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, + args: { + tenantId: v.string(), + viewerId: v.id("users"), + range: v.optional(v.string()), + companyId: v.optional(v.id("companies")), + dateFrom: v.optional(v.string()), + dateTo: v.optional(v.string()), + }, handler: slaOverviewHandler, }); diff --git a/src/app/api/reports/backlog.xlsx/route.ts b/src/app/api/reports/backlog.xlsx/route.ts index 8a4d932..af27e42 100644 --- a/src/app/api/reports/backlog.xlsx/route.ts +++ b/src/app/api/reports/backlog.xlsx/route.ts @@ -17,6 +17,8 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url) const range = searchParams.get("range") ?? undefined const companyId = searchParams.get("companyId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined const context = await createConvexContext({ tenantId, @@ -26,7 +28,12 @@ export async function GET(request: Request) { role: session.user.role.toUpperCase(), }) - const artifact = await buildBacklogWorkbook(context, { range, companyId: companyId ?? undefined }) + const artifact = await buildBacklogWorkbook(context, { + range, + companyId: companyId ?? undefined, + dateFrom, + dateTo, + }) return new NextResponse(artifact.buffer, { headers: { diff --git a/src/app/api/reports/category-insights.xlsx/route.ts b/src/app/api/reports/category-insights.xlsx/route.ts new file mode 100644 index 0000000..e2fb5c4 --- /dev/null +++ b/src/app/api/reports/category-insights.xlsx/route.ts @@ -0,0 +1,50 @@ +import { NextResponse } from "next/server" + +import { assertAuthenticatedSession } from "@/lib/auth-server" +import { DEFAULT_TENANT_ID } from "@/lib/constants" +import { buildCategoryInsightsWorkbook, createConvexContext } from "@/server/report-exporters" + +export const runtime = "nodejs" + +export async function GET(request: Request) { + const session = await assertAuthenticatedSession() + if (!session) { + return NextResponse.json({ error: "Não autorizado" }, { status: 401 }) + } + + const { searchParams } = new URL(request.url) + const range = searchParams.get("range") ?? undefined + const companyId = searchParams.get("companyId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined + + const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID + + try { + const context = await createConvexContext({ + tenantId, + name: session.user.name ?? session.user.email, + email: session.user.email, + avatarUrl: session.user.avatarUrl, + role: session.user.role.toUpperCase(), + }) + + const artifact = await buildCategoryInsightsWorkbook(context, { + range, + companyId: companyId || undefined, + dateFrom, + dateTo, + }) + + return new NextResponse(artifact.buffer, { + headers: { + "Content-Type": artifact.mimeType, + "Content-Disposition": `attachment; filename="${artifact.fileName}"`, + "Cache-Control": "no-store", + }, + }) + } catch (error) { + console.error("Failed to generate category insights export", error) + return NextResponse.json({ error: "Falha ao gerar planilha de categorias" }, { status: 500 }) + } +} diff --git a/src/app/api/reports/csat.xlsx/route.ts b/src/app/api/reports/csat.xlsx/route.ts index 7e7d351..5d51b5a 100644 --- a/src/app/api/reports/csat.xlsx/route.ts +++ b/src/app/api/reports/csat.xlsx/route.ts @@ -14,6 +14,8 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url) const range = searchParams.get("range") ?? undefined const companyId = searchParams.get("companyId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID @@ -26,7 +28,12 @@ export async function GET(request: Request) { role: session.user.role.toUpperCase(), }) - const artifact = await buildCsatWorkbook(context, { range, companyId: companyId ?? undefined }) + const artifact = await buildCsatWorkbook(context, { + range, + companyId: companyId ?? undefined, + dateFrom, + dateTo, + }) return new NextResponse(artifact.buffer, { headers: { diff --git a/src/app/api/reports/hours-by-client.xlsx/route.ts b/src/app/api/reports/hours-by-client.xlsx/route.ts index 6b11f07..c84601a 100644 --- a/src/app/api/reports/hours-by-client.xlsx/route.ts +++ b/src/app/api/reports/hours-by-client.xlsx/route.ts @@ -13,6 +13,8 @@ export async function GET(request: Request) { const range = searchParams.get("range") ?? undefined const q = searchParams.get("q")?.toLowerCase().trim() ?? "" const companyId = searchParams.get("companyId") ?? "" + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID @@ -29,6 +31,8 @@ export async function GET(request: Request) { range, companyId: companyId || undefined, search: q || undefined, + dateFrom, + dateTo, }) return new NextResponse(artifact.buffer, { diff --git a/src/app/api/reports/machine-category.xlsx/route.ts b/src/app/api/reports/machine-category.xlsx/route.ts new file mode 100644 index 0000000..15e64e1 --- /dev/null +++ b/src/app/api/reports/machine-category.xlsx/route.ts @@ -0,0 +1,54 @@ +import { NextResponse } from "next/server" + +import { assertAuthenticatedSession } from "@/lib/auth-server" +import { DEFAULT_TENANT_ID } from "@/lib/constants" +import { buildMachineCategoryWorkbook, createConvexContext } from "@/server/report-exporters" + +export const runtime = "nodejs" + +export async function GET(request: Request) { + const session = await assertAuthenticatedSession() + if (!session) { + return NextResponse.json({ error: "Não autorizado" }, { status: 401 }) + } + + const { searchParams } = new URL(request.url) + const range = searchParams.get("range") ?? undefined + const companyId = searchParams.get("companyId") ?? undefined + const machineId = searchParams.get("machineId") ?? undefined + const userId = searchParams.get("userId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined + + const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID + + try { + const context = await createConvexContext({ + tenantId, + name: session.user.name ?? session.user.email, + email: session.user.email, + avatarUrl: session.user.avatarUrl, + role: session.user.role.toUpperCase(), + }) + + const artifact = await buildMachineCategoryWorkbook(context, { + range, + companyId: companyId || undefined, + machineId: machineId || undefined, + userId: userId || undefined, + dateFrom, + dateTo, + }) + + return new NextResponse(artifact.buffer, { + headers: { + "Content-Type": artifact.mimeType, + "Content-Disposition": `attachment; filename="${artifact.fileName}"`, + "Cache-Control": "no-store", + }, + }) + } catch (error) { + console.error("Failed to generate machine category export", error) + return NextResponse.json({ error: "Falha ao gerar planilha de máquinas x categorias" }, { status: 500 }) + } +} diff --git a/src/app/api/reports/sla.xlsx/route.ts b/src/app/api/reports/sla.xlsx/route.ts index 9646c26..7a14886 100644 --- a/src/app/api/reports/sla.xlsx/route.ts +++ b/src/app/api/reports/sla.xlsx/route.ts @@ -14,6 +14,8 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url) const range = searchParams.get("range") ?? undefined const companyId = searchParams.get("companyId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID @@ -26,7 +28,12 @@ export async function GET(request: Request) { role: session.user.role.toUpperCase(), }) - const artifact = await buildSlaWorkbook(context, { range, companyId: companyId ?? undefined }) + const artifact = await buildSlaWorkbook(context, { + range, + companyId: companyId ?? undefined, + dateFrom, + dateTo, + }) return new NextResponse(artifact.buffer, { headers: { diff --git a/src/app/api/reports/tickets-by-channel.xlsx/route.ts b/src/app/api/reports/tickets-by-channel.xlsx/route.ts index cd968fc..ea9bf68 100644 --- a/src/app/api/reports/tickets-by-channel.xlsx/route.ts +++ b/src/app/api/reports/tickets-by-channel.xlsx/route.ts @@ -14,6 +14,8 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url) const range = searchParams.get("range") ?? undefined // "7d" | "30d" | undefined(=90d) const companyId = searchParams.get("companyId") ?? undefined + const dateFrom = searchParams.get("dateFrom") ?? undefined + const dateTo = searchParams.get("dateTo") ?? undefined const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID @@ -29,6 +31,8 @@ export async function GET(request: Request) { const artifact = await buildTicketsByChannelWorkbook(context, { range, companyId: companyId ?? undefined, + dateFrom, + dateTo, }) return new NextResponse(artifact.buffer, { diff --git a/src/app/tickets/new/page.tsx b/src/app/tickets/new/page.tsx index 3f4cd62..33831f2 100644 --- a/src/app/tickets/new/page.tsx +++ b/src/app/tickets/new/page.tsx @@ -101,7 +101,6 @@ export default function NewTicketPage() { }, [rawCustomers, viewerCustomer]) const [subject, setSubject] = useState("") - const [summary, setSummary] = useState("") const [priority, setPriority] = useState("MEDIUM") const [channel, setChannel] = useState("MANUAL") const [queueName, setQueueName] = useState(null) @@ -265,7 +264,6 @@ export default function NewTicketPage() { actorId: convexUserId as Id<"users">, tenantId: DEFAULT_TENANT_ID, subject: trimmedSubject, - summary: summary.trim() || undefined, priority, channel, queueId, @@ -321,18 +319,6 @@ export default function NewTicketPage() { /> {subjectError ?

{subjectError}

: null} -
- -