"use client";
import { useMemo } from "react";
import { Avatar, Badge, Card, Flex, Group, ScrollArea, SimpleGrid, Stack, Text } from "@mantine/core";
import { IconClock } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { useTimeAgo } from "@homarr/common";
import { useI18n } from "@homarr/translation/client";
import { getSafeApplicationUrl, SAFE_NEW_TAB_REL } from "../common/application-url";
import { getUsableWidgetQueryData } from "../common/query-state";
import { WidgetQueryErrorIndicator } from "../common/query-state-indicator";
import type { WidgetComponentProps } from "../definition";
import { getNotificationDisplay } from "./display";
export default function NotificationsWidget({
options,
integrationIds,
width,
height,
displayMode,
}: WidgetComponentProps<"notifications">) {
const notificationsQuery = clientApi.widget.notifications.getNotifications.useQuery({
...options,
integrationIds,
});
const notificationData = getUsableWidgetQueryData(notificationsQuery);
const notificationIntegrations = useMemo(() => notificationData ?? [], [notificationData]);
const { isPending } = notificationsQuery;
const t = useI18n();
const board = useRequiredBoard();
const sortedNotifications = useMemo(
() =>
notificationIntegrations
.flatMap((integration) =>
integration.data.map((notification) => ({
...notification,
compositeKey: `${integration.integration.id}:${notification.id}`,
integrationName: integration.integration.name,
})),
)
.sort((entryA, entryB) => entryB.time.getTime() - entryA.time.getTime()),
[notificationIntegrations],
);
const failedIntegrations = notificationIntegrations.filter(
(integration): integration is typeof integration & { error: string } => Boolean(integration.error),
);
const isDense = width < 280 || height < 180;
const isRoomy = width >= 360 && height >= 220;
const bodyLineClamp = height < 112 ? 1 : isDense ? 2 : height >= 300 ? 8 : 4;
const notificationDisplay = getNotificationDisplay({
displayMode,
hideLogos: options.hideLogos,
isRoomy,
bodyLineClamp,
});
const columns = width >= 720 ? 2 : 1;
const spacing = isRoomy ? "sm" : "xs";
return (
{notificationsQuery.error && (
)}
{failedIntegrations.length > 0 && (
{failedIntegrations.map((integration) => (
{integration.integration.name}: {t("common.error")}
))}
)}
{isPending ? (
{t("common.action.loading")}
) : sortedNotifications.length > 0 ? (
{sortedNotifications.map((notification) => {
const href = getSafeApplicationUrl(notification.href);
return (
1 ? undefined : "transparent"}
style={{
color: "inherit",
textDecoration: "none",
borderBottom:
columns > 1
? undefined
: "1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4))",
}}
>
{notificationDisplay.showLogos && notification.source?.iconUrl && (
)}
{notification.title && (
{notification.title}
)}
{notification.body}
);
})}
) : (
{t("widget.notifications.noItems")}
)}
);
}
const InfoDisplay = ({ date, source, dense }: { date: Date; source?: string; dense: boolean }) => {
const timeAgo = useTimeAgo(date, 30000); // update every 30sec
return (
{timeAgo}
{source && •}
{source && (
{source}
)}
);
};