diff --git a/src/components/PreregLink.tsx b/src/components/PreregLink.tsx new file mode 100644 index 0000000..2928c58 --- /dev/null +++ b/src/components/PreregLink.tsx @@ -0,0 +1,35 @@ +import { Activity } from "../lib/activity"; +import { Class } from "../lib/class"; +import { LuClipboardList, LuExternalLink } from "react-icons/lu"; + +import { Tooltip } from "./ui/tooltip"; +import { LinkButton } from "./ui/link-button"; + +/** A link to SIPB Matrix's class group chat importer UI */ +export function PreregLink(props: { selectedActivities: Array }) { + const { selectedActivities } = props; + + // reference: https://github.com/gabrc52/class_group_chats/tree/main/src/routes/import + const matrixLink = `https://student.mit.edu/cgi-bin/sfprwtrm.sh?${( + selectedActivities.filter( + (activity) => activity instanceof Class, + ) as Class[] + ) + .map((cls) => cls.number) + .join(",")}`; + + return ( + + + Pre-register for these classes + + + ); +} diff --git a/src/components/TermSwitcher.tsx b/src/components/TermSwitcher.tsx new file mode 100644 index 0000000..26813da --- /dev/null +++ b/src/components/TermSwitcher.tsx @@ -0,0 +1,99 @@ +import { + SelectContent, + SelectItem, + SelectLabel, + SelectRoot, + SelectTrigger, + SelectValueText, +} from "./ui/select"; + +import { createListCollection } from "@chakra-ui/react"; + +import { Term } from "../lib/dates"; + +/** Given a urlName like i22, return its corresponding URL. */ +function toFullUrl(urlName: string, latestUrlName: string): string { + const url = new URL(window.location.href); + Array.from(url.searchParams.keys()).forEach((key) => { + url.searchParams.delete(key); + }); + if (urlName !== latestUrlName) { + url.searchParams.set("t", urlName); + } + return url.href; +} + +/** Given a urlName like "i22", return the previous one, "f21". */ +function getLastUrlName(urlName: string): string { + const { semester, year } = new Term({ urlName }); + if (semester === "f") { + return `s${year}`; + } else if (semester === "s") { + return `i${year}`; + } else { + return `f${parseInt(year, 10) - 1}`; + } +} + +/** urlNames that don't have a State */ +const EXCLUDED_URLS = ["i23", "i24", "i25"]; + +/** Earliest urlName we have a State for. */ +const EARLIEST_URL = "f22"; + +/** Return all urlNames before the given one. */ +function getUrlNames(latestTerm: string): Array { + let urlName = latestTerm; + const res = []; + while (urlName !== EARLIEST_URL) { + res.push(urlName); + do { + urlName = getLastUrlName(urlName); + } while (EXCLUDED_URLS.includes(urlName)); + } + res.push(EARLIEST_URL); + return res; +} + +export function TermSwitcher(props: { state: State }) { + const { state } = props; + const toUrl = (urlName: string) => + toFullUrl(urlName, state.latestTerm.urlName); + const defaultValue = toUrl(state.term.urlName); + + return ( + { + const { niceName } = new Term({ urlName }); + return { + label: niceName, + value: toUrl(urlName), + }; + }), + })} + value={[defaultValue]} + onValueChange={(e) => { + window.location.href = e.value[0]; + }} + size="sm" + w="8rem" + mr={3} + > + + + + + + {getUrlNames(state.latestTerm.urlName).map((urlName) => { + const { niceName } = new Term({ urlName }); + return ( + + {niceName} + + ); + })} + + + ); +} diff --git a/src/components/ui/menu.tsx b/src/components/ui/menu.tsx new file mode 100644 index 0000000..763005b --- /dev/null +++ b/src/components/ui/menu.tsx @@ -0,0 +1,110 @@ +"use client" + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react" +import * as React from "react" +import { LuCheck, LuChevronRight } from "react-icons/lu" + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const MenuContent = React.forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const MenuArrow = React.forwardRef< + HTMLDivElement, + ChakraMenu.ArrowProps +>(function MenuArrow(props, ref) { + return ( + + + + ) +}) + +export const MenuCheckboxItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.CheckboxItemProps +>(function MenuCheckboxItem(props, ref) { + return ( + + + {props.children} + + ) +}) + +export const MenuRadioItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.RadioItemProps +>(function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) +}) + +export const MenuItemGroup = React.forwardRef< + HTMLDivElement, + ChakraMenu.ItemGroupProps +>(function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && ( + + {title} + + )} + {children} + + ) +}) + +export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode +} + +export const MenuTriggerItem = React.forwardRef< + HTMLDivElement, + MenuTriggerItemProps +>(function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) +}) + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +export const MenuContextTrigger = ChakraMenu.ContextTrigger +export const MenuRoot = ChakraMenu.Root +export const MenuSeparator = ChakraMenu.Separator + +export const MenuItem = ChakraMenu.Item +export const MenuItemText = ChakraMenu.ItemText +export const MenuItemCommand = ChakraMenu.ItemCommand +export const MenuTrigger = ChakraMenu.Trigger