refactor(widgets): migrate all widgets from useSuspenseQuery to useQuery
Non-blocking data fetching for all 38 widget components. Widgets now render immediately and show data when available, with loading state delegated to the global refresh indicator in the board header.
This commit is contained in:
38 files changed
+96
-67
No files matched your search
@@ -144,7 +144,7 @@ interface AnchorNoteWidgetContentProps {
|
||||
|
||||
const AnchorNoteWidgetContent = ({ options, integrationId, noteId }: AnchorNoteWidgetContentProps) => {
|
||||
const t = useScopedI18n("widget.anchorNote");
|
||||
const [note, { refetch }] = clientApi.widget.anchorNotes.getNote.useSuspenseQuery(
|
||||
const { data: note, refetch } = clientApi.widget.anchorNotes.getNote.useQuery(
|
||||
{
|
||||
integrationId,
|
||||
noteId,
|
||||
@@ -159,31 +159,32 @@ const AnchorNoteWidgetContent = ({ options, integrationId, noteId }: AnchorNoteW
|
||||
const { mutateAsync: updateNoteAsync, isPending: isUpdating } = clientApi.widget.anchorNotes.updateNote.useMutation();
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [draftTitle, setDraftTitle] = useState(note.title);
|
||||
const [draftContent, setDraftContent] = useState(note.content ?? "");
|
||||
const [draftTitle, setDraftTitle] = useState(note?.title ?? "");
|
||||
const [draftContent, setDraftContent] = useState(note?.content ?? "");
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) return;
|
||||
if (isEditing || !note) return;
|
||||
|
||||
startTransition(() => {
|
||||
setDraftTitle(note.title);
|
||||
setDraftContent(note.content ?? "");
|
||||
});
|
||||
}, [isEditing, note.content, note.title]);
|
||||
}, [isEditing, note]);
|
||||
|
||||
const canEdit = canEditPermission(note.permission);
|
||||
const isViewer = note.permission === "viewer";
|
||||
const updatedAt = useMemo(() => new Date(note.updatedAt), [note.updatedAt]);
|
||||
const canEdit = canEditPermission(note?.permission ?? "viewer");
|
||||
const isViewer = !note || note.permission === "viewer";
|
||||
const updatedAt = useMemo(() => (note ? new Date(note.updatedAt) : new Date()), [note]);
|
||||
const updatedAtRelative = useTimeAgo(updatedAt, 30000);
|
||||
|
||||
const hasChanges = useMemo(() => {
|
||||
if (!note) return false;
|
||||
const normalizedTitle = draftTitle.trim() || note.title;
|
||||
return normalizedTitle !== note.title || draftContent !== (note.content ?? "");
|
||||
}, [draftContent, draftTitle, note.content, note.title]);
|
||||
}, [draftContent, draftTitle, note]);
|
||||
|
||||
const editorValue = useMemo(() => parseStoredContent(draftContent), [draftContent]);
|
||||
const readOnlyValue = useMemo(() => parseStoredContent(note.content), [note.content]);
|
||||
const readOnlyValue = useMemo(() => parseStoredContent(note?.content), [note?.content]);
|
||||
const handleEditorChange = useCallback<ReactQuillOnChange>(
|
||||
(_html, _delta, source, editor) => {
|
||||
if (!isEditing || source !== "user") return;
|
||||
@@ -194,26 +195,27 @@ const AnchorNoteWidgetContent = ({ options, integrationId, noteId }: AnchorNoteW
|
||||
);
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
if (!canEdit) return;
|
||||
if (!canEdit || !note) return;
|
||||
startTransition(() => {
|
||||
setDraftTitle(note.title);
|
||||
setDraftContent(note.content ?? "");
|
||||
setSaveError(null);
|
||||
setIsEditing(true);
|
||||
});
|
||||
}, [canEdit, note.content, note.title]);
|
||||
}, [canEdit, note]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
if (!note) return;
|
||||
startTransition(() => {
|
||||
setDraftTitle(note.title);
|
||||
setDraftContent(note.content ?? "");
|
||||
setSaveError(null);
|
||||
setIsEditing(false);
|
||||
});
|
||||
}, [note.content, note.title]);
|
||||
}, [note]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!canEdit) return;
|
||||
if (!canEdit || !note) return;
|
||||
|
||||
const normalizedTitle = draftTitle.trim() || note.title || t("untitled");
|
||||
|
||||
@@ -260,7 +262,9 @@ const AnchorNoteWidgetContent = ({ options, integrationId, noteId }: AnchorNoteW
|
||||
},
|
||||
},
|
||||
);
|
||||
}, [canEdit, draftContent, draftTitle, hasChanges, integrationId, note.title, noteId, refetch, t, updateNoteAsync]);
|
||||
}, [canEdit, draftContent, draftTitle, hasChanges, integrationId, note, noteId, refetch, t, updateNoteAsync]);
|
||||
|
||||
if (!note) return null;
|
||||
|
||||
return (
|
||||
<Stack h="100%" gap="xs" p="sm">
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
const t = useI18n();
|
||||
const settings = useSettings();
|
||||
const board = useRequiredBoard();
|
||||
const [app] = clientApi.app.byId.useSuspenseQuery(
|
||||
const { data: app } = clientApi.app.byId.useQuery(
|
||||
{
|
||||
id: options.appId,
|
||||
},
|
||||
@@ -34,8 +34,8 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
},
|
||||
);
|
||||
useRegisterSpotlightContextResults(
|
||||
`app-${app.id}`,
|
||||
app.href
|
||||
`app-${app?.id ?? options.appId}`,
|
||||
app?.href
|
||||
? [
|
||||
{
|
||||
id: app.id,
|
||||
@@ -44,7 +44,6 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
interaction() {
|
||||
return {
|
||||
type: "link",
|
||||
// We checked above that app.href is defined
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
href: app.href!,
|
||||
newTab: options.openInNewTab,
|
||||
@@ -56,6 +55,8 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
[app, options.openInNewTab],
|
||||
);
|
||||
|
||||
if (!app) return null;
|
||||
|
||||
const isTiny = height < 100 || width < 100;
|
||||
const isColumnLayout = options.layout.startsWith("column");
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ interface PingIndicatorProps {
|
||||
}
|
||||
|
||||
export const PingIndicator = ({ appId }: PingIndicatorProps) => {
|
||||
const [ping] = clientApi.widget.app.ping.useSuspenseQuery(
|
||||
const { data: ping } = clientApi.widget.app.ping.useQuery(
|
||||
{
|
||||
id: appId,
|
||||
},
|
||||
@@ -21,7 +21,7 @@ export const PingIndicator = ({ appId }: PingIndicatorProps) => {
|
||||
},
|
||||
);
|
||||
|
||||
const [pingResult, setPingResult] = useState<RouterOutputs["widget"]["app"]["ping"]>(ping);
|
||||
const [pingResult, setPingResult] = useState<RouterOutputs["widget"]["app"]["ping"] | undefined>(ping);
|
||||
|
||||
clientApi.widget.app.updatedPing.useSubscription(
|
||||
{ id: appId },
|
||||
@@ -32,16 +32,19 @@ export const PingIndicator = ({ appId }: PingIndicatorProps) => {
|
||||
},
|
||||
);
|
||||
|
||||
const isError = "error" in pingResult || pingResult.statusCode >= 500;
|
||||
const current = pingResult ?? ping;
|
||||
if (!current) return null;
|
||||
|
||||
const isError = "error" in current || current.statusCode >= 500;
|
||||
|
||||
return (
|
||||
<PingDot
|
||||
icon={isError ? IconX : IconCheck}
|
||||
color={isError ? "red" : "green"}
|
||||
tooltip={
|
||||
"statusCode" in pingResult
|
||||
? `${pingResult.statusCode} - ${pingResult.durationMs.toFixed(0)}ms`
|
||||
: pingResult.error
|
||||
"statusCode" in current
|
||||
? `${current.statusCode} - ${current.durationMs.toFixed(0)}ms`
|
||||
: current.error
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import classes from "./bookmark.module.css";
|
||||
|
||||
export default function BookmarksWidget({ options, itemId }: WidgetComponentProps<"bookmarks">) {
|
||||
const board = useRequiredBoard();
|
||||
const [data] = clientApi.app.byIds.useSuspenseQuery(options.items, {
|
||||
const { data = [] } = clientApi.app.byIds.useQuery(options.items, {
|
||||
select(data) {
|
||||
return data.sort((appA, appB) => options.items.indexOf(appA.id) - options.items.indexOf(appB.id));
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ interface CoolifyContentProps {
|
||||
}
|
||||
|
||||
function CoolifyContent({ integrationIds, options, width }: CoolifyContentProps) {
|
||||
const [instancesData] = clientApi.widget.coolify.getInstancesInfo.useSuspenseQuery({ integrationIds });
|
||||
const { data: instancesData = [] } = clientApi.widget.coolify.getInstancesInfo.useQuery({ integrationIds });
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
clientApi.widget.coolify.subscribeInstancesInfo.useSubscription(
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function DnsHoleControlsWidget({
|
||||
.map(({ id }) => id)
|
||||
.filter((id) => integrationIds.includes(id));
|
||||
|
||||
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
|
||||
const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { widgetKind } from ".";
|
||||
import type { WidgetComponentProps, WidgetProps } from "../../definition";
|
||||
|
||||
export default function DnsHoleSummaryWidget({ options, integrationIds }: WidgetComponentProps<typeof widgetKind>) {
|
||||
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
|
||||
const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -204,8 +204,9 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo
|
||||
const isTiny = width <= 256;
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
const [{ containers, timestamp }] = clientApi.docker.getContainers.useSuspenseQuery();
|
||||
const relativeTime = useTimeAgo(timestamp);
|
||||
const { data } = clientApi.docker.getContainers.useQuery();
|
||||
const containers = data?.containers ?? [];
|
||||
const relativeTime = useTimeAgo(data?.timestamp ?? new Date());
|
||||
|
||||
clientApi.docker.subscribeContainers.useSubscription(undefined, {
|
||||
onData(data) {
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function DownloadClientsWidget({
|
||||
integrationIds.includes(id) ? [id] : [],
|
||||
);
|
||||
|
||||
const [currentItems] = clientApi.widget.downloads.getJobsAndStatuses.useSuspenseQuery(
|
||||
const { data: currentItems = [] } = clientApi.widget.downloads.getJobsAndStatuses.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
limitPerIntegration: options.limitPerIntegration,
|
||||
|
||||
@@ -184,7 +184,7 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget
|
||||
|
||||
export const useUpdatingCpuStatus = (integrationIds: string[]) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const [firewallsCpuData] = clientApi.widget.firewall.getFirewallCpuStatus.useSuspenseQuery(
|
||||
const { data: firewallsCpuData = [] } = clientApi.widget.firewall.getFirewallCpuStatus.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
@@ -225,7 +225,7 @@ export const useUpdatingCpuStatus = (integrationIds: string[]) => {
|
||||
|
||||
export const useUpdatingMemoryStatus = (integrationIds: string[]) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const [firewallsMemoryData] = clientApi.widget.firewall.getFirewallMemoryStatus.useSuspenseQuery(
|
||||
const { data: firewallsMemoryData = [] } = clientApi.widget.firewall.getFirewallMemoryStatus.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
@@ -266,7 +266,7 @@ export const useUpdatingMemoryStatus = (integrationIds: string[]) => {
|
||||
|
||||
export const useUpdatingVersionStatus = (integrationIds: string[]) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const [firewallsVersionData] = clientApi.widget.firewall.getFirewallVersionStatus.useSuspenseQuery(
|
||||
const { data: firewallsVersionData = [] } = clientApi.widget.firewall.getFirewallVersionStatus.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
@@ -306,7 +306,7 @@ export const useUpdatingVersionStatus = (integrationIds: string[]) => {
|
||||
|
||||
export const useUpdatingInterfacesStatus = (integrationIds: string[]) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const [firewallsInterfacesData] = clientApi.widget.firewall.getFirewallInterfacesStatus.useSuspenseQuery(
|
||||
const { data: firewallsInterfacesData = [] } = clientApi.widget.firewall.getFirewallInterfacesStatus.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ export const ClusterHealthMonitoring = ({
|
||||
width,
|
||||
}: WidgetComponentProps<"healthMonitoring"> & { integrationId: string }) => {
|
||||
const t = useI18n();
|
||||
const [healthData] = clientApi.widget.healthMonitoring.getClusterHealthStatus.useSuspenseQuery(
|
||||
const { data: healthData } = clientApi.widget.healthMonitoring.getClusterHealthStatus.useQuery(
|
||||
{
|
||||
integrationId,
|
||||
},
|
||||
@@ -56,6 +56,8 @@ export const ClusterHealthMonitoring = ({
|
||||
},
|
||||
);
|
||||
|
||||
if (!healthData) return null;
|
||||
|
||||
const activeNodes = healthData.nodes.reduce(running, 0);
|
||||
const activeVMs = healthData.vms.reduce(running, 0);
|
||||
const activeLXCs = healthData.lxcs.reduce(running, 0);
|
||||
|
||||
@@ -18,7 +18,7 @@ const isClusterIntegration = (integration: { kind: IntegrationKind }) =>
|
||||
integration.kind === "proxmox" || integration.kind === "mock";
|
||||
|
||||
export default function HealthMonitoringWidget(props: WidgetComponentProps<"healthMonitoring">) {
|
||||
const [integrations] = clientApi.integration.byIds.useSuspenseQuery(props.integrationIds);
|
||||
const { data: integrations = [] } = clientApi.integration.byIds.useQuery(props.integrationIds);
|
||||
const t = useI18n();
|
||||
|
||||
const clusterIntegrationId = integrations.find(isClusterIntegration)?.id;
|
||||
|
||||
@@ -51,7 +51,7 @@ export const SystemHealthMonitoring = ({
|
||||
width,
|
||||
}: WidgetComponentProps<"healthMonitoring">) => {
|
||||
const t = useI18n();
|
||||
const [healthData] = clientApi.widget.healthMonitoring.getSystemHealthStatus.useSuspenseQuery(
|
||||
const { data: healthData = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -21,11 +21,13 @@ export default function ImmichAlbumCarouselWidget({
|
||||
return <NoAlbumSelected />;
|
||||
}
|
||||
|
||||
const [album] = clientApi.widget.immich.getAlbum.useSuspenseQuery({
|
||||
const { data: album } = clientApi.widget.immich.getAlbum.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
albumId: options.albumId,
|
||||
});
|
||||
|
||||
if (!album) return null;
|
||||
|
||||
if (album.assets.length === 0) {
|
||||
return <NoPhotosInAlbum />;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ export default function ImmichServerStatsWidget({
|
||||
options,
|
||||
}: WidgetComponentProps<"immich-serverStats">) {
|
||||
const t = useI18n();
|
||||
const [stats] = clientApi.widget.immich.getServerStats.useSuspenseQuery({
|
||||
const { data: stats } = clientApi.widget.immich.getServerStats.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
});
|
||||
|
||||
if (!stats) return null;
|
||||
|
||||
return (
|
||||
<Stack gap="md" h="100%" p="md">
|
||||
{options.showUsers && (
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function IndexerManagerWidget({
|
||||
height,
|
||||
}: WidgetComponentProps<"indexerManager">) {
|
||||
const t = useI18n();
|
||||
const [indexersData] = clientApi.widget.indexerManager.getIndexersStatus.useSuspenseQuery(
|
||||
const { data: indexersData = [] } = clientApi.widget.indexerManager.getIndexersStatus.useQuery(
|
||||
{ integrationIds },
|
||||
{
|
||||
refetchOnMount: false,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { OverflowBadge } from "@homarr/ui";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
|
||||
export default function MediaReleasesWidget({ options, integrationIds }: WidgetComponentProps<"mediaReleases">) {
|
||||
const [releases] = clientApi.widget.mediaRelease.getMediaReleases.useSuspenseQuery({
|
||||
const { data: releases = [] } = clientApi.widget.mediaRelease.getMediaReleases.useQuery({
|
||||
integrationIds,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function MediaServerWidget({
|
||||
options,
|
||||
width,
|
||||
}: WidgetComponentProps<"mediaRequests-requestList">) {
|
||||
const [mediaRequests] = clientApi.widget.mediaRequests.getLatestRequests.useSuspenseQuery(
|
||||
const { data: mediaRequests = [] } = clientApi.widget.mediaRequests.getLatestRequests.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function MediaServerWidget({
|
||||
width,
|
||||
}: WidgetComponentProps<"mediaRequests-requestStats">) {
|
||||
const t = useScopedI18n("widget.mediaRequests-requestStats");
|
||||
const [requestStats] = clientApi.widget.mediaRequests.getStats.useSuspenseQuery(
|
||||
const { data: requestStats } = clientApi.widget.mediaRequests.getStats.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
@@ -45,6 +45,7 @@ export default function MediaServerWidget({
|
||||
|
||||
const board = useRequiredBoard();
|
||||
|
||||
if (!requestStats) return null;
|
||||
if (requestStats.users.length === 0 && requestStats.stats.length === 0) throw new NoIntegrationDataError();
|
||||
|
||||
const data = [
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function MediaServerWidget({
|
||||
integrationIds,
|
||||
isEditMode,
|
||||
}: WidgetComponentProps<"mediaServer">) {
|
||||
const [currentStreams] = clientApi.widget.mediaServer.getCurrentStreams.useSuspenseQuery(
|
||||
const { data: currentStreams = [] } = clientApi.widget.mediaServer.getCurrentStreams.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
showOnlyPlaying: options.showOnlyPlaying,
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function MediaTranscodingWidget({
|
||||
pageSize: queuePageSize,
|
||||
page: queuePage,
|
||||
};
|
||||
const [transcodingData] = clientApi.widget.mediaTranscoding.getDataAsync.useSuspenseQuery(input, {
|
||||
const { data: transcodingData } = clientApi.widget.mediaTranscoding.getDataAsync.useQuery(input, {
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
@@ -51,6 +51,9 @@ export default function MediaTranscodingWidget({
|
||||
});
|
||||
|
||||
const [view, setView] = useState<View>(options.defaultView);
|
||||
|
||||
if (!transcodingData) return null;
|
||||
|
||||
const totalQueuePages = Math.ceil((transcodingData.data.queue.totalCount || 1) / queuePageSize);
|
||||
|
||||
const t = useI18n("widget.mediaTranscoding");
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
|
||||
export default function MinecraftServerStatusWidget({ options }: WidgetComponentProps<"minecraftServerStatus">) {
|
||||
const [{ data }] = clientApi.widget.minecraft.getServerStatus.useSuspenseQuery(options);
|
||||
const { data: result } = clientApi.widget.minecraft.getServerStatus.useQuery(options);
|
||||
const utils = clientApi.useUtils();
|
||||
clientApi.widget.minecraft.subscribeServerStatus.useSubscription(options, {
|
||||
onData(data) {
|
||||
@@ -22,6 +22,9 @@ export default function MinecraftServerStatusWidget({ options }: WidgetComponent
|
||||
});
|
||||
const tStatus = useScopedI18n("widget.minecraftServerStatus.status");
|
||||
|
||||
if (!result) return null;
|
||||
const { data } = result;
|
||||
|
||||
const title = options.title.trim().length > 0 ? options.title : options.domain;
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function NetworkControllerNetworkStatusWidget({
|
||||
options,
|
||||
integrationIds,
|
||||
}: WidgetComponentProps<"networkControllerStatus">) {
|
||||
const [summaries] = clientApi.widget.networkController.summary.useSuspenseQuery(
|
||||
const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ dayjs.extend(duration);
|
||||
export default function NetworkControllerSummaryWidget({
|
||||
integrationIds,
|
||||
}: WidgetComponentProps<"networkControllerSummary">) {
|
||||
const [summaries] = clientApi.widget.networkController.summary.useSuspenseQuery(
|
||||
const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
|
||||
export default function NotificationsWidget({ options, integrationIds }: WidgetComponentProps<"notifications">) {
|
||||
const [notificationIntegrations] = clientApi.widget.notifications.getNotifications.useSuspenseQuery(
|
||||
const { data: notificationIntegrations = [] } = clientApi.widget.notifications.getNotifications.useQuery(
|
||||
{
|
||||
...options,
|
||||
integrationIds,
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
);
|
||||
}, [groupedRepositories]);
|
||||
|
||||
const [results] = clientApi.useSuspenseQueries((t) =>
|
||||
const queryResults = clientApi.useQueries((t) =>
|
||||
batchedRepositories.flatMap(({ integrationId, repositories }) =>
|
||||
t.widget.releases.getLatest({
|
||||
integrationId,
|
||||
@@ -94,6 +94,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
),
|
||||
);
|
||||
|
||||
const results = queryResults.map((q) => q.data).filter(Boolean);
|
||||
|
||||
const repositories = useMemo(() => {
|
||||
const formattedResults = options.repositories
|
||||
.map((repository) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { WidgetComponentProps } from "../definition";
|
||||
import classes from "./component.module.scss";
|
||||
|
||||
const useLiveFeedEntries = (input: RouterInputs["widget"]["rssFeed"]["getFeeds"]) => {
|
||||
const [feedEntries] = clientApi.widget.rssFeed.getFeeds.useSuspenseQuery(input, {
|
||||
const { data: feedEntries = [] } = clientApi.widget.rssFeed.getFeeds.useQuery(input, {
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function SmartHomeEntityStateWidget({
|
||||
entityId: options.entityId,
|
||||
integrationId,
|
||||
};
|
||||
const [entityState] = clientApi.widget.smartHome.entityState.useSuspenseQuery(input);
|
||||
const { data: entityState } = clientApi.widget.smartHome.entityState.useQuery(input);
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function SpeedtestTrackerWidget({
|
||||
isEditMode,
|
||||
}: WidgetComponentProps<"speedtestTracker">) {
|
||||
const t = useScopedI18n("widget.speedtestTracker");
|
||||
const [dashboardData] = clientApi.widget.speedtestTracker.getDashboard.useSuspenseQuery({ integrationIds });
|
||||
const { data: dashboardData = [] } = clientApi.widget.speedtestTracker.getDashboard.useQuery({ integrationIds });
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
clientApi.widget.speedtestTracker.subscribeToDashboard.useSubscription(
|
||||
|
||||
@@ -24,7 +24,10 @@ function calculateChangePercentage(currentPrice: number, previousClose: number)
|
||||
export default function StockPriceWidget({ options, width, height }: WidgetComponentProps<"stockPrice">) {
|
||||
const t = useScopedI18n("widget.stockPrice");
|
||||
const theme = useMantineTheme();
|
||||
const [{ data }] = clientApi.widget.stockPrice.getPriceHistory.useSuspenseQuery(options);
|
||||
const { data: result } = clientApi.widget.stockPrice.getPriceHistory.useQuery(options);
|
||||
|
||||
if (!result) return null;
|
||||
const { data } = result;
|
||||
|
||||
const stockValuesChange = round(calculateChange(data.priceHistory.at(-1) ?? 0, data.previousClose));
|
||||
const stockValuesChangePercentage = round(
|
||||
|
||||
@@ -32,7 +32,7 @@ const getDisplayText = (item: { used: string; available: string; percentage: num
|
||||
|
||||
export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemDisks">) {
|
||||
const queryInput = { integrationIds };
|
||||
const [data] = clientApi.widget.healthMonitoring.getSystemHealthStatus.useSuspenseQuery(queryInput);
|
||||
const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery(queryInput);
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
const board = useRequiredBoard();
|
||||
|
||||
@@ -18,7 +18,7 @@ const MAX_QUEUE_SIZE = 15;
|
||||
export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemResources">) {
|
||||
const { ref, width } = useElementSize();
|
||||
|
||||
const [data] = clientApi.widget.healthMonitoring.getSystemHealthStatus.useSuspenseQuery({
|
||||
const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery({
|
||||
integrationIds,
|
||||
});
|
||||
const memoryCapacityInBytes =
|
||||
|
||||
@@ -26,7 +26,7 @@ interface TimetableWidgetInnerProps {
|
||||
}
|
||||
|
||||
const TimetableWidgetInner = ({ station, integrationId }: TimetableWidgetInnerProps) => {
|
||||
const [timetable] = clientApi.widget.timetable.getTimetable.useSuspenseQuery({
|
||||
const { data: timetable } = clientApi.widget.timetable.getTimetable.useQuery({
|
||||
integrationId,
|
||||
stationId: station.value,
|
||||
limit: 10,
|
||||
@@ -36,7 +36,7 @@ const TimetableWidgetInner = ({ station, integrationId }: TimetableWidgetInnerPr
|
||||
return (
|
||||
<Stack w="100%" gap="xs" p="sm">
|
||||
<Text fw="bold">{t("title", { station: station.label })}</Text>
|
||||
{timetable.entries.map((entry) => (
|
||||
{(timetable?.entries ?? []).map((entry) => (
|
||||
<Group key={`${entry.timestamp.toISOString()}-${entry.location}`} justify="space-between" w="100%">
|
||||
<Group gap="sm">
|
||||
{entry.line && (
|
||||
|
||||
@@ -35,7 +35,7 @@ interface TracearrContentProps {
|
||||
|
||||
function TracearrContent({ integrationIds, options, width, isEditMode }: TracearrContentProps) {
|
||||
const t = useScopedI18n("widget.tracearr");
|
||||
const [dashboardData] = clientApi.widget.tracearr.getDashboard.useSuspenseQuery({ integrationIds });
|
||||
const { data: dashboardData = [] } = clientApi.widget.tracearr.getDashboard.useQuery({ integrationIds });
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
clientApi.widget.tracearr.subscribeToDashboard.useSubscription(
|
||||
|
||||
@@ -40,7 +40,7 @@ export function UmamiContent({
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057";
|
||||
|
||||
const [results] = clientApi.widget.umami.getVisitorStats.useSuspenseQuery({
|
||||
const { data: results = [] } = clientApi.widget.umami.getVisitorStats.useQuery({
|
||||
integrationIds,
|
||||
websiteId,
|
||||
timeFrame,
|
||||
@@ -48,7 +48,7 @@ export function UmamiContent({
|
||||
});
|
||||
|
||||
const activeVisitorsInput = { integrationId: integrationIds[0] ?? "", websiteId };
|
||||
const [activeVisitors] = clientApi.widget.umami.getActiveVisitors.useSuspenseQuery(activeVisitorsInput);
|
||||
const { data: activeVisitors = 0 } = clientApi.widget.umami.getActiveVisitors.useQuery(activeVisitorsInput);
|
||||
const utils = clientApi.useUtils();
|
||||
clientApi.widget.umami.subscribeActiveVisitors.useSubscription(activeVisitorsInput, {
|
||||
onData(count) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export function UmamiEventsContent({
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057";
|
||||
|
||||
const [series] = clientApi.widget.umami.getMultiEventTimeSeries.useSuspenseQuery({
|
||||
const { data: series = [] } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
|
||||
@@ -15,7 +15,7 @@ interface UmamiTopListProps {
|
||||
|
||||
export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useScopedI18n("widget.umami");
|
||||
const [data] = clientApi.widget.umami.getTopPages.useSuspenseQuery({
|
||||
const { data = [] } = clientApi.widget.umami.getTopPages.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
@@ -26,7 +26,7 @@ export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, lim
|
||||
|
||||
export function UmamiTopReferrersContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useScopedI18n("widget.umami");
|
||||
const [data] = clientApi.widget.umami.getTopReferrers.useSuspenseQuery({
|
||||
const { data = [] } = clientApi.widget.umami.getTopReferrers.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function WeatherWidget({ isEditMode, options }: WidgetComponentPr
|
||||
latitude: options.location.latitude,
|
||||
longitude: options.location.longitude,
|
||||
};
|
||||
const [weather] = clientApi.widget.weather.atLocation.useSuspenseQuery(input, {
|
||||
const { data: weather } = clientApi.widget.weather.atLocation.useQuery(input, {
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
@@ -29,6 +29,8 @@ export default function WeatherWidget({ isEditMode, options }: WidgetComponentPr
|
||||
onData: (data) => utils.widget.weather.atLocation.setData(input, data),
|
||||
});
|
||||
|
||||
if (!weather) return null;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
|
||||
Reference in new issue
Block a user