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}`
|
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) {
|
function normalizeWidgetConfig(type: WidgetType, config: unknown) {
|
||||||
if (config && typeof config === "object") {
|
if (config && typeof config === "object") {
|
||||||
return config
|
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({
|
export const duplicateWidget = mutation({
|
||||||
args: {
|
args: {
|
||||||
tenantId: v.string(),
|
tenantId: v.string(),
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,7 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
|
||||||
const [isDeletingDashboard, setIsDeletingDashboard] = useState(false)
|
const [isDeletingDashboard, setIsDeletingDashboard] = useState(false)
|
||||||
const fullscreenContainerRef = useRef<HTMLDivElement | null>(null)
|
const fullscreenContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const previousSidebarStateRef = useRef<{ open: boolean; openMobile: boolean } | null>(null)
|
const previousSidebarStateRef = useRef<{ open: boolean; openMobile: boolean } | null>(null)
|
||||||
|
const ensureQueueSummaryRequestedRef = useRef(false)
|
||||||
|
|
||||||
const updateLayoutMutation = useMutation(api.dashboards.updateLayout)
|
const updateLayoutMutation = useMutation(api.dashboards.updateLayout)
|
||||||
const updateFiltersMutation = useMutation(api.dashboards.updateFilters)
|
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 duplicateWidgetMutation = useMutation(api.dashboards.duplicateWidget)
|
||||||
const removeWidgetMutation = useMutation(api.dashboards.removeWidget)
|
const removeWidgetMutation = useMutation(api.dashboards.removeWidget)
|
||||||
const updateMetadataMutation = useMutation(api.dashboards.updateMetadata)
|
const updateMetadataMutation = useMutation(api.dashboards.updateMetadata)
|
||||||
|
const ensureQueueSummaryWidgetMutation = useMutation(api.dashboards.ensureQueueSummaryWidget)
|
||||||
const archiveDashboardMutation = useMutation(api.dashboards.archive)
|
const archiveDashboardMutation = useMutation(api.dashboards.archive)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -625,6 +627,43 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
|
||||||
layoutRef.current = layoutState
|
layoutRef.current = layoutState
|
||||||
}, [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(() => {
|
useEffect(() => {
|
||||||
if (sections.length === 0) {
|
if (sections.length === 0) {
|
||||||
setActiveSectionIndex(0)
|
setActiveSectionIndex(0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue