Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/backend/src/controllers/calendar.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,12 +624,13 @@ export default class CalendarController {
static async getIcsFeed(req: Request, res: Response, next: NextFunction) {
try {
const { token } = req.params as Record<string, string>;
const { org, calendars } = req.query as Record<string, string | undefined>;
const { org, calendars, events } = req.query as Record<string, string | undefined>;
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"');
Expand Down
20 changes: 17 additions & 3 deletions src/backend/src/services/calendar.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -2836,6 +2836,19 @@ 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 },
eventType: { calendars: { some: { organizationId } } }
},
...getEventQueryArgs(organizationId)
});
return events.map(eventTransformer);
}

const userTeamIds = [
...user.teamsAsMember.map((t) => t.teamId),
...user.teamsAsLead.map((t) => t.teamId),
Expand All @@ -2845,7 +2858,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: {
Expand All @@ -2856,7 +2869,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,
...[]
]
},
...getEventQueryArgs(organizationId)
Expand Down
2 changes: 0 additions & 2 deletions src/frontend/src/pages/CalendarPage/CalendarDayCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({
marginRight={0.5}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setTooltipHovered(false);
setTimeout(() => {
if (!isLockedRef.current && !tooltipHoveredRef.current) {
setIsHovered(false);
Expand Down Expand Up @@ -489,7 +488,6 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({
<Box
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setTooltipHovered(false);
setTimeout(() => {
if (!isLockedRef.current && !tooltipHoveredRef.current) {
Comment thread
glickgNU marked this conversation as resolved.
setIsHovered(false);
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/pages/CalendarPage/CalendarWeekView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -500,11 +500,11 @@ const CalendarWeekView: React.FC<CalendarWeekViewProps> = ({
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 tooltipHoveredRef = useRef(false);
tooltipHoveredRef.current = tooltipHovered;
const isOpen = isLocked || blockHovered || tooltipHovered;

const baseColor = getEventColor(event);
Expand Down
44 changes: 38 additions & 6 deletions src/frontend/src/pages/CalendarPage/EventClickPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -34,7 +36,13 @@ 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';
Expand Down Expand Up @@ -137,6 +145,15 @@ export const EventClickContent: React.FC<EventClickContentProps> = ({

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 (
<Box
sx={{
Expand Down Expand Up @@ -188,8 +205,22 @@ export const EventClickContent: React.FC<EventClickContentProps> = ({
</Typography>
</Box>

{!disable && canEditOrDelete && (
<Stack direction="row" spacing={0.5} sx={{ flexShrink: 0 }}>
<Stack direction="row" spacing={0.5} sx={{ flexShrink: 0 }}>
<IconButton
size="small"
onClick={(e) => {
stopClick(e);
handleExport(event);
}}
sx={{
color: theme.palette.grey[500],
'&:hover': { color: theme.palette.common.white, bgcolor: 'transparent' }
}}
>
<ExitToAppIcon fontSize="small" />
</IconButton>

{!disable && canEditOrDelete && (
<IconButton
size="small"
onClick={(e) => {
Expand All @@ -203,7 +234,9 @@ export const EventClickContent: React.FC<EventClickContentProps> = ({
>
<EditIcon fontSize="small" />
</IconButton>
)}

{!disable && canEditOrDelete && (
<IconButton
size="small"
onClick={(e) => {
Expand All @@ -217,8 +250,8 @@ export const EventClickContent: React.FC<EventClickContentProps> = ({
>
<DeleteIcon fontSize="small" />
</IconButton>
</Stack>
)}
)}
</Stack>
</Stack>

<Stack direction="row" spacing={1} alignItems="center" sx={{ mt: 0.5, flexWrap: 'wrap' }}>
Expand Down Expand Up @@ -508,7 +541,6 @@ export const EventClickPopup: React.FC<EventClickPopupProps> = ({
const [showEditModal, setShowEditModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showSeriesDeleteModal, setShowSeriesDeleteModal] = useState(false);

const { mutateAsync: deleteEvent } = useDeleteEvent(clickedEvent?.eventId ?? '');
const { mutateAsync: deleteScheduleSlot } = useDeleteScheduleSlot(
clickedEvent?.eventId ?? '',
Expand Down
8 changes: 6 additions & 2 deletions src/frontend/src/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ****************/
Expand Down
Loading