feat: portal reopen, reports, templates and remote access

This commit is contained in:
Esdras Renan 2025-11-13 23:22:17 -03:00
parent 6a75a0a9ed
commit 52c03ff1cf
16 changed files with 1387 additions and 16 deletions

View file

@ -1317,6 +1317,150 @@ export const ticketsByChannel = query({
handler: ticketsByChannelHandler,
});
type MachineCategoryDailyEntry = {
date: string
machineId: Id<"machines"> | null
machineHostname: string | null
companyId: Id<"companies"> | null
companyName: string | null
categoryId: Id<"ticketCategories"> | null
categoryName: string
total: number
}
export async function ticketsByMachineAndCategoryHandler(
ctx: QueryCtx,
{
tenantId,
viewerId,
range,
companyId,
}: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> }
) {
const viewer = await requireStaff(ctx, viewerId, tenantId)
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 tickets = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId)
const categoriesMap = await fetchCategoryMap(ctx, tenantId)
const companyIds = new Set<Id<"companies">>()
for (const ticket of tickets) {
if (ticket.companyId) {
companyIds.add(ticket.companyId)
}
}
const companiesById = new Map<string, Doc<"companies"> | null>()
await Promise.all(
Array.from(companyIds).map(async (id) => {
const doc = await ctx.db.get(id)
companiesById.set(String(id), doc ?? null)
})
)
const aggregated = new Map<string, MachineCategoryDailyEntry>()
for (const ticket of tickets) {
const createdAt = typeof ticket.createdAt === "number" ? ticket.createdAt : null
if (createdAt === null || createdAt < startMs || createdAt >= endMs) continue
const hasMachine = Boolean(ticket.machineId) || Boolean(ticket.machineSnapshot)
if (!hasMachine) continue
const date = formatDateKey(createdAt)
const machineId = (ticket.machineId ?? null) as Id<"machines"> | null
const machineSnapshot = (ticket.machineSnapshot ?? null) as
| {
hostname?: string | null
}
| null
const rawHostname =
typeof machineSnapshot?.hostname === "string" && machineSnapshot.hostname.trim().length > 0
? machineSnapshot.hostname.trim()
: null
const machineHostname = rawHostname ?? null
const snapshot = (ticket.slaSnapshot ?? null) as { categoryId?: Id<"ticketCategories">; categoryName?: string } | null
const rawCategoryId =
ticket.categoryId && typeof ticket.categoryId === "string"
? String(ticket.categoryId)
: snapshot?.categoryId
? String(snapshot.categoryId)
: null
const categoryName = resolveCategoryName(rawCategoryId, snapshot, categoriesMap)
const companyIdValue = (ticket.companyId ?? null) as Id<"companies"> | null
let companyName: string | null = null
if (companyIdValue) {
const company = companiesById.get(String(companyIdValue))
if (company?.name && company.name.trim().length > 0) {
companyName = company.name.trim()
}
}
if (!companyName) {
const companySnapshot = (ticket.companySnapshot ?? null) as { name?: string | null } | null
if (companySnapshot?.name && companySnapshot.name.trim().length > 0) {
companyName = companySnapshot.name.trim()
}
}
if (!companyName) {
companyName = "Sem empresa"
}
const key = [
date,
machineId ? String(machineId) : "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,
machineHostname,
companyId: companyIdValue,
companyName,
categoryId: (rawCategoryId as Id<"ticketCategories"> | null) ?? null,
categoryName,
total: 1,
})
}
}
const items = Array.from(aggregated.values()).sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date)
const machineA = (a.machineHostname ?? "").toLowerCase()
const machineB = (b.machineHostname ?? "").toLowerCase()
if (machineA !== machineB) return machineA.localeCompare(machineB)
return a.categoryName.localeCompare(b.categoryName, "pt-BR")
})
return {
rangeDays: days,
items,
}
}
export const ticketsByMachineAndCategory = query({
args: {
tenantId: v.string(),
viewerId: v.id("users"),
range: v.optional(v.string()),
companyId: v.optional(v.id("companies")),
},
handler: ticketsByMachineAndCategoryHandler,
})
export async function hoursByClientHandler(
ctx: QueryCtx,
{ tenantId, viewerId, range }: { tenantId: string; viewerId: Id<"users">; range?: string }