diff --git a/src/features/Events/EventPreview/EventPreviewHeader.tsx b/src/features/Events/EventPreview/EventPreviewHeader.tsx index da17806..474c780 100644 --- a/src/features/Events/EventPreview/EventPreviewHeader.tsx +++ b/src/features/Events/EventPreview/EventPreviewHeader.tsx @@ -1,4 +1,4 @@ -import { Box, IconButton } from '@linagora/twake-mui' +import { Box, IconButton, Tooltip } from '@linagora/twake-mui' import CloseIcon from '@mui/icons-material/Close' import EditIcon from '@mui/icons-material/Edit' import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined' @@ -13,23 +13,26 @@ interface EventPreviewHeaderProps { isOwn: boolean isWriteDelegated: boolean isNotPrivate: boolean + canEdit: boolean onClose: () => void onEdit: () => void onMoreClick: (e: React.MouseEvent) => void + onEditInOrganizerCalendar?: () => void + editInOrganizerCalendarTooltip?: string } export function EventPreviewHeader({ event, eventId, - isOrganizer, isOwn, - isWriteDelegated, isNotPrivate, + canEdit, onClose, onEdit, - onMoreClick + onMoreClick, + onEditInOrganizerCalendar, + editInOrganizerCalendarTooltip }: EventPreviewHeaderProps) { - const canEdit = isOrganizer && (isOwn || (isWriteDelegated && isNotPrivate)) const canSeeMore = isNotPrivate || isOwn return ( @@ -70,6 +73,13 @@ export function EventPreviewHeader({ )} + {!canEdit && onEditInOrganizerCalendar && ( + + + + + + )} {canSeeMore && ( diff --git a/src/features/Events/EventPreview/EventPreviewModal.tsx b/src/features/Events/EventPreview/EventPreviewModal.tsx index 70fb083..7a4c83a 100644 --- a/src/features/Events/EventPreview/EventPreviewModal.tsx +++ b/src/features/Events/EventPreview/EventPreviewModal.tsx @@ -43,6 +43,8 @@ export default function EventPreviewModal({ isWriteDelegated, isOrganizer, isNotPrivate, + canEdit, + organizerWritableCalendar, openUpdateModal, setOpenUpdateModal, openDuplicateModal, @@ -51,6 +53,7 @@ export default function EventPreviewModal({ setHidePreview, toggleActionMenu, setToggleActionMenu, + updateModalCalId, openEditModePopup, setOpenEditModePopup, setTypeOfAction, @@ -58,6 +61,7 @@ export default function EventPreviewModal({ setAfterChoiceFunc, resolvedTypeOfAction, handleEditClick, + handleEditInOrganizerCalendar, handleDeleteClick, handleDuplicateClick } = useEventPreviewState(eventId, calId, tempEvent, open, onClose) @@ -101,9 +105,23 @@ export default function EventPreviewModal({ isOwn={isOwn} isWriteDelegated={isWriteDelegated} isNotPrivate={isNotPrivate} + canEdit={canEdit} onClose={() => onClose({}, 'backdropClick')} onEdit={handleEditClick} onMoreClick={e => setToggleActionMenu(e.currentTarget)} + onEditInOrganizerCalendar={ + organizerWritableCalendar + ? handleEditInOrganizerCalendar + : undefined + } + editInOrganizerCalendarTooltip={ + organizerWritableCalendar + ? t('eventPreview.editInOrganizerCalendar').replace( + '%{calendarName}', + organizerWritableCalendar.name + ) + : undefined + } /> } actions={ @@ -209,7 +227,7 @@ export default function EventPreviewModal({ onClose({}, 'backdropClick') }} eventId={eventId} - calId={calId} + calId={updateModalCalId} typeOfAction={resolvedTypeOfAction} /> diff --git a/src/features/Events/EventPreview/useEventPreviewState.ts b/src/features/Events/EventPreview/useEventPreviewState.ts index 5c6c683..f75dbe6 100644 --- a/src/features/Events/EventPreview/useEventPreviewState.ts +++ b/src/features/Events/EventPreview/useEventPreviewState.ts @@ -1,4 +1,5 @@ import { useAppDispatch, useAppSelector } from '@/app/hooks' +import { Calendar } from '@/features/Calendars/CalendarTypes' import { assertThunkSuccess } from '@/utils/assertThunkSuccess' import { getEffectiveEmail } from '@/utils/getEffectiveEmail' import { isEventOrganiser } from '@/utils/isEventOrganiser' @@ -31,6 +32,7 @@ export function useEventPreviewState( const [openDuplicateModal, setOpenDuplicateModal] = useState(false) const [hidePreview, setHidePreview] = useState(false) const [toggleActionMenu, setToggleActionMenu] = useState(null) + const [updateModalCalId, setUpdateModalCalId] = useState(calId) // Recurring event handling const [openEditModePopup, setOpenEditModePopup] = useState( @@ -68,6 +70,22 @@ export function useEventPreviewState( const isNotPrivate = event?.class !== 'PRIVATE' && event?.class !== 'CONFIDENTIAL' + const canEdit = isOrganizer && (isOwn || (isWriteDelegated && isNotPrivate)) + + // If the user cannot edit here but has write access to the organizer's delegated + // calendar, surface a shortcut so they don't have to hunt for the source event. + const organizerEmail = event?.organizer?.cal_address?.toLowerCase() + const organizerWritableCalendar: Calendar | undefined = + !canEdit && organizerEmail + ? Object.values(calendars.list).find( + cal => + cal.delegated && + cal.access?.write && + cal.owner?.emails?.some(e => e.toLowerCase() === organizerEmail) && + cal.events[eventId] !== undefined + ) + : undefined + const contextualizedEvent = event && calendar && user ? createEventContext(event, calendar, user) : null @@ -97,6 +115,22 @@ export function useEventPreviewState( // Action handlers const handleEditClick = () => { + setUpdateModalCalId(calId) + if (isRecurring) { + setAfterChoiceFunc(() => () => { + setHidePreview(true) + setOpenUpdateModal(true) + }) + setOpenEditModePopup('edit') + } else { + setHidePreview(true) + setOpenUpdateModal(true) + } + } + + const handleEditInOrganizerCalendar = () => { + if (!organizerWritableCalendar) return + setUpdateModalCalId(organizerWritableCalendar.id) if (isRecurring) { setAfterChoiceFunc(() => () => { setHidePreview(true) @@ -158,6 +192,8 @@ export function useEventPreviewState( isWriteDelegated, isOrganizer, isNotPrivate, + canEdit, + organizerWritableCalendar, // Modal state openUpdateModal, @@ -168,6 +204,7 @@ export function useEventPreviewState( setHidePreview, toggleActionMenu, setToggleActionMenu, + updateModalCalId, // Recurring state openEditModePopup, @@ -180,6 +217,7 @@ export function useEventPreviewState( // Handlers handleEditClick, + handleEditInOrganizerCalendar, handleDeleteClick, handleDuplicateClick, diff --git a/src/locales/en.json b/src/locales/en.json index 5d65d52..3aab55a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -319,6 +319,7 @@ }, "showMore": "Show more", "showLess": "Show less", + "editInOrganizerCalendar": "Edit in %{calendarName}", "joinVideo": "Join the video conference", "joinVideoShort": "Join", "guests": "%{count} guests", diff --git a/src/locales/fr.json b/src/locales/fr.json index 3eaeffe..fc8bf95 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -320,6 +320,7 @@ }, "showMore": "Afficher plus", "showLess": "Afficher moins", + "editInOrganizerCalendar": "Modifier dans %{calendarName}", "joinVideo": "Rejoindre la visioconférence", "joinVideoShort": "Rejoindre", "guests": "%{count} participants", diff --git a/src/locales/ru.json b/src/locales/ru.json index 6426461..81a108f 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -320,6 +320,7 @@ }, "showMore": "Показать больше", "showLess": "Показать меньше", + "editInOrganizerCalendar": "Редактировать в %{calendarName}", "joinVideo": "Присоединиться к видеоконференции", "joinVideoShort": "Присоединиться", "guests": "%{count} гостей", diff --git a/src/locales/vi.json b/src/locales/vi.json index 8389121..0741ed9 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -318,6 +318,7 @@ }, "showMore": "Xem thêm", "showLess": "Thu gọn", + "editInOrganizerCalendar": "Chỉnh sửa trong %{calendarName}", "joinVideo": "Tham gia cuộc họp video", "joinVideoShort": "Tham gia", "guests": "%{count} khách",