feat: export reports as xlsx and add machine inventory

This commit is contained in:
Esdras Renan 2025-10-27 18:00:28 -03:00
parent 29b865885c
commit 714b199879
34 changed files with 2304 additions and 245 deletions

View file

@ -3,6 +3,7 @@ import { NextResponse } from "next/server"
import { hashPassword } from "better-auth/crypto"
import { ConvexHttpClient } from "convex/browser"
import type { UserRole } from "@prisma/client"
import type { Id } from "@/convex/_generated/dataModel"
import { prisma } from "@/lib/prisma"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
@ -52,6 +53,13 @@ export async function GET() {
name: true,
},
},
manager: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: { createdAt: "desc" },
})
@ -102,6 +110,10 @@ export async function GET() {
role: normalizeRole(user.role),
companyId: user.companyId,
companyName: user.company?.name ?? null,
jobTitle: user.jobTitle ?? null,
managerId: user.managerId,
managerName: user.manager?.name ?? null,
managerEmail: user.manager?.email ?? null,
tenantId: user.tenantId,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt.toISOString(),
@ -124,6 +136,8 @@ export async function POST(request: Request) {
email?: string
role?: string
tenantId?: string
jobTitle?: string | null
managerId?: string | null
} | null
if (!body || typeof body !== "object") {
@ -141,6 +155,25 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
}
const rawJobTitle = typeof body.jobTitle === "string" ? body.jobTitle.trim() : null
const jobTitle = rawJobTitle ? rawJobTitle : null
const rawManagerId = typeof body.managerId === "string" ? body.managerId.trim() : ""
const managerId = rawManagerId.length > 0 ? rawManagerId : null
let managerRecord: { id: string; email: string; tenantId: string; name: string } | null = null
if (managerId) {
managerRecord = await prisma.user.findUnique({
where: { id: managerId },
select: { id: true, email: true, tenantId: true, name: true },
})
if (!managerRecord) {
return NextResponse.json({ error: "Gestor informado não foi encontrado." }, { status: 400 })
}
if (managerRecord.tenantId !== tenantId) {
return NextResponse.json({ error: "Gestor pertence a outro cliente." }, { status: 400 })
}
}
const normalizedRole = normalizeRole(body.role)
const authRole = normalizedRole.toLowerCase()
const userRole = normalizedRole as UserRole
@ -176,12 +209,16 @@ export async function POST(request: Request) {
name,
role: userRole,
tenantId,
jobTitle,
managerId: managerRecord?.id ?? null,
},
create: {
name,
email,
role: userRole,
tenantId,
jobTitle,
managerId: managerRecord?.id ?? null,
},
})
@ -192,12 +229,28 @@ export async function POST(request: Request) {
if (convexUrl) {
try {
const convex = new ConvexHttpClient(convexUrl)
let managerConvexId: Id<"users"> | undefined
if (managerRecord?.email) {
try {
const convexManager = await convex.query(api.users.findByEmail, {
tenantId,
email: managerRecord.email,
})
if (convexManager?._id) {
managerConvexId = convexManager._id as Id<"users">
}
} catch (error) {
console.warn("[admin/users] Falha ao localizar gestor no Convex", error)
}
}
await convex.mutation(api.users.ensureUser, {
tenantId,
email,
name,
avatarUrl: authUser.avatarUrl ?? undefined,
role: userRole,
jobTitle: jobTitle ?? undefined,
managerId: managerConvexId,
})
} catch (error) {
console.error("[admin/users] ensureUser failed", error)
@ -213,6 +266,10 @@ export async function POST(request: Request) {
role: authRole,
tenantId: domainUser.tenantId,
createdAt: domainUser.createdAt.toISOString(),
jobTitle,
managerId: managerRecord?.id ?? null,
managerName: managerRecord?.name ?? null,
managerEmail: managerRecord?.email ?? null,
},
temporaryPassword,
})