diff --git a/packages/api/src/router/widgets/calendar.ts b/packages/api/src/router/widgets/calendar.ts index 871ec6239..5c8322299 100644 --- a/packages/api/src/router/widgets/calendar.ts +++ b/packages/api/src/router/widgets/calendar.ts @@ -1,6 +1,11 @@ +import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; +import type { Modify } from "@homarr/common/types"; +import type { Integration } from "@homarr/db/schema"; +import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; +import type { CalendarEvent } from "@homarr/integrations/types"; import { radarrReleaseTypes } from "@homarr/integrations/types"; import { calendarMonthRequestHandler } from "@homarr/request-handler/calendar"; @@ -19,14 +24,56 @@ export const calendarRouter = createTRPCRouter({ ) .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar"))) .query(async ({ ctx, input }) => { - const results = await Promise.all( + return await Promise.all( ctx.integrations.map(async (integration) => { - const innerHandler = calendarMonthRequestHandler.handler(integration, input); + const { integrationIds: _integrationIds, ...handlerInput } = input; + const innerHandler = calendarMonthRequestHandler.handler(integration, handlerInput); const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return data; + return { + events: data, + integration: { + id: integration.id, + name: integration.name, + kind: integration.kind, + }, + }; }), ); - return results.flat(); + }), + subscribeToEvents: publicProcedure + .input( + z.object({ + year: z.number(), + month: z.number(), + releaseType: z.array(z.enum(radarrReleaseTypes)), + showUnmonitored: z.boolean(), + }), + ) + .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar"))) + .subscription(({ ctx, input }) => { + return observable<{ + integration: Modify }>; + events: CalendarEvent[]; + }>((emit) => { + const unsubscribes: (() => void)[] = []; + for (const integrationWithSecrets of ctx.integrations) { + const { decryptedSecrets: _, ...integration } = integrationWithSecrets; + const { integrationIds: _integrationIds, ...handlerInput } = input; + const innerHandler = calendarMonthRequestHandler.handler(integrationWithSecrets, handlerInput); + const unsubscribe = innerHandler.subscribe((events) => { + emit.next({ + integration, + events, + }); + }); + unsubscribes.push(unsubscribe); + } + return () => { + unsubscribes.forEach((unsubscribe) => { + unsubscribe(); + }); + }; + }); }), }); diff --git a/packages/widgets/src/calendar/component.tsx b/packages/widgets/src/calendar/component.tsx index 7aa081b73..1677b2ba2 100644 --- a/packages/widgets/src/calendar/component.tsx +++ b/packages/widgets/src/calendar/component.tsx @@ -7,7 +7,6 @@ import { Calendar } from "@mantine/dates"; import { useElementSize } from "@mantine/hooks"; import dayjs from "dayjs"; -import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { useRequiredBoard } from "@homarr/boards/context"; import type { CalendarEvent } from "@homarr/integrations/types"; @@ -33,28 +32,43 @@ interface FetchCalendarProps extends WidgetComponentProps<"calendar"> { } const FetchCalendar = ({ month, setMonth, isEditMode, integrationIds, options }: FetchCalendarProps) => { - const [events] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery( - { - integrationIds, - month: month.getMonth(), - year: month.getFullYear(), - releaseType: options.releaseType, - showUnmonitored: options.showUnmonitored, + const input = { + integrationIds, + month: month.getMonth(), + year: month.getFullYear(), + releaseType: options.releaseType, + showUnmonitored: options.showUnmonitored, + }; + const [data] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery(input, { + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + retry: false, + }); + + const utils = clientApi.useUtils(); + clientApi.widget.calendar.subscribeToEvents.useSubscription(input, { + onData(data) { + utils.widget.calendar.findAllEvents.setData(input, (old) => { + return old?.map((item) => { + if (item.integration.id !== data.integration.id) return item; + return { + ...item, + events: data.events, + }; + }); + }); }, - { - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + }); + + const events = useMemo(() => data.flatMap((item) => item.events), [data]); return ; }; interface CalendarBaseProps { isEditMode: boolean; - events: RouterOutputs["widget"]["calendar"]["findAllEvents"]; + events: CalendarEvent[]; month: Date; setMonth: (date: Date) => void; options: WidgetComponentProps<"calendar">["options"];