From 1967665a83006e2a5646dfa30d71ba4d6949118b Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Sun, 10 May 2026 12:44:25 -0400 Subject: [PATCH 1/7] added export calendar for event --- .../src/controllers/calendar.controllers.ts | 7 ++- src/backend/src/services/calendar.services.ts | 7 ++- .../pages/CalendarPage/CalendarDayCard.tsx | 45 +++++++-------- .../pages/CalendarPage/CalendarWeekView.tsx | 56 ++++++++----------- .../pages/CalendarPage/EventClickPopup.tsx | 54 ++++++++++++++---- .../pages/CalendarPage/IcsSubscribeModal.tsx | 2 +- src/frontend/src/utils/urls.ts | 8 ++- 7 files changed, 102 insertions(+), 77 deletions(-) diff --git a/src/backend/src/controllers/calendar.controllers.ts b/src/backend/src/controllers/calendar.controllers.ts index d75cad9b40..ad500663fd 100644 --- a/src/backend/src/controllers/calendar.controllers.ts +++ b/src/backend/src/controllers/calendar.controllers.ts @@ -624,12 +624,13 @@ export default class CalendarController { static async getIcsFeed(req: Request, res: Response, next: NextFunction) { try { const { token } = req.params as Record; - const { org, calendars } = req.query as Record; + const { org, calendars, events } = req.query as Record; const organizationId = org ?? ''; const calendarIds = calendars ? calendars.split(',').filter(Boolean) : []; + const eventIds = events ? events.split(',').filter(Boolean) : []; - const events = await CalendarService.getIcsFeedEvents(token, organizationId, calendarIds); - const icsContent = generateIcsFeed(events); + const event = await CalendarService.getIcsFeedEvents(token, organizationId, calendarIds, eventIds); + const icsContent = generateIcsFeed(event); res.setHeader('Content-Type', 'text/calendar; charset=utf-8'); res.setHeader('Content-Disposition', 'attachment; filename="finishline.ics"'); diff --git a/src/backend/src/services/calendar.services.ts b/src/backend/src/services/calendar.services.ts index d625ca051a..a1f8069bb7 100644 --- a/src/backend/src/services/calendar.services.ts +++ b/src/backend/src/services/calendar.services.ts @@ -2824,7 +2824,7 @@ export default class CalendarService { return token; } - static async getIcsFeedEvents(icsToken: string, organizationId: string, calendarIds: string[]) { + static async getIcsFeedEvents(icsToken: string, organizationId: string, calendarIds: string[], eventIds: string[] = []) { const user = await prisma.user.findUnique({ where: { icsToken }, include: { @@ -2847,6 +2847,8 @@ export default class CalendarService { ? [{ eventType: { calendars: { some: { calendarId: { in: calendarIds }, organizationId } } } }] : []; + const eventFilter = eventIds.length > 0 ? [{ eventId: { in: eventIds } }] : []; + const events = await prisma.event.findMany({ where: { dateDeleted: null, @@ -2856,7 +2858,8 @@ export default class CalendarService { { requiredMembers: { some: { userId: user.userId } } }, { optionalMembers: { some: { userId: user.userId } } }, ...(userTeamIds.length > 0 ? [{ teams: { some: { teamId: { in: userTeamIds } } } }] : []), - ...calendarFilter + ...calendarFilter, + ...eventFilter ] }, ...getEventQueryArgs(organizationId) diff --git a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx index 9d2a12ee5c..6800c491de 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx @@ -13,10 +13,11 @@ import EventPartialInfoView from './EventPartialInfoView'; import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import NERDeleteModal from '../../components/NERDeleteModal'; -import { useDeleteEvent, useDeleteScheduleSlot } from '../../hooks/calendar.hooks'; +import { useDeleteEvent, useDeleteScheduleSlot, useGetIcsToken } from '../../hooks/calendar.hooks'; import { useToast } from '../../hooks/toasts.hooks'; import { getMutedColor } from '../../utils/calendar.utils'; import { TaskClickContent } from './TaskClickPopup'; +import { apiUrls } from '../../utils/urls'; export const getTeamTypeIcon = (teamTypeName: string, isLarge?: boolean) => { const teamIcons: Map = new Map([ @@ -85,6 +86,8 @@ const CalendarDayCard: React.FC = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); + const { data: tokenData } = useGetIcsToken(); + const [, setCopied] = useState(false); const toast = useToast(); // Ref and state for dynamic event count calculation @@ -160,6 +163,16 @@ const CalendarDayCard: React.FC = ({ } }; + const handleExport = (event: EventInstance) => { + setSelectedEvent(event); + if (!tokenData) return; + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); + navigator.clipboard.writeText(feedUrl); + setCopied(true); + toast.success('Copied calendar with event to clipboard!'); + setTimeout(() => setCopied(false), 2000); + }; + const allItems = [...events, ...tasks]; const totalItems = allItems.length; @@ -168,10 +181,6 @@ const CalendarDayCard: React.FC = ({ const [tooltipHovered, setTooltipHovered] = useState(false); const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -181,9 +190,8 @@ const CalendarDayCard: React.FC = ({ marginRight={0.5} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) { + if (!isLocked && !tooltipHovered) { setIsHovered(false); } }, 100); @@ -304,10 +312,6 @@ const CalendarDayCard: React.FC = ({ event.approved === ConflictStatus.DENIED; const bgColor = isPending ? getMutedColor(baseColor, 0.35) : baseColor; const isLocked = lockedTooltipEventId === event.eventId; - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -317,9 +321,8 @@ const CalendarDayCard: React.FC = ({ marginRight={0.5} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) { + if (!isLocked && !tooltipHovered) { setIsHovered(false); } }, 100); @@ -368,6 +371,7 @@ const CalendarDayCard: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} + onExport={handleExport} clickedDate={cardDate} /> @@ -424,10 +428,6 @@ const CalendarDayCard: React.FC = ({ const [isHovered, setIsHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); const isLocked = lockedTooltipEventId === event.eventId; - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -452,6 +452,7 @@ const CalendarDayCard: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} + onExport={handleExport} clickedDate={cardDate} /> @@ -489,9 +490,8 @@ const CalendarDayCard: React.FC = ({ setIsHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) { + if (!isLocked && !tooltipHovered) { setIsHovered(false); } }, 100); @@ -512,10 +512,6 @@ const CalendarDayCard: React.FC = ({ const [tooltipHovered, setTooltipHovered] = useState(false); const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -566,9 +562,8 @@ const CalendarDayCard: React.FC = ({ setIsHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) { + if (!isLocked && !tooltipHovered) { setIsHovered(false); } }, 100); diff --git a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx index b0289549b7..a00fa72ac0 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx @@ -12,12 +12,13 @@ import { EventClickContent } from './EventClickPopup'; import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import NERDeleteModal from '../../components/NERDeleteModal'; -import { useDeleteEvent, useDeleteScheduleSlot } from '../../hooks/calendar.hooks'; +import { useDeleteEvent, useDeleteScheduleSlot, useGetIcsToken } from '../../hooks/calendar.hooks'; import { useToast } from '../../hooks/toasts.hooks'; import { useCurrentUser } from '../../hooks/users.hooks'; import { getMutedColor } from '../../utils/calendar.utils'; import { getTeamTypeIcon } from './CalendarDayCard'; import { TaskClickContent } from './TaskClickPopup'; +import { apiUrls } from '../../utils/urls'; // ─── Layout constants ──────────────────────────────────────────────────────── @@ -164,6 +165,8 @@ const CalendarWeekView: React.FC = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [dragState, setDragState] = useState(null); + const { data: tokenData } = useGetIcsToken(); + const [, setCopied] = useState(false); const isDraggingRef = useRef(false); const dragStartRef = useRef<{ dayIndex: number; dayDate: Date; startMinutes: number } | null>(null); @@ -374,16 +377,22 @@ const CalendarWeekView: React.FC = ({ } }; + const handleExport = (event: EventInstance) => { + setSelectedEvent(event); + if (!tokenData) return; + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); + navigator.clipboard.writeText(feedUrl); + setCopied(true); + toast.success('Copied calendar with event to clipboard!'); + setTimeout(() => setCopied(false), 2000); + }; + // ─── Sub-components ──────────────────────────────────────────────────────── const WeekEventBlock = ({ event, layout }: { event: EventInstance; layout: LayoutEvent }) => { const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; const isLocked = lockedTooltipEventId === event.eventId + event.scheduleSlotId; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const isOpen = isLocked || blockHovered || tooltipHovered; const baseColor = getEventColor(event); @@ -409,11 +418,7 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} - onMouseLeave={() => setTooltipHovered(false)} - onMouseDown={(e) => e.stopPropagation()} - > + setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} + onExport={handleExport} clickedDate={new Date(event.startTime)} /> @@ -446,9 +452,8 @@ const CalendarWeekView: React.FC = ({ data-testid="week-event-block" onMouseEnter={() => setBlockHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); + if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); }} onClick={(e) => { @@ -500,11 +505,7 @@ const CalendarWeekView: React.FC = ({ const AllDayEventBlock = ({ event }: { event: EventInstance }) => { const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; const isLocked = lockedTooltipEventId === event.eventId + event.scheduleSlotId; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const isOpen = isLocked || blockHovered || tooltipHovered; const baseColor = getEventColor(event); @@ -526,11 +527,7 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} - onMouseLeave={() => setTooltipHovered(false)} - onMouseDown={(e) => e.stopPropagation()} - > + setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} + onExport={handleExport} clickedDate={new Date(event.startTime)} /> @@ -562,9 +560,8 @@ const CalendarWeekView: React.FC = ({ setBlockHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); + if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); }} onClick={(e) => { @@ -593,12 +590,8 @@ const CalendarWeekView: React.FC = ({ const AllDayTaskBlock = ({ task }: { task: CalendarTask }) => { const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); - const tooltipHoveredRef = useRef(false); - tooltipHoveredRef.current = tooltipHovered; const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; - const isLockedRef = useRef(false); - isLockedRef.current = isLocked; const isOpen = isLocked || blockHovered || tooltipHovered; return ( @@ -612,11 +605,7 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} - onMouseLeave={() => setTooltipHovered(false)} - onMouseDown={(e) => e.stopPropagation()} - > + setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> setLockedTooltipEventId(null)} /> } @@ -638,9 +627,8 @@ const CalendarWeekView: React.FC = ({ setBlockHovered(true)} onMouseLeave={() => { - setTooltipHovered(false); setTimeout(() => { - if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); + if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); }} onClick={(e) => { diff --git a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx index 2694f2fc13..4b6fece309 100644 --- a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx +++ b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx @@ -11,6 +11,7 @@ import { isHead, wbsPipe } from 'shared'; +import { apiUrls } from '../../utils/urls'; import { useCurrentUser } from '../../hooks/users.hooks'; import { Link as RouterLink } from 'react-router-dom'; import { routes } from '../../utils/routes'; @@ -24,6 +25,7 @@ import DoNotDisturbIcon from '@mui/icons-material/DoNotDisturb'; import ConstructionIcon from '@mui/icons-material/Construction'; import StorefrontIcon from '@mui/icons-material/Storefront'; import BusinessCenterIcon from '@mui/icons-material/BusinessCenter'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import LinkIcon from '@mui/icons-material/Link'; import ArticleIcon from '@mui/icons-material/Article'; import DescriptionIcon from '@mui/icons-material/Description'; @@ -34,12 +36,18 @@ import DeleteIcon from '@mui/icons-material/Delete'; import WarningAmberIcon from '@mui/icons-material/WarningAmber'; import NERSuccessButton from '../../components/NERSuccessButton'; import NERFailButton from '../../components/NERFailButton'; -import { useApproveEvent, useDeleteEvent, useDeleteScheduleSlot, useDenyEvent } from '../../hooks/calendar.hooks'; +import { + useApproveEvent, + useDeleteEvent, + useDeleteScheduleSlot, + useDenyEvent, + useGetIcsToken +} from '../../hooks/calendar.hooks'; import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import { useToast } from '../../hooks/toasts.hooks'; import NERDeleteModal from '../../components/NERDeleteModal'; -import NotificationsIcon from '@mui/icons-material/Notifications'; + import { getPendingReason } from '../../utils/calendar.utils'; export const getStatusIcon = (status: string, isLarge?: boolean) => { @@ -66,6 +74,7 @@ interface EventClickContentProps { onClose: () => void; onEdit: (event: EventInstance) => void; onDelete: (event: EventInstance) => void; + onExport: (event: EventInstance) => void; clickedDate?: Date; } @@ -87,6 +96,7 @@ export const EventClickContent: React.FC = ({ onClose, onEdit, onDelete, + onExport, clickedDate }) => { const { mutateAsync: approveEvent } = useApproveEvent(event.eventId); @@ -188,8 +198,22 @@ export const EventClickContent: React.FC = ({ - {!disable && canEditOrDelete && ( - + + { + stopClick(e); + onExport(event); + }} + sx={{ + color: theme.palette.grey[500], + '&:hover': { color: theme.palette.common.white, bgcolor: 'transparent' } + }} + > + + + + {!disable && canEditOrDelete && ( { @@ -203,7 +227,9 @@ export const EventClickContent: React.FC = ({ > + )} + {!disable && canEditOrDelete && ( { @@ -217,8 +243,8 @@ export const EventClickContent: React.FC = ({ > - - )} + )} + @@ -436,9 +462,6 @@ export const EventClickContent: React.FC = ({ Status: {event.status} - {specificEventType?.sendSlackNotifications && (event.teams.length > 0 || event.workPackages.length > 0) && ( - - )} )} @@ -508,7 +531,8 @@ export const EventClickPopup: React.FC = ({ const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); - + const { data: tokenData } = useGetIcsToken(); + const [, setCopied] = useState(false); const { mutateAsync: deleteEvent } = useDeleteEvent(clickedEvent?.eventId ?? ''); const { mutateAsync: deleteScheduleSlot } = useDeleteScheduleSlot( clickedEvent?.eventId ?? '', @@ -559,6 +583,15 @@ export const EventClickPopup: React.FC = ({ } }; + const handleExport = (event: EventInstance) => { + if (!tokenData) return; + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); + navigator.clipboard.writeText(feedUrl); + setCopied(true); + toast.success('Copied calendar with event to clipboard!'); + setTimeout(() => setCopied(false), 2000); + }; + return ( = ({ onClose={onClose} onEdit={handleEdit} onDelete={handleDelete} + onExport={handleExport} clickedDate={clickedDate} /> )} diff --git a/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx b/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx index 67db187949..bdfb846a75 100644 --- a/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx +++ b/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx @@ -46,7 +46,7 @@ const IcsSubscribeModal: React.FC = ({ open, onClose }) const handleCopy = async () => { if (!tokenData) return; - const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, selectedCalendarIds); + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, selectedCalendarIds, []); await navigator.clipboard.writeText(feedUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index f8804b746a..41edc7815e 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -491,9 +491,13 @@ const calendarScheduleEvent = (eventId: string) => `${calendar()}/event/${eventI const calendarIcsToken = () => `${calendar()}/ics/token`; // Generates ICS URL to be given to calendars for integration, not directly hit by FL frontend -const icsFeed = (token: string, organizationId: string, calendarIds: string[]) => { +const icsFeed = (token: string, organizationId: string, calendarIds: string[], eventIds: string[]) => { const base = `${API_URL}/ics/${token}?org=${organizationId}`; - return calendarIds.length > 0 ? `${base}&calendars=${calendarIds.join(',')}` : base; + let icsUrl = calendarIds.length > 0 ? `${base}&calendars=${calendarIds.join(',')}` : base; + if (eventIds.length > 0) { + icsUrl += `&events=${eventIds.join(',')}`; + } + return icsUrl; }; /**************** Attendance Endpoints ****************/ From 2bdc74f01ca300995a4b25df26e55ab0f0191dd9 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Sun, 10 May 2026 18:55:36 -0400 Subject: [PATCH 2/7] small changes. still need to fix bugs --- src/backend/src/services/calendar.services.ts | 16 +++++++++++++--- .../src/pages/CalendarPage/IcsSubscribeModal.tsx | 2 +- src/frontend/src/utils/urls.ts | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/backend/src/services/calendar.services.ts b/src/backend/src/services/calendar.services.ts index a1f8069bb7..a14c56fa2d 100644 --- a/src/backend/src/services/calendar.services.ts +++ b/src/backend/src/services/calendar.services.ts @@ -2836,6 +2836,18 @@ export default class CalendarService { if (!user) throw new NotFoundException('User', 'icsToken'); + // specific events case + if (eventIds.length > 0) { + const events = await prisma.event.findMany({ + where: { + dateDeleted: null, + eventId: { in: eventIds } + }, + ...getEventQueryArgs(organizationId) + }); + return events.map(eventTransformer); + } + const userTeamIds = [ ...user.teamsAsMember.map((t) => t.teamId), ...user.teamsAsLead.map((t) => t.teamId), @@ -2847,8 +2859,6 @@ export default class CalendarService { ? [{ eventType: { calendars: { some: { calendarId: { in: calendarIds }, organizationId } } } }] : []; - const eventFilter = eventIds.length > 0 ? [{ eventId: { in: eventIds } }] : []; - const events = await prisma.event.findMany({ where: { dateDeleted: null, @@ -2859,7 +2869,7 @@ export default class CalendarService { { optionalMembers: { some: { userId: user.userId } } }, ...(userTeamIds.length > 0 ? [{ teams: { some: { teamId: { in: userTeamIds } } } }] : []), ...calendarFilter, - ...eventFilter + ...[] ] }, ...getEventQueryArgs(organizationId) diff --git a/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx b/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx index bdfb846a75..67db187949 100644 --- a/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx +++ b/src/frontend/src/pages/CalendarPage/IcsSubscribeModal.tsx @@ -46,7 +46,7 @@ const IcsSubscribeModal: React.FC = ({ open, onClose }) const handleCopy = async () => { if (!tokenData) return; - const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, selectedCalendarIds, []); + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, selectedCalendarIds); await navigator.clipboard.writeText(feedUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index 41edc7815e..ee39409179 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -491,7 +491,7 @@ const calendarScheduleEvent = (eventId: string) => `${calendar()}/event/${eventI const calendarIcsToken = () => `${calendar()}/ics/token`; // Generates ICS URL to be given to calendars for integration, not directly hit by FL frontend -const icsFeed = (token: string, organizationId: string, calendarIds: string[], eventIds: string[]) => { +const icsFeed = (token: string, organizationId: string, calendarIds: string[], eventIds: string[] = []) => { const base = `${API_URL}/ics/${token}?org=${organizationId}`; let icsUrl = calendarIds.length > 0 ? `${base}&calendars=${calendarIds.join(',')}` : base; if (eventIds.length > 0) { From b4fcbf971c52e53d00d1e1be6d80d36a37d08092 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Mon, 11 May 2026 14:26:12 -0400 Subject: [PATCH 3/7] latest version, need look into eventids --- src/backend/src/services/calendar.services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/services/calendar.services.ts b/src/backend/src/services/calendar.services.ts index a14c56fa2d..6149872192 100644 --- a/src/backend/src/services/calendar.services.ts +++ b/src/backend/src/services/calendar.services.ts @@ -2857,7 +2857,7 @@ export default class CalendarService { const calendarFilter = calendarIds.length > 0 ? [{ eventType: { calendars: { some: { calendarId: { in: calendarIds }, organizationId } } } }] - : []; + : [{ eventType: { calendars: { some: { organizationId } } } }]; const events = await prisma.event.findMany({ where: { From 8f598fba1c503e32a4251b52f5fc504e31c1b7d0 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Wed, 13 May 2026 14:38:42 -0400 Subject: [PATCH 4/7] fixed issues related to using older verison of database --- .../pages/CalendarPage/CalendarDayCard.tsx | 16 +++++++++ .../pages/CalendarPage/CalendarWeekView.tsx | 33 +++++++++++++++++-- .../pages/CalendarPage/EventClickPopup.tsx | 5 ++- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx index 6800c491de..ce02ac532b 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx @@ -181,6 +181,10 @@ const CalendarDayCard: React.FC = ({ const [tooltipHovered, setTooltipHovered] = useState(false); const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -312,6 +316,10 @@ const CalendarDayCard: React.FC = ({ event.approved === ConflictStatus.DENIED; const bgColor = isPending ? getMutedColor(baseColor, 0.35) : baseColor; const isLocked = lockedTooltipEventId === event.eventId; + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -428,6 +436,10 @@ const CalendarDayCard: React.FC = ({ const [isHovered, setIsHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); const isLocked = lockedTooltipEventId === event.eventId; + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( @@ -512,6 +524,10 @@ const CalendarDayCard: React.FC = ({ const [tooltipHovered, setTooltipHovered] = useState(false); const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const shouldBeOpen = isLocked || isHovered || tooltipHovered; return ( diff --git a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx index a00fa72ac0..51a775593e 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx @@ -392,7 +392,11 @@ const CalendarWeekView: React.FC = ({ const WeekEventBlock = ({ event, layout }: { event: EventInstance; layout: LayoutEvent }) => { const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; const isLocked = lockedTooltipEventId === event.eventId + event.scheduleSlotId; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const isOpen = isLocked || blockHovered || tooltipHovered; const baseColor = getEventColor(event); @@ -418,7 +422,11 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> + setTooltipHovered(true)} + onMouseLeave={() => setTooltipHovered(false)} + onMouseDown={(e) => e.stopPropagation()} + > = ({ data-testid="week-event-block" onMouseEnter={() => setBlockHovered(true)} onMouseLeave={() => { + setTooltipHovered(false); setTimeout(() => { if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); @@ -506,6 +515,10 @@ const CalendarWeekView: React.FC = ({ const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); const isLocked = lockedTooltipEventId === event.eventId + event.scheduleSlotId; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; const isOpen = isLocked || blockHovered || tooltipHovered; const baseColor = getEventColor(event); @@ -527,7 +540,11 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> + setTooltipHovered(true)} + onMouseLeave={() => setTooltipHovered(false)} + onMouseDown={(e) => e.stopPropagation()} + > = ({ setBlockHovered(true)} onMouseLeave={() => { + setTooltipHovered(false); setTimeout(() => { if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); @@ -590,8 +608,12 @@ const CalendarWeekView: React.FC = ({ const AllDayTaskBlock = ({ task }: { task: CalendarTask }) => { const [blockHovered, setBlockHovered] = useState(false); const [tooltipHovered, setTooltipHovered] = useState(false); + const tooltipHoveredRef = useRef(false); + tooltipHoveredRef.current = tooltipHovered; const tooltipKey = `task-${task.taskId}`; const isLocked = lockedTooltipEventId === tooltipKey; + const isLockedRef = useRef(false); + isLockedRef.current = isLocked; const isOpen = isLocked || blockHovered || tooltipHovered; return ( @@ -605,7 +627,11 @@ const CalendarWeekView: React.FC = ({ enterDelay={0} leaveDelay={200} title={ - setTooltipHovered(true)} onMouseLeave={() => setTooltipHovered(false)}> + setTooltipHovered(true)} + onMouseLeave={() => setTooltipHovered(false)} + onMouseDown={(e) => e.stopPropagation()} + > setLockedTooltipEventId(null)} /> } @@ -627,6 +653,7 @@ const CalendarWeekView: React.FC = ({ setBlockHovered(true)} onMouseLeave={() => { + setTooltipHovered(false); setTimeout(() => { if (!isLocked && !tooltipHovered) setBlockHovered(false); }, 100); diff --git a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx index 4b6fece309..5d33073621 100644 --- a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx +++ b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx @@ -47,7 +47,7 @@ import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import { useToast } from '../../hooks/toasts.hooks'; import NERDeleteModal from '../../components/NERDeleteModal'; - +import NotificationsIcon from '@mui/icons-material/Notifications'; import { getPendingReason } from '../../utils/calendar.utils'; export const getStatusIcon = (status: string, isLarge?: boolean) => { @@ -462,6 +462,9 @@ export const EventClickContent: React.FC = ({ Status: {event.status} + {specificEventType?.sendSlackNotifications && (event.teams.length > 0 || event.workPackages.length > 0) && ( + + )} )} From e851421df2d2c506dc002a8b0f4901443d240780 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Thu, 14 May 2026 10:55:42 -0400 Subject: [PATCH 5/7] removed setCopied, and restored other features --- .../src/pages/CalendarPage/CalendarDayCard.tsx | 13 ++++++------- .../src/pages/CalendarPage/CalendarWeekView.tsx | 9 +++------ .../src/pages/CalendarPage/EventClickPopup.tsx | 3 --- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx index ce02ac532b..7441498bc1 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx @@ -87,7 +87,6 @@ const CalendarDayCard: React.FC = ({ const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); const { data: tokenData } = useGetIcsToken(); - const [, setCopied] = useState(false); const toast = useToast(); // Ref and state for dynamic event count calculation @@ -168,9 +167,7 @@ const CalendarDayCard: React.FC = ({ if (!tokenData) return; const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); navigator.clipboard.writeText(feedUrl); - setCopied(true); toast.success('Copied calendar with event to clipboard!'); - setTimeout(() => setCopied(false), 2000); }; const allItems = [...events, ...tasks]; @@ -195,7 +192,7 @@ const CalendarDayCard: React.FC = ({ onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => { setTimeout(() => { - if (!isLocked && !tooltipHovered) { + if (!isLockedRef.current && !tooltipHoveredRef.current) { setIsHovered(false); } }, 100); @@ -329,8 +326,9 @@ const CalendarDayCard: React.FC = ({ marginRight={0.5} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => { + setTooltipHovered(false); setTimeout(() => { - if (!isLocked && !tooltipHovered) { + if (!isLockedRef.current && !tooltipHoveredRef.current) { setIsHovered(false); } }, 100); @@ -503,7 +501,7 @@ const CalendarDayCard: React.FC = ({ onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => { setTimeout(() => { - if (!isLocked && !tooltipHovered) { + if (!isLockedRef.current && !tooltipHoveredRef.current) { setIsHovered(false); } }, 100); @@ -578,8 +576,9 @@ const CalendarDayCard: React.FC = ({ setIsHovered(true)} onMouseLeave={() => { + setTooltipHovered(false); setTimeout(() => { - if (!isLocked && !tooltipHovered) { + if (!isLockedRef.current && !tooltipHoveredRef.current) { setIsHovered(false); } }, 100); diff --git a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx index 51a775593e..02194792ba 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx @@ -166,7 +166,6 @@ const CalendarWeekView: React.FC = ({ const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [dragState, setDragState] = useState(null); const { data: tokenData } = useGetIcsToken(); - const [, setCopied] = useState(false); const isDraggingRef = useRef(false); const dragStartRef = useRef<{ dayIndex: number; dayDate: Date; startMinutes: number } | null>(null); @@ -382,9 +381,7 @@ const CalendarWeekView: React.FC = ({ if (!tokenData) return; const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); navigator.clipboard.writeText(feedUrl); - setCopied(true); toast.success('Copied calendar with event to clipboard!'); - setTimeout(() => setCopied(false), 2000); }; // ─── Sub-components ──────────────────────────────────────────────────────── @@ -462,7 +459,7 @@ const CalendarWeekView: React.FC = ({ onMouseLeave={() => { setTooltipHovered(false); setTimeout(() => { - if (!isLocked && !tooltipHovered) setBlockHovered(false); + if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); }, 100); }} onClick={(e) => { @@ -579,7 +576,7 @@ const CalendarWeekView: React.FC = ({ onMouseLeave={() => { setTooltipHovered(false); setTimeout(() => { - if (!isLocked && !tooltipHovered) setBlockHovered(false); + if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); }, 100); }} onClick={(e) => { @@ -655,7 +652,7 @@ const CalendarWeekView: React.FC = ({ onMouseLeave={() => { setTooltipHovered(false); setTimeout(() => { - if (!isLocked && !tooltipHovered) setBlockHovered(false); + if (!isLockedRef.current && !tooltipHoveredRef.current) setBlockHovered(false); }, 100); }} onClick={(e) => { diff --git a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx index 5d33073621..f6deb4485a 100644 --- a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx +++ b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx @@ -535,7 +535,6 @@ export const EventClickPopup: React.FC = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const { data: tokenData } = useGetIcsToken(); - const [, setCopied] = useState(false); const { mutateAsync: deleteEvent } = useDeleteEvent(clickedEvent?.eventId ?? ''); const { mutateAsync: deleteScheduleSlot } = useDeleteScheduleSlot( clickedEvent?.eventId ?? '', @@ -590,9 +589,7 @@ export const EventClickPopup: React.FC = ({ if (!tokenData) return; const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); navigator.clipboard.writeText(feedUrl); - setCopied(true); toast.success('Copied calendar with event to clipboard!'); - setTimeout(() => setCopied(false), 2000); }; return ( From f2c3a63df644be8854139bdeb9f5361ec56b49c2 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Fri, 15 May 2026 10:00:12 -0400 Subject: [PATCH 6/7] removed prop drilling + added organization check --- src/backend/src/services/calendar.services.ts | 3 ++- .../pages/CalendarPage/CalendarDayCard.tsx | 13 +---------- .../pages/CalendarPage/CalendarWeekView.tsx | 14 +----------- .../pages/CalendarPage/EventClickPopup.tsx | 22 +++++++++---------- 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/backend/src/services/calendar.services.ts b/src/backend/src/services/calendar.services.ts index 6149872192..8e07d2f609 100644 --- a/src/backend/src/services/calendar.services.ts +++ b/src/backend/src/services/calendar.services.ts @@ -2841,7 +2841,8 @@ export default class CalendarService { const events = await prisma.event.findMany({ where: { dateDeleted: null, - eventId: { in: eventIds } + eventId: { in: eventIds }, + eventType: { calendars: { some: { organizationId } } } }, ...getEventQueryArgs(organizationId) }); diff --git a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx index 7441498bc1..6b889a39bd 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx @@ -13,7 +13,7 @@ import EventPartialInfoView from './EventPartialInfoView'; import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import NERDeleteModal from '../../components/NERDeleteModal'; -import { useDeleteEvent, useDeleteScheduleSlot, useGetIcsToken } from '../../hooks/calendar.hooks'; +import { useDeleteEvent, useDeleteScheduleSlot } from '../../hooks/calendar.hooks'; import { useToast } from '../../hooks/toasts.hooks'; import { getMutedColor } from '../../utils/calendar.utils'; import { TaskClickContent } from './TaskClickPopup'; @@ -86,7 +86,6 @@ const CalendarDayCard: React.FC = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); - const { data: tokenData } = useGetIcsToken(); const toast = useToast(); // Ref and state for dynamic event count calculation @@ -162,14 +161,6 @@ const CalendarDayCard: React.FC = ({ } }; - const handleExport = (event: EventInstance) => { - setSelectedEvent(event); - if (!tokenData) return; - const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); - navigator.clipboard.writeText(feedUrl); - toast.success('Copied calendar with event to clipboard!'); - }; - const allItems = [...events, ...tasks]; const totalItems = allItems.length; @@ -377,7 +368,6 @@ const CalendarDayCard: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} - onExport={handleExport} clickedDate={cardDate} /> @@ -462,7 +452,6 @@ const CalendarDayCard: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} - onExport={handleExport} clickedDate={cardDate} /> diff --git a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx index 02194792ba..0ac4628318 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx @@ -12,13 +12,12 @@ import { EventClickContent } from './EventClickPopup'; import EditEventModal from './Components/EditEventModal'; import DeleteSeriesConfirmationModal from './Components/DeleteSeriesConfirmationModal'; import NERDeleteModal from '../../components/NERDeleteModal'; -import { useDeleteEvent, useDeleteScheduleSlot, useGetIcsToken } from '../../hooks/calendar.hooks'; +import { useDeleteEvent, useDeleteScheduleSlot } from '../../hooks/calendar.hooks'; import { useToast } from '../../hooks/toasts.hooks'; import { useCurrentUser } from '../../hooks/users.hooks'; import { getMutedColor } from '../../utils/calendar.utils'; import { getTeamTypeIcon } from './CalendarDayCard'; import { TaskClickContent } from './TaskClickPopup'; -import { apiUrls } from '../../utils/urls'; // ─── Layout constants ──────────────────────────────────────────────────────── @@ -165,7 +164,6 @@ const CalendarWeekView: React.FC = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); const [dragState, setDragState] = useState(null); - const { data: tokenData } = useGetIcsToken(); const isDraggingRef = useRef(false); const dragStartRef = useRef<{ dayIndex: number; dayDate: Date; startMinutes: number } | null>(null); @@ -376,14 +374,6 @@ const CalendarWeekView: React.FC = ({ } }; - const handleExport = (event: EventInstance) => { - setSelectedEvent(event); - if (!tokenData) return; - const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); - navigator.clipboard.writeText(feedUrl); - toast.success('Copied calendar with event to clipboard!'); - }; - // ─── Sub-components ──────────────────────────────────────────────────────── const WeekEventBlock = ({ event, layout }: { event: EventInstance; layout: LayoutEvent }) => { @@ -433,7 +423,6 @@ const CalendarWeekView: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} - onExport={handleExport} clickedDate={new Date(event.startTime)} /> @@ -551,7 +540,6 @@ const CalendarWeekView: React.FC = ({ onClose={() => setLockedTooltipEventId(null)} onEdit={handleEdit} onDelete={handleDelete} - onExport={handleExport} clickedDate={new Date(event.startTime)} /> diff --git a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx index f6deb4485a..643bd3bbd2 100644 --- a/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx +++ b/src/frontend/src/pages/CalendarPage/EventClickPopup.tsx @@ -74,7 +74,6 @@ interface EventClickContentProps { onClose: () => void; onEdit: (event: EventInstance) => void; onDelete: (event: EventInstance) => void; - onExport: (event: EventInstance) => void; clickedDate?: Date; } @@ -96,7 +95,6 @@ export const EventClickContent: React.FC = ({ onClose, onEdit, onDelete, - onExport, clickedDate }) => { const { mutateAsync: approveEvent } = useApproveEvent(event.eventId); @@ -147,6 +145,15 @@ export const EventClickContent: React.FC = ({ const pendingReason = getPendingReason(event); + const { data: tokenData } = useGetIcsToken(); + + const handleExport = (event: EventInstance) => { + if (!tokenData) return; + const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); + navigator.clipboard.writeText(feedUrl); + toast.success('Copied calendar with event to clipboard!'); + }; + return ( = ({ size="small" onClick={(e) => { stopClick(e); - onExport(event); + handleExport(event); }} sx={{ color: theme.palette.grey[500], @@ -534,7 +541,6 @@ export const EventClickPopup: React.FC = ({ const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false); - const { data: tokenData } = useGetIcsToken(); const { mutateAsync: deleteEvent } = useDeleteEvent(clickedEvent?.eventId ?? ''); const { mutateAsync: deleteScheduleSlot } = useDeleteScheduleSlot( clickedEvent?.eventId ?? '', @@ -585,13 +591,6 @@ export const EventClickPopup: React.FC = ({ } }; - const handleExport = (event: EventInstance) => { - if (!tokenData) return; - const feedUrl = apiUrls.icsFeed(tokenData.icsToken, tokenData.organizationId, [], [event.eventId]); - navigator.clipboard.writeText(feedUrl); - toast.success('Copied calendar with event to clipboard!'); - }; - return ( = ({ onClose={onClose} onEdit={handleEdit} onDelete={handleDelete} - onExport={handleExport} clickedDate={clickedDate} /> )} From 759ab625eeefd5eb58742ab29a9349469e8d3503 Mon Sep 17 00:00:00 2001 From: Gil Glick Date: Fri, 15 May 2026 10:01:47 -0400 Subject: [PATCH 7/7] removed unused import --- src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx index 6b889a39bd..cb5b7236e0 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx @@ -17,7 +17,6 @@ import { useDeleteEvent, useDeleteScheduleSlot } from '../../hooks/calendar.hook import { useToast } from '../../hooks/toasts.hooks'; import { getMutedColor } from '../../utils/calendar.utils'; import { TaskClickContent } from './TaskClickPopup'; -import { apiUrls } from '../../utils/urls'; export const getTeamTypeIcon = (teamTypeName: string, isLarge?: boolean) => { const teamIcons: Map = new Map([