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
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} />
<Comp

View File

@@ -98,56 +98,59 @@ export const createManyIntegrationMiddleware = <TKind extends IntegrationKind>(
action: IntegrationAction,
...kinds: AtLeastOneOf<TKind> // Ensure at least one kind is provided
) => {
return publicProcedure
.input(z.object({ integrationIds: z.array(z.string()).min(1) }))
.use(async ({ ctx, input, next }) => {
const dbIntegrations = await ctx.db.query.integrations.findMany({
where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)),
with: {
secrets: true,
items: {
return publicProcedure.input(z.object({ integrationIds: z.array(z.string()) })).use(async ({ ctx, input, next }) => {
const dbIntegrations =
input.integrationIds.length >= 1
? await ctx.db.query.integrations.findMany({
where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)),
with: {
item: {
secrets: true,
items: {
with: {
section: {
columns: {
boardId: true,
item: {
with: {
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 (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(",")}])`,
});
}
if (dbIntegrations.length >= 1) {
await throwIfActionIsNotAllowedAsync(action, ctx.db, dbIntegrations, ctx.session);
}
return next({
ctx: {
integrations: dbIntegrations.map(
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
...rest,
kind: kind as TKind,
decryptedSecrets: secrets.map((secret) => ({
...secret,
value: decryptSecret(secret.value),
})),
}),
),
},
});
return next({
ctx: {
integrations: dbIntegrations.map(
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
...rest,
kind: kind as TKind,
decryptedSecrets: secrets.map((secret) => ({
...secret,
value: decryptSecret(secret.value),
})),
}),
),
},
});
});
};
/**

View File

@@ -5,6 +5,7 @@ import { useParams } from "next/navigation";
import { Calendar } from "@mantine/dates";
import dayjs from "dayjs";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import type { CalendarEvent } from "@homarr/integrations/types";
@@ -12,8 +13,22 @@ import type { WidgetComponentProps } from "../definition";
import { CalendarDay } from "./calender-day";
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());
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(
{
integrationIds,
@@ -28,6 +43,19 @@ export default function CalendarWidget({ isEditMode, integrationIds, options }:
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 locale = params.locale as string;
const [firstDayOfWeek] = clientApi.user.getFirstDayOfWeekForUserOrDefault.useSuspenseQuery();
@@ -37,6 +65,7 @@ export default function CalendarWidget({ isEditMode, integrationIds, options }:
defaultDate={new Date()}
onPreviousMonth={setMonth}
onNextMonth={setMonth}
highlightToday
locale={locale}
hideWeekdays={false}
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"),
integrationsRequired: false,
}).withDynamicImport(() => import("./component"));

View File

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