feat: machine reports with filters and hours

This commit is contained in:
Esdras Renan 2025-11-13 23:45:24 -03:00
parent 6938bebdbb
commit 82875a2252
2 changed files with 403 additions and 11 deletions

View file

@ -1335,7 +1335,16 @@ export async function ticketsByMachineAndCategoryHandler(
viewerId,
range,
companyId,
}: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> }
machineId,
userId,
}: {
tenantId: string
viewerId: Id<"users">
range?: string
companyId?: Id<"companies">
machineId?: Id<"machines">
userId?: Id<"users">
}
) {
const viewer = await requireStaff(ctx, viewerId, tenantId)
const days = range === "7d" ? 7 : range === "30d" ? 30 : 90
@ -1372,8 +1381,11 @@ export async function ticketsByMachineAndCategoryHandler(
const hasMachine = Boolean(ticket.machineId) || Boolean(ticket.machineSnapshot)
if (!hasMachine) continue
if (machineId && ticket.machineId !== machineId) continue
if (userId && ticket.requesterId !== userId) continue
const date = formatDateKey(createdAt)
const machineId = (ticket.machineId ?? null) as Id<"machines"> | null
const machineIdValue = (ticket.machineId ?? null) as Id<"machines"> | null
const machineSnapshot = (ticket.machineSnapshot ?? null) as
| {
hostname?: string | null
@ -1414,19 +1426,19 @@ export async function ticketsByMachineAndCategoryHandler(
const key = [
date,
machineId ? String(machineId) : "null",
machineIdValue ? String(machineIdValue) : "null",
machineHostname ?? "",
rawCategoryId ?? "uncategorized",
companyIdValue ? String(companyIdValue) : "null",
].join("|")
const existing = aggregated.get(key)
if (existing) {
existing.total += 1
} else {
aggregated.set(key, {
date,
machineId,
const existing = aggregated.get(key)
if (existing) {
existing.total += 1
} else {
aggregated.set(key, {
date,
machineId: machineIdValue,
machineHostname,
companyId: companyIdValue,
companyName,
@ -1457,10 +1469,142 @@ export const ticketsByMachineAndCategory = query({
viewerId: v.id("users"),
range: v.optional(v.string()),
companyId: v.optional(v.id("companies")),
machineId: v.optional(v.id("machines")),
userId: v.optional(v.id("users")),
},
handler: ticketsByMachineAndCategoryHandler,
})
type MachineHoursEntry = {
machineId: Id<"machines">
machineHostname: string | null
companyId: Id<"companies"> | null
companyName: string | null
internalMs: number
externalMs: number
totalMs: number
}
export async function hoursByMachineHandler(
ctx: QueryCtx,
{
tenantId,
viewerId,
range,
companyId,
machineId,
userId,
}: {
tenantId: string
viewerId: Id<"users">
range?: string
companyId?: Id<"companies">
machineId?: Id<"machines">
userId?: Id<"users">
}
) {
const viewer = await requireStaff(ctx, viewerId, tenantId)
const tickets = await fetchScopedTickets(ctx, tenantId, viewer)
const days = range === "7d" ? 7 : range === "30d" ? 30 : 90
const end = new Date()
end.setUTCHours(0, 0, 0, 0)
const endMs = end.getTime() + ONE_DAY_MS
const startMs = endMs - days * ONE_DAY_MS
const machinesById = new Map<string, Doc<"machines"> | null>()
const companiesById = new Map<string, Doc<"companies"> | null>()
const map = new Map<string, MachineHoursEntry>()
for (const t of tickets) {
if (t.updatedAt < startMs || t.updatedAt >= endMs) continue
if (companyId && t.companyId && t.companyId !== companyId) continue
if (machineId && t.machineId !== machineId) continue
if (userId && t.requesterId !== userId) continue
const machineIdValue = (t.machineId ?? null) as Id<"machines"> | null
if (!machineIdValue) continue
const key = String(machineIdValue)
let acc = map.get(key)
if (!acc) {
let machineDoc = machinesById.get(key)
if (machineDoc === undefined) {
machineDoc = (await ctx.db.get(machineIdValue)) as Doc<"machines"> | null
machinesById.set(key, machineDoc ?? null)
}
const snapshot = (t.machineSnapshot ?? null) as { hostname?: string | null } | null
const machineHostname =
typeof machineDoc?.hostname === "string" && machineDoc.hostname.trim().length > 0
? machineDoc.hostname.trim()
: snapshot?.hostname && snapshot.hostname.trim().length > 0
? snapshot.hostname.trim()
: null
const companyIdValue = (t.companyId ??
(machineDoc?.companyId as Id<"companies"> | undefined) ??
null) as Id<"companies"> | null
let companyName: string | null = null
if (companyIdValue) {
const companyKey = String(companyIdValue)
let companyDoc = companiesById.get(companyKey)
if (companyDoc === undefined) {
companyDoc = (await ctx.db.get(companyIdValue)) as Doc<"companies"> | null
companiesById.set(companyKey, companyDoc ?? null)
}
if (companyDoc?.name && companyDoc.name.trim().length > 0) {
companyName = companyDoc.name.trim()
}
}
acc = {
machineId: machineIdValue,
machineHostname,
companyId: companyIdValue,
companyName,
internalMs: 0,
externalMs: 0,
totalMs: 0,
}
map.set(key, acc)
}
const internal = (t.internalWorkedMs ?? 0) as number
const external = (t.externalWorkedMs ?? 0) as number
acc.internalMs += internal
acc.externalMs += external
acc.totalMs += internal + external
}
const items = Array.from(map.values()).sort((a, b) => {
if (b.totalMs !== a.totalMs) return b.totalMs - a.totalMs
const hostA = (a.machineHostname ?? "").toLowerCase()
const hostB = (b.machineHostname ?? "").toLowerCase()
return hostA.localeCompare(hostB)
})
return {
rangeDays: days,
items,
}
}
export const hoursByMachine = query({
args: {
tenantId: v.string(),
viewerId: v.id("users"),
range: v.optional(v.string()),
companyId: v.optional(v.id("companies")),
machineId: v.optional(v.id("machines")),
userId: v.optional(v.id("users")),
},
handler: hoursByMachineHandler,
})
export async function hoursByClientHandler(
ctx: QueryCtx,
{ tenantId, viewerId, range }: { tenantId: string; viewerId: Id<"users">; range?: string }