chore: sync staging
This commit is contained in:
parent
c5ddd54a3e
commit
561b19cf66
610 changed files with 105285 additions and 1206 deletions
|
|
@ -1,104 +1,45 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import { ConvexHttpClient } from "convex/browser"
|
||||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { env } from "@/lib/env"
|
||||
import { assertAuthenticatedSession } from "@/lib/auth-server"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { buildXlsxWorkbook } from "@/lib/xlsx"
|
||||
import { buildHoursWorkbook, 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 convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
||||
if (!convexUrl) return NextResponse.json({ error: "Convex não configurado" }, { status: 500 })
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const range = searchParams.get("range") ?? undefined
|
||||
const q = searchParams.get("q")?.toLowerCase().trim() ?? ""
|
||||
const companyId = searchParams.get("companyId") ?? ""
|
||||
|
||||
const client = new ConvexHttpClient(convexUrl)
|
||||
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
|
||||
|
||||
let viewerId: string | null = null
|
||||
try {
|
||||
const ensuredUser = await client.mutation(api.users.ensureUser, {
|
||||
const context = await createConvexContext({
|
||||
tenantId,
|
||||
name: session.user.name ?? session.user.email,
|
||||
email: session.user.email,
|
||||
avatarUrl: session.user.avatarUrl ?? undefined,
|
||||
avatarUrl: session.user.avatarUrl,
|
||||
role: session.user.role.toUpperCase(),
|
||||
})
|
||||
viewerId = ensuredUser?._id ?? null
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Falha ao sincronizar usuário com Convex" }, { status: 500 })
|
||||
}
|
||||
if (!viewerId) return NextResponse.json({ error: "Usuário não encontrado no Convex" }, { status: 403 })
|
||||
|
||||
try {
|
||||
const report = await client.query(api.reports.hoursByClient, {
|
||||
tenantId,
|
||||
viewerId: viewerId as unknown as Id<"users">,
|
||||
const artifact = await buildHoursWorkbook(context, {
|
||||
range,
|
||||
companyId: companyId || undefined,
|
||||
search: q || undefined,
|
||||
})
|
||||
|
||||
const summaryRows: Array<Array<unknown>> = [
|
||||
["Relatório", "Horas por cliente"],
|
||||
["Período", report.rangeDays ? `Últimos ${report.rangeDays} dias` : range ?? "90d"],
|
||||
]
|
||||
if (q) summaryRows.push(["Filtro", q])
|
||||
if (companyId) summaryRows.push(["EmpresaId", companyId])
|
||||
summaryRows.push(["Total de clientes", (report.items as Array<unknown>).length])
|
||||
|
||||
type Item = { companyId: string; name: string; isAvulso: boolean; internalMs: number; externalMs: number; totalMs: number; contractedHoursPerMonth: number | null }
|
||||
let items = (report.items as Item[])
|
||||
if (companyId) items = items.filter((i) => String(i.companyId) === companyId)
|
||||
if (q) items = items.filter((i) => i.name.toLowerCase().includes(q))
|
||||
|
||||
const dataRows = items.map((item) => {
|
||||
const internalHours = item.internalMs / 3600000
|
||||
const externalHours = item.externalMs / 3600000
|
||||
const totalHours = item.totalMs / 3600000
|
||||
const contracted = item.contractedHoursPerMonth
|
||||
const usagePct = contracted ? (totalHours / contracted) * 100 : null
|
||||
return [
|
||||
item.name,
|
||||
item.isAvulso ? "Sim" : "Não",
|
||||
Number(internalHours.toFixed(2)),
|
||||
Number(externalHours.toFixed(2)),
|
||||
Number(totalHours.toFixed(2)),
|
||||
contracted ?? null,
|
||||
usagePct !== null ? Number(usagePct.toFixed(1)) : null,
|
||||
]
|
||||
})
|
||||
|
||||
const workbook = buildXlsxWorkbook([
|
||||
{
|
||||
name: "Resumo",
|
||||
headers: ["Item", "Valor"],
|
||||
rows: summaryRows,
|
||||
},
|
||||
{
|
||||
name: "Clientes",
|
||||
headers: ["Cliente", "Avulso", "Horas internas", "Horas externas", "Horas totais", "Horas contratadas/mês", "% uso"],
|
||||
rows: dataRows.length > 0 ? dataRows : [["—", "—", 0, 0, 0, null, null]],
|
||||
},
|
||||
])
|
||||
|
||||
const body = new Uint8Array(workbook)
|
||||
|
||||
return new NextResponse(body, {
|
||||
return new NextResponse(artifact.buffer, {
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"Content-Disposition": `attachment; filename="hours-by-client-${tenantId}-${report.rangeDays ?? '90'}d${companyId ? `-${companyId}` : ''}${q ? `-${encodeURIComponent(q)}` : ''}.xlsx"`,
|
||||
"Content-Type": artifact.mimeType,
|
||||
"Content-Disposition": `attachment; filename="${artifact.fileName}"`,
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error("Falha ao gerar planilha de horas por cliente", error)
|
||||
return NextResponse.json({ error: "Falha ao gerar planilha de horas por cliente" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue