Ajusta placeholders, formulários e widgets
This commit is contained in:
parent
343f0c8c64
commit
b94cea2f9a
33 changed files with 2122 additions and 462 deletions
|
|
@ -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"> }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue