feat: export reports as xlsx and add machine inventory
This commit is contained in:
parent
29b865885c
commit
714b199879
34 changed files with 2304 additions and 245 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import type { UserRole } from "@prisma/client"
|
||||
import type { Prisma, UserRole } from "@prisma/client"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { ConvexHttpClient } from "convex/browser"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
|
@ -57,6 +57,9 @@ export async function GET(_: Request, { params }: { params: Promise<{ id: string
|
|||
select: {
|
||||
companyId: true,
|
||||
company: { select: { name: true } },
|
||||
jobTitle: true,
|
||||
managerId: true,
|
||||
manager: { select: { id: true, name: true, email: true } },
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -72,6 +75,10 @@ export async function GET(_: Request, { params }: { params: Promise<{ id: string
|
|||
companyId: domain?.companyId ?? null,
|
||||
companyName: domain?.company?.name ?? null,
|
||||
machinePersona: user.machinePersona ?? null,
|
||||
jobTitle: domain?.jobTitle ?? null,
|
||||
managerId: domain?.managerId ?? null,
|
||||
managerName: domain?.manager?.name ?? null,
|
||||
managerEmail: domain?.manager?.email ?? null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -90,6 +97,8 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
role?: RoleOption
|
||||
tenantId?: string
|
||||
companyId?: string | null
|
||||
jobTitle?: string | null
|
||||
managerId?: string | null
|
||||
} | null
|
||||
|
||||
if (!payload || typeof payload !== "object") {
|
||||
|
|
@ -106,6 +115,39 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
const nextRole = normalizeRole(payload.role ?? user.role)
|
||||
const nextTenant = (payload.tenantId ?? user.tenantId ?? DEFAULT_TENANT_ID).trim() || DEFAULT_TENANT_ID
|
||||
const companyId = payload.companyId ? payload.companyId : null
|
||||
const hasJobTitleField = Object.prototype.hasOwnProperty.call(payload, "jobTitle")
|
||||
let jobTitle: string | null | undefined
|
||||
if (hasJobTitleField) {
|
||||
if (typeof payload.jobTitle === "string") {
|
||||
const trimmed = payload.jobTitle.trim()
|
||||
jobTitle = trimmed.length > 0 ? trimmed : null
|
||||
} else {
|
||||
jobTitle = null
|
||||
}
|
||||
}
|
||||
const hasManagerField = Object.prototype.hasOwnProperty.call(payload, "managerId")
|
||||
let managerIdValue: string | null | undefined
|
||||
if (hasManagerField) {
|
||||
if (typeof payload.managerId === "string") {
|
||||
const trimmed = payload.managerId.trim()
|
||||
managerIdValue = trimmed.length > 0 ? trimmed : null
|
||||
} else {
|
||||
managerIdValue = null
|
||||
}
|
||||
}
|
||||
let managerRecord: { id: string; email: string; tenantId: string; name: string } | null = null
|
||||
if (managerIdValue && managerIdValue !== null) {
|
||||
managerRecord = await prisma.user.findUnique({
|
||||
where: { id: managerIdValue },
|
||||
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 !== nextTenant) {
|
||||
return NextResponse.json({ error: "Gestor pertence a outro cliente." }, { status: 400 })
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEmail || !nextEmail.includes("@")) {
|
||||
return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
|
||||
|
|
@ -159,33 +201,58 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
return NextResponse.json({ error: "Empresa não encontrada" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (domainUser && managerRecord && managerRecord.id === domainUser.id) {
|
||||
return NextResponse.json({ error: "Um usuário não pode ser gestor de si mesmo." }, { status: 400 })
|
||||
}
|
||||
|
||||
if (domainUser) {
|
||||
const updateData: Prisma.UserUncheckedUpdateInput = {
|
||||
email: nextEmail,
|
||||
name: nextName || domainUser.name,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
}
|
||||
if (hasJobTitleField) {
|
||||
updateData.jobTitle = jobTitle ?? null
|
||||
}
|
||||
if (hasManagerField) {
|
||||
updateData.managerId = managerRecord?.id ?? null
|
||||
}
|
||||
await prisma.user.update({
|
||||
where: { id: domainUser.id },
|
||||
data: {
|
||||
email: nextEmail,
|
||||
name: nextName || domainUser.name,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
},
|
||||
data: updateData,
|
||||
})
|
||||
} else {
|
||||
const upsertUpdate: Prisma.UserUncheckedUpdateInput = {
|
||||
name: nextName || nextEmail,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
}
|
||||
if (hasJobTitleField) {
|
||||
upsertUpdate.jobTitle = jobTitle ?? null
|
||||
}
|
||||
if (hasManagerField) {
|
||||
upsertUpdate.managerId = managerRecord?.id ?? null
|
||||
}
|
||||
const upsertCreate: Prisma.UserUncheckedCreateInput = {
|
||||
email: nextEmail,
|
||||
name: nextName || nextEmail,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
}
|
||||
if (hasJobTitleField) {
|
||||
upsertCreate.jobTitle = jobTitle ?? null
|
||||
}
|
||||
if (hasManagerField) {
|
||||
upsertCreate.managerId = managerRecord?.id ?? null
|
||||
}
|
||||
await prisma.user.upsert({
|
||||
where: { email: nextEmail },
|
||||
update: {
|
||||
name: nextName || nextEmail,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
},
|
||||
create: {
|
||||
email: nextEmail,
|
||||
name: nextName || nextEmail,
|
||||
role: mapToUserRole(nextRole),
|
||||
tenantId: nextTenant,
|
||||
companyId: companyId ?? null,
|
||||
},
|
||||
update: upsertUpdate,
|
||||
create: upsertCreate,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -193,19 +260,60 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
if (convexUrl) {
|
||||
try {
|
||||
const convex = new ConvexHttpClient(convexUrl)
|
||||
await convex.mutation(api.users.ensureUser, {
|
||||
let managerConvexId: Id<"users"> | undefined
|
||||
if (hasManagerField && managerRecord?.email) {
|
||||
try {
|
||||
const managerUser = await convex.query(api.users.findByEmail, {
|
||||
tenantId: nextTenant,
|
||||
email: managerRecord.email,
|
||||
})
|
||||
if (managerUser?._id) {
|
||||
managerConvexId = managerUser._id as Id<"users">
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Falha ao localizar gestor no Convex", error)
|
||||
}
|
||||
}
|
||||
const ensurePayload: {
|
||||
tenantId: string
|
||||
email: string
|
||||
name: string
|
||||
avatarUrl?: string
|
||||
role: string
|
||||
companyId?: Id<"companies">
|
||||
jobTitle?: string | undefined
|
||||
managerId?: Id<"users">
|
||||
} = {
|
||||
tenantId: nextTenant,
|
||||
email: nextEmail,
|
||||
name: nextName || nextEmail,
|
||||
avatarUrl: updated.avatarUrl ?? undefined,
|
||||
role: nextRole.toUpperCase(),
|
||||
companyId: companyId ? (companyId as Id<"companies">) : undefined,
|
||||
})
|
||||
}
|
||||
if (companyId) {
|
||||
ensurePayload.companyId = companyId as Id<"companies">
|
||||
}
|
||||
if (hasJobTitleField) {
|
||||
ensurePayload.jobTitle = jobTitle ?? undefined
|
||||
}
|
||||
if (hasManagerField) {
|
||||
ensurePayload.managerId = managerConvexId
|
||||
}
|
||||
await convex.mutation(api.users.ensureUser, ensurePayload)
|
||||
} catch (error) {
|
||||
console.warn("Falha ao sincronizar usuário no Convex", error)
|
||||
}
|
||||
}
|
||||
|
||||
const updatedDomain = await prisma.user.findUnique({
|
||||
where: { email: nextEmail },
|
||||
select: {
|
||||
jobTitle: true,
|
||||
managerId: true,
|
||||
manager: { select: { name: true, email: true } },
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user: {
|
||||
id: updated.id,
|
||||
|
|
@ -218,6 +326,10 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
companyId,
|
||||
companyName: companyData?.name ?? null,
|
||||
machinePersona: updated.machinePersona ?? null,
|
||||
jobTitle: updatedDomain?.jobTitle ?? null,
|
||||
managerId: updatedDomain?.managerId ?? null,
|
||||
managerName: updatedDomain?.manager?.name ?? null,
|
||||
managerEmail: updatedDomain?.manager?.email ?? null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue