feat: ensure queue summary widget in dashboards
This commit is contained in:
parent
a542846313
commit
343f0c8c64
2 changed files with 201 additions and 0 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -570,6 +570,7 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
|
|||
const [isDeletingDashboard, setIsDeletingDashboard] = useState(false)
|
||||
const fullscreenContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const previousSidebarStateRef = useRef<{ open: boolean; openMobile: boolean } | null>(null)
|
||||
const ensureQueueSummaryRequestedRef = useRef(false)
|
||||
|
||||
const updateLayoutMutation = useMutation(api.dashboards.updateLayout)
|
||||
const updateFiltersMutation = useMutation(api.dashboards.updateFilters)
|
||||
|
|
@ -578,6 +579,7 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
|
|||
const duplicateWidgetMutation = useMutation(api.dashboards.duplicateWidget)
|
||||
const removeWidgetMutation = useMutation(api.dashboards.removeWidget)
|
||||
const updateMetadataMutation = useMutation(api.dashboards.updateMetadata)
|
||||
const ensureQueueSummaryWidgetMutation = useMutation(api.dashboards.ensureQueueSummaryWidget)
|
||||
const archiveDashboardMutation = useMutation(api.dashboards.archive)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -625,6 +627,43 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
|
|||
layoutRef.current = layoutState
|
||||
}, [layoutState])
|
||||
|
||||
useEffect(() => {
|
||||
if (!dashboard || !convexUserId || !isStaff) return
|
||||
if (widgets.length === 0) return
|
||||
const queueIndex = widgets.findIndex((widget) => {
|
||||
const type = (widget.type ?? "").toLowerCase()
|
||||
if (type === "queue-summary") return true
|
||||
const configType =
|
||||
widget.config && typeof widget.config === "object"
|
||||
? ((widget.config as WidgetConfig).type ?? "").toLowerCase()
|
||||
: ""
|
||||
return configType === "queue-summary"
|
||||
})
|
||||
if (queueIndex === 0) {
|
||||
ensureQueueSummaryRequestedRef.current = false
|
||||
return
|
||||
}
|
||||
if (queueIndex === -1 || queueIndex > 0) {
|
||||
if (ensureQueueSummaryRequestedRef.current) return
|
||||
ensureQueueSummaryRequestedRef.current = true
|
||||
ensureQueueSummaryWidgetMutation({
|
||||
tenantId,
|
||||
actorId: convexUserId as Id<"users">,
|
||||
dashboardId: dashboard.id as Id<"dashboards">,
|
||||
}).catch((error) => {
|
||||
console.error("[dashboards] Failed to ensure queue summary widget", error)
|
||||
ensureQueueSummaryRequestedRef.current = false
|
||||
})
|
||||
}
|
||||
}, [
|
||||
dashboard,
|
||||
widgets,
|
||||
convexUserId,
|
||||
isStaff,
|
||||
tenantId,
|
||||
ensureQueueSummaryWidgetMutation,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (sections.length === 0) {
|
||||
setActiveSectionIndex(0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue