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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ yarn-error.log*
/test-results/
/blob-report/
/playwright/.cache/
/screenshots/
.
2 changes: 1 addition & 1 deletion src/app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@

input::placeholder,
textarea::placeholder {
color: var(--color-base/50); /* Make input placeholder gray */
@apply text-base-content/50;
}
}

Expand Down
116 changes: 9 additions & 107 deletions src/components/calendar/CalendarPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import { $schedule, scheduleTypes } from "@/api/schedule";
import { $schedule } from "@/api/schedule";
import { $sport } from "@/api/sport";
import { Calendar } from "@/components/calendar/Calendar.tsx";
import { URLType } from "@/components/calendar/CalendarViewer.tsx";
import {
filterUpcomingCheckedInSchedule,
fromCalendarSpace,
trainingScheduleToCalendarEvent,
} from "@/components/sport/sport-calendar-events.ts";
import { toScheduleApiDateTime } from "@/components/sport/sport-week-utils.ts";
import { useWhen2MeetCalendarEvents } from "@/components/calendar/useWhen2MeetCalendarEvents.ts";
import {
getImportedLink,
getICSLink,
getMyMoodleLink,
getMyMusicRoomLink,
getMyRoomBookingsLink,
getMyEventsLink,
} from "@/api/schedule/links.ts";
import { SchemaLinkedCalendarView } from "@/api/schedule/types.ts";
import { calendarSourceToUrl, getCalendarSources } from "./calendar-sources.ts";
import { useWhen2MeetCalendarEvents } from "./useWhen2MeetCalendarEvents.ts";
import type { EventInput } from "@fullcalendar/core";
import { useMemo, useRef, useState } from "react";

Expand Down Expand Up @@ -81,21 +72,15 @@ export function CalendarPage() {
<Calendar
extraEvents={when2MeetEvents}
urls={
scheduleUser?.favorite_event_groups === undefined ||
scheduleUser?.hidden_event_groups === undefined ||
predefined === undefined ||
eventGroups === undefined
!scheduleUser || !predefined || !eventGroups
? []
: getCalendarsToShow(
scheduleUser.favorite_event_groups,
scheduleUser.hidden_event_groups,
: getCalendarSources(
scheduleUser,
predefined.event_groups,
eventGroups,
scheduleUser.id,
scheduleUser.music_room_hidden,
scheduleUser.moodle_hidden,
Object.values(scheduleUser.linked_calendars || {}),
)
// Sport trainings come from the sport API as extra events below.
{ excludeSport: true },
).map(calendarSourceToUrl)
}
sportEvents={sportEvents}
onVisibleRangeChange={setVisibleRange}
Expand All @@ -114,86 +99,3 @@ export function CalendarPage() {
</div>
);
}

function getCalendarsToShow(
favorites: string[],
hidden: string[],
predefined: string[],
eventGroups: scheduleTypes.SchemaListEventGroupsResponse,
userId: number | undefined,
music_room_hidden: boolean,
moodle_hidden: boolean,
imported: SchemaLinkedCalendarView[],
): URLType[] {
const visibleAliases = new Set(
favorites.concat(predefined).filter((alias) => !hidden.includes(alias)),
);
const hasAssistantEnglish = [...visibleAliases].some((alias) =>
alias.startsWith("english-"),
);

// Remove hidden calendars
const toShow: URLType[] = [...visibleAliases].flatMap((alias) => {
const group = eventGroups.event_groups.find(
(eventGroup) => eventGroup.alias === alias,
);
if (!group) return [];
const isCoreCourses = group.tags?.some(
(tag) => tag.type === "core-courses",
);
return [
{
url: getICSLink(group.alias, userId),
eventGroup: group,
excludeEnglish: hasAssistantEnglish && isCoreCourses,
},
];
});

// Add personal calendars
if (!music_room_hidden) {
toShow.push({
url: getMyMusicRoomLink(),
color: "seagreen",
sourceLink: "https://telegram.me/InnoMusicRoomBot",
updatedAt: new Date().toISOString(),
});
}

if (!moodle_hidden) {
toShow.push({
url: getMyMoodleLink(),
color: "seagreen",
sourceLink:
"https://moodle.innopolis.university/calendar/view.php?view=month",
updatedAt: new Date().toISOString(),
});
}

toShow.push({
url: getMyEventsLink(),
color: "seagreen",
sourceLink: "https://innohassle.ru/events",
updatedAt: new Date().toISOString(),
});

toShow.push({
url: getMyRoomBookingsLink(),
color: "seagreen",
sourceLink: "https://innohassle.ru/room-booking",
updatedAt: new Date().toISOString(),
});

// Add imported calendars
imported.forEach((v) => {
if (v.is_active)
toShow.push({
url: getImportedLink(userId, v.alias),
color: v.color || undefined,
updatedAt: new Date().toISOString(),
});
});

// Return unique items
return toShow.filter((value, index, array) => array.indexOf(value) === index);
}
144 changes: 144 additions & 0 deletions src/components/calendar/calendar-sources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type { scheduleTypes } from "@/api/schedule";
import {
getImportedLink,
getICSLink,
getMyEventsLink,
getMyMoodleLink,
getMyMusicRoomLink,
getMyRoomBookingsLink,
getMySportLink,
} from "@/api/schedule/links.ts";

export type CalendarSource =
| {
id: string;
kind: "group";
group: scheduleTypes.SchemaViewEventGroup;
userId: number;
excludeEnglish: boolean;
}
| {
id: string;
kind: "imported";
calendar: scheduleTypes.SchemaLinkedCalendarView;
userId: number;
}
| {
id: string;
kind: "music-room" | "sport" | "moodle" | "events" | "room-bookings";
};

export type CalendarSelection = {
id: number;
favorite_event_groups?: string[];
hidden_event_groups?: string[];
music_room_hidden: boolean;
sports_hidden: boolean;
moodle_hidden: boolean;
linked_calendars?: Record<
string,
scheduleTypes.SchemaLinkedCalendarView
> | null;
};

/** Shared selection for the full calendar and dashboard; only the latter excludes sport. */
export function getCalendarSources(
user: CalendarSelection,
predefined: string[],
eventGroups: scheduleTypes.SchemaListEventGroupsResponse,
{ excludeSport = false } = {},
): CalendarSource[] {
const { favorite_event_groups, hidden_event_groups } = user;
if (!favorite_event_groups || !hidden_event_groups) return [];
const visibleAliases = new Set(
[...favorite_event_groups, ...predefined].filter(
(alias) => !hidden_event_groups.includes(alias),
),
);
const hasAssistantEnglish = [...visibleAliases].some((alias) =>
alias.startsWith("english-"),
);
const sources: CalendarSource[] = [];
for (const alias of visibleAliases) {
const group = eventGroups.event_groups.find(
(eventGroup) => eventGroup.alias === alias,
);
if (!group) continue;
const isCoreCourses = group.tags?.some(
(tag) => tag.type === "core-courses",
);
sources.push({
id: `group:${alias}`,
kind: "group",
group,
userId: user.id,
excludeEnglish: hasAssistantEnglish && !!isCoreCourses,
});
}
if (!user.music_room_hidden)
sources.push({ id: "music-room", kind: "music-room" });
if (!excludeSport && !user.sports_hidden)
sources.push({ id: "sport", kind: "sport" });
if (!user.moodle_hidden) sources.push({ id: "moodle", kind: "moodle" });
sources.push(
{ id: "events", kind: "events" },
{ id: "room-bookings", kind: "room-bookings" },
);
for (const calendar of Object.values(user.linked_calendars ?? {})) {
if (calendar.is_active)
sources.push({
id: `imported:${calendar.alias}`,
kind: "imported",
calendar,
userId: user.id,
});
}
return sources;
}

/** Keep the full calendar's URL and display metadata unchanged. No UI imports. */
export function calendarSourceToUrl(source: CalendarSource) {
if (source.kind === "group") {
return {
url: getICSLink(source.group.alias, source.userId),
eventGroup: source.group,
excludeEnglish: source.excludeEnglish,
};
}
if (source.kind === "imported") {
return {
url: getImportedLink(source.userId, source.calendar.alias),
color: source.calendar.color || undefined,
updatedAt: new Date().toISOString(),
};
}
const personal = {
"music-room": {
url: getMyMusicRoomLink,
sourceLink: "https://telegram.me/InnoMusicRoomBot",
},
sport: {
url: getMySportLink,
sourceLink: "https://sport.innopolis.university",
},
moodle: {
url: getMyMoodleLink,
sourceLink:
"https://moodle.innopolis.university/calendar/view.php?view=month",
},
events: {
url: getMyEventsLink,
sourceLink: "https://innohassle.ru/events",
},
"room-bookings": {
url: getMyRoomBookingsLink,
sourceLink: "https://innohassle.ru/room-booking",
},
}[source.kind];
return {
url: personal.url(),
sourceLink: personal.sourceLink,
color: "seagreen",
updatedAt: new Date().toISOString(),
};
}
Loading
Loading