Ajusta placeholders, formulários e widgets

This commit is contained in:
Esdras Renan 2025-11-06 23:13:41 -03:00
parent 343f0c8c64
commit b94cea2f9a
33 changed files with 2122 additions and 462 deletions

View file

@ -725,6 +725,143 @@ export const agentProductivity = query({
handler: agentProductivityHandler,
})
type CategoryAgentAccumulator = {
id: Id<"ticketCategories"> | null
name: string
total: number
resolved: number
agents: Map<string, { agentId: Id<"users"> | null; name: string | null; total: number }>
}
export async function ticketCategoryInsightsHandler(
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 inRange = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId)
const categories = await ctx.db
.query("ticketCategories")
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
.collect()
const categoriesById = new Map<Id<"ticketCategories">, Doc<"ticketCategories">>()
for (const category of categories) {
categoriesById.set(category._id, category)
}
const stats = new Map<string, CategoryAgentAccumulator>()
for (const ticket of inRange) {
const categoryKey = ticket.categoryId ? String(ticket.categoryId) : "uncategorized"
let stat = stats.get(categoryKey)
if (!stat) {
const categoryDoc = ticket.categoryId ? categoriesById.get(ticket.categoryId) : null
stat = {
id: ticket.categoryId ?? null,
name: categoryDoc?.name ?? (ticket.categoryId ? "Categoria removida" : "Sem categoria"),
total: 0,
resolved: 0,
agents: new Map(),
}
stats.set(categoryKey, stat)
}
stat.total += 1
if (typeof ticket.resolvedAt === "number" && ticket.resolvedAt >= startMs && ticket.resolvedAt < endMs) {
stat.resolved += 1
}
const agentKey = ticket.assigneeId ? String(ticket.assigneeId) : "unassigned"
let agent = stat.agents.get(agentKey)
if (!agent) {
const snapshotName = ticket.assigneeSnapshot?.name ?? null
const fallbackName = ticket.assigneeId ? null : "Sem responsável"
agent = {
agentId: ticket.assigneeId ?? null,
name: snapshotName ?? fallbackName ?? "Agente",
total: 0,
}
stat.agents.set(agentKey, agent)
}
agent.total += 1
}
const categoriesData = Array.from(stats.values())
.map((stat) => {
const agents = Array.from(stat.agents.values()).sort((a, b) => b.total - a.total)
const topAgent = agents[0] ?? null
return {
id: stat.id ? String(stat.id) : null,
name: stat.name,
total: stat.total,
resolved: stat.resolved,
topAgent: topAgent
? {
id: topAgent.agentId ? String(topAgent.agentId) : null,
name: topAgent.name,
total: topAgent.total,
}
: null,
agents: agents.slice(0, 5).map((agent) => ({
id: agent.agentId ? String(agent.agentId) : null,
name: agent.name,
total: agent.total,
})),
}
})
.sort((a, b) => b.total - a.total)
const spotlight = categoriesData.reduce<
| null
| {
categoryId: string | null
categoryName: string
agentId: string | null
agentName: string | null
tickets: number
}
>((best, current) => {
if (!current.topAgent) return best
if (!best || current.topAgent.total > best.tickets) {
return {
categoryId: current.id,
categoryName: current.name,
agentId: current.topAgent.id,
agentName: current.topAgent.name,
tickets: current.topAgent.total,
}
}
return best
}, null)
return {
rangeDays: days,
totalTickets: inRange.length,
categories: categoriesData,
spotlight,
}
}
export const categoryInsights = query({
args: {
tenantId: v.string(),
viewerId: v.id("users"),
range: v.optional(v.string()),
companyId: v.optional(v.id("companies")),
},
handler: ticketCategoryInsightsHandler,
})
export async function dashboardOverviewHandler(
ctx: QueryCtx,
{ tenantId, viewerId }: { tenantId: string; viewerId: Id<"users"> }