feat: ensure queue summary widget in dashboards

This commit is contained in:
Esdras Renan 2025-11-06 17:23:29 -03:00
parent a542846313
commit 343f0c8c64
2 changed files with 201 additions and 0 deletions

View file

@ -57,6 +57,28 @@ function generateWidgetKey(dashboardId: Id<"dashboards">) {
return `${dashboardId.toString().slice(-6)}-${rand}`
}
function normalizeQueueSummaryConfig(config: unknown) {
return {
type: "queue-summary",
title: "Resumo das filas",
dataSource: { metricKey: "queues.summary_cards" },
...(typeof config === "object" && config ? (config as Record<string, unknown>) : {}),
}
}
function queueSummaryLayout(widgetKey: string) {
return {
i: widgetKey,
x: 0,
y: 0,
w: 12,
h: 6,
minW: 8,
minH: 4,
static: false,
}
}
function normalizeWidgetConfig(type: WidgetType, config: unknown) {
if (config && typeof config === "object") {
return config
@ -580,6 +602,146 @@ export const updateWidget = mutation({
},
})
export const ensureQueueSummaryWidget = mutation({
args: {
tenantId: v.string(),
actorId: v.id("users"),
dashboardId: v.id("dashboards"),
},
handler: async (ctx, { tenantId, actorId, dashboardId }) => {
await requireStaff(ctx, actorId, tenantId)
const dashboard = await ctx.db.get(dashboardId)
if (!dashboard || dashboard.tenantId !== tenantId) {
throw new ConvexError("Dashboard não encontrado")
}
const widgets = await ctx.db
.query("dashboardWidgets")
.withIndex("by_dashboard_order", (q) => q.eq("dashboardId", dashboardId))
.collect()
widgets.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
const now = Date.now()
let queueWidget = widgets.find((widget) => widget.type === "queue-summary")
let changed = false
if (!queueWidget) {
// Shift existing widgets to make room at the top.
await Promise.all(
widgets.map((widget) =>
ctx.db.patch(widget._id, {
order: widget.order + 1,
updatedAt: now,
updatedBy: actorId,
}),
),
)
const widgetKey = generateWidgetKey(dashboardId)
const config = normalizeQueueSummaryConfig(undefined)
const layout = queueSummaryLayout(widgetKey)
const widgetId = await ctx.db.insert("dashboardWidgets", {
tenantId,
dashboardId,
widgetKey,
title: config.title,
type: "queue-summary",
config,
layout,
order: 0,
createdBy: actorId,
updatedBy: actorId,
createdAt: now,
updatedAt: now,
isHidden: false,
})
const createdWidget = await ctx.db.get(widgetId)
if (!createdWidget) {
throw new ConvexError("Falha ao criar widget de resumo por fila.")
}
queueWidget = createdWidget
widgets.unshift(queueWidget)
changed = true
} else {
// Ensure the existing widget is first and has the expected config.
const desiredConfig = normalizeQueueSummaryConfig(queueWidget.config)
if (JSON.stringify(queueWidget.config) !== JSON.stringify(desiredConfig)) {
await ctx.db.patch(queueWidget._id, { config: desiredConfig, updatedAt: now, updatedBy: actorId })
queueWidget = { ...queueWidget, config: desiredConfig }
changed = true
}
if (queueWidget.order !== 0) {
let nextOrder = 1
for (const widget of widgets) {
if (widget._id === queueWidget._id) continue
if (widget.order !== nextOrder) {
await ctx.db.patch(widget._id, { order: nextOrder, updatedAt: now, updatedBy: actorId })
}
nextOrder += 1
}
await ctx.db.patch(queueWidget._id, { order: 0, updatedAt: now, updatedBy: actorId })
changed = true
}
}
if (!queueWidget) {
throw new ConvexError("Não foi possível garantir o widget de resumo por fila.")
}
const widgetKey = queueWidget.widgetKey
// Normalize dashboard layout (queue summary first).
const currentLayout = Array.isArray(dashboard.layout) ? dashboard.layout : []
const filteredLayout = currentLayout.filter((item) => item.i !== widgetKey)
const normalizedLayout = [queueSummaryLayout(widgetKey), ...filteredLayout]
let dashboardPatch: Partial<Doc<"dashboards">> = {}
if (JSON.stringify(currentLayout) !== JSON.stringify(normalizedLayout)) {
dashboardPatch.layout = normalizedLayout
changed = true
}
// Ensure sections used in TV playlists include the widget on the first slide.
if (Array.isArray(dashboard.sections) && dashboard.sections.length > 0) {
const updatedSections = dashboard.sections.map((section, index) => {
const keys = Array.isArray(section.widgetKeys) ? section.widgetKeys : []
const without = keys.filter((key) => key !== widgetKey)
if (index === 0) {
const nextKeys = [widgetKey, ...without]
if (JSON.stringify(nextKeys) !== JSON.stringify(keys)) {
changed = true
return { ...section, widgetKeys: nextKeys }
}
return section
}
if (without.length !== keys.length) {
changed = true
return { ...section, widgetKeys: without }
}
return section
})
if (changed && JSON.stringify(updatedSections) !== JSON.stringify(dashboard.sections)) {
dashboardPatch.sections = updatedSections
}
}
if (Object.keys(dashboardPatch).length > 0) {
dashboardPatch.updatedAt = now
dashboardPatch.updatedBy = actorId
await ctx.db.patch(dashboardId, dashboardPatch)
} else if (changed) {
await ctx.db.patch(dashboardId, { updatedAt: now, updatedBy: actorId })
}
if (!changed) {
// Nothing changed; still return success.
return { ensured: false, widgetKey }
}
return { ensured: true, widgetKey }
},
})
export const duplicateWidget = mutation({
args: {
tenantId: v.string(),