1e636fdbe2
Three reusable-widget follow-up fixes:
1. Reference sort_order independently reorderable. Reordering a
referenced widget now updates the widget_references.sort_order (per-
dashboard), not the shared widget instance sort_order. New backend
update_widget_reference method + PUT /api/widgets/references/{id}
endpoint. Frontend moveInstance checks _ref_id to choose the right
mutation (updateRef for references, saveWidget for owned).
2. Detach preserves service_id. detach_widget_reference now copies the
original widget's service_id into the clone, so service-bound widgets
(Grafana chart, Jellyfin activity) continue to render after detach.
3. Named dashboards support widget references. NamedDashboardPage
fetches useWidgetReferences('named:<slug>') and renders them via
WidgetInstanceCard alongside pinned links. 'Edit widgets' button
opens WidgetConfigDialog with dashboardScope='named:<slug>'.
Also: removed useMemo on combinedWidgets in WidgetConfigDialog to fix
a react-hooks/preserve-manual-memoization lint error (the React Compiler
ESLint plugin couldn't verify the spread+sort memoization).
283 backend tests pass (+1 update_reference test); 128 frontend tests
pass; ruff clean; 0 lint errors.
121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
createWidgetInstance,
|
|
createWidgetReference,
|
|
deleteWidgetInstance,
|
|
deleteWidgetReference,
|
|
detachWidgetReference,
|
|
fetchBuiltinWidgetKinds,
|
|
fetchWidgetData,
|
|
fetchWidgetInstances,
|
|
fetchWidgetReferences,
|
|
updateWidgetInstance,
|
|
updateWidgetReference,
|
|
} from "../api/widgets";
|
|
import type { WidgetInstanceInput } from "../types";
|
|
|
|
export function useWidgetInstances(
|
|
serviceId?: string,
|
|
scope?: "dashboard" | "service",
|
|
) {
|
|
return useQuery({
|
|
queryKey: ["widgets", "instances", serviceId ?? null, scope ?? null],
|
|
queryFn: () => fetchWidgetInstances(serviceId, scope),
|
|
refetchInterval: 60_000,
|
|
});
|
|
}
|
|
|
|
export function useWidgetData(widgetId: string, refreshInterval: number) {
|
|
return useQuery({
|
|
queryKey: ["widgets", "data", widgetId],
|
|
queryFn: () => fetchWidgetData(widgetId),
|
|
refetchInterval: refreshInterval || false,
|
|
enabled: !!widgetId,
|
|
retry: 1,
|
|
});
|
|
}
|
|
|
|
export function useSaveWidgetInstance() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: WidgetInstanceInput) =>
|
|
input.id ? updateWidgetInstance(input) : createWidgetInstance(input),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets", "instances"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteWidgetInstance() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (widgetId: string) => deleteWidgetInstance(widgetId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets", "instances"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useBuiltinWidgetKinds() {
|
|
return useQuery({
|
|
queryKey: ["widgets", "builtin"],
|
|
queryFn: fetchBuiltinWidgetKinds,
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
export function useWidgetReferences(dashboardScope: string | undefined) {
|
|
return useQuery({
|
|
queryKey: ["widgets", "references", dashboardScope ?? null],
|
|
queryFn: () => fetchWidgetReferences(dashboardScope!),
|
|
enabled: !!dashboardScope,
|
|
refetchInterval: 60_000,
|
|
});
|
|
}
|
|
|
|
export function useCreateWidgetReference() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: createWidgetReference,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets", "references"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteWidgetReference() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: deleteWidgetReference,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets", "references"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDetachWidgetReference() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: detachWidgetReference,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateWidgetReference() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({
|
|
referenceId,
|
|
sortOrder,
|
|
}: {
|
|
referenceId: string;
|
|
sortOrder: number;
|
|
}) => updateWidgetReference(referenceId, sortOrder),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["widgets", "references"] });
|
|
},
|
|
});
|
|
}
|