Fix: WidgetConfigDialog scope + reorder race condition
Two bugs in the widget config dialog: 1. Service Overview edit showed main-dashboard widgets. The dialog called useWidgetInstances() with no args, fetching ALL widgets. Now accepts a serviceId prop; OverviewTab passes instance.id so the dialog lists + creates only service-scoped widgets. New built-in widgets added from a service Overview inherit the serviceId. 2. Reorder up/down buttons did nothing. moveInstance fired two saveWidget mutations via Promise.all — the first mutation's onSuccess cache invalidation triggered a refetch before the second completed, reverting the swap. Changed to sequential awaits so both sort_order writes land before the cache refreshes. 127 tests pass; lint/build green.
This commit is contained in:
@@ -38,6 +38,8 @@ import {
|
|||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
/** When set, scope the dialog to a specific service instance's widgets. */
|
||||||
|
serviceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Draft {
|
interface Draft {
|
||||||
@@ -164,8 +166,8 @@ function WidgetConfigEditor({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WidgetConfigDialog({ open, onClose }: Props) {
|
export function WidgetConfigDialog({ open, onClose, serviceId }: Props) {
|
||||||
const { data: instances = [] } = useWidgetInstances();
|
const { data: instances = [] } = useWidgetInstances(serviceId);
|
||||||
const { data: services = [] } = useServiceInstances();
|
const { data: services = [] } = useServiceInstances();
|
||||||
const { data: tasks = [] } = useTasks();
|
const { data: tasks = [] } = useTasks();
|
||||||
const saveWidget = useSaveWidgetInstance();
|
const saveWidget = useSaveWidgetInstance();
|
||||||
@@ -184,7 +186,7 @@ export function WidgetConfigDialog({ open, onClose }: Props) {
|
|||||||
function startAddBuiltIn(kind: string) {
|
function startAddBuiltIn(kind: string) {
|
||||||
const binding = BUILTIN_WIDGETS[kind];
|
const binding = BUILTIN_WIDGETS[kind];
|
||||||
setDraft({
|
setDraft({
|
||||||
serviceId: null,
|
serviceId: serviceId ?? null,
|
||||||
widgetKind: kind,
|
widgetKind: kind,
|
||||||
title: binding?.name ?? kind,
|
title: binding?.name ?? kind,
|
||||||
config: { ...(binding?.defaultConfig ?? {}) },
|
config: { ...(binding?.defaultConfig ?? {}) },
|
||||||
@@ -255,10 +257,10 @@ export function WidgetConfigDialog({ open, onClose }: Props) {
|
|||||||
if (targetIndex < 0 || targetIndex >= sortedInstances.length) return;
|
if (targetIndex < 0 || targetIndex >= sortedInstances.length) return;
|
||||||
const a = sortedInstances[index];
|
const a = sortedInstances[index];
|
||||||
const b = sortedInstances[targetIndex];
|
const b = sortedInstances[targetIndex];
|
||||||
await Promise.all([
|
// Sequential (not Promise.all) to avoid a race where the first mutation's
|
||||||
saveWidget.mutateAsync({ ...a, sort_order: b.sort_order }),
|
// cache invalidation refetches before the second completes, reverting the swap.
|
||||||
saveWidget.mutateAsync({ ...b, sort_order: a.sort_order }),
|
await saveWidget.mutateAsync({ ...a, sort_order: b.sort_order });
|
||||||
]);
|
await saveWidget.mutateAsync({ ...b, sort_order: a.sort_order });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeInstance(instance: WidgetInstance) {
|
async function removeInstance(instance: WidgetInstance) {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export function OverviewTab({ instance }: { instance: ServiceInstance }) {
|
|||||||
<WidgetConfigDialog
|
<WidgetConfigDialog
|
||||||
open={configOpen}
|
open={configOpen}
|
||||||
onClose={() => setConfigOpen(false)}
|
onClose={() => setConfigOpen(false)}
|
||||||
|
serviceId={instance.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user