Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -25,6 +25,7 @@ const preloadedState = {
|
|||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
name: "Calendar",
|
name: "Calendar",
|
||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
|
owner: { emails: ["test@test.com"] },
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
uid: "event1",
|
uid: "event1",
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ describe("helpers", () => {
|
|||||||
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(1, "resource-123");
|
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(1, "resource-123");
|
||||||
expect(mockedGetResourceDetails).toHaveBeenCalledWith("resource-123");
|
expect(mockedGetResourceDetails).toHaveBeenCalledWith("resource-123");
|
||||||
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(2, "creator-456");
|
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(2, "creator-456");
|
||||||
expect(result).toEqual({ ...mockCreator, resource: true });
|
expect(result).toEqual({
|
||||||
|
...mockCreator,
|
||||||
|
resource: true,
|
||||||
|
administrators: undefined,
|
||||||
|
resourceIcon: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw error when getUserDetails fails with non-404 error", async () => {
|
it("should throw error when getUserDetails fails with non-404 error", async () => {
|
||||||
|
|||||||
@@ -129,4 +129,55 @@ describe("AttendanceValidation - delegation", () => {
|
|||||||
expect(container.firstChild).not.toBeNull();
|
expect(container.firstChild).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resource calendar", () => {
|
||||||
|
const resourceContext = makeContext({
|
||||||
|
isOwn: false,
|
||||||
|
calendar: {
|
||||||
|
id: "res1/cal1",
|
||||||
|
name: "Resource Cal",
|
||||||
|
delegated: false,
|
||||||
|
owner: {
|
||||||
|
emails: ["resource@example.com"],
|
||||||
|
resource: true,
|
||||||
|
administrators: [{ id: "admin1" }],
|
||||||
|
},
|
||||||
|
events: {},
|
||||||
|
} as Calendar,
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders when user is an administrator of the resource", () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<AttendanceValidation
|
||||||
|
contextualizedEvent={resourceContext}
|
||||||
|
user={
|
||||||
|
{
|
||||||
|
...makeUser("admin@example.com"),
|
||||||
|
openpaasId: "admin1",
|
||||||
|
} as userData
|
||||||
|
}
|
||||||
|
setAfterChoiceFunc={noopSetFunc}
|
||||||
|
setOpenEditModePopup={noopSetFunc}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(getByText("eventPreview.authorizeQuestion")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when user is not an administrator of the resource", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<AttendanceValidation
|
||||||
|
contextualizedEvent={resourceContext}
|
||||||
|
user={
|
||||||
|
{
|
||||||
|
...makeUser("other@example.com"),
|
||||||
|
openpaasId: "other1",
|
||||||
|
} as userData
|
||||||
|
}
|
||||||
|
setAfterChoiceFunc={noopSetFunc}
|
||||||
|
setOpenEditModePopup={noopSetFunc}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ describe("Event Preview Display", () => {
|
|||||||
id: "otherCal/cal",
|
id: "otherCal/cal",
|
||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
|
owner: { emails: ["other@test.com"] },
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
uid: "event1",
|
uid: "event1",
|
||||||
@@ -901,7 +902,7 @@ describe("Event Preview Display", () => {
|
|||||||
name: "Calendar 1",
|
name: "Calendar 1",
|
||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
// ownerEmails missing
|
owner: {},
|
||||||
events: {
|
events: {
|
||||||
event1: {
|
event1: {
|
||||||
calId: "667037022b752d0026472254/cal1",
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ describe("EventUpdateModal Timezone Handling", () => {
|
|||||||
allday: false,
|
allday: false,
|
||||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
||||||
};
|
URL: "/calendars/667037022b752d0026472254/cal1/test-event-1.ics",
|
||||||
|
} as CalendarEvent;
|
||||||
|
|
||||||
const stateWithEvent = {
|
const stateWithEvent = {
|
||||||
...preloadedState,
|
...preloadedState,
|
||||||
@@ -122,7 +123,8 @@ describe("EventUpdateModal Timezone Handling", () => {
|
|||||||
allday: false,
|
allday: false,
|
||||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
||||||
};
|
URL: "/calendars/667037022b752d0026472254/cal1/test-event-2.ics",
|
||||||
|
} as CalendarEvent;
|
||||||
|
|
||||||
const stateWithEvent = {
|
const stateWithEvent = {
|
||||||
...preloadedState,
|
...preloadedState,
|
||||||
@@ -181,7 +183,8 @@ describe("EventUpdateModal Timezone Handling", () => {
|
|||||||
cutype: "RESOURCE",
|
cutype: "RESOURCE",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
URL: "/calendars/667037022b752d0026472254/cal1/test-event-resource.ics",
|
||||||
|
} as CalendarEvent;
|
||||||
|
|
||||||
const stateWithEvent = {
|
const stateWithEvent = {
|
||||||
...preloadedState,
|
...preloadedState,
|
||||||
@@ -239,6 +242,10 @@ describe("EventUpdateModal Timezone Handling", () => {
|
|||||||
expect(resource).toBeDefined();
|
expect(resource).toBeDefined();
|
||||||
expect(resource!.cn).toBe("Conference Room");
|
expect(resource!.cn).toBe("Conference Room");
|
||||||
expect(resource!.cal_address).toBe("room@test.com");
|
expect(resource!.cal_address).toBe("room@test.com");
|
||||||
|
expect(resource!.partstat).toBe("ACCEPTED");
|
||||||
|
expect(resource!.rsvp).toBe("TRUE");
|
||||||
|
expect(resource!.role).toBe("REQ-PARTICIPANT");
|
||||||
|
expect(resource!.cutype).toBe("RESOURCE");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -294,7 +301,8 @@ describe("EventUpdateModal Recurring to Non-Recurring Conversion", () => {
|
|||||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
||||||
URL: `/calendars/${calId}/${baseUID}.ics`,
|
URL: `/calendars/${calId}/${baseUID}.ics`,
|
||||||
};
|
timezone: "UTC",
|
||||||
|
} as CalendarEvent;
|
||||||
|
|
||||||
const instance1 = {
|
const instance1 = {
|
||||||
...masterEvent,
|
...masterEvent,
|
||||||
@@ -445,6 +453,7 @@ describe("EventUpdateModal Recurring to Non-Recurring Conversion", () => {
|
|||||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
||||||
URL: `/calendars/${calId}/${baseUID}.ics`,
|
URL: `/calendars/${calId}/${baseUID}.ics`,
|
||||||
|
timezone: "UTC",
|
||||||
} as CalendarEvent;
|
} as CalendarEvent;
|
||||||
|
|
||||||
const instance1 = {
|
const instance1 = {
|
||||||
@@ -582,7 +591,8 @@ describe("EventUpdateModal Recurring to Non-Recurring Conversion", () => {
|
|||||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
attendee: [{ cn: "test", cal_address: "test@test.com" }],
|
||||||
URL: `/calendars/${calId}/${baseUID}.ics`,
|
URL: `/calendars/${calId}/${baseUID}.ics`,
|
||||||
};
|
timezone: "UTC",
|
||||||
|
} as CalendarEvent;
|
||||||
|
|
||||||
const instance1 = {
|
const instance1 = {
|
||||||
...masterEvent,
|
...masterEvent,
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
import { Avatar } from "@linagora/twake-mui";
|
import { Avatar, Box } from "@linagora/twake-mui";
|
||||||
import LayersOutlinedIcon from "@mui/icons-material/LayersOutlined";
|
import LayersOutlinedIcon from "@mui/icons-material/LayersOutlined";
|
||||||
|
|
||||||
interface ResourceIconProps {
|
interface ResourceIconProps {
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
colorIcon?: boolean;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ResourceIcon({ avatarUrl }: ResourceIconProps) {
|
export function ResourceIcon({
|
||||||
|
avatarUrl,
|
||||||
|
colorIcon,
|
||||||
|
color,
|
||||||
|
}: ResourceIconProps) {
|
||||||
|
if (colorIcon && avatarUrl) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "24px",
|
||||||
|
height: "24px",
|
||||||
|
backgroundColor: color,
|
||||||
|
maskImage: `url(${avatarUrl})`,
|
||||||
|
maskSize: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return avatarUrl ? (
|
return avatarUrl ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ backgroundColor: "transparent", width: "24px", height: "24px" }}
|
sx={{ backgroundColor: "transparent", width: "24px", height: "24px" }}
|
||||||
|
|||||||
@@ -133,7 +133,13 @@ export function CalendarAccessRights({
|
|||||||
access: invite.access,
|
access: invite.access,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
?.filter((invite) => !!invite) || []) satisfies UserInCalendar[];
|
?.filter(
|
||||||
|
(invite) =>
|
||||||
|
!!invite &&
|
||||||
|
!calendar.owner.administrators?.some(
|
||||||
|
(admin) => admin.id === invite.id
|
||||||
|
)
|
||||||
|
) || []) as UserInCalendar[];
|
||||||
|
|
||||||
const loaded = await handleLoadUsers(usersInCal, cancelled);
|
const loaded = await handleLoadUsers(usersInCal, cancelled);
|
||||||
|
|
||||||
@@ -154,7 +160,13 @@ export function CalendarAccessRights({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [calendar.invite, handleLoadUsers, onChange, onInvitesLoaded]);
|
}, [
|
||||||
|
calendar.invite,
|
||||||
|
calendar.owner.administrators,
|
||||||
|
handleLoadUsers,
|
||||||
|
onChange,
|
||||||
|
onInvitesLoaded,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isResource = calendar.owner.resource;
|
const isResource = calendar.owner.resource;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Box, Typography } from "@linagora/twake-mui";
|
|||||||
import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
|
import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
|
||||||
import { useI18n } from "twake-i18n";
|
import { useI18n } from "twake-i18n";
|
||||||
import { OwnerCaption } from "./OwnerCaption";
|
import { OwnerCaption } from "./OwnerCaption";
|
||||||
|
import { ResourceIcon } from "../Attendees/ResourceIcon";
|
||||||
|
|
||||||
export function CalendarName({ calendar }: { calendar: Calendar }) {
|
export function CalendarName({ calendar }: { calendar: Calendar }) {
|
||||||
const userData = useAppSelector((state) => state.user.userData);
|
const userData = useAppSelector((state) => state.user.userData);
|
||||||
@@ -15,8 +16,9 @@ export function CalendarName({ calendar }: { calendar: Calendar }) {
|
|||||||
const ownerId = calendar.id.split("/")[0];
|
const ownerId = calendar.id.split("/")[0];
|
||||||
const ownerDisplayName = makeDisplayName(calendar) ?? "";
|
const ownerDisplayName = makeDisplayName(calendar) ?? "";
|
||||||
const isOwnCalendar = userData.openpaasId === ownerId;
|
const isOwnCalendar = userData.openpaasId === ownerId;
|
||||||
|
const isResource = calendar.owner?.resource;
|
||||||
const showCaption =
|
const showCaption =
|
||||||
calendar.name !== "#default" && !isOwnCalendar && !calendar.owner?.resource;
|
calendar.name !== "#default" && !isOwnCalendar && !isResource;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -27,13 +29,21 @@ export function CalendarName({ calendar }: { calendar: Calendar }) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SquareRoundedIcon
|
{isResource ? (
|
||||||
style={{
|
<ResourceIcon
|
||||||
color: calendar.color?.light ?? defaultColors[0].light,
|
colorIcon
|
||||||
width: 24,
|
avatarUrl={calendar.owner.resourceIcon}
|
||||||
height: 24,
|
color={calendar.color?.light ?? defaultColors[0].light}
|
||||||
}}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<SquareRoundedIcon
|
||||||
|
style={{
|
||||||
|
color: calendar.color?.light ?? defaultColors[0].light,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Box style={{ display: "flex", flexDirection: "column" }}>
|
<Box style={{ display: "flex", flexDirection: "column" }}>
|
||||||
<Typography variant="body2" sx={{ wordBreak: "break-word" }}>
|
<Typography variant="body2" sx={{ wordBreak: "break-word" }}>
|
||||||
{renameDefault(calendar.name, ownerDisplayName, t, isOwnCalendar)}
|
{renameDefault(calendar.name, ownerDisplayName, t, isOwnCalendar)}
|
||||||
|
|||||||
@@ -137,7 +137,10 @@ export default function CalendarSelection({
|
|||||||
(id) => extractEventBaseUuid(id) === userId
|
(id) => extractEventBaseUuid(id) === userId
|
||||||
);
|
);
|
||||||
const delegatedCalendars = Object.keys(calendars || {}).filter(
|
const delegatedCalendars = Object.keys(calendars || {}).filter(
|
||||||
(id) => extractEventBaseUuid(id) !== userId && calendars[id]?.delegated
|
(id) =>
|
||||||
|
extractEventBaseUuid(id) !== userId &&
|
||||||
|
calendars[id]?.delegated &&
|
||||||
|
!calendars?.[id]?.owner?.resource
|
||||||
);
|
);
|
||||||
const sharedCalendars = Object.keys(calendars || {}).filter(
|
const sharedCalendars = Object.keys(calendars || {}).filter(
|
||||||
(id) =>
|
(id) =>
|
||||||
@@ -147,9 +150,7 @@ export default function CalendarSelection({
|
|||||||
);
|
);
|
||||||
const resourceCalendars = Object.keys(calendars || {}).filter(
|
const resourceCalendars = Object.keys(calendars || {}).filter(
|
||||||
(id) =>
|
(id) =>
|
||||||
extractEventBaseUuid(id) !== userId &&
|
extractEventBaseUuid(id) !== userId && calendars?.[id]?.owner?.resource
|
||||||
!calendars?.[id]?.delegated &&
|
|
||||||
calendars?.[id]?.owner?.resource
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCalendarToggle = (name: string) => {
|
const handleCalendarToggle = (name: string) => {
|
||||||
|
|||||||
@@ -15,10 +15,22 @@ import { buildFamilyName } from "@/utils/buildFamilyName";
|
|||||||
import { isEventOrganiser } from "@/utils/isEventOrganiser";
|
import { isEventOrganiser } from "@/utils/isEventOrganiser";
|
||||||
|
|
||||||
function updateEventAttendees(
|
function updateEventAttendees(
|
||||||
|
calendar: Calendar,
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
user: userData | undefined,
|
user: userData | undefined,
|
||||||
rsvp: PartStat
|
rsvp: PartStat
|
||||||
) {
|
) {
|
||||||
|
if (calendar.owner?.resource) {
|
||||||
|
const updatedAttendees =
|
||||||
|
event.attendee?.map((attendeeData) =>
|
||||||
|
attendeeData.cutype === "RESOURCE" && attendeeData.cn === calendar.name
|
||||||
|
? { ...attendeeData, partstat: rsvp }
|
||||||
|
: attendeeData
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
return { attendee: updatedAttendees };
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error("Cannot update attendees without user data");
|
throw new Error("Cannot update attendees without user data");
|
||||||
}
|
}
|
||||||
@@ -100,7 +112,7 @@ export async function handleRSVP(
|
|||||||
) {
|
) {
|
||||||
const newEvent = {
|
const newEvent = {
|
||||||
...event,
|
...event,
|
||||||
...updateEventAttendees(event, user, rsvp),
|
...updateEventAttendees(calendar, event, user, rsvp),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeOfAction === "solo") {
|
if (typeOfAction === "solo") {
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export const fetchOwnerOfResource = async (
|
|||||||
return {
|
return {
|
||||||
...ownerData,
|
...ownerData,
|
||||||
administrators: data.administrators,
|
administrators: data.administrators,
|
||||||
|
// The `icon` from resource detail contains the name only, so we must map with URL to have full URL of icon
|
||||||
|
resourceIcon:
|
||||||
|
window.CALENDAR_BASE_URL && data.icon
|
||||||
|
? `${window.CALENDAR_BASE_URL}/images/icon/${data.icon}.svg`
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch resource details for ${resourceId}:`, error);
|
console.error(`Failed to fetch resource details for ${resourceId}:`, error);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function AttendanceValidation({
|
|||||||
setAfterChoiceFunc,
|
setAfterChoiceFunc,
|
||||||
setOpenEditModePopup,
|
setOpenEditModePopup,
|
||||||
}: AttendanceValidationProps) {
|
}: AttendanceValidationProps) {
|
||||||
const { currentUserAttendee, isOwn } = contextualizedEvent;
|
const { currentUserAttendee, isOwn, calendar } = contextualizedEvent;
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [loadingValue, setLoadingValue] = useState<PartStat | null>(null);
|
const [loadingValue, setLoadingValue] = useState<PartStat | null>(null);
|
||||||
@@ -38,7 +38,16 @@ export function AttendanceValidation({
|
|||||||
(!contextualizedEvent.event.class ||
|
(!contextualizedEvent.event.class ||
|
||||||
contextualizedEvent.event.class === "PUBLIC");
|
contextualizedEvent.event.class === "PUBLIC");
|
||||||
|
|
||||||
if (!(editRightInSelfCalendar || isDelegatedPublicEvent)) {
|
const { owner: resourceOwner } = calendar;
|
||||||
|
const isAdminOfResource =
|
||||||
|
resourceOwner?.resource &&
|
||||||
|
resourceOwner?.administrators?.some(
|
||||||
|
(admin) => admin.id === user?.openpaasId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(editRightInSelfCalendar || isDelegatedPublicEvent || isAdminOfResource)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +69,9 @@ export function AttendanceValidation({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography sx={{ marginRight: 2 }}>
|
<Typography sx={{ marginRight: 2 }}>
|
||||||
{t("eventPreview.attendingQuestion")}
|
{calendar.owner?.resource
|
||||||
|
? t("eventPreview.authorizeQuestion")
|
||||||
|
: t("eventPreview.attendingQuestion")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box display="flex" gap="15px" alignItems="center">
|
<Box display="flex" gap="15px" alignItems="center">
|
||||||
<RSVPButton rsvpValue="ACCEPTED" {...commonButtonProps} />
|
<RSVPButton rsvpValue="ACCEPTED" {...commonButtonProps} />
|
||||||
|
|||||||
@@ -13,17 +13,22 @@ import { useI18n } from "twake-i18n";
|
|||||||
import { CalendarEvent } from "../EventsTypes";
|
import { CalendarEvent } from "../EventsTypes";
|
||||||
import { EventPreviewAttendees } from "./EventPreviewAttendees";
|
import { EventPreviewAttendees } from "./EventPreviewAttendees";
|
||||||
import { makeRecurrenceString } from "./utils/makeRecurrenceString";
|
import { makeRecurrenceString } from "./utils/makeRecurrenceString";
|
||||||
|
import { renderAttendeeBadge } from "@/components/Event/utils/eventUtils";
|
||||||
|
|
||||||
interface EventPreviewDetailsProps {
|
interface EventPreviewDetailsProps {
|
||||||
event: CalendarEvent;
|
event: CalendarEvent;
|
||||||
isOwn: boolean;
|
isOwn: boolean;
|
||||||
isNotPrivate: boolean;
|
isNotPrivate: boolean;
|
||||||
|
isResourceEventPreview?: boolean;
|
||||||
|
calendarName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EventPreviewDetails({
|
export function EventPreviewDetails({
|
||||||
event,
|
event,
|
||||||
isOwn,
|
isOwn,
|
||||||
isNotPrivate,
|
isNotPrivate,
|
||||||
|
isResourceEventPreview,
|
||||||
|
calendarName,
|
||||||
}: EventPreviewDetailsProps) {
|
}: EventPreviewDetailsProps) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -31,8 +36,14 @@ export function EventPreviewDetails({
|
|||||||
const infoIconSx = { minWidth: "25px", marginRight: 2, color: infoIconColor };
|
const infoIconSx = { minWidth: "25px", marginRight: 2, color: infoIconColor };
|
||||||
|
|
||||||
const resources = useMemo(
|
const resources = useMemo(
|
||||||
() => event?.attendee?.filter((attendee) => attendee.cutype === "RESOURCE"),
|
() =>
|
||||||
[event?.attendee]
|
event?.attendee?.filter(
|
||||||
|
(attendee) =>
|
||||||
|
attendee.cutype === "RESOURCE" &&
|
||||||
|
((isResourceEventPreview && calendarName !== attendee.cn) ||
|
||||||
|
!isResourceEventPreview)
|
||||||
|
),
|
||||||
|
[calendarName, event?.attendee, isResourceEventPreview]
|
||||||
);
|
);
|
||||||
const eventAttendees = useMemo(
|
const eventAttendees = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -106,8 +117,12 @@ export function EventPreviewDetails({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isResourceEventPreview &&
|
||||||
|
organizer &&
|
||||||
|
renderAttendeeBadge(organizer, "org", t, true, true)}
|
||||||
|
|
||||||
{/* Attendees */}
|
{/* Attendees */}
|
||||||
{attendees.length > 0 && (
|
{!isResourceEventPreview && attendees.length > 0 && (
|
||||||
<EventPreviewAttendees
|
<EventPreviewAttendees
|
||||||
attendees={attendees}
|
attendees={attendees}
|
||||||
organizer={organizer}
|
organizer={organizer}
|
||||||
@@ -144,6 +159,7 @@ export function EventPreviewDetails({
|
|||||||
}
|
}
|
||||||
content={resources.map((resource, index) => (
|
content={resources.map((resource, index) => (
|
||||||
<Box
|
<Box
|
||||||
|
key={resource.cn}
|
||||||
sx={{
|
sx={{
|
||||||
marginRight: "5px",
|
marginRight: "5px",
|
||||||
}}
|
}}
|
||||||
@@ -172,7 +188,7 @@ export function EventPreviewDetails({
|
|||||||
color: "#717D96",
|
color: "#717D96",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t(`eventPreview.${resource.partstat}`)}
|
{t(`eventPreview.attendingStatus.${resource.partstat}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ export default function EventPreviewModal({
|
|||||||
|
|
||||||
if (!user || !event || !calendar) return null;
|
if (!user || !event || !calendar) return null;
|
||||||
|
|
||||||
|
const isAdminOfResource = Boolean(
|
||||||
|
calendar.owner?.resource &&
|
||||||
|
user.openpaasId &&
|
||||||
|
calendar.owner?.administrators?.some(
|
||||||
|
(admin) => admin.id === user.openpaasId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResponsiveDialog
|
<ResponsiveDialog
|
||||||
@@ -82,7 +90,9 @@ export default function EventPreviewModal({
|
|||||||
onClose={() => onClose({}, "backdropClick")}
|
onClose={() => onClose({}, "backdropClick")}
|
||||||
showHeaderActions={false}
|
showHeaderActions={false}
|
||||||
actionsBorderTop={
|
actionsBorderTop={
|
||||||
!!(event.attendee?.find((p) => p.cal_address === user.email) && isOwn)
|
!!(
|
||||||
|
event.attendee?.find((p) => p.cal_address === user.email) && isOwn
|
||||||
|
) || isAdminOfResource
|
||||||
}
|
}
|
||||||
actionsJustifyContent="center"
|
actionsJustifyContent="center"
|
||||||
style={{ overflow: "auto" }}
|
style={{ overflow: "auto" }}
|
||||||
@@ -163,6 +173,8 @@ export default function EventPreviewModal({
|
|||||||
event={event}
|
event={event}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
isNotPrivate={isNotPrivate}
|
isNotPrivate={isNotPrivate}
|
||||||
|
isResourceEventPreview={calendar.owner?.resource}
|
||||||
|
calendarName={calendar.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Calendar label */}
|
{/* Calendar label */}
|
||||||
|
|||||||
@@ -648,7 +648,10 @@ function EventUpdateModal({
|
|||||||
newEvent.attendee.push({
|
newEvent.attendee.push({
|
||||||
cn: resource?.displayName ?? "",
|
cn: resource?.displayName ?? "",
|
||||||
cal_address: resource?.email ?? "",
|
cal_address: resource?.email ?? "",
|
||||||
partstat: "NEEDS-ACTION",
|
partstat:
|
||||||
|
event.attendee?.find(
|
||||||
|
(a) => a.cutype === "RESOURCE" && a.cn === resource.displayName
|
||||||
|
)?.partstat || "NEEDS-ACTION",
|
||||||
rsvp: "TRUE",
|
rsvp: "TRUE",
|
||||||
role: "REQ-PARTICIPANT",
|
role: "REQ-PARTICIPANT",
|
||||||
cutype: "RESOURCE",
|
cutype: "RESOURCE",
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ export function createEventContext(
|
|||||||
const attendeeEmail = calendar.delegated
|
const attendeeEmail = calendar.delegated
|
||||||
? calendar.owner?.emails?.[0]
|
? calendar.owner?.emails?.[0]
|
||||||
: user.email;
|
: user.email;
|
||||||
const currentUserAttendee = event.attendee?.find(
|
const currentUserAttendee = calendar.owner?.resource
|
||||||
(person) => person.cal_address === attendeeEmail
|
? event.attendee?.find(
|
||||||
);
|
(person) => person.cutype === "RESOURCE" && person.cn === calendar.name
|
||||||
|
)
|
||||||
|
: event.attendee?.find((person) => person.cal_address === attendeeEmail);
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
calendar,
|
calendar,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface OpenPaasUserData {
|
|||||||
id: string;
|
id: string;
|
||||||
objectType: string;
|
objectType: string;
|
||||||
}[];
|
}[];
|
||||||
|
resourceIcon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToUserData(
|
export function ToUserData(
|
||||||
|
|||||||
@@ -281,10 +281,17 @@
|
|||||||
"tooltip": "Others see you as available during the time range of this event."
|
"tooltip": "Others see you as available during the time range of this event."
|
||||||
},
|
},
|
||||||
"attendingQuestion": "Attending?",
|
"attendingQuestion": "Attending?",
|
||||||
|
"authorizeQuestion": "Authorize?",
|
||||||
"ACCEPTED": "Accept",
|
"ACCEPTED": "Accept",
|
||||||
"TENTATIVE": "Maybe",
|
"TENTATIVE": "Maybe",
|
||||||
"DECLINED": "Decline",
|
"DECLINED": "Decline",
|
||||||
"NEEDS-ACTION": "Pending",
|
"NEEDS-ACTION": "Pending",
|
||||||
|
"attendingStatus": {
|
||||||
|
"ACCEPTED": "Accepted",
|
||||||
|
"TENTATIVE": "Maybe",
|
||||||
|
"DECLINED": "Declined",
|
||||||
|
"NEEDS-ACTION": "Pending"
|
||||||
|
},
|
||||||
"showMore": "Show more",
|
"showMore": "Show more",
|
||||||
"showLess": "Show less",
|
"showLess": "Show less",
|
||||||
"joinVideo": "Join the video conference",
|
"joinVideo": "Join the video conference",
|
||||||
|
|||||||
@@ -282,10 +282,17 @@
|
|||||||
"tooltip": "Les autres vous voient comme disponible pendant la plage horaire de cet événement."
|
"tooltip": "Les autres vous voient comme disponible pendant la plage horaire de cet événement."
|
||||||
},
|
},
|
||||||
"attendingQuestion": "Participer ?",
|
"attendingQuestion": "Participer ?",
|
||||||
|
"authorizeQuestion": "Autoriser ?",
|
||||||
"ACCEPTED": "Accepter",
|
"ACCEPTED": "Accepter",
|
||||||
"TENTATIVE": "Peut-être",
|
"TENTATIVE": "Peut-être",
|
||||||
"DECLINED": "Décliner",
|
"DECLINED": "Décliner",
|
||||||
"NEEDS-ACTION": "En attente",
|
"NEEDS-ACTION": "En attente",
|
||||||
|
"attendingStatus": {
|
||||||
|
"ACCEPTED": "Accepté",
|
||||||
|
"TENTATIVE": "Peut-être",
|
||||||
|
"DECLINED": "Refusé",
|
||||||
|
"NEEDS-ACTION": "En attente"
|
||||||
|
},
|
||||||
"showMore": "Afficher plus",
|
"showMore": "Afficher plus",
|
||||||
"showLess": "Afficher moins",
|
"showLess": "Afficher moins",
|
||||||
"joinVideo": "Rejoindre la visioconférence",
|
"joinVideo": "Rejoindre la visioconférence",
|
||||||
|
|||||||
@@ -282,10 +282,17 @@
|
|||||||
"tooltip": "Другие видят вас свободным."
|
"tooltip": "Другие видят вас свободным."
|
||||||
},
|
},
|
||||||
"attendingQuestion": "Присоединитесь?",
|
"attendingQuestion": "Присоединитесь?",
|
||||||
|
"authorizeQuestion": "Авторизовать?",
|
||||||
"ACCEPTED": "Да",
|
"ACCEPTED": "Да",
|
||||||
"TENTATIVE": "Возможно",
|
"TENTATIVE": "Возможно",
|
||||||
"DECLINED": "Нет",
|
"DECLINED": "Нет",
|
||||||
"NEEDS-ACTION": "В ожидании",
|
"NEEDS-ACTION": "В ожидании",
|
||||||
|
"attendingStatus": {
|
||||||
|
"ACCEPTED": "Принято",
|
||||||
|
"TENTATIVE": "Возможно",
|
||||||
|
"DECLINED": "Отклонено",
|
||||||
|
"NEEDS-ACTION": "В ожидании"
|
||||||
|
},
|
||||||
"showMore": "Показать больше",
|
"showMore": "Показать больше",
|
||||||
"showLess": "Показать меньше",
|
"showLess": "Показать меньше",
|
||||||
"joinVideo": "Присоединиться к видеоконференции",
|
"joinVideo": "Присоединиться к видеоконференции",
|
||||||
|
|||||||
@@ -280,10 +280,17 @@
|
|||||||
"tooltip": "Người khác sẽ thấy bạn rảnh trong khoảng thời gian này."
|
"tooltip": "Người khác sẽ thấy bạn rảnh trong khoảng thời gian này."
|
||||||
},
|
},
|
||||||
"attendingQuestion": "Tham gia?",
|
"attendingQuestion": "Tham gia?",
|
||||||
|
"authorizeQuestion": "Cho phép?",
|
||||||
"ACCEPTED": "Chấp nhận",
|
"ACCEPTED": "Chấp nhận",
|
||||||
"TENTATIVE": "Có thể",
|
"TENTATIVE": "Có thể",
|
||||||
"DECLINED": "Từ chối",
|
"DECLINED": "Từ chối",
|
||||||
"NEEDS-ACTION": "Đang chờ",
|
"NEEDS-ACTION": "Đang chờ",
|
||||||
|
"attendingStatus": {
|
||||||
|
"ACCEPTED": "Đã chấp nhận",
|
||||||
|
"TENTATIVE": "Có thể",
|
||||||
|
"DECLINED": "Đã từ chối",
|
||||||
|
"NEEDS-ACTION": "Đang chờ"
|
||||||
|
},
|
||||||
"showMore": "Xem thêm",
|
"showMore": "Xem thêm",
|
||||||
"showLess": "Thu gọn",
|
"showLess": "Thu gọn",
|
||||||
"joinVideo": "Tham gia cuộc họp video",
|
"joinVideo": "Tham gia cuộc họp video",
|
||||||
|
|||||||
Reference in New Issue
Block a user