#602 adminstrator can manage calendar resources (#703)

Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
lethemanh
2026-03-25 14:28:24 +07:00
committed by GitHub
parent fdc9d7651b
commit 43424607d4
21 changed files with 237 additions and 36 deletions
@@ -21,7 +21,7 @@ export function AttendanceValidation({
setAfterChoiceFunc,
setOpenEditModePopup,
}: AttendanceValidationProps) {
const { currentUserAttendee, isOwn } = contextualizedEvent;
const { currentUserAttendee, isOwn, calendar } = contextualizedEvent;
const { t } = useI18n();
const [isLoading, setIsLoading] = useState(false);
const [loadingValue, setLoadingValue] = useState<PartStat | null>(null);
@@ -38,7 +38,16 @@ export function AttendanceValidation({
(!contextualizedEvent.event.class ||
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;
}
@@ -60,7 +69,9 @@ export function AttendanceValidation({
return (
<>
<Typography sx={{ marginRight: 2 }}>
{t("eventPreview.attendingQuestion")}
{calendar.owner?.resource
? t("eventPreview.authorizeQuestion")
: t("eventPreview.attendingQuestion")}
</Typography>
<Box display="flex" gap="15px" alignItems="center">
<RSVPButton rsvpValue="ACCEPTED" {...commonButtonProps} />
@@ -13,17 +13,22 @@ import { useI18n } from "twake-i18n";
import { CalendarEvent } from "../EventsTypes";
import { EventPreviewAttendees } from "./EventPreviewAttendees";
import { makeRecurrenceString } from "./utils/makeRecurrenceString";
import { renderAttendeeBadge } from "@/components/Event/utils/eventUtils";
interface EventPreviewDetailsProps {
event: CalendarEvent;
isOwn: boolean;
isNotPrivate: boolean;
isResourceEventPreview?: boolean;
calendarName?: string;
}
export function EventPreviewDetails({
event,
isOwn,
isNotPrivate,
isResourceEventPreview,
calendarName,
}: EventPreviewDetailsProps) {
const { t } = useI18n();
const theme = useTheme();
@@ -31,8 +36,14 @@ export function EventPreviewDetails({
const infoIconSx = { minWidth: "25px", marginRight: 2, color: infoIconColor };
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(
() =>
@@ -106,8 +117,12 @@ export function EventPreviewDetails({
/>
)}
{isResourceEventPreview &&
organizer &&
renderAttendeeBadge(organizer, "org", t, true, true)}
{/* Attendees */}
{attendees.length > 0 && (
{!isResourceEventPreview && attendees.length > 0 && (
<EventPreviewAttendees
attendees={attendees}
organizer={organizer}
@@ -144,6 +159,7 @@ export function EventPreviewDetails({
}
content={resources.map((resource, index) => (
<Box
key={resource.cn}
sx={{
marginRight: "5px",
}}
@@ -172,7 +188,7 @@ export function EventPreviewDetails({
color: "#717D96",
}}
>
{t(`eventPreview.${resource.partstat}`)}
{t(`eventPreview.attendingStatus.${resource.partstat}`)}
</Typography>
</Box>
))}
@@ -75,6 +75,14 @@ export default function EventPreviewModal({
if (!user || !event || !calendar) return null;
const isAdminOfResource = Boolean(
calendar.owner?.resource &&
user.openpaasId &&
calendar.owner?.administrators?.some(
(admin) => admin.id === user.openpaasId
)
);
return (
<>
<ResponsiveDialog
@@ -82,7 +90,9 @@ export default function EventPreviewModal({
onClose={() => onClose({}, "backdropClick")}
showHeaderActions={false}
actionsBorderTop={
!!(event.attendee?.find((p) => p.cal_address === user.email) && isOwn)
!!(
event.attendee?.find((p) => p.cal_address === user.email) && isOwn
) || isAdminOfResource
}
actionsJustifyContent="center"
style={{ overflow: "auto" }}
@@ -163,6 +173,8 @@ export default function EventPreviewModal({
event={event}
isOwn={isOwn}
isNotPrivate={isNotPrivate}
isResourceEventPreview={calendar.owner?.resource}
calendarName={calendar.name}
/>
{/* Calendar label */}
+4 -1
View File
@@ -648,7 +648,10 @@ function EventUpdateModal({
newEvent.attendee.push({
cn: resource?.displayName ?? "",
cal_address: resource?.email ?? "",
partstat: "NEEDS-ACTION",
partstat:
event.attendee?.find(
(a) => a.cutype === "RESOURCE" && a.cn === resource.displayName
)?.partstat || "NEEDS-ACTION",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "RESOURCE",
+5 -3
View File
@@ -15,9 +15,11 @@ export function createEventContext(
const attendeeEmail = calendar.delegated
? calendar.owner?.emails?.[0]
: user.email;
const currentUserAttendee = event.attendee?.find(
(person) => person.cal_address === attendeeEmail
);
const currentUserAttendee = calendar.owner?.resource
? event.attendee?.find(
(person) => person.cutype === "RESOURCE" && person.cn === calendar.name
)
: event.attendee?.find((person) => person.cal_address === attendeeEmail);
return {
event,
calendar,