diff --git a/__test__/features/Calendars/api/addCalendarResourceAsync.test.tsx b/__test__/features/Calendars/api/addCalendarResourceAsync.test.tsx
index 5260d05..2599a00 100644
--- a/__test__/features/Calendars/api/addCalendarResourceAsync.test.tsx
+++ b/__test__/features/Calendars/api/addCalendarResourceAsync.test.tsx
@@ -1,6 +1,6 @@
import { addCalendarResourceAsync } from "@/features/Calendars/api/addCalendarResourceAsync";
import { addSharedCalendar } from "@/features/Calendars/CalendarApi";
-import { getResourceDetails } from "@/features/User/userAPI";
+import { getResourceDetails, getUserDetails } from "@/features/User/userAPI";
import { toRejectedError } from "@/utils/errorUtils";
import { configureStore } from "@reduxjs/toolkit";
@@ -10,6 +10,7 @@ jest.mock("@/utils/errorUtils");
const mockedAddSharedCalendar = addSharedCalendar as jest.Mock;
const mockedGetResourceDetails = getResourceDetails as jest.Mock;
+const mockedGetUserDetails = getUserDetails as jest.Mock;
const mockedToRejectedError = toRejectedError as jest.Mock;
describe("addCalendarResourceAsync thunk", () => {
@@ -55,6 +56,11 @@ describe("addCalendarResourceAsync thunk", () => {
it("should add shared calendar, fetch resource details, map userData", async () => {
mockedGetResourceDetails.mockResolvedValueOnce(mockResolvedResourceData);
+ mockedGetUserDetails.mockResolvedValueOnce({
+ firstname: "Creator",
+ lastname: "User",
+ emails: ["creator@example.com"],
+ });
mockedAddSharedCalendar.mockResolvedValueOnce({});
const result = await addCalendarResourceAsync(
@@ -67,6 +73,7 @@ describe("addCalendarResourceAsync thunk", () => {
mockPayload.cal
);
expect(mockedGetResourceDetails).toHaveBeenCalledWith("res-456");
+ expect(mockedGetUserDetails).toHaveBeenCalledWith("user-789");
expect(result.type).toBe("calendars/addCalendarResource/fulfilled");
expect(result.payload).toEqual({
@@ -75,7 +82,12 @@ describe("addCalendarResourceAsync thunk", () => {
desc: "A meeting room",
link: "/calendars/user-123/cal-123.json",
name: "Resource Room A",
- owner: mockResolvedResourceData,
+ owner: {
+ firstname: "Creator",
+ lastname: "User",
+ emails: ["creator@example.com"],
+ resource: true,
+ },
});
});
diff --git a/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx b/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx
index c51b899..dd95c85 100644
--- a/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx
+++ b/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx
@@ -5,7 +5,7 @@ import {
getUserDetails,
} from "@/features/User/userAPI";
import { getCalendars } from "@/features/Calendars/CalendarApi";
-import { formatReduxError, toRejectedError } from "@/utils/errorUtils";
+import { formatReduxError } from "@/utils/errorUtils";
import { normalizeCalendar } from "@/features/Calendars/utils/normalizeCalendar";
jest.mock("@/features/User/userAPI");
@@ -176,16 +176,20 @@ describe("getCalendarsListAsync", () => {
ownerId: "resource-123",
});
- // getUserDetails fails with 404 for the resource ID
- mockedGetUserDetails.mockRejectedValueOnce({ response: { status: 404 } });
+ // getUserDetails fails with 404 for the resource ID, succeeds for the creator
+ mockedGetUserDetails.mockImplementation((id: string) => {
+ if (id === "resource-123")
+ return Promise.reject({ response: { status: 404 } });
+ if (id === "creator-456")
+ return Promise.resolve({
+ firstname: "Creator",
+ lastname: "User",
+ emails: [],
+ });
+ return Promise.resolve({ firstname: "", lastname: "", emails: [] });
+ });
// Then getResourceDetails is called and succeeds
mockedGetResourceDetails.mockResolvedValueOnce({ creator: "creator-456" });
- // Then getUserDetails is called for the creator and succeeds
- mockedGetUserDetails.mockResolvedValueOnce({
- firstname: "Creator",
- lastname: "User",
- emails: [],
- });
const thunk = getCalendarsListAsync();
const result = await thunk(dispatch, getState, undefined);
diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx
index f6cc215..ce6badf 100644
--- a/__test__/features/Events/EventDisplay.test.tsx
+++ b/__test__/features/Events/EventDisplay.test.tsx
@@ -65,6 +65,14 @@ describe("Event Preview Display", () => {
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
+ {
+ cn: "Projector Room",
+ cal_address: "room1@test.com",
+ partstat: "ACCEPTED",
+ rsvp: "TRUE",
+ role: "REQ-PARTICIPANT",
+ cutype: "RESOURCE",
+ },
],
},
event2: {
@@ -163,6 +171,8 @@ describe("Event Preview Display", () => {
expect(screen.getByText(/– 10:00/)).toBeInTheDocument();
expect(screen.getByText("Calendar")).toBeInTheDocument();
+
+ expect(screen.getByText("Projector Room")).toBeInTheDocument();
});
it("calls onClose when Cancel clicked", () => {
renderWithProviders(
diff --git a/__test__/features/Events/EventModal.test.tsx b/__test__/features/Events/EventModal.test.tsx
index cf716a3..680b6b8 100644
--- a/__test__/features/Events/EventModal.test.tsx
+++ b/__test__/features/Events/EventModal.test.tsx
@@ -81,6 +81,22 @@ describe("EventPopover", () => {
},
],
},
+ {
+ id: "room1@example.com",
+ objectType: "resource",
+ emailAddresses: [
+ {
+ value: "room1@example.com",
+ type: "default",
+ },
+ ],
+ names: [
+ {
+ displayName: "Room 1",
+ type: "default",
+ },
+ ],
+ },
];
(api.post as jest.Mock).mockReturnValue({
json: jest.fn().mockResolvedValue(mockUsers),
@@ -211,71 +227,137 @@ describe("EventPopover", () => {
});
it("adds a attendee", async () => {
jest.useFakeTimers();
- jest
- .spyOn(calendarsApi, "getCalendars")
- .mockReturnValue({ json: jest.fn() });
- renderPopover();
- fireEvent.change(screen.getByLabelText("event.form.title"), {
- target: { value: "newEvent" },
- });
- const select = screen.getByLabelText("peopleSearch.label");
+ try {
+ jest
+ .spyOn(calendarsApi, "getCalendars")
+ .mockReturnValue({ json: jest.fn() });
+ renderPopover();
+ fireEvent.change(screen.getByLabelText("event.form.title"), {
+ target: { value: "newEvent" },
+ });
+ const select = screen.getByLabelText("peopleSearch.label");
- act(() => {
- select.focus();
- fireEvent.mouseDown(select);
- userEvent.type(select, "john");
- });
- await act(async () => {
- jest.advanceTimersByTime(400);
- });
- await waitFor(() => expect(api.post).toHaveBeenCalledTimes(1));
+ act(() => {
+ select.focus();
+ fireEvent.mouseDown(select);
+ userEvent.type(select, "john");
+ });
+ await act(async () => {
+ jest.advanceTimersByTime(400);
+ });
+ await waitFor(() => expect(api.post).toHaveBeenCalledTimes(1));
- await waitFor(() => {
- expect(screen.getByText("John Doe")).toBeInTheDocument();
- });
- await act(async () => {
- userEvent.click(screen.getByText("John Doe"));
- });
-
- const spy = jest
- .spyOn(eventThunks, "putEventAsync")
- .mockImplementation((payload) => {
- const promise = Promise.resolve(payload);
- (promise as any).unwrap = () => promise;
- return () => promise as any;
+ await waitFor(() => {
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ });
+ await act(async () => {
+ userEvent.click(screen.getByText("John Doe"));
});
- fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
+ const spy = jest
+ .spyOn(eventThunks, "putEventAsync")
+ .mockImplementation((payload) => {
+ const promise = Promise.resolve(payload);
+ (promise as any).unwrap = () => promise;
+ return () => promise as any;
+ });
- await waitFor(() => {
- expect(spy).toHaveBeenCalled();
- });
+ fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
- const receivedPayload = spy.mock.calls[0][0];
+ await waitFor(() => {
+ expect(spy).toHaveBeenCalled();
+ });
- expect(receivedPayload.cal).toEqual(
- preloadedState.calendars.list["667037022b752d0026472254/cal1"]
- );
+ const receivedPayload = spy.mock.calls[0][0];
- expect(receivedPayload.newEvent.attendee).toHaveLength(2);
- expect(receivedPayload.newEvent.attendee).toStrictEqual([
- {
- cn: "test",
- cal_address: "test@test.com",
- partstat: "ACCEPTED",
- rsvp: "FALSE",
- role: "CHAIR",
- cutype: "INDIVIDUAL",
- },
- {
- cn: "John Doe",
- cal_address: "john@example.com",
+ expect(receivedPayload.cal).toEqual(
+ preloadedState.calendars.list["667037022b752d0026472254/cal1"]
+ );
+
+ expect(receivedPayload.newEvent.attendee).toHaveLength(2);
+ expect(receivedPayload.newEvent.attendee).toStrictEqual([
+ {
+ cn: "test",
+ cal_address: "test@test.com",
+ partstat: "ACCEPTED",
+ rsvp: "FALSE",
+ role: "CHAIR",
+ cutype: "INDIVIDUAL",
+ },
+ {
+ cn: "John Doe",
+ cal_address: "john@example.com",
+ partstat: "NEEDS-ACTION",
+ rsvp: "FALSE",
+ role: "REQ-PARTICIPANT",
+ cutype: "INDIVIDUAL",
+ },
+ ]);
+ } finally {
+ jest.useRealTimers();
+ }
+ });
+
+ it("adds a resource", async () => {
+ jest.useFakeTimers();
+ try {
+ renderPopover();
+ fireEvent.change(screen.getByLabelText("event.form.title"), {
+ target: { value: "newEventWithResource" },
+ });
+
+ fireEvent.click(
+ screen.getByRole("button", { name: "common.moreOptions" })
+ );
+
+ const resourceCombobox = screen.getByPlaceholderText(
+ "resourceSearch.placeholder"
+ );
+
+ act(() => {
+ resourceCombobox.focus();
+ fireEvent.mouseDown(resourceCombobox);
+ });
+ await userEvent.type(resourceCombobox, "room");
+
+ await act(async () => {
+ jest.advanceTimersByTime(400);
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText("Room 1")).toBeInTheDocument();
+ });
+
+ await userEvent.click(screen.getByText("Room 1"));
+
+ const spy = jest
+ .spyOn(eventThunks, "putEventAsync")
+ .mockImplementation((payload) => {
+ const promise = Promise.resolve(payload);
+ (promise as any).unwrap = () => promise;
+ return () => promise as any;
+ });
+
+ fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
+
+ await waitFor(() => {
+ expect(spy).toHaveBeenCalled();
+ });
+
+ const receivedPayload = spy.mock.calls[0][0];
+
+ expect(receivedPayload.newEvent.attendee).toHaveLength(2); // Organizer + 1 resource
+ expect(receivedPayload.newEvent.attendee[1]).toStrictEqual({
+ cn: "Room 1",
+ cal_address: "room1@example.com",
partstat: "NEEDS-ACTION",
- rsvp: "FALSE",
+ rsvp: "TRUE",
role: "REQ-PARTICIPANT",
- cutype: "INDIVIDUAL",
- },
- ]);
+ cutype: "RESOURCE",
+ });
+ } finally {
+ jest.useRealTimers();
+ }
});
it("dispatches putEventAsync and calls onClose when Save is clicked", async () => {
diff --git a/__test__/features/Events/EventUpdateModal.test.tsx b/__test__/features/Events/EventUpdateModal.test.tsx
index f7340cd..5b1fc09 100644
--- a/__test__/features/Events/EventUpdateModal.test.tsx
+++ b/__test__/features/Events/EventUpdateModal.test.tsx
@@ -157,6 +157,89 @@ describe("EventUpdateModal Timezone Handling", () => {
// Verify the timezone is still preserved (should be Asia/Bangkok)
expect(titleInput).toHaveValue("Updated Event");
});
+
+ it("preserves resources when editing an event", async () => {
+ const eventDateUTC = new Date("2025-01-15T07:00:00.000Z");
+
+ const eventData = {
+ uid: "test-event-resource",
+ title: "Resource Event",
+ calId: "667037022b752d0026472254/cal1",
+ start: eventDateUTC.toISOString(),
+ end: new Date(eventDateUTC.getTime() + 3600000).toISOString(),
+ timezone: "Asia/Bangkok",
+ allday: false,
+ organizer: { cn: "test", cal_address: "test@test.com" },
+ attendee: [
+ { cn: "test", cal_address: "test@test.com" },
+ {
+ cn: "Conference Room",
+ cal_address: "room@test.com",
+ partstat: "ACCEPTED",
+ rsvp: "TRUE",
+ role: "REQ-PARTICIPANT",
+ cutype: "RESOURCE",
+ },
+ ],
+ };
+
+ const stateWithEvent = {
+ ...preloadedState,
+ calendars: {
+ ...preloadedState.calendars,
+ list: {
+ "667037022b752d0026472254/cal1": {
+ ...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
+ events: {
+ "test-event-resource": eventData,
+ },
+ },
+ },
+ },
+ };
+
+ const mockPutEvent = jest.spyOn(EventApi, "putEvent").mockResolvedValue({
+ status: 201,
+ url: `/calendars/667037022b752d0026472254/cal1/test-event-resource.ics`,
+ } as any);
+
+ renderWithProviders(
+ ,
+ stateWithEvent
+ );
+
+ // Edit the title
+ const titleInput = screen.getByDisplayValue("Resource Event");
+ fireEvent.change(titleInput, {
+ target: { value: "Updated Resource Event" },
+ });
+
+ // Click Save
+ const saveButton = screen.getByRole("button", { name: "actions.save" });
+ await act(async () => {
+ fireEvent.click(saveButton);
+ });
+
+ await waitFor(() => {
+ expect(mockPutEvent).toHaveBeenCalled();
+ });
+
+ const putEventCall = mockPutEvent.mock.calls[0][0];
+ expect(putEventCall.title).toBe("Updated Resource Event");
+
+ // Check that the resource is still in the attendee list!
+ const attendees = putEventCall.attendee;
+ const resource = attendees.find((a: any) => a.cutype === "RESOURCE");
+ expect(resource).toBeDefined();
+ expect(resource!.cn).toBe("Conference Room");
+ expect(resource!.cal_address).toBe("room@test.com");
+ });
});
describe("EventUpdateModal Recurring to Non-Recurring Conversion", () => {
diff --git a/src/components/Calendar/CalendarResources.tsx b/src/components/Calendar/CalendarResources.tsx
index e4cdfa3..9032d69 100644
--- a/src/components/Calendar/CalendarResources.tsx
+++ b/src/components/Calendar/CalendarResources.tsx
@@ -291,7 +291,10 @@ export default function CalendarResources({
const successfulCals = results
.filter((result) => result.status === "fulfilled")
- .map((result) => (result as PromiseFulfilledResult).value)
+ .map(
+ (result) =>
+ (result as PromiseFulfilledResult).value
+ )
.flat()
.filter(Boolean);
diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx
index 552cf9c..08cc530 100644
--- a/src/components/Event/EventFormFields.tsx
+++ b/src/components/Event/EventFormFields.tsx
@@ -50,6 +50,7 @@ import RepeatEvent from "./EventRepeat";
import { useAllDayToggle } from "./hooks/useAllDayToggle";
import { combineDateTime, splitDateTime } from "./utils/dateTimeHelpers";
import { validateEventForm } from "./utils/formValidation";
+import { Resource, ResourceSearch } from "../Attendees/ResourceSearch";
interface EventFormFieldsProps {
// Form state
@@ -84,6 +85,8 @@ interface EventFormFieldsProps {
setHasVideoConference: (hasVideoConference: boolean) => void;
meetingLink: string | null;
setMeetingLink: (meetingLink: string | null) => void;
+ selectedResources: Resource[];
+ setSelectedResources: (resources: Resource[]) => void;
// UI state
showMore: boolean;
@@ -166,6 +169,8 @@ export default function EventFormFields({
onValidationChange,
showValidationErrors = false,
onHasEndDateChangedChange,
+ selectedResources,
+ setSelectedResources,
}: EventFormFieldsProps) {
const { t } = useI18n();
@@ -478,6 +483,10 @@ export default function EventFormFields({
onCalendarChange?.(newCalendarId);
};
+ const handleResourceChange = (resources: Resource[]) => {
+ setSelectedResources(resources);
+ };
+
return (
<>
+
+
+ }
+ onChange={async (
+ _event: React.SyntheticEvent,
+ value: Resource[]
+ ) => {
+ handleResourceChange(value);
+ }}
+ hideLabel={true}
+ />
+
+
+
{icon}
diff --git a/src/features/Calendars/api/addCalendarResourceAsync.ts b/src/features/Calendars/api/addCalendarResourceAsync.ts
index 5dfc8bb..684cbd0 100644
--- a/src/features/Calendars/api/addCalendarResourceAsync.ts
+++ b/src/features/Calendars/api/addCalendarResourceAsync.ts
@@ -1,5 +1,5 @@
import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData";
-import { getResourceDetails } from "@/features/User/userAPI";
+import { getResourceDetails, getUserDetails } from "@/features/User/userAPI";
import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit";
import { addSharedCalendar } from "../CalendarApi";
@@ -27,14 +27,24 @@ export const addCalendarResourceAsync = createAsyncThunk<
>(
"calendars/addCalendarResource",
async ({ userId, calId, cal }, { rejectWithValue }) => {
+ const resourceId = cal.cal._links.self?.href
+ ?.replace("/calendars/", "")
+ ?.replace(".json", "")
+ ?.split("/")[0];
+
+ let owner: OpenPaasUserData = {
+ firstname: "",
+ lastname: cal.cal["dav:name"] ?? "",
+ emails: [],
+ resource: true,
+ };
try {
await addSharedCalendar(userId, calId, cal);
- const ownerData = await getResourceDetails(
- cal.cal._links.self.href
- .replace("/calendars/", "")
- .replace(".json", "")
- .split("/")[0]
- );
+ const resource = await getResourceDetails(resourceId!);
+ owner = {
+ ...(await getUserDetails(resource.creator)),
+ resource: true,
+ };
return {
calId: cal.cal._links.self?.href
@@ -44,7 +54,7 @@ export const addCalendarResourceAsync = createAsyncThunk<
link: `/calendars/${userId}/${calId}.json`,
desc: cal.cal["caldav:description"] ?? "",
name: cal.cal["dav:name"] ?? "",
- owner: ownerData,
+ owner,
};
} catch (err) {
return rejectWithValue(toRejectedError(err));
diff --git a/src/features/Events/EventModal.tsx b/src/features/Events/EventModal.tsx
index 9c8fb1c..6a493da 100644
--- a/src/features/Events/EventModal.tsx
+++ b/src/features/Events/EventModal.tsx
@@ -43,6 +43,7 @@ import { userAttendee } from "../User/models/attendee";
import { CalendarEvent, RepetitionObject } from "./EventsTypes";
import { useEventOrganizer } from "./useEventOrganizer";
import { buildDelegatedEventURL } from "./utils/buildDelegatedEventURL";
+import { Resource } from "@/components/Attendees/ResourceSearch";
function EventPopover({
open,
@@ -86,6 +87,16 @@ function EventPopover({
return resolveTimezone(tz);
}, [calendarTimezone]);
+ const resources: Resource[] = useMemo(() => {
+ const resourcesInEvent =
+ event?.attendee?.filter((attendee) => attendee.cutype === "RESOURCE") ??
+ [];
+ return resourcesInEvent.map((resource) => ({
+ email: resource.cal_address,
+ displayName: resource.cn,
+ }));
+ }, [event?.attendee]);
+
const [showMore, setShowMore] = useState(false);
const [showDescription, setShowDescription] = useState(
event?.description ? true : false
@@ -127,6 +138,7 @@ function EventPopover({
const [repetition, setRepetition] = useState(
event?.repetition ?? ({} as RepetitionObject)
);
+ const [selectedResources, setSelectedResources] = useState(resources ?? []);
// Derive the effective organizer based on the selected calendar.
// When a delegated calendar is selected, the organizer must be the
@@ -139,11 +151,16 @@ function EventPopover({
const [attendees, setAttendees] = useState(
event?.attendee
- ? event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
+ ? event.attendee.filter(
+ (a) =>
+ a.cal_address !== organizer?.cal_address && a.cutype !== "RESOURCE"
+ )
: []
);
const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? "");
- const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC");
+ const [eventClass, setEventClass] = useState(
+ event?.class ?? "PUBLIC"
+ );
const [busy, setBusy] = useState(event?.transp ?? "OPAQUE");
const [timezone, setTimezone] = useState(
event?.timezone ? resolveTimezone(event.timezone) : resolvedCalendarTimezone
@@ -200,6 +217,7 @@ function EventPopover({
setHasVideoConference(false);
setMeetingLink(null);
setHasEndDateChanged(false);
+ setSelectedResources([]);
}, [resolvedCalendarTimezone, defaultCalendarId]);
// Track if we should sync from selectedRange (only on initial selection, not on toggle)
@@ -466,7 +484,9 @@ function EventPopover({
setAttendees(
event.attendee
? event.attendee.filter(
- (a) => a.cal_address !== organizer?.cal_address
+ (a) =>
+ a.cal_address !== organizer?.cal_address &&
+ a.cutype !== "RESOURCE"
)
: []
);
@@ -491,17 +511,23 @@ function EventPopover({
setDescription(event.description);
}
}
+ setSelectedResources(resources ?? []);
} else if (event && event.attendee && event.attendee.length > 0) {
// Handle tempEvent case (no uid but has attendees from temp calendar search)
setAttendees(
- event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
+ event.attendee.filter(
+ (a) =>
+ a.cal_address !== organizer?.cal_address && a.cutype !== "RESOURCE"
+ )
);
+ setSelectedResources(resources ?? []);
}
}, [
event,
organizer?.cal_address,
resolvedCalendarTimezone,
defaultCalendarId,
+ resources,
]);
// Reset state when creating new event (event is empty object or undefined)
@@ -535,6 +561,7 @@ function EventPopover({
setHasVideoConference(false);
setMeetingLink(null);
setHasEndDateChanged(false);
+ setSelectedResources([]);
}
if (!isCreatingNew) {
@@ -640,6 +667,7 @@ function EventPopover({
resetAllStateToDefault();
setStart("");
setEnd("");
+ setSelectedResources([]);
shouldSyncFromRangeRef.current = true; // Reset for next time
isCalendarIdUserSelectedRef.current = false; // Reset so next open gets fresh default
};
@@ -666,6 +694,7 @@ function EventPopover({
showDescription,
showRepeat,
hasEndDateChanged,
+ resources: selectedResources,
};
return buildEventFormTempData(formState);
}, [
@@ -688,6 +717,7 @@ function EventPopover({
showDescription,
showRepeat,
hasEndDateChanged,
+ selectedResources,
]);
// Check for temp data when modal opens
@@ -722,6 +752,7 @@ function EventPopover({
setShowDescription,
setShowRepeat,
setHasEndDateChanged,
+ setSelectedResources,
});
// Clear the error flag but keep data until successful save
const updatedTempData = { ...tempData, fromError: false };
@@ -766,7 +797,7 @@ function EventPopover({
uid: newEventUID,
description,
location,
- class: eventClass,
+ class: eventClass as "PUBLIC" | "PRIVATE" | "CONFIDENTIAL",
repetition,
organizer,
timezone,
@@ -787,6 +818,20 @@ function EventPopover({
x_openpass_videoconference: meetingLink || undefined,
};
+ // Map data of resources to attendee before creating event
+ if (selectedResources?.length) {
+ selectedResources.forEach((resource: Resource) => {
+ newEvent.attendee.push({
+ cn: resource?.displayName ?? "",
+ cal_address: resource?.email ?? "",
+ partstat: "NEEDS-ACTION",
+ rsvp: "TRUE",
+ role: "REQ-PARTICIPANT",
+ cutype: "RESOURCE",
+ });
+ });
+ }
+
if (allday) {
const startDateOnly = (start || "").split("T")[0];
const endDateOnlyUI = (end || start || "").split("T")[0];
@@ -957,6 +1002,8 @@ function EventPopover({
onValidationChange={setIsFormValid}
showValidationErrors={showValidationErrors}
onHasEndDateChangedChange={setHasEndDateChanged}
+ setSelectedResources={setSelectedResources}
+ selectedResources={selectedResources}
/>
);
diff --git a/src/features/Events/EventPreview/EventPreviewActionMenu.tsx b/src/features/Events/EventPreview/EventPreviewActionMenu.tsx
index 3dbd284..1907da1 100644
--- a/src/features/Events/EventPreview/EventPreviewActionMenu.tsx
+++ b/src/features/Events/EventPreview/EventPreviewActionMenu.tsx
@@ -28,7 +28,9 @@ export function EventPreviewActionMenu({
const mailSpaUrl = window.MAIL_SPA_URL ?? null;
const attendees = event.attendee ?? [];
- const otherAttendees = attendees.filter((a) => a.cal_address !== userEmail);
+ const otherAttendees = attendees.filter(
+ (a) => a.cal_address !== userEmail && a.cutype !== "RESOURCE"
+ );
return (