feat: modernize dashboard widgets

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-13 19:54:28 +02:00
1 parent 1a5e083fd0
commit 6f2cd7fc2b
270 files changed
+10189 -3544

No files matched your search

+8 -1
View File
@@ -7,7 +7,12 @@ import { useScopedI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
import { UmamiContent } from "./umami-content";
export default function UmamiWidget({ options, integrationIds }: WidgetComponentProps<"umami">) {
export default function UmamiWidget({
options,
integrationIds,
width,
displayMode = "compact",
}: WidgetComponentProps<"umami">) {
if (!options.websiteId || options.websiteId.trim() === "") {
return <NoWebsiteConfigured />;
}
@@ -23,6 +28,8 @@ export default function UmamiWidget({ options, integrationIds }: WidgetComponent
chartType={options.chartType}
viewMode={options.viewMode}
topCount={options.topCount}
width={width}
displayMode={displayMode}
/>
);
}
+3
View File
@@ -8,8 +8,11 @@ export const timeFrameValues = ["today", "24h", "7d", "30d", "month", "lastMonth
export const { definition, componentLoader } = createWidgetDefinition("umami", {
icon: IconChartBar,
queryKey: [["widget", "umami"]],
refetchInterval: null,
supportedIntegrations: ["umami"],
integrationsRequired: true,
maxIntegrations: 1,
createOptions() {
return optionsBuilder.from(
(factory) => ({
+91 -66
View File
@@ -1,7 +1,7 @@
"use client";
import { BarChart, LineChart } from "@mantine/charts";
import { Box, Group, Stack, Text, useMantineColorScheme } from "@mantine/core";
import { Box, Group, SimpleGrid, Stack, Text, useMantineColorScheme } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { formatDuration } from "@homarr/common";
@@ -23,6 +23,8 @@ interface UmamiContentProps {
chartType: string;
viewMode: string;
topCount: number;
width: number;
displayMode: "compact" | "advanced";
}
export function UmamiContent({
@@ -35,6 +37,8 @@ export function UmamiContent({
chartType,
viewMode,
topCount,
width,
displayMode,
}: UmamiContentProps) {
const t = useScopedI18n("widget.umami");
const { colorScheme } = useMantineColorScheme();
@@ -88,6 +92,63 @@ export function UmamiContent({
const isOverlay = chartStyle === "overlay" && hasEventSeries;
const overlayBarSize = 16;
const selectedView =
viewMode === "events" ? (
<UmamiEventsContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
eventNames={eventNames}
chartType={chartType}
/>
) : viewMode === "topPages" ? (
<UmamiTopPagesContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
) : viewMode === "topReferrers" ? (
<UmamiTopReferrersContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
) : chartType === "sparkline" ? (
<LineChart
h="100%"
data={chartData}
dataKey="label"
series={series}
withDots={false}
curveType="monotone"
tickLine="none"
gridAxis="none"
withLegend={false}
withTooltip
withXAxis
withYAxis={false}
xAxisProps={{ tick: { fontSize: 9, fill: tickColor }, interval: "preserveStartEnd" }}
/>
) : (
<BarChart
h="100%"
data={chartData}
dataKey="label"
series={series}
tickLine="none"
gridAxis="none"
withLegend={false}
withTooltip
withXAxis
withYAxis={false}
barProps={isOverlay ? { barSize: overlayBarSize, radius: 2 } : { radius: 2 }}
barChartProps={isOverlay ? { barGap: -overlayBarSize } : undefined}
xAxisProps={{ tick: { fontSize: 9, fill: tickColor }, interval: "preserveStartEnd" }}
/>
);
return (
<Stack gap={4} p="xs" h="100%">
<Group justify="space-between" align="baseline" wrap="nowrap">
@@ -140,74 +201,38 @@ export function UmamiContent({
<Text size="xs" c="dimmed">
{visitorStats.totalVisits.toLocaleString()} {t("visits")}
</Text>
<Text size="xs" c="dimmed">
{visitorStats.bounceRate}% {t("bounceRate")}
</Text>
<Text size="xs" c="dimmed">
{formatDuration(visitorStats.avgDuration * 1000)} {t("avgDuration")}
</Text>
{(displayMode === "advanced" || width >= 260) && (
<>
<Text size="xs" c="dimmed">
{visitorStats.bounceRate}% {t("bounceRate")}
</Text>
<Text size="xs" c="dimmed">
{formatDuration(visitorStats.avgDuration * 1000)} {t("avgDuration")}
</Text>
</>
)}
</Group>
<Box mt={4} style={{ flex: 1, minHeight: 0 }}>
{viewMode === "events" ? (
<UmamiEventsContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
eventNames={eventNames}
chartType={chartType}
/>
) : viewMode === "topPages" ? (
<UmamiTopPagesContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
) : viewMode === "topReferrers" ? (
<UmamiTopReferrersContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
) : chartType === "sparkline" ? (
<LineChart
h="100%"
data={chartData}
dataKey="label"
series={series}
withDots={false}
curveType="monotone"
tickLine="none"
gridAxis="none"
withLegend={false}
withTooltip
withXAxis
withYAxis={false}
xAxisProps={{
tick: { fontSize: 9, fill: tickColor },
interval: "preserveStartEnd",
}}
/>
{displayMode === "advanced" ? (
<SimpleGrid cols={width >= 900 ? 2 : 1} spacing="md" h="100%">
<Box mih={260}>{selectedView}</Box>
<SimpleGrid cols={2} spacing="md" mih={260}>
<UmamiTopPagesContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
<UmamiTopReferrersContent
integrationIds={integrationIds}
websiteId={websiteId}
timeFrame={timeFrame}
limit={topCount}
/>
</SimpleGrid>
</SimpleGrid>
) : (
<BarChart
h="100%"
data={chartData}
dataKey="label"
series={series}
tickLine="none"
gridAxis="none"
withLegend={false}
withTooltip
withXAxis
withYAxis={false}
barProps={isOverlay ? { barSize: overlayBarSize, radius: 2 } : { radius: 2 }}
barChartProps={isOverlay ? { barGap: -overlayBarSize } : undefined}
xAxisProps={{
tick: { fontSize: 9, fill: tickColor },
interval: "preserveStartEnd",
}}
/>
selectedView
)}
</Box>
</Stack>