Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend semester dropdown to future semesters #30

Merged
merged 5 commits into from
Oct 11, 2021
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
24 changes: 24 additions & 0 deletions src/components/basket/export-options/crn-script.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,30 @@ const CRNScript = ({isOpen, onClose}: CRNScriptProps) => {
)
}

{
basketState.courseIds.length > 0 && (
<Alert status="warning" rounded="md">
<AlertIcon/>
<AlertTitle>Warning:</AlertTitle>
<AlertDescription>
You have {basketState.courseIds.length} {basketState.courseIds.length > 2 ? 'courses' : 'course'} (instead of {basketState.courseIds.length > 2 ? 'sections' : 'section'}) in your basket. They will not be added to the generated script.
</AlertDescription>
</Alert>
)
}

{
basketState.searchQueries.length > 0 && (
<Alert status="warning" rounded="md">
<AlertIcon/>
<AlertTitle>Warning:</AlertTitle>
<AlertDescription>
You have {basketState.searchQueries.length} search queries in your basket. They will not be added to the generated script.
</AlertDescription>
</Alert>
)
}

<FormControl>
<FormLabel>Operating system:</FormLabel>

Expand Down
185 changes: 138 additions & 47 deletions src/components/basket/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TimeDisplay from 'src/components/sections-table/time-display';
import useStore from 'src/lib/state/context';
import getCreditsString from 'src/lib/get-credits-str';
import {BasketState} from 'src/lib/state/basket';
import {ICourseFromAPI} from 'src/lib/api-types';
import styles from './styles/table.module.scss';

const SkeletonRow = () => (
Expand Down Expand Up @@ -42,12 +43,15 @@ const SkeletonRow = () => (
);

type RowProps = {
section: BasketState['sections'][0];
isForCapture: boolean;
isForCapture?: boolean;
handleSearch: (query: string) => void;
};

const Row = observer(({section, isForCapture, handleSearch}: RowProps) => {
type SectionRowProps = RowProps & {
section: BasketState['sections'][0];
};

const SectionRow = observer(({section, isForCapture, handleSearch}: SectionRowProps) => {
const {basketState, apiState} = useStore();

return (
Expand Down Expand Up @@ -124,6 +128,107 @@ const Row = observer(({section, isForCapture, handleSearch}: RowProps) => {
);
});

type CourseRowProps = RowProps & {
course: ICourseFromAPI;
};

const CourseRow = observer(({isForCapture, handleSearch, course}: CourseRowProps) => {
const {basketState} = useStore();

return (
<Tr>
<Td colSpan={6}>
{course.title}
</Td>

<Td isNumeric>
{course.credits}
</Td>

{
!isForCapture && (
<>
<Td/>
<Td isNumeric>
<IconButton
colorScheme="blue"
icon={<Search2Icon/>}
size="sm"
aria-label="Go to course"
onClick={() => {
handleSearch(`${course.subject}${course.crse}`);
}}/>
</Td>
<Td isNumeric>
<IconButton
colorScheme="red"
icon={<DeleteIcon/>}
size="sm"
aria-label="Remove from basket"
onClick={() => {
basketState.removeCourse(course.id);
}}/>
</Td>
</>
)
}
</Tr>
);
});

type SearchQueryRowProps = RowProps & {
query: {
query: string;
credits?: [number, number];
};
};

const SearchQueryRow = observer(({isForCapture, handleSearch, query}: SearchQueryRowProps) => {
const {basketState} = useStore();

return (
<Tr>
<Td colSpan={6}>
<Tag size="lg">
{query.query}
</Tag>
</Td>

<Td isNumeric>
{query.credits ? getCreditsString(...query.credits) : ''}
</Td>

{
!isForCapture && (
<>
<Td/>
<Td isNumeric>
<IconButton
colorScheme="blue"
icon={<Search2Icon/>}
size="sm"
aria-label="Go to section"
onClick={() => {
handleSearch(query.query);
}}/>
</Td>
<Td isNumeric>
<IconButton
colorScheme="red"
icon={<DeleteIcon/>}
size="sm"
aria-label="Remove from basket"
onClick={() => {
basketState.removeSearchQuery(query.query);
}}/>
</Td>
</>
)
}
</Tr>
);
});

type BasketTableProps = {
onClose?: () => void;
isForCapture?: boolean;
Expand All @@ -142,56 +247,42 @@ const BodyWithData = observer(({onClose, isForCapture}: BasketTableProps) => {

return (
<Tbody>
{basketState.sections.map(section => (
<Row
key={section.id}
section={section}
isForCapture={isForCapture ?? false}
handleSearch={handleSearch}/>
))}
{
basketState.searchQueries.map(query => (
<Tr key={query}>
<Td colSpan={isForCapture ? 6 : 8}>
<Tag size="lg">
{query}
</Tag>
</Td>
{
!isForCapture && (
<>
<Td isNumeric>
<IconButton
colorScheme="blue"
icon={<Search2Icon/>}
size="sm"
aria-label="Go to section"
onClick={() => {
handleSearch(query);
}}/>
</Td>
<Td isNumeric>
<IconButton
colorScheme="red"
icon={<DeleteIcon/>}
size="sm"
aria-label="Remove from basket"
onClick={() => {
basketState.removeSearchQuery(query);
}}/>
</Td>
</>
)
}
</Tr>
basketState.sections.map(section => (
<SectionRow
key={section.id}
section={section}
isForCapture={isForCapture}
handleSearch={handleSearch}/>
))
}

{
basketState.courses.map(course => (
<CourseRow
key={course.id}
course={course}
isForCapture={isForCapture}
handleSearch={handleSearch}/>
))
}

{
basketState.parsedQueries.map(query => (
<SearchQueryRow
key={query.query}
query={query}
isForCapture={isForCapture}
handleSearch={handleSearch}/>
))
}

<Tr fontWeight="bold">
<Td>Total:</Td>
{/* eslint-disable-next-line react/no-array-index-key */}
{Array.from({length: 5}).map((_, i) => (<Td key={i}/>))}
<Td isNumeric>{basketState.totalCredits}</Td>
<Td colSpan={5}/>
<Td isNumeric>
{getCreditsString(...basketState.totalCredits)}
</Td>
{
!isForCapture && (
<>
Expand Down
86 changes: 73 additions & 13 deletions src/components/courses-table/details-row.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import React from 'react';
import {Tr, Td, VStack, Text, Box, Heading, Button, Collapse, IconButton, HStack, Spacer} from '@chakra-ui/react';
import {
Tr,
Td,
VStack,
Text,
Box,
Heading,
Button,
Collapse,
IconButton,
HStack,
Spacer,
Stack,
} from '@chakra-ui/react';
import {observer} from 'mobx-react-lite';
import {faShare} from '@fortawesome/free-solid-svg-icons';
import SectionsTable from 'src/components/sections-table';
import CourseStats from 'src/components/course-stats';
import useStore from 'src/lib/state/context';
import {ICourseWithFilteredSections} from 'src/lib/state/ui';
import WrappedFontAwesomeIcon from 'src/components/wrapped-font-awesome-icon';
import toTitleCase from 'src/lib/to-title-case';
import {AddIcon, DeleteIcon} from '@chakra-ui/icons';

const Stats = observer(({courseKey}: {courseKey: string}) => {
const store = useStore();
Expand All @@ -29,8 +44,21 @@ const Stats = observer(({courseKey}: {courseKey: string}) => {
});

const DetailsRow = ({course, onlyShowSections, onShowEverything, onShareCourse}: {course: ICourseWithFilteredSections; onlyShowSections: boolean; onShowEverything: () => void; onShareCourse: () => void}) => {
const {basketState} = useStore();
const courseKey = `${course.course.subject}${course.course.crse}`;

const courseSections = course.sections.wasFiltered ? course.sections.filtered : course.sections.all;

const isCourseInBasket = basketState.hasCourse(course.course.id);

const handleBasketAction = () => {
if (isCourseInBasket) {
basketState.removeCourse(course.course.id);
} else {
basketState.addCourse(course.course.id);
}
};

return (
<Tr>
<Td colSpan={5}>
Expand All @@ -47,13 +75,41 @@ const DetailsRow = ({course, onlyShowSections, onShowEverything, onShareCourse}:
<VStack spacing={10} align="flex-start" w="full">
<VStack spacing={4} align="flex-start" w="full">
<HStack w="full">
<Text whiteSpace="normal">
<b>Description: </b>
{course.course.description}
</Text>
<Stack>
<Text whiteSpace="normal">
<b>Description: </b>
{course.course.description}
</Text>

{
course.course.offered.length > 0 && (
<Text whiteSpace="normal">
<b>Semesters offered: </b>
{toTitleCase(course.course.offered.join(', '))}
</Text>
)
}
</Stack>

<Spacer/>
<IconButton icon={<WrappedFontAwesomeIcon icon={faShare}/>} aria-label="Share course" variant="ghost" colorScheme="brand" title="Share course" onClick={onShareCourse}/>

<VStack>
<IconButton
icon={<WrappedFontAwesomeIcon icon={faShare}/>}
aria-label="Share course"
variant="ghost"
colorScheme="brand"
title="Share course"
onClick={onShareCourse}/>

<IconButton
icon={isCourseInBasket ? <DeleteIcon/> : <AddIcon/>}
aria-label="Add course to basket"
title="Add course to basket"
size="xs"
colorScheme={isCourseInBasket ? 'red' : undefined}
onClick={handleBasketAction}/>
</VStack>
</HStack>

{
Expand All @@ -70,17 +126,21 @@ const DetailsRow = ({course, onlyShowSections, onShowEverything, onShareCourse}:
</VStack>
</Collapse>

<Box w="100%">
{!onlyShowSections && (
<Heading mb={4}>Sections</Heading>
)}
{
courseSections.length > 0 && (
<Box w="100%">
{!onlyShowSections && (
<Heading mb={4}>Sections</Heading>
)}

<SectionsTable shadow="base" borderRadius="md" sections={course.sections.wasFiltered ? course.sections.filtered : course.sections.all}/>
</Box>
<SectionsTable shadow="base" borderRadius="md" sections={courseSections}/>
</Box>
)
}
</VStack>
</Td>
</Tr>
);
};

export default DetailsRow;
export default observer(DetailsRow);
Loading