Atualiza portal e admin com bloqueio de máquinas desativadas

This commit is contained in:
Esdras Renan 2025-10-18 00:02:15 -03:00
parent e5085962e9
commit 630110bf3a
31 changed files with 1756 additions and 244 deletions

View file

@ -440,6 +440,7 @@ export const register = mutation({
lastHeartbeatAt: now,
updatedAt: now,
status: "online",
isActive: true,
registeredBy: args.registeredBy ?? existing.registeredBy,
persona: existing.persona,
assignedUserId: existing.assignedUserId,
@ -463,6 +464,7 @@ export const register = mutation({
metadata: metadataPatch ? mergeMetadata(undefined, metadataPatch) : undefined,
lastHeartbeatAt: now,
status: "online",
isActive: true,
createdAt: now,
updatedAt: now,
registeredBy: args.registeredBy,
@ -716,6 +718,7 @@ export const resolveToken = mutation({
status: machine.status,
lastHeartbeatAt: machine.lastHeartbeatAt,
metadata: machine.metadata,
isActive: machine.isActive ?? true,
},
token: {
expiresAt: token.expiresAt,
@ -753,7 +756,9 @@ export const listByTenant = query({
const staleThresholdMs = getStaleThresholdMs(offlineThresholdMs)
const manualStatus = (machine.status ?? "").toLowerCase()
let derivedStatus: string
if (["maintenance", "blocked"].includes(manualStatus)) {
if (machine.isActive === false) {
derivedStatus = "deactivated"
} else if (["maintenance", "blocked"].includes(manualStatus)) {
derivedStatus = manualStatus
} else if (machine.lastHeartbeatAt) {
const age = now - machine.lastHeartbeatAt
@ -810,6 +815,7 @@ export const listByTenant = query({
assignedUserName: machine.assignedUserName ?? null,
assignedUserRole: machine.assignedUserRole ?? null,
status: derivedStatus,
isActive: machine.isActive ?? true,
lastHeartbeatAt: machine.lastHeartbeatAt ?? null,
heartbeatAgeMs: machine.lastHeartbeatAt ? now - machine.lastHeartbeatAt : null,
registeredBy: machine.registeredBy ?? null,
@ -988,6 +994,7 @@ export const getContext = query({
assignedUserRole: machine.assignedUserRole ?? null,
metadata: machine.metadata ?? null,
authEmail: machine.authEmail ?? null,
isActive: machine.isActive ?? true,
}
},
})
@ -1069,6 +1076,37 @@ export const rename = mutation({
},
})
export const toggleActive = mutation({
args: {
machineId: v.id("machines"),
actorId: v.id("users"),
active: v.boolean(),
},
handler: async (ctx, { machineId, actorId, active }) => {
const machine = await ctx.db.get(machineId)
if (!machine) {
throw new ConvexError("Máquina não encontrada")
}
const actor = await ctx.db.get(actorId)
if (!actor || actor.tenantId !== machine.tenantId) {
throw new ConvexError("Acesso negado ao tenant da máquina")
}
const normalizedRole = (actor.role ?? "AGENT").toUpperCase()
const STAFF = new Set(["ADMIN", "MANAGER", "AGENT"])
if (!STAFF.has(normalizedRole)) {
throw new ConvexError("Apenas equipe interna pode atualizar o status da máquina")
}
await ctx.db.patch(machineId, {
isActive: active,
updatedAt: Date.now(),
})
return { ok: true }
},
})
export const remove = mutation({
args: {
machineId: v.id("machines"),

View file

@ -375,8 +375,20 @@ export const dashboardOverview = query({
const awaitingTickets = tickets.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status)));
const atRiskTickets = awaitingTickets.filter((ticket) => ticket.dueAt && ticket.dueAt < now);
const surveys = await collectCsatSurveys(ctx, tickets);
const averageScore = average(surveys.map((item) => item.score));
const resolvedLastWindow = tickets.filter(
(ticket) => ticket.resolvedAt && ticket.resolvedAt >= lastWindowStart && ticket.resolvedAt < now
);
const resolvedPreviousWindow = tickets.filter(
(ticket) =>
ticket.resolvedAt &&
ticket.resolvedAt >= previousWindowStart &&
ticket.resolvedAt < lastWindowStart
);
const resolutionRate = tickets.length > 0 ? (resolvedLastWindow.length / tickets.length) * 100 : null;
const resolutionDelta =
resolvedPreviousWindow.length > 0
? ((resolvedLastWindow.length - resolvedPreviousWindow.length) / resolvedPreviousWindow.length) * 100
: null;
return {
newTickets: {
@ -394,9 +406,11 @@ export const dashboardOverview = query({
total: awaitingTickets.length,
atRisk: atRiskTickets.length,
},
csat: {
averageScore,
totalSurveys: surveys.length,
resolution: {
resolvedLast7d: resolvedLastWindow.length,
previousResolved: resolvedPreviousWindow.length,
rate: resolutionRate,
deltaPercentage: resolutionDelta,
},
};
},

View file

@ -264,6 +264,7 @@ export default defineSchema({
metadata: v.optional(v.any()),
lastHeartbeatAt: v.optional(v.number()),
status: v.optional(v.string()),
isActive: v.optional(v.boolean()),
createdAt: v.number(),
updatedAt: v.number(),
registeredBy: v.optional(v.string()),