feat: add queue summary widget and layout fixes
This commit is contained in:
parent
f7976e2c39
commit
a542846313
12 changed files with 350 additions and 45 deletions
|
|
@ -13,6 +13,7 @@ const WIDGET_TYPES = [
|
|||
"radar",
|
||||
"gauge",
|
||||
"table",
|
||||
"queue-summary",
|
||||
"text",
|
||||
] as const
|
||||
|
||||
|
|
@ -145,6 +146,12 @@ function normalizeWidgetConfig(type: WidgetType, config: unknown) {
|
|||
],
|
||||
options: { downloadCSV: true },
|
||||
}
|
||||
case "queue-summary":
|
||||
return {
|
||||
type: "queue-summary",
|
||||
title: "Resumo por fila",
|
||||
dataSource: { metricKey: "queues.summary_cards" },
|
||||
}
|
||||
case "text":
|
||||
default:
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,24 @@ function filterTicketsByQueue<T extends { queueId?: Id<"queues"> | null }>(
|
|||
})
|
||||
}
|
||||
|
||||
const QUEUE_RENAME_LOOKUP: Record<string, string> = {
|
||||
"Suporte N1": "Chamados",
|
||||
"suporte-n1": "Chamados",
|
||||
chamados: "Chamados",
|
||||
"Suporte N2": "Laboratório",
|
||||
"suporte-n2": "Laboratório",
|
||||
laboratorio: "Laboratório",
|
||||
Laboratorio: "Laboratório",
|
||||
visitas: "Visitas",
|
||||
}
|
||||
|
||||
function renameQueueName(value: string) {
|
||||
const direct = QUEUE_RENAME_LOOKUP[value]
|
||||
if (direct) return direct
|
||||
const normalizedKey = value.replace(/\s+/g, "-").toLowerCase()
|
||||
return QUEUE_RENAME_LOOKUP[normalizedKey] ?? value
|
||||
}
|
||||
|
||||
type AgentStatsRaw = {
|
||||
agentId: Id<"users">
|
||||
name: string | null
|
||||
|
|
@ -441,6 +459,97 @@ const metricResolvers: Record<string, MetricResolver> = {
|
|||
data,
|
||||
}
|
||||
},
|
||||
"queues.summary_cards": async (ctx, { tenantId, viewer, params }) => {
|
||||
const queueFilter = parseQueueIds(params)
|
||||
const filterHas = queueFilter && queueFilter.length > 0
|
||||
const normalizeKey = (id: Id<"queues"> | null) => (id ? String(id) : "sem-fila")
|
||||
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).collect()
|
||||
const queueNameMap = new Map<string, string>()
|
||||
queues.forEach((queue) => {
|
||||
const key = String(queue._id)
|
||||
queueNameMap.set(key, renameQueueName(queue.name))
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
const stats = new Map<
|
||||
string,
|
||||
{ id: string; name: string; pending: number; inProgress: number; paused: number; breached: number }
|
||||
>()
|
||||
|
||||
const ensureEntry = (key: string, fallbackName?: string) => {
|
||||
if (!stats.has(key)) {
|
||||
const resolvedName =
|
||||
queueNameMap.get(key) ??
|
||||
(key === "sem-fila" ? "Sem fila" : fallbackName ?? "Fila desconhecida")
|
||||
stats.set(key, {
|
||||
id: key,
|
||||
name: resolvedName,
|
||||
pending: 0,
|
||||
inProgress: 0,
|
||||
paused: 0,
|
||||
breached: 0,
|
||||
})
|
||||
}
|
||||
return stats.get(key)!
|
||||
}
|
||||
|
||||
for (const queue of queues) {
|
||||
const key = String(queue._id)
|
||||
if (filterHas && queueFilter && !queueFilter.includes(key)) continue
|
||||
ensureEntry(key)
|
||||
}
|
||||
|
||||
const scopedTickets = await fetchScopedTickets(ctx, tenantId, viewer)
|
||||
for (const ticket of scopedTickets) {
|
||||
const key = normalizeKey(ticket.queueId ?? null)
|
||||
if (filterHas && queueFilter && !queueFilter.includes(key)) continue
|
||||
const entry = ensureEntry(key)
|
||||
const status = normalizeStatus(ticket.status)
|
||||
if (status === "PENDING") {
|
||||
entry.pending += 1
|
||||
} else if (status === "AWAITING_ATTENDANCE") {
|
||||
entry.inProgress += 1
|
||||
} else if (status === "PAUSED") {
|
||||
entry.paused += 1
|
||||
}
|
||||
if (status !== "RESOLVED") {
|
||||
const dueAt = typeof ticket.dueAt === "number" ? ticket.dueAt : null
|
||||
if (dueAt && dueAt < now) {
|
||||
entry.breached += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(filterHas && queueFilter && !queueFilter.includes("sem-fila"))) {
|
||||
ensureEntry("sem-fila", "Sem fila")
|
||||
} else if (filterHas) {
|
||||
stats.delete("sem-fila")
|
||||
}
|
||||
|
||||
const data = Array.from(stats.values()).map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
pending: item.pending,
|
||||
inProgress: item.inProgress,
|
||||
paused: item.paused,
|
||||
breached: item.breached,
|
||||
}))
|
||||
|
||||
data.sort((a, b) => {
|
||||
const totalA = a.pending + a.inProgress + a.paused
|
||||
const totalB = b.pending + b.inProgress + b.paused
|
||||
if (totalA === totalB) {
|
||||
return a.name.localeCompare(b.name, "pt-BR")
|
||||
}
|
||||
return totalB - totalA
|
||||
})
|
||||
|
||||
return {
|
||||
meta: { kind: "collection", key: "queues.summary_cards" },
|
||||
data,
|
||||
}
|
||||
},
|
||||
"tickets.sla_compliance_by_queue": async (ctx, { tenantId, viewer, params }) => {
|
||||
const rangeDays = parseRange(params)
|
||||
const companyId = parseCompanyId(params)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue