From ca298cd6882db25a2013981a2863b93cae43b875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zachary=20Gu=C3=A9not?= Date: Sat, 12 Sep 2026 16:05:00 +0200 Subject: [PATCH] fix(widgets): report every health state in the UniFi site summary Co-Authored-By: Claude Opus 5 --- .../src/unifi-site-manager/component.tsx | 5 +- .../src/unifi-site-manager/health-bar.tsx | 88 ++++++++++++------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/packages/widgets/src/unifi-site-manager/component.tsx b/packages/widgets/src/unifi-site-manager/component.tsx index aabc67386..92aa9376b 100644 --- a/packages/widgets/src/unifi-site-manager/component.tsx +++ b/packages/widgets/src/unifi-site-manager/component.tsx @@ -25,7 +25,7 @@ export default function UnifiSiteManagerWidget({ width, }: WidgetComponentProps<"unifiSiteManager">) { const t = useI18n(); - const { data: results = [] } = clientApi.widget.unifiSiteManager.overview.useQuery({ integrationIds }); + const { data: results = [], isFetching } = clientApi.widget.unifiSiteManager.overview.useQuery({ integrationIds }); const sites = results[0]?.overview.sites ?? []; const isNarrow = width < NARROW_WIDTH; @@ -165,6 +165,9 @@ export default function UnifiSiteManagerWidget({ records={sites} columns={columns} idAccessor="siteId" + // Sans cela, « aucun site » s'affiche pendant la première requête : une + // affirmation sur le compte, fausse tant qu'on n'a rien reçu. + fetching={isFetching && sites.length === 0} noRecordsText={t("widget.unifiSiteManager.noSites")} /> diff --git a/packages/widgets/src/unifi-site-manager/health-bar.tsx b/packages/widgets/src/unifi-site-manager/health-bar.tsx index 11ffe892d..cda5a5f74 100644 --- a/packages/widgets/src/unifi-site-manager/health-bar.tsx +++ b/packages/widgets/src/unifi-site-manager/health-bar.tsx @@ -1,6 +1,6 @@ "use client"; -import { Box, Group, Text, Tooltip } from "@mantine/core"; +import { Box, Group, Stack, Text, Tooltip } from "@mantine/core"; import type { UnifiSiteHealthSegment, UnifiSiteHealthState, UnifiSiteHealthSummary } from "@homarr/integrations/types"; @@ -20,6 +20,25 @@ const STATE_COLOR: Record = { const SEGMENT_HEIGHT = 14; +const NOTABLE_STATES = ["down", "degraded", "unknown"] as const; + +/** + * TOUS les états non nuls sont listés, sans plafond sur leur nombre. + * N'en retenir qu'un ferait disparaître les heures des autres, et le lecteur + * en déduirait un reste sain qui serait faux. `unknown` compte au même titre : + * une fenêtre partiellement rapportée n'est pas une fenêtre partiellement + * dégradée, et la taire laisse croire que les heures manquantes étaient mauvaises. + * Pour qui ne distingue pas les trois couleurs de la barre, cette phrase EST la + * donnée — toute omission ici est une omission tout court. + */ +const composeSummary = (summary: UnifiSiteHealthSummary, labels: Record) => { + const notable = NOTABLE_STATES.filter((state) => summary[state] > 0).map( + (state) => `${summary[state]}h ${labels[state]}`, + ); + + return notable.length > 0 ? notable.join(" · ") : `${summary.healthy}h ${labels.healthy}`; +}; + export const HealthBar = ({ segments, summary, @@ -30,35 +49,40 @@ export const HealthBar = ({ summary: UnifiSiteHealthSummary; formatHour: (at: Date) => string; labels: Record; -}) => ( - - - {segments.map((segment) => ( - - - - ))} - +}) => { + const summaryText = composeSummary(summary, labels); - {/* Le canal qui porte l'information quand la couleur ne passe pas. */} - - {summary.down > 0 - ? `${summary.down}h ${labels.down}` - : summary.degraded > 0 - ? `${summary.degraded}h ${labels.degraded}` - : `${summary.healthy}h ${labels.healthy}`} - - -); + return ( + + {/* Les segments n'ont que le survol : sans nom accessible, la barre est + muette au clavier et au toucher. Le nom réutilise le résumé composé + ci-dessus pour que les deux ne puissent pas diverger. */} + + {segments.map((segment) => ( + + + + ))} + + + {/* Le canal qui porte l'information quand la couleur ne passe pas. + Sous la barre et sans `nowrap` : il dispose de toute la largeur de la + cellule et passe à la ligne plutôt que d'être tronqué. */} + + {summaryText} + + + ); +};