Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Select by instructor #473

Merged
merged 37 commits into from Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d88b512
created ProfessorGroup component
firejake308 Nov 11, 2020
a71e1fd
Moved instructor header to ProfessorGroup
firejake308 Nov 11, 2020
4b368ea
Added tests for ProfessorGroup
firejake308 Nov 11, 2020
80fe240
Changed to sectionRange prop
firejake308 Nov 11, 2020
1268cea
Fixed bug and turned on ESLint
firejake308 Nov 11, 2020
0395057
Fixed checkbox color
firejake308 Nov 11, 2020
6771b29
Added left indent, fixed color, renames areAllSelected
firejake308 Nov 11, 2020
7cc2dcf
Alternating colors
firejake308 Nov 16, 2020
0cb5db4
Dynamically decides 1 or 2 rows
firejake308 Nov 22, 2020
2991ae6
Added 2px padding
firejake308 Nov 22, 2020
4579539
Won't highlight if there is only one meeting
firejake308 Nov 22, 2020
942586a
Centered hover circle
firejake308 Nov 22, 2020
24b96fc
Added comment and fixed test
firejake308 Nov 22, 2020
f69e9cb
Added indeterminate
firejake308 Jan 11, 2021
6276265
Change to space evenly
firejake308 Jan 11, 2021
d315fd9
Resolve merge conflicts after select all
firejake308 Jan 16, 2021
6df36ca
Changed "allOn" to "all on" for better a11y
firejake308 Jan 16, 2021
5751e2d
Fixed tests using different value for prof select
firejake308 Jan 16, 2021
31609d3
Fix tr back to div
firejake308 Jan 16, 2021
f49f2b9
Added docstrings
firejake308 Jan 24, 2021
76b42f5
Prof name is a button
firejake308 Feb 8, 2021
309b252
Merge branch 'master' into frontend/instructor-select
firejake308 Feb 8, 2021
13c5665
Moved onClick from checkbox to button
firejake308 Feb 8, 2021
e439df9
Moved back instructional method icon
firejake308 Feb 14, 2021
5affb7b
Center align meeting times
firejake308 Feb 14, 2021
c6c159e
Aligned instructor checkbox with select all
firejake308 Feb 28, 2021
5fd39ff
Applied CSS grid to meeting info
firejake308 Feb 28, 2021
5b3ba03
Adjusting style fixes for 2-column layout
firejake308 Mar 1, 2021
bebb8a7
Increased cutoff to 1600px
firejake308 Mar 1, 2021
2668847
Merge branch 'master' into frontend/instructor-select
firejake308 Mar 8, 2021
5dbfb2c
Merge branch 'master' into frontend/instructor-select
firejake308 Mar 15, 2021
0c26b68
Use the right HonorsIcon
firejake308 Mar 15, 2021
ee589f2
Centered building and days
firejake308 Mar 21, 2021
1e729a5
Fixed DOM nesting
firejake308 Mar 21, 2021
5fc1a8a
Revert "Centered building and days"
firejake308 Mar 28, 2021
a747aa1
Move up CSS Grid
firejake308 Mar 28, 2021
5e62906
Re-enable linebreak linting
firejake308 Apr 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions autoscheduler/frontend/src/.eslintrc.js
Expand Up @@ -63,5 +63,6 @@ module.exports = {
"@typescript-eslint/semi": ["error"],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"linebreak-style": 0,
firejake308 marked this conversation as resolved.
Show resolved Hide resolved
},
};
Expand Up @@ -68,7 +68,7 @@ const GradeDist: React.FC<GradeDistProps> = ({ grades }) => {
disablePortal: true,
}}
>
<ListSubheader style={{ lineHeight: 'inherit' }} disableGutters className={styles.gpaUnderline}>
<ListSubheader style={{ lineHeight: 'inherit' }} disableGutters className={styles.gpaUnderline} component="span">
{`${grades.gpa.toFixed(2)}`}
</ListSubheader>
</Tooltip>
Expand Down
@@ -0,0 +1,88 @@
import {
Button, Checkbox, Divider, ListSubheader,
} from '@material-ui/core';
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import HonorsIcon from '../../../../../Icons/HonorsIcon/HonorsIcon';
import { setSelected } from '../../../../../../redux/actions/courseCards';
import { RootState } from '../../../../../../redux/reducer';
import { SectionSelected } from '../../../../../../types/CourseCardOptions';
import GradeDist from './GradeDist/GradeDist';
import SectionInfo from './SectionInfo';
import * as styles from './SectionSelect.css';

interface ProfessorGroupProps {
courseCardId: number;
sectionRange: [number, number];
}

/**
* Renders a group of sections that have the same professors and honors status, including the
* instructor header at the top.
*
* @param props This component takes 2 props, `courseCardId` and `sectionRange`. `sectionRange`
* should be a tuple of 2 numbers `[startIdx, endIdx]`, where `startIdx` is the first section
* that should be rendered in this group and `endIdx` is one more than the last section in this
* group
*/
const ProfessorGroup: React.FC<ProfessorGroupProps> = ({ courseCardId, sectionRange }) => {
firejake308 marked this conversation as resolved.
Show resolved Hide resolved
const [startIdx, endIdx] = sectionRange;

const dispatch = useDispatch();
const sections = useSelector<RootState, SectionSelected[]>(
(state) => state.termData.courseCards[courseCardId].sections.slice(startIdx, endIdx),
);
const areAllSelected = sections.every((secData) => secData.selected);
const areAnySelected = sections.some((secData) => secData.selected);
const firstSection = sections[0].section;
const toggleAllSelected = (): void => {
sections.forEach((_, idx) => dispatch(
setSelected(courseCardId, startIdx + idx, !areAnySelected),
));
};

const instructorHeader = (
<ListSubheader disableGutters className={styles.listSubheaderDense}>
<div className={styles.listSubheaderContent}>
<Button
className={styles.nameHonorsIcon}
onClick={toggleAllSelected}
aria-label="Select all for professor"
>
<Checkbox
checked={areAnySelected}
indeterminate={areAnySelected && !areAllSelected}
size="small"
color="primary"
value={areAllSelected ? 'professor on' : 'professor off'}
classes={{ root: styles.lessCheckboxPadding }}
/>
{firstSection.instructor.name}
{firstSection.honors ? <HonorsIcon /> : null}
</Button>
<GradeDist grades={firstSection.grades} />
</div>
<div className={styles.dividerContainer}>
<Divider />
</div>
</ListSubheader>
);

return (
<ul className={styles.noStartPadding}>
{instructorHeader}
{sections.map((secData, offset) => (
<SectionInfo
secIdx={startIdx + offset}
courseCardId={courseCardId}
sectionData={secData}
addInstructorLabel={offset === 0}
isLastSection={offset === endIdx - 1}
key={secData.section.id}
/>
))}
</ul>
);
};

export default ProfessorGroup;
@@ -1,20 +1,17 @@
import {
ListSubheader, ListItemText, Divider, Typography, Checkbox, ListItem, ListItemIcon,
ListItemText, Divider, Typography, Checkbox, ListItem, ListItemIcon,
} from '@material-ui/core';
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { toggleSelected } from '../../../../../../redux/actions/courseCards';
import { SectionSelected } from '../../../../../../types/CourseCardOptions';
import Meeting from '../../../../../../types/Meeting';
import formatMeetingDays from '../../../../../../utils/formatMeetingDays';
import { formatTime } from '../../../../../../utils/timeUtil';
import meetingBuilding from '../../../../../../utils/meetingBuilding';
import meetingsForSection from '../../../../../../utils/meetingsForSection';
import meetingTimeText from '../../../../../../utils/meetingTimeText';
import GradeDist from './GradeDist/GradeDist';
import MeetingTypeDisplay from './MeetingType/MeetingTypeDisplay';
import InstructionalMethodIcon from './InstructionalMethodIcon/InstructionalMethodIcon';
import * as styles from './SectionSelect.css';
import HonorsIcon from '../../../../../Icons/HonorsIcon/HonorsIcon';

interface SectionInfoProps {
sectionData: SectionSelected;
Expand All @@ -25,62 +22,54 @@ interface SectionInfoProps {
}

const SectionInfo: React.FC<SectionInfoProps> = ({
sectionData, courseCardId, secIdx, addInstructorLabel, isLastSection,
sectionData, courseCardId, secIdx, isLastSection,
}) => {
const { section, meetings, selected } = sectionData;
const dispatch = useDispatch();

const instructorLabel = addInstructorLabel
? (
<>
<ListSubheader disableGutters className={styles.listSubheaderDense}>
<div className={styles.listSubheaderContent}>
<div className={styles.nameHonorsIcon}>
{section.instructor.name}
{section.honors ? <HonorsIcon /> : null}
</div>
<GradeDist grades={section.grades} />
</div>
<div className={styles.dividerContainer}>
<Divider />
</div>
</ListSubheader>
</>
)
: null;
const formatMeetingDays = (meeting: Meeting): string => {
const DAYS_OF_WEEK = ['M', 'T', 'W', 'R', 'F', 'S', 'U'];
return meeting.meetingDays.reduce((acc, curr, idx) => (curr ? acc + DAYS_OF_WEEK[idx] : acc), '');
};

const getMeetingTimeText = (mtg: Meeting): string => {
if (mtg.startTimeHours === 0) {
// If the time is 00:00, then it's meeting time is not applicable
return 'N/A';
}

// Returns it in the format 12:00 - 1:00
return `${formatTime(mtg.startTimeHours, mtg.startTimeMinutes)}
- ${formatTime(mtg.endTimeHours, mtg.endTimeMinutes)}`;
};

// builds a div containing the section's number and available/max enrollment
const remainingSeats = section.maxEnrollment - section.currentEnrollment;
const remainingSeatsColor = remainingSeats > 0 ? 'black' : 'red';
// show section number and remaining seats if this is the first meeting for a section
const sectionHeader = (
<Typography className={styles.denseListItem} component="tr">
<td>
<span className={styles.sectionNameContainer}>
{section.sectionNum}
&nbsp;
<InstructionalMethodIcon instructionalMethod={section.instructionalMethod} />
</span>
</td>
<td
style={{ color: remainingSeatsColor, textAlign: 'right' }}
colSpan={3}
<Typography className={styles.denseListItem} component="div" style={{ display: 'flex', gridColumn: '1 / -1' }}>
<div style={{ flex: 1, display: 'flex' }}>
{section.sectionNum}
&nbsp;
<InstructionalMethodIcon instructionalMethod={section.instructionalMethod} />
</div>
<div
style={{ flex: 3, color: remainingSeatsColor, textAlign: 'right' }}
>
{`${remainingSeats}/${section.maxEnrollment} seats left`}
</td>
</div>
</Typography>
);

const renderMeeting = (mtg: Meeting, showSectionNum: boolean): JSX.Element => (
<React.Fragment key={mtg.id}>
{showSectionNum ? sectionHeader : null}
<Typography className={styles.denseListItem} color="textSecondary" component="tr">
<td>
<MeetingTypeDisplay meeting={mtg} />
</td>
<td>{meetingBuilding(mtg)}</td>
<td>{formatMeetingDays(mtg)}</td>
<td>{meetingTimeText(mtg)}</td>
<Typography className={`${styles.denseListItem} ${styles.meetingInfoWrapper}`} color="textSecondary" component="div">
<div><MeetingTypeDisplay meeting={mtg} /></div>
<div>{meetingBuilding(mtg)}</div>
<div className={styles.meetingDays}>{formatMeetingDays(mtg)}</div>
<div className={styles.meetingTime}>{getMeetingTimeText(mtg)}</div>
</Typography>
</React.Fragment>
);
Expand All @@ -91,10 +80,10 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
));

// makes a list of the meetings in this section, along with one checkbox for all of them
const sectionDetails = (
return (
<ListItem
onClick={(): void => { dispatch(toggleSelected(courseCardId, secIdx)); }}
className={styles.noExtraSpace}
className={`${styles.noExtraSpace} ${styles.indentForCheckbox}`}
dense
disableGutters
button
Expand All @@ -110,28 +99,13 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
/>
</ListItemIcon>
<ListItemText disableTypography className={styles.noExtraSpace}>
<table className={styles.sectionDetailsTable}>
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="20%" />
<col width="45%" />
</colgroup>
<tbody>
{meetingRows}
</tbody>
</table>
<div className={styles.sectionDetailsTable}>
{meetingRows}
</div>
{!isLastSection && <Divider className={styles.addBottomSpace} />}
</ListItemText>
</ListItem>
);

return (
<React.Fragment key={section.sectionNum}>
{instructorLabel}
{sectionDetails}
</React.Fragment>
);
};

const propsAreEqual = (
Expand Down
Expand Up @@ -12,7 +12,7 @@
}

.my-icon-button {
padding: 3px !important;
padding: 0px !important;
}

.dense-list-item:global(.MuiTypography-body1) {
Expand Down Expand Up @@ -40,6 +40,12 @@
min-height: 20px;
}

.name-honors-icon:global(.MuiButton-root) {
text-transform: unset;
color:rgb(97, 97, 97);
padding: 0;
}

.meeting-type {
border-bottom: 1px solid rgb(221, 221, 221);
}
Expand All @@ -54,10 +60,8 @@

.section-details-table {
width: 100%;
}

.section-details-table tr td:nth-child(2), .section-details-table tr td:nth-child(3) {
text-align: center;
display: grid;
grid-template-columns: 3fr 4fr 4fr 9fr;
}

/* The next 2 rules remove extra padding on ListItem components */
Expand All @@ -71,10 +75,68 @@
margin-top: 8px;
}

.indent-for-checkbox:global(.MuiListItem-root) {
padding-left: 14px;
}

.name-honors-icon :global(.MuiButton-label) {
justify-content: normal;
}

.no-start-padding {
padding-inline-start: 0;
}

.meeting-info-wrapper {
display: contents;
}

@media (max-width: 1600px) {
.section-details-table {
grid-template-columns: 1fr 1fr;
}
.meeting-info-wrapper > div:nth-child(2) {
text-align: right; /* applies to building in 2-column view */
}
.meeting-info-wrapper > div:nth-child(3) {
text-align: left; /* applies to days in 2-column view */
}
}

.meeting-info-wrapper:nth-child(2n) div {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.meeting-info-wrapper:nth-child(2n) div {
.meeting-info-wrapper:nth-child(even) div {

I don't actually care if you make this change since this is perfectly readable but just so you know, there are nth-child(even) and nth-child(odd) selectors

background: #50000020;
}

/* Removes background from 1-meeting sections */
.meeting-info-wrapper:nth-child(2):last-child {
background: unset;
}

.meeting-info-wrapper > div {
padding: 2px;
}

.meeting-info-wrapper > div:first-child {
text-align: left;
}

.meeting-info-wrapper > div:last-child {
text-align: right;
white-space: nowrap; /* ensures that meeting times don't wrap */
}

.meeting-time {
text-align: center;
}

.meeting-days {
text-align: center;
}

.less-checkbox-padding:global(.MuiCheckbox-root) {
margin: 4px 4px 4px 0;
padding: 0px;
}
.section-name-container {
display: flex;
align-items: center;
Expand Down
Expand Up @@ -8,11 +8,21 @@ declare namespace SectionSelectCssNamespace {
denseListItem: string;
"divider-container": string;
dividerContainer: string;
"indent-for-checkbox": string;
indentForCheckbox: string;
"less-checkbox-padding": string;
lessCheckboxPadding: string;
"list-subheader-content": string;
"list-subheader-dense": string;
listSubheaderContent: string;
listSubheaderDense: string;
"meeting-days": string;
"meeting-info-wrapper": string;
"meeting-time": string;
"meeting-type": string;
meetingDays: string;
meetingInfoWrapper: string;
meetingTime: string;
meetingType: string;
"my-icon-button": string;
"my-list-item-icon": string;
Expand Down