feat: add support for calendar without integrations, add today mark (#2016)

* feat: add support for calendar without integrations, add today mark

* fix: ci issues
This commit is contained in:
Meier Lukas
2025-01-21 11:01:52 +01:00
committed by GitHub
parent b30c192b8f
commit 9d85677046
5 changed files with 81 additions and 41 deletions

View File

@@ -79,7 +79,13 @@ const InnerContent = ({ item, ...dimensions }: InnerContentProps) => {
> >
<Throw <Throw
error={new NoIntegrationSelectedError()} error={new NoIntegrationSelectedError()}
when={widgetSupportsIntegrations && item.integrationIds.length === 0} when={
widgetSupportsIntegrations &&
item.integrationIds.length === 0 &&
"integrationsRequired" in definition &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
definition.integrationsRequired !== false
}
/> />
<BoardItemMenu offset={4} item={newItem} /> <BoardItemMenu offset={4} item={newItem} />
<Comp <Comp

View File

@@ -98,56 +98,59 @@ export const createManyIntegrationMiddleware = <TKind extends IntegrationKind>(
action: IntegrationAction, action: IntegrationAction,
...kinds: AtLeastOneOf<TKind> // Ensure at least one kind is provided ...kinds: AtLeastOneOf<TKind> // Ensure at least one kind is provided
) => { ) => {
return publicProcedure return publicProcedure.input(z.object({ integrationIds: z.array(z.string()) })).use(async ({ ctx, input, next }) => {
.input(z.object({ integrationIds: z.array(z.string()).min(1) })) const dbIntegrations =
.use(async ({ ctx, input, next }) => { input.integrationIds.length >= 1
const dbIntegrations = await ctx.db.query.integrations.findMany({ ? await ctx.db.query.integrations.findMany({
where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)), where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)),
with: {
secrets: true,
items: {
with: { with: {
item: { secrets: true,
items: {
with: { with: {
section: { item: {
columns: { with: {
boardId: true, section: {
columns: {
boardId: true,
},
},
}, },
}, },
}, },
}, },
userPermissions: true,
groupPermissions: true,
}, },
}, })
userPermissions: true, : [];
groupPermissions: true,
}, const offset = input.integrationIds.length - dbIntegrations.length;
if (offset !== 0) {
throw new TRPCError({
code: "NOT_FOUND",
message: `${offset} of the specified integrations not found or not of kinds ${kinds.join(",")}: ([${input.integrationIds.join(",")}] compared to [${dbIntegrations.map(({ id, kind }) => `${kind}:${id}`).join(",")}])`,
}); });
}
const offset = input.integrationIds.length - dbIntegrations.length; if (dbIntegrations.length >= 1) {
if (offset !== 0) {
throw new TRPCError({
code: "NOT_FOUND",
message: `${offset} of the specified integrations not found or not of kinds ${kinds.join(",")}: ([${input.integrationIds.join(",")}] compared to [${dbIntegrations.map(({ id, kind }) => `${kind}:${id}`).join(",")}])`,
});
}
await throwIfActionIsNotAllowedAsync(action, ctx.db, dbIntegrations, ctx.session); await throwIfActionIsNotAllowedAsync(action, ctx.db, dbIntegrations, ctx.session);
}
return next({ return next({
ctx: { ctx: {
integrations: dbIntegrations.map( integrations: dbIntegrations.map(
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({ ({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
...rest, ...rest,
kind: kind as TKind, kind: kind as TKind,
decryptedSecrets: secrets.map((secret) => ({ decryptedSecrets: secrets.map((secret) => ({
...secret, ...secret,
value: decryptSecret(secret.value), value: decryptSecret(secret.value),
})), })),
}), }),
), ),
}, },
});
}); });
});
}; };
/** /**

View File

@@ -5,6 +5,7 @@ import { useParams } from "next/navigation";
import { Calendar } from "@mantine/dates"; import { Calendar } from "@mantine/dates";
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import type { CalendarEvent } from "@homarr/integrations/types"; import type { CalendarEvent } from "@homarr/integrations/types";
@@ -12,8 +13,22 @@ import type { WidgetComponentProps } from "../definition";
import { CalendarDay } from "./calender-day"; import { CalendarDay } from "./calender-day";
import classes from "./component.module.css"; import classes from "./component.module.css";
export default function CalendarWidget({ isEditMode, integrationIds, options }: WidgetComponentProps<"calendar">) { export default function CalendarWidget(props: WidgetComponentProps<"calendar">) {
const [month, setMonth] = useState(new Date()); const [month, setMonth] = useState(new Date());
if (props.integrationIds.length === 0) {
return <CalendarBase {...props} events={[]} month={month} setMonth={setMonth} />;
}
return <FetchCalendar month={month} setMonth={setMonth} {...props} />;
}
interface FetchCalendarProps extends WidgetComponentProps<"calendar"> {
month: Date;
setMonth: (date: Date) => void;
}
const FetchCalendar = ({ month, setMonth, isEditMode, integrationIds, options }: FetchCalendarProps) => {
const [events] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery( const [events] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery(
{ {
integrationIds, integrationIds,
@@ -28,6 +43,19 @@ export default function CalendarWidget({ isEditMode, integrationIds, options }:
retry: false, retry: false,
}, },
); );
return <CalendarBase isEditMode={isEditMode} events={events} month={month} setMonth={setMonth} options={options} />;
};
interface CalendarBaseProps {
isEditMode: boolean;
events: RouterOutputs["widget"]["calendar"]["findAllEvents"];
month: Date;
setMonth: (date: Date) => void;
options: WidgetComponentProps<"calendar">["options"];
}
const CalendarBase = ({ isEditMode, events, month, setMonth, options }: CalendarBaseProps) => {
const params = useParams(); const params = useParams();
const locale = params.locale as string; const locale = params.locale as string;
const [firstDayOfWeek] = clientApi.user.getFirstDayOfWeekForUserOrDefault.useSuspenseQuery(); const [firstDayOfWeek] = clientApi.user.getFirstDayOfWeekForUserOrDefault.useSuspenseQuery();
@@ -37,6 +65,7 @@ export default function CalendarWidget({ isEditMode, integrationIds, options }:
defaultDate={new Date()} defaultDate={new Date()}
onPreviousMonth={setMonth} onPreviousMonth={setMonth}
onNextMonth={setMonth} onNextMonth={setMonth}
highlightToday
locale={locale} locale={locale}
hideWeekdays={false} hideWeekdays={false}
date={month} date={month}
@@ -95,4 +124,4 @@ export default function CalendarWidget({ isEditMode, integrationIds, options }:
}} }}
/> />
); );
} };

View File

@@ -27,4 +27,5 @@ export const { definition, componentLoader } = createWidgetDefinition("calendar"
}), }),
})), })),
supportedIntegrations: getIntegrationKindsByCategory("calendar"), supportedIntegrations: getIntegrationKindsByCategory("calendar"),
integrationsRequired: false,
}).withDynamicImport(() => import("./component")); }).withDynamicImport(() => import("./component"));

View File

@@ -29,6 +29,7 @@ export const createWidgetDefinition = <TKind extends WidgetKind, TDefinition ext
export interface WidgetDefinition { export interface WidgetDefinition {
icon: TablerIcon; icon: TablerIcon;
supportedIntegrations?: IntegrationKind[]; supportedIntegrations?: IntegrationKind[];
integrationsRequired?: boolean;
options: WidgetOptionsRecord; options: WidgetOptionsRecord;
errors?: Partial< errors?: Partial<
Record< Record<