Skip to content

website: Implement random course picker button #4039

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
45 changes: 45 additions & 0 deletions website/src/views/components/searchkit/RandomPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
PageSizeAccessor,
PaginationAccessor,
SearchkitComponent,
SearchkitComponentProps,
} from 'searchkit';
import { ModuleCode } from 'types/modules';

export type RandomPickerProps = {
getRandomModuleCode: () => Promise<ModuleCode>;
};

interface SearchkitRandomPickerProps extends SearchkitComponentProps {
buttonComponent: React.ElementType<RandomPickerProps>;
}

type State = Record<string, never>;

export default class RandomPicker extends SearchkitComponent<SearchkitRandomPickerProps, State> {
paginationAccessor() {
return this.accessor as PaginationAccessor;
}

// eslint-disable-next-line class-methods-use-this
override defineAccessor() {
return new PaginationAccessor('p');
}

getRandomModuleCode = async (): Promise<ModuleCode> => {
const sizeAccessor = this.searchkit.getAccessorByType(PageSizeAccessor) as PageSizeAccessor;
const totalPages = Math.ceil(this.searchkit.getHitsCount() / sizeAccessor.getSize());
const randomPage = Math.floor(Math.random() * totalPages);

this.paginationAccessor().state = this.paginationAccessor().state.setValue(randomPage);
const { hits } = (await this.searchkit.performSearch()).results.hits;
const randomHit = hits[Math.floor(Math.random() * hits.length)];
/* eslint-disable no-underscore-dangle */
return randomHit._source.moduleCode;
};

override render() {
const { buttonComponent: Button } = this.props;
return <Button getRandomModuleCode={this.getRandomModuleCode} />;
}
}
4 changes: 4 additions & 0 deletions website/src/views/modules/ModuleFinderSidebar.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
@import "~styles/utils/modules-entry";

.moduleFilters {
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;

.filterHeader {
Expand All @@ -23,6 +26,7 @@

@include media-breakpoint-up(md) {
max-width: 16rem;
padding-right: 0.5rem;
}

@include media-breakpoint-down(sm) {
Expand Down
7 changes: 6 additions & 1 deletion website/src/views/modules/ModuleFinderSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ import SideMenu, { OPEN_MENU_LABEL } from 'views/components/SideMenu';
import FilterContainer from 'views/components/filters/FilterContainer';
import CheckboxItem from 'views/components/filters/CheckboxItem';
import DropdownListFilters from 'views/components/filters/DropdownListFilters';
import RandomPicker from 'views/components/searchkit/RandomPicker';

import { getSemesterTimetableLessons } from 'selectors/timetables';
import { getSemesterModules } from 'utils/timetables';
import { getModuleSemesterData } from 'utils/modules';
import { notNull } from 'types/utils';

import config from 'config';
import styles from './ModuleFinderSidebar.scss';
import ChecklistFilter, { FilterItem } from '../components/filters/ChecklistFilter';
import ModuleRandomButton from './ModuleRandomButton';

import styles from './ModuleFinderSidebar.scss';

const RESET_FILTER_OPTIONS = { filter: true };

Expand Down Expand Up @@ -217,6 +220,8 @@ const ModuleFinderSidebar: React.FC = () => {
containerComponent={FilterContainer}
itemComponent={CheckboxItem}
/>

<RandomPicker buttonComponent={ModuleRandomButton} />
</div>
</SideMenu>
);
Expand Down
11 changes: 11 additions & 0 deletions website/src/views/modules/ModuleRandomButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@import '~styles/utils/modules-entry';

.moduleRandomButton {
composes: btn btn-secondary btn-svg from global;
flex-grow: 1;
margin-bottom: 2rem;

@include media-breakpoint-down(md) {
margin: 0 1rem;
}
}
31 changes: 31 additions & 0 deletions website/src/views/modules/ModuleRandomButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Shuffle } from 'react-feather';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';

import { openNotification } from 'actions/app';
import { RandomPickerProps } from 'views/components/searchkit/RandomPicker';
import { modulePage } from 'views/routes/paths';

import styles from './ModuleRandomButton.scss';

const ModuleRandomButton: React.FC<RandomPickerProps> = ({ getRandomModuleCode }) => {
const history = useHistory();
const dispatch = useDispatch();

const handleClick = () => {
getRandomModuleCode()
.then((moduleCode) => history.push(modulePage(moduleCode)))
.catch(() => {
dispatch(openNotification('Failed to fetch a random course.'));
});
};

return (
<button type="button" className={styles.moduleRandomButton} onClick={handleClick}>
<Shuffle className="svg svg-small" />
Random Course
</button>
);
};

export default ModuleRandomButton;