feat: add SLA statistics and real-time monitoring to central-chamados
- Introduced new queries to fetch SLA statistics and real-time SLA data for better ticket management insights. - Enhanced the central-chamados route to display SLA performance metrics, including compliance rates and ticket statuses by priority. - Implemented fallback logic for statistics calculation to ensure data availability even when queries return undefined. - Refactored the UI to include a dedicated section for SLA performance, improving user experience and data visibility.
This commit is contained in:
@@ -520,6 +520,102 @@ export const listarSlaConfigs = query({
|
||||
},
|
||||
});
|
||||
|
||||
export const obterEstatisticasChamados = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
await assertAuth(ctx);
|
||||
const todosTickets = await ctx.db.query("tickets").collect();
|
||||
|
||||
const total = todosTickets.length;
|
||||
const abertos = todosTickets.filter((t) => t.status === "aberto").length;
|
||||
const emAndamento = todosTickets.filter((t) => t.status === "em_andamento").length;
|
||||
const vencidos = todosTickets.filter(
|
||||
(t) => (t.prazoConclusao && t.prazoConclusao < Date.now()) || t.status === "cancelado"
|
||||
).length;
|
||||
|
||||
return { total, abertos, emAndamento, vencidos };
|
||||
},
|
||||
});
|
||||
|
||||
export const obterDadosSlaGrafico = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
await assertAuth(ctx);
|
||||
const agora = Date.now();
|
||||
const todosTickets = await ctx.db.query("tickets").collect();
|
||||
const slaConfigs = await ctx.db.query("slaConfigs").collect();
|
||||
|
||||
// Agrupar SLAs por prioridade
|
||||
const slaPorPrioridade = new Map<string, Doc<"slaConfigs">>();
|
||||
slaConfigs.filter(s => s.ativo).forEach(sla => {
|
||||
if (sla.prioridade) {
|
||||
slaPorPrioridade.set(sla.prioridade, sla);
|
||||
}
|
||||
});
|
||||
|
||||
// Calcular status de SLA para cada ticket
|
||||
const statusSla = {
|
||||
dentroPrazo: 0,
|
||||
proximoVencimento: 0,
|
||||
vencido: 0,
|
||||
semPrazo: 0,
|
||||
};
|
||||
|
||||
const porPrioridade = {
|
||||
baixa: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 },
|
||||
media: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 },
|
||||
alta: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 },
|
||||
critica: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 },
|
||||
};
|
||||
|
||||
todosTickets.forEach(ticket => {
|
||||
if (!ticket.prazoConclusao) {
|
||||
statusSla.semPrazo++;
|
||||
return;
|
||||
}
|
||||
|
||||
const prazoConclusao = ticket.prazoConclusao;
|
||||
const horasRestantes = (prazoConclusao - agora) / (1000 * 60 * 60);
|
||||
const sla = slaPorPrioridade.get(ticket.prioridade);
|
||||
const alertaHoras = sla?.alertaAntecedenciaHoras ?? 2;
|
||||
|
||||
if (prazoConclusao < agora) {
|
||||
statusSla.vencido++;
|
||||
if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) {
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].vencido++;
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++;
|
||||
}
|
||||
} else if (horasRestantes <= alertaHoras) {
|
||||
statusSla.proximoVencimento++;
|
||||
if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) {
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].proximoVencimento++;
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++;
|
||||
}
|
||||
} else {
|
||||
statusSla.dentroPrazo++;
|
||||
if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) {
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].dentroPrazo++;
|
||||
porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Calcular taxa de cumprimento
|
||||
const totalComPrazo = statusSla.dentroPrazo + statusSla.proximoVencimento + statusSla.vencido;
|
||||
const taxaCumprimento = totalComPrazo > 0
|
||||
? Math.round((statusSla.dentroPrazo / totalComPrazo) * 100)
|
||||
: 100;
|
||||
|
||||
return {
|
||||
statusSla,
|
||||
porPrioridade,
|
||||
taxaCumprimento,
|
||||
totalComPrazo,
|
||||
atualizadoEm: agora,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const salvarSlaConfig = mutation({
|
||||
args: {
|
||||
slaId: v.optional(v.id("slaConfigs")),
|
||||
@@ -718,40 +814,3 @@ export const generateUploadUrl = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Migração: Adiciona o campo 'prioridade' aos SLAs antigos que não possuem
|
||||
* Esta mutation corrige documentos criados antes da migração do schema
|
||||
*/
|
||||
export const migrarSlaConfigs = mutation({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const usuario = await assertAuth(ctx);
|
||||
|
||||
// Buscar todos os SLAs
|
||||
const slaConfigs = await ctx.db.query("slaConfigs").collect();
|
||||
|
||||
let migrados = 0;
|
||||
for (const sla of slaConfigs) {
|
||||
// Verificar se o documento não tem o campo 'prioridade'
|
||||
// Usando type assertion para acessar campos não tipados
|
||||
const slaDoc = sla as any;
|
||||
if (!slaDoc.prioridade) {
|
||||
// Adicionar prioridade padrão "media" para SLAs antigos
|
||||
await ctx.db.patch(sla._id, {
|
||||
prioridade: "media" as "baixa" | "media" | "alta" | "critica",
|
||||
atualizadoPor: usuario._id,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
migrados++;
|
||||
console.log(`✅ SLA migrado: ${sla.nome} (ID: ${sla._id})`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sucesso: true,
|
||||
migrados,
|
||||
total: slaConfigs.length,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user