Commit f55ae767 authored by David Haynes's avatar David Haynes 🙆
Browse files

Finished Massive Refactor

- renaming and sticking with idiomatic react/redux
parent ea240443
export const ADD_SECTION: string = '[Schedule] ADD_SECTION';
export const REMOVE_SECTION: string = '[Schedule] REMOVE_SECTION';
export const ADD_ENTRY: string = '[Schedule] ADD_ENTRY';
export const REMOVE_ENTRY: string = '[Schedule] REMOVE_ENTRY';
import { Section } from '../../util/section';
import { ADD_SECTION, REMOVE_SECTION } from './schedule.action-types';
import { CourseEntry } from '../../util/CourseEntry';
import { ADD_ENTRY, REMOVE_ENTRY } from './schedule.action-types';
export interface ScheduleAction {
type: string; // What action is to be performed
section: Section; // The section that is being added/removed
entry: CourseEntry; // The section that is being added/removed
}
/**
* Add a section to the Schedule
* @param section The section that is to be added
*/
export const addSection = (section: Section): ScheduleAction => ({
type: ADD_SECTION,
section: section,
export const addEntry = (entry: CourseEntry): ScheduleAction => ({
type: ADD_ENTRY,
entry: entry,
});
/**
* Remove a section from the Schedule
* @param section The section that is to be removed
*/
export const removeSection = (section: Section): ScheduleAction => ({
type: REMOVE_SECTION,
section: section,
export const removeEntry = (entry: CourseEntry): ScheduleAction => ({
type: REMOVE_ENTRY,
entry: entry,
});
export const SET_SEARCH_SECTIONS: string = '[Search] SET_SECTIONS';
export const SET_SEARCH_RESULTS: string = '[Search] SET_SEARCH_RESULTS';
import { Section } from '../../util/section';
import { SET_SEARCH_SECTIONS } from './search.action-types';
import { CourseEntry } from '../../util/CourseEntry';
import { SET_SEARCH_RESULTS } from './search.action-types';
export interface SearchAction {
type: string;
sections: Section[];
searchResults: CourseEntry[];
}
export const searchSections = (crn: string) => async (dispatch: any) => {
export const searchCourses = (crn: string) => async (dispatch: any) => {
const response = await fetch(`http://localhost:3000/api/search?crn=${crn}`);
const object = await response.json();
const section: Section[] = [
const results: CourseEntry[] = [
{
id: object.id,
name: object.name,
......@@ -25,7 +25,7 @@ export const searchSections = (crn: string) => async (dispatch: any) => {
];
dispatch({
type: SET_SEARCH_SECTIONS,
sections: section,
type: SET_SEARCH_RESULTS,
searchResults: results,
});
};
import * as React from 'react';
import Schedule from '../containers/Schedule';
import Search from '../containers/Search';
import Header from './Header';
......@@ -6,6 +7,7 @@ const App = () => (
<div>
<Header />
<Search />
<Schedule />
</div>
);
......
import * as React from 'react';
import { Section } from '../util/section';
import { CourseEntry } from '../util/CourseEntry';
interface Props {
sections: Section[];
buttonText: string;
selectSectionCallback?: (section: Section) => void;
courses: CourseEntry[];
selectCourseCallback?: (entry: CourseEntry) => void;
}
export default class SectionList extends React.Component<Props, any> {
export default class ScheduleList extends React.Component<Props, any> {
constructor(props: Props) {
super(props);
}
......@@ -18,7 +17,7 @@ export default class SectionList extends React.Component<Props, any> {
<tbody>
<tr>
<th>Course</th>
<th>Section Name</th>
<th>course Name</th>
<th>CRN</th>
<th>Days</th>
<th>Instructor</th>
......@@ -26,37 +25,35 @@ export default class SectionList extends React.Component<Props, any> {
<th>Time</th>
<th />
</tr>
{this.renderRowsForSections(this.props.sections)}
{this.renderRowsForCourses(this.props.courses)}
</tbody>
</table>
);
}
renderRowsForSections(sections: Section[]): JSX.Element[] {
return sections.map(section => {
renderRowsForCourses(courses: CourseEntry[]): JSX.Element[] {
return courses.map(course => {
return (
<tr key={section.id}>
<td>{section.name}</td>
<td>{section.title}</td>
<td>{section.crn}</td>
<td>{section.days}</td>
<td>{section.instructor}</td>
<td>{section.location}</td>
<td>{[section.startTime, section.endTime].join(' - ')}</td>
{this.renderSelectSectionColumn(section.crn)}
<tr key={course.id}>
<td>{course.name}</td>
<td>{course.title}</td>
<td>{course.crn}</td>
<td>{course.days}</td>
<td>{course.instructor}</td>
<td>{course.location}</td>
<td>{[course.startTime, course.endTime].join(' - ')}</td>
{this.renderSelectCourseColumn(course.crn)}
</tr>
);
});
}
renderSelectSectionColumn(rowCRN: string): JSX.Element {
if (this.props.selectSectionCallback) {
const sectionWithCRN = this.getSectionWithCRN(rowCRN);
renderSelectCourseColumn(rowCRN: string): JSX.Element {
if (this.props.selectCourseCallback) {
const courseWithCRN = this.getcourseWithCRN(rowCRN);
return (
<td>
<button onClick={() => this.props.selectSectionCallback(sectionWithCRN)}>
{this.props.buttonText}
</button>
<button onClick={() => this.props.selectCourseCallback(courseWithCRN)}>"Add to schedule"</button>
</td>
);
} else {
......@@ -64,7 +61,7 @@ export default class SectionList extends React.Component<Props, any> {
}
}
getSectionWithCRN(crn: string): Section {
return this.props.sections.find(section => section.crn === crn);
getcourseWithCRN(crn: string): CourseEntry {
return this.props.courses.find(course => course.crn === crn);
}
}
import * as React from 'react';
import { CourseEntry } from '../util/CourseEntry';
import ScheduleList from './ScheduleList';
interface SearchRootProps {
schedule: CourseEntry[];
removeEntry: (CourseEntry: CourseEntry) => any;
}
// const generateSchedule = (schedule: CourseEntry[]): void => {
// const crns = schedule.map(entry => entry.crn);
// postData(ENDPOINTS.generateCalendar, crns)
// .then(response => response.text())
// .then(icalText => downloadCalendar(icalText));
// };
const ScheduleRoot = ({ schedule, removeEntry }: SearchRootProps) => (
<div>
<ScheduleList courses={schedule} selectCourseCallback={removeEntry} />
{/* <button onClick={generateSchedule}>Generate Schedule</button> */}
</div>
);
export default ScheduleRoot;
import * as React from 'react';
import SearchBar from '../components/SearchBar';
import SectionList from '../components/SectionList';
import { Section } from '../util/section';
interface SearchProps {
searchedSections: Section[];
searchSections: (crn: string) => any;
addSection: (section: Section) => any;
}
const Search = ({ searchedSections, searchSections, addSection }: SearchProps) => (
<div>
<SearchBar onSearch={searchSections} />
<SectionList sections={searchedSections} buttonText="Add to schedule" selectSectionCallback={addSection} />
</div>
);
export default Search;
import * as React from 'react';
interface Props {
onSearch: (searchTerm: string) => void;
onSearch: (crn: string) => void;
}
interface State {
......
import * as React from 'react';
import SearchBar from '../components/SearchBar';
import SectionList from '../components/ScheduleList';
import { CourseEntry } from '../util/CourseEntry';
interface SearchRootProps {
searchResults: CourseEntry[];
searchCourses: (crn: string) => void;
addEntry: (entry: CourseEntry) => void;
}
const SearchRoot = ({ searchResults, searchCourses, addEntry }: SearchRootProps) => (
<div>
<SearchBar onSearch={searchCourses} />
<SectionList courses={searchResults} selectCourseCallback={addEntry} />
</div>
);
export default SearchRoot;
import * as React from 'react';
import { connect } from 'react-redux';
import { removeSection } from '../actions/schedule/schedule.actions';
import SectionList from '../components/SectionList';
import { State } from '../reducers';
import { Section } from '../util/section';
import { downloadCalendar, ENDPOINTS, postData } from '../util/utilities';
interface AppProps {
schedule: Section[];
removeSection: (section: Section) => any;
}
class App extends React.Component<AppProps> {
constructor(props: AppProps) {
super(props);
}
render() {
return (
<div>
<h2>Your schedule</h2>
<SectionList
sections={this.props.schedule}
buttonText="Remove from schedule"
selectSectionCallback={this.props.removeSection}
/>
<button onClick={this.generateSchedule}>Generate Schedule</button>
</div>
);
}
// TODO: Only view logic should be in the component.
generateSchedule = () => {
const crns = this.props.schedule.map(section => section.crn);
postData(ENDPOINTS.generateCalendar, crns)
.then(response => response.text())
.then(icalText => downloadCalendar(icalText));
};
}
/**
* Take the current schedule in the store and map it to the <App /> component.
* @param state The current Redux store state.
*/
const mapStateToProps = (state: State) => ({
schedule: state.schedule,
});
/**
* Ensure that the Redux state is passed into the component.
*/
export default connect(
mapStateToProps,
{ removeSection }
)(App);
import { connect } from 'react-redux';
import { addEntry, removeEntry } from '../actions/schedule/schedule.actions';
import ScheduleRoot from '../components/ScheduleRoot';
import { State } from '../reducers';
const mapStateToProps = (state: State) => ({
schedule: state.schedule,
});
export default connect(
mapStateToProps,
{ addEntry, removeEntry }
)(ScheduleRoot);
import { connect } from 'react-redux';
import { addSection } from '../actions/schedule/schedule.actions';
import { searchSections } from '../actions/search/search.actions';
import Search from '../components/Search';
import { addEntry } from '../actions/schedule/schedule.actions';
import { searchCourses } from '../actions/search/search.actions';
import SearchRoot from '../components/SearchRoot';
import { State } from '../reducers';
const mapStateToProps = (state: State) => ({
searchedSections: state.search.searchedSections,
searchResults: state.searchResults,
});
export default connect(
mapStateToProps,
{ searchSections, addSection }
)(Search);
{ searchCourses, addEntry }
)(SearchRoot);
......@@ -8,7 +8,7 @@ import { search, SearchState } from './search.reducer';
export interface State {
schedule: ScheduleState;
search: SearchState;
searchResults: SearchState;
}
/**
......@@ -16,9 +16,7 @@ export interface State {
*/
const defaultState: State = {
schedule: [],
search: {
searchedSections: [],
},
searchResults: [],
};
/**
......@@ -28,5 +26,5 @@ const defaultState: State = {
*/
export const allReducers = (state: State = defaultState, action: any) => ({
schedule: schedule(state.schedule, action),
search: search(state.search, action),
searchResults: search(state.searchResults, action),
});
......@@ -4,18 +4,18 @@
* Perform operations on the current state of the "Schedule" list in the store
* and return a new definition of the state.
*/
import { ADD_SECTION, REMOVE_SECTION } from '../actions/schedule/schedule.action-types';
import { ADD_ENTRY, REMOVE_ENTRY } from '../actions/schedule/schedule.action-types';
import { ScheduleAction } from '../actions/schedule/schedule.actions';
import { Section } from '../util/section';
import { CourseEntry } from '../util/CourseEntry';
export type ScheduleState = Section[];
export type ScheduleState = CourseEntry[];
export const schedule = (state: ScheduleState = [], action: ScheduleAction) => {
switch (action.type) {
case ADD_SECTION:
return state.findIndex(s => s.crn === action.section.crn) === -1 ? [...state, action.section] : state;
case REMOVE_SECTION:
return state.filter(s => s.crn !== action.section.crn);
case ADD_ENTRY:
return state.findIndex(s => s.crn === action.entry.crn) === -1 ? [...state, action.entry] : state;
case REMOVE_ENTRY:
return state.filter(s => s.crn !== action.entry.crn);
default:
return state;
}
......
......@@ -4,20 +4,16 @@
* Perform operations on the current state of the "search.searchedSections"
* list in the store and return a new definition of the state.
*/
import { SET_SEARCH_SECTIONS } from '../actions/search/search.action-types';
import { SET_SEARCH_RESULTS } from '../actions/search/search.action-types';
import { SearchAction } from '../actions/search/search.actions';
import { Section } from '../util/section';
import { CourseEntry } from '../util/CourseEntry';
export interface SearchState {
searchedSections: Section[];
}
export type SearchState = CourseEntry[];
export const search = (state: SearchState = { searchedSections: [] }, action: SearchAction) => {
export const search = (state: SearchState = [], action: SearchAction): SearchState => {
switch (action.type) {
case SET_SEARCH_SECTIONS:
return Object.assign({}, state, {
searchedSections: [...action.sections],
});
case SET_SEARCH_RESULTS:
return action.searchResults;
default:
return state;
}
......
/**
* util/section.ts
* util/CourseEntry.ts
*
* Common object interface for all "Section"s.
*/
export interface Section {
export interface CourseEntry {
id: number;
name: string;
title: string;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment