From f0d8e97f8d06fd131acdb41b8b9750ba7153e95b Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 2 Aug 2026 17:25:02 +0200 Subject: [PATCH] fix: address final widget review --- .../widgets/src/audio-stats/component.tsx | 34 ++++++++++--------- .../src/smart-home/entity-state/component.tsx | 17 ++++++++-- packages/widgets/src/umami/umami-content.tsx | 9 +++-- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/widgets/src/audio-stats/component.tsx b/packages/widgets/src/audio-stats/component.tsx index fe78ea689..eb9adb1a5 100644 --- a/packages/widgets/src/audio-stats/component.tsx +++ b/packages/widgets/src/audio-stats/component.tsx @@ -8,9 +8,9 @@ import { formatBytes, formatDuration } from "@homarr/common"; import type { AudiobookshelfDashboardData } from "@homarr/integrations/types"; import { WidgetEmptyState } from "../common/empty-state"; -import { IntegrationErrorIndicator } from "../common/integration-error-indicator"; import type { WidgetComponentProps } from "../definition"; import { setWidgetRuntimeQueries } from "../definition"; +import { NoIntegrationDataError } from "../errors/no-data-integration"; import { AudioStatsContent } from "./audio-stats-content"; export default function AudioStatsWidget({ @@ -22,17 +22,22 @@ export default function AudioStatsWidget({ widgetStateRef, }: WidgetComponentProps<"audioStats">) { const statsInput = { integrationId: integrationIds[0] ?? "" }; - const { data: response } = clientApi.widget.audioStats.getStats.useQuery(statsInput); - const streamsInput = { integrationIds, showOnlyPlaying: false }; + const { data: response, error: statsError } = clientApi.widget.audioStats.getStats.useQuery(statsInput); + const streamsInput = { integrationIds: [statsInput.integrationId], showOnlyPlaying: true }; const streamsEnabled = displayMode === "advanced" && response?.kind === "navidrome"; - const { data: streamResults = [] } = clientApi.widget.mediaServer.getCurrentStreams.useQuery(streamsInput, { - enabled: streamsEnabled, - }); + const { data: streamResults, error: streamsError } = clientApi.widget.mediaServer.getCurrentStreams.useQuery( + streamsInput, + { enabled: streamsEnabled }, + ); setWidgetRuntimeQueries(widgetStateRef, [ getQueryKey(clientApi.widget.audioStats.getStats, statsInput, "query"), ...(streamsEnabled ? [getQueryKey(clientApi.widget.mediaServer.getCurrentStreams, streamsInput, "query")] : []), ]); + if (statsError && response === undefined) throw statsError; + if (streamsEnabled && streamsError && streamResults === undefined) throw streamsError; + const currentStreams = streamResults ?? []; + if (streamsEnabled && currentStreams.some(({ error }) => Boolean(error))) throw new NoIntegrationDataError(); if (!response) return ; const summary = ( @@ -46,22 +51,17 @@ export default function AudioStatsWidget({ ); if (displayMode === "compact") return summary; - const sessions = streamResults.flatMap((result) => + const sessions = currentStreams.flatMap((result) => result.sessions.map((session) => ({ integrationId: result.integrationId, session })), ); - const hasStreamFailures = streamResults.some(({ error }) => Boolean(error)); const audiobookStats = response.kind === "audiobookshelf" ? (response.data as AudiobookshelfDashboardData) : null; + const advancedGridColumns = width >= 760 ? 2 : 1; return ( - {hasStreamFailures && ( - - - - )}
{summary}
{response.kind === "navidrome" ? ( - = 760 ? 2 : 1} spacing="xs"> + {sessions.map(({ integrationId, session }) => { const currentlyPlaying = session.currentlyPlaying; if (!currentlyPlaying) return null; @@ -75,13 +75,15 @@ export default function AudioStatsWidget({ {currentlyPlaying.seasonName ?? currentlyPlaying.albumName ?? session.user.username}
- {session.sessionName} + + {session.sessionName} + ); })} ) : ( - = 760 ? 2 : 1} spacing="xs"> + {(audiobookStats?.libraries ?? []).map((library) => ( diff --git a/packages/widgets/src/smart-home/entity-state/component.tsx b/packages/widgets/src/smart-home/entity-state/component.tsx index 385d27205..94950135c 100644 --- a/packages/widgets/src/smart-home/entity-state/component.tsx +++ b/packages/widgets/src/smart-home/entity-state/component.tsx @@ -6,6 +6,7 @@ import { IconBinaryTree } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useIntegrationsWithInteractAccess } from "@homarr/auth/client"; +import { showErrorNotification } from "@homarr/notifications"; import { useRegisterSpotlightContextActions } from "@homarr/spotlight"; import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client"; @@ -29,18 +30,27 @@ export default function SmartHomeEntityStateWidget({ entityId: options.entityId, integrationId, }; - const { data: entity } = clientApi.widget.smartHome.entityDetails.useQuery(input); + const { + data: entity, + isPending: isEntityPending, + error: entityError, + } = clientApi.widget.smartHome.entityDetails.useQuery(input); const canInteract = useIntegrationsWithInteractAccess().some(({ id }) => id === integrationId); const utils = clientApi.useUtils(); const { mutate, isPending, error } = clientApi.widget.smartHome.switchEntity.useMutation({ onSettled: () => void utils.widget.smartHome.entityDetails.invalidate(input), + onError: () => + showErrorNotification({ + title: t("common.error"), + message: t("widget.smartHome-entityState.error.toggleFailed"), + }), }); const apiUnit = entity?.attributes.unit_of_measurement; const unit = options.entityUnit || (typeof apiUnit === "string" ? apiUnit : ""); const attribute = unit.length > 0 ? ` ${unit}` : ""; - const isActionable = options.clickable && canInteract && !isPending; + const isActionable = options.clickable && canInteract && entity !== undefined && !isEntityPending && !isPending; const handleClick = useCallback(() => { if (isEditMode) { @@ -77,6 +87,9 @@ export default function SmartHomeEntityStateWidget({ ], [handleClick, isActionable, options.displayName, options.entityId], ); + + if (entityError && entity === undefined) throw entityError; + const isTiny = width < 128 || height < 96; const advancedAttributes = [ { key: "unit" as const, value: entity?.attributes.unit_of_measurement }, diff --git a/packages/widgets/src/umami/umami-content.tsx b/packages/widgets/src/umami/umami-content.tsx index 9eb6d8cdf..08d782df9 100644 --- a/packages/widgets/src/umami/umami-content.tsx +++ b/packages/widgets/src/umami/umami-content.tsx @@ -48,7 +48,11 @@ export function UmamiContent({ const { colorScheme } = useMantineColorScheme(); const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; - const { data: results = [], isPending: isStatsPending } = clientApi.widget.umami.getVisitorStats.useQuery( + const { + data: results, + isPending: isStatsPending, + error: statsError, + } = clientApi.widget.umami.getVisitorStats.useQuery( { integrationIds, websiteId, timeFrame, eventName }, umamiQueryOptions, ); @@ -68,7 +72,7 @@ export function UmamiContent({ ? multiEventSeries.flatMap(({ dataPoints }) => dataPoints).reduce((sum, { y }) => sum + y, 0) : undefined; - const firstResult = results[0]; + const firstResult = results?.[0]; if (isStatsPending) { return ( @@ -78,6 +82,7 @@ export function UmamiContent({ ); } + if (statsError && results === undefined) throw statsError; if (!firstResult) { return (