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:
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 }:
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ export const { definition, componentLoader } = createWidgetDefinition("calendar"
|
|||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
supportedIntegrations: getIntegrationKindsByCategory("calendar"),
|
supportedIntegrations: getIntegrationKindsByCategory("calendar"),
|
||||||
|
integrationsRequired: false,
|
||||||
}).withDynamicImport(() => import("./component"));
|
}).withDynamicImport(() => import("./component"));
|
||||||
|
|||||||
@@ -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<
|
||||||
|
|||||||
Reference in New Issue
Block a user