Commit 5039a3a2 authored by Mattias J Duffy's avatar Mattias J Duffy
Browse files

merging the two searchbar and facility card branches

parents 017ce650 c3abe938
......@@ -2,4 +2,6 @@ export const TOGGLE_DRAWER = 'TOGGLE_DRAWER';
export const SET_FACILITIES = 'SET_FACILITIES';
export const GET_FACILITIES = 'GET_FACILITIES';
export const SET_SIDEBAR = 'SET_SIDEBAR';
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
\ No newline at end of file
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
export const ADD_FAVORITE_FACILITY = 'ADD_FAVORITE_FACILITY';
export const REMOVE_FAVORITE_FACILITY = 'REMOVE_FAVORITE_FACILITY';
\ No newline at end of file
import { CALL_API,SET_FACILITIES,GET_FACILITIES } from './action-types'
import {GET_FACILITIES, SET_FACILITIES} from './action-types'
export const apiTest = () =>{
return (dispatch) =>{
export const apiTest = () => {
return (dispatch) => {
return fetch('/api/facilities',{'method':'get'}).then((res)=>{
return res.json()
},(error)=>{console.log(error)}).then(json =>{
console.log(json[0])
})
return fetch('/api/facilities', {'method': 'get'}).then((res) => {
return res.json()
}, (error) => {
console.log(error)
}).then(json => {
console.log(json[0])
})
}
}
};
export const getFacilities = () => dispatch => {
// var url = new URL('https://localhost:3000/api/facilities')
// url.searchParams.append('format','json')
dispatch({
type:GET_FACILITIES
})
const request = new Request('https://api.srct.gmu.edu/whatsopen/v2/facilities/', {
method: 'GET',
})
console.log(request)
return fetch(request).then((res)=>{
if (res.status < 200 || res.status >= 300) {
throw new Error(res.statusText);
}
return res.json()
}).then((json) => {
dispatch(setFacilities(JSON.stringify(json)))
})
}
type: GET_FACILITIES
});
//A request for all the open facilities
const requestOpen = new Request('https://api.srct.gmu.edu/whatsopen/v2/facilities/?open_now=True&format=json', {
method: 'GET'
});
//A request for all the closed facilities
const requestClosed = new Request('https://api.srct.gmu.edu/whatsopen/v2/facilities/?closed_now=True&format=json', {
method: 'GET'
});
/**
* Merges the two promises (returned from the fetch operations) in order to dispatch only when both the
* open and closed requests are completed.
*/
return Promise.all([
fetch(requestOpen)
.then(res => {
if (res.status < 200 || res.status >= 300) {
throw new Error(res.statusText);
}
return res.json();
}),
fetch(requestClosed)
.then(res => {
if (res.status < 200 || res.status >= 300) {
throw new Error(res.statusText);
}
return res.json();
})])
.then(facilitiesByStatus => { //facilitiesByStatus is in the format: [[openFacilities], [closedFacilities]]
/**
* Iterates over the open and closed facility arrays and adds the isOpen property which drives styling
* in the view.
*/
facilitiesByStatus[0].forEach(openFacility => {
openFacility.isOpen = true;
});
facilitiesByStatus[1].forEach(closedFacility => {
closedFacility.isOpen = false;
});
//Merges the two facility status arrays and sorts by alphabetical order.
const allFacilities = facilitiesByStatus[0].concat(facilitiesByStatus[1])
.sort((a, b) => a.facility_name > b.facility_name ? 1 : -1);
dispatch(setFacilities(JSON.stringify(allFacilities)));
});
};
export const setFacilities = (facilities) => {
localStorage.setItem('facilities',facilities)
localStorage.setItem('facilities', facilities);
return {
type:SET_FACILITIES,
facilities:JSON.parse(facilities)
type: SET_FACILITIES,
facilities: JSON.parse(facilities)
}
}
\ No newline at end of file
};
\ No newline at end of file
<<<<<<< HEAD
import { TOGGLE_DRAWER,SET_SIDEBAR,SET_SEARCH_TERM } from './action-types';
=======
import {
ADD_FAVORITE_FACILITY,
REMOVE_FAVORITE_FACILITY,
SET_SEARCH_TERM,
SET_SIDEBAR,
TOGGLE_DRAWER
} from './action-types';
>>>>>>> c3abe9389ebadac8437111d3fa63f4ac278bb160
export const toggleDrawer = () => ({
type:TOGGLE_DRAWER,
export const toggleDrawer = () => ({
type: TOGGLE_DRAWER,
});
export const setSidebar = (facility) => ({
type:SET_SIDEBAR,
type: SET_SIDEBAR,
facility,
})
});
<<<<<<< HEAD
export const setSearchTerm = (term) => {
return {
type:SET_SEARCH_TERM,
term,
}
}
=======
export const setSearchTerm = (term) => ({
type: SET_SEARCH_TERM,
term,
});
export const addFavoriteFacility = slug => ({
type: ADD_FAVORITE_FACILITY,
slug
});
export const removeFavoriteFacility = slug => ({
type: REMOVE_FAVORITE_FACILITY,
slug
});
>>>>>>> c3abe9389ebadac8437111d3fa63f4ac278bb160
......@@ -8,6 +8,8 @@ import {compose} from 'redux'
import {connect} from 'react-redux'
import {setSidebar} from '../actions/ui'
import FacilityStatus from './FacilityStatus';
import FavoriteButton from './FavoriteButton';
import {removeBrackets} from '../utils/nameUtils';
import {
amber,
......@@ -31,6 +33,8 @@ import {
yellow
} from 'material-ui/colors';
const materialColors = [red, pink, purple, deepPurple, indigo, blue, lightBlue, cyan, teal, green,
lightGreen, lime, yellow, amber, orange, deepOrange, brown, grey, blueGrey];
const FacilityCard = ({classes, facility, setSidebar}) => {
......@@ -38,17 +42,6 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
setSidebar(facility)
};
const removeBrackets = (name) => {
if (typeof(name) === "undefined") {
return ""
}
const openBracket = name.indexOf('[');
if (openBracket !== -1) {
return name.substring(0, openBracket)
}
return name
};
/**
* Gets the the initials for a facility name. The initials will be the first character of the first and last word
* of the facility. Initial lengths range from 1-2.
......@@ -62,10 +55,11 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
let words = removeBrackets(name).split(/[ -]+/); //TODO: Add case change to the regex (ex. IndAroma should be IA, not I).
/*
Words that start with ( must be removed.
TODO: Probably want this to be a regex test and remove any useless word / symbol (ex. the, and, &, etc.)
Words that are empty or start with ( must be removed.
Example: 'Recreation and Athletic Complex (RAC)' will result in the initials 'R(' without the filter.
*/
words = words.filter(word => !word.startsWith("("));
words = words.filter(word => word && !word.startsWith("("));
if (words.length === 0) {
return "";
......@@ -77,8 +71,6 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
return words[0].substring(0, 1).toUpperCase() + words[words.length - 1].substring(0, 1).toUpperCase();
};
const materialColors = [red, pink, purple, deepPurple, indigo, blue, lightBlue, cyan, teal, green,
lightGreen, lime, yellow, amber, orange, deepOrange, brown, grey, blueGrey];
/**
* Gets a material color based off the facility's slug.
......@@ -115,8 +107,9 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
};
return (
<Card onClick={handleClick} className={classes.root}>
<Card onClick={handleClick} className={classes.root} raised>
{/*<CardMedia className={classes.media} image={require('../images/chipotleLogo.png')}/>*/}
<FavoriteButton facility={facility}/>
<CardContent className={classes.cardContent}>
<Grid container>
<Grid item xs={4} className={classes.avatarContainer}>
......@@ -125,15 +118,21 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
</Grid>
<Grid item xs={8}>
<Typography type={'title'} align={'center'} className={classes.title} noWrap>
{removeBrackets(facility.facility_name)}
</Typography>
<FacilityStatus facility={facility}/>
<Typography type={'caption'} align={'center'} className={classes.location} noWrap>
{removeBrackets(facility.facility_location.building)}
</Typography>
<Grid container direction={'column'}>
<Grid item className={classes.smallGridItemSpacing}>
<Typography type={'title'} align={'center'} className={classes.title} noWrap>
{removeBrackets(facility.facility_name)}
</Typography>
</Grid>
<Grid item className={classes.smallGridItemSpacing}>
<FacilityStatus facility={facility}/>
</Grid>
<Grid item className={classes.smallGridItemSpacing}>
<Typography type={'caption'} align={'center'} className={classes.location} noWrap>
{removeBrackets(facility.facility_location.building)}
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</CardContent>
......@@ -143,7 +142,14 @@ const FacilityCard = ({classes, facility, setSidebar}) => {
const styleSheet = {
root: {
width: 250,
height: 100,
borderRadius: '5px',
position: 'relative'
},
cardContent: {
paddingBottom: '16px !important'
},
smallGridItemSpacing: {
padding: '2px !important'
},
/**media: {
flex: 1,
......@@ -165,7 +171,7 @@ const styleSheet = {
},
location: {
fontFamily: 'Nunito'
}
},
};
export default compose(connect(null, {setSidebar}), withStyles(styleSheet))(FacilityCard);
\ No newline at end of file
......@@ -5,18 +5,183 @@ import Typography from 'material-ui/Typography';
import {green, red} from 'material-ui/colors'
const FacilityStatus = ({classes, facility}) => {
const isOpen = facility => {
//TODO
return true;
/**
* Helper function to calculate the number days between dayFrom and dayTo.
*
* @param dayFrom The index of the start day (0-6)
* @param dayTo The index of the end day (0-6)
* @returns {number} The number of days between the two.
*/
const daysTill = (dayFrom, dayTo) => {
let days = 0;
while (dayFrom !== dayTo) {
days++;
if (++dayFrom > 6) {
dayFrom = 0;
}
}
return days;
};
/**
* Calculates the time until the facility is open.
* This function does not work correctly if the facility is open.
*
* @param schedule The active schedule for the facility.
* @return {number} The time (in minutes) until the facility opens.
*/
const calcTimeTillOpen = schedule => {
const curDateTime = new Date();
//Converts the JS day of week (0 is sunday), to the API day of week (0 is monday).
const dayOfWeek = [6, 0, 1, 2, 3, 4, 5][curDateTime.getDay()];
let timeTillOpen = null; //The time till the facility is open (in ms)
//Iterates over each schedule, setting the timeTillOpen to be the smallest it can be.
for (let i = 0; i < schedule.open_times.length; i++) {
const scheduleEntry = schedule.open_times[i];
let daysTillOpen = daysTill(dayOfWeek, scheduleEntry.start_day);
const timeInParts = scheduleEntry.start_time.split(':');
const openTime = new Date(
curDateTime.getFullYear(),
curDateTime.getMonth(),
curDateTime.getDate() + daysTillOpen,
timeInParts[0],
timeInParts[1],
timeInParts[2]);
const entryTillOpen = openTime - curDateTime;
if (!timeTillOpen || (entryTillOpen > 0 && entryTillOpen < timeTillOpen)) {
timeTillOpen = entryTillOpen;
}
}
return timeTillOpen / 60000; //Set to minutes
};
/**
* Calculates the time until the facility is closed.
* This function does not work correctly if the facility is closed.
*
* @param schedule The active schedule for the facility.
* @returns {number} The time (in minutes) until the facility closes.
*/
const calcTimeTillClose = schedule => {
const curDateTime = new Date();
//Converts the JS day of week (0 is sunday), to the API day of week (0 is monday).
const dayOfWeek = [6, 0, 1, 2, 3, 4, 5][curDateTime.getDay()];
let currentEntry;
//Finds the active entry for the schedule.
for (let i = 0; i < schedule.open_times.length; i++) {
const scheduleEntry = schedule.open_times[i];
const startTimeInParts = scheduleEntry.start_time.split(":");
const endTimeInParts = scheduleEntry.end_time.split(":");
/*
Only the times are being compared, therefore set the year, month, and date to 0.
*/
const curTime = new Date(0, 0, 0, curDateTime.getHours(), curDateTime.getMinutes(), curDateTime.getSeconds());
const startTime = new Date(0, 0, 0, startTimeInParts[0], startTimeInParts[1], startTimeInParts[2]);
const endTime = new Date(0, 0, 0, endTimeInParts[0], endTimeInParts[1], endTimeInParts[2]);
/*
First block accounts for entries where the end day is larger than the start day
ex. start day is Monday (0), end day is Tuesday (1)
Second block Accounts for entries where the end day is smaller than the start day
ex. start day is Sunday (6), end day is Monday (0)
*/
if ((scheduleEntry.start_day <= scheduleEntry.end_day &&
dayOfWeek >= scheduleEntry.start_day &&
dayOfWeek <= scheduleEntry.end_day) ||
(scheduleEntry.start_day > scheduleEntry.end_day &&
dayOfWeek >= scheduleEntry.start_day ||
dayOfWeek <= scheduleEntry.end_day)
) {
/*
This logic makes sure that if the current day is the start day / end day, the current time
is within the start / end times. This is important for cases such as Southside.
If this logic was not here, then example: if the day was Friday (4) at 18:00:00,
the schedule that starts on Thursday (3) at 07:00:00 and ends on Friday (4) at 02:00:00 would be
selected as it is the first to be iterated that either begins or ends on Friday (4)
This logic prevents this from occurring and instead selects starting on Friday (4) at 07:00:00 and
ending on Saturday (5) at 02:00:00
*/
if ((dayOfWeek === scheduleEntry.start_day && curTime > startTime) ||
(dayOfWeek === scheduleEntry.end_day && curTime <= endTime) ||
(dayOfWeek !== scheduleEntry.start_day && dayOfWeek !== scheduleEntry.end_day)) {
currentEntry = scheduleEntry;
break;
}
}
}
let daysTillClose = daysTill(dayOfWeek, currentEntry.end_day);
const timeInParts = currentEntry.end_time.split(':');
const closeTime = new Date(
curDateTime.getFullYear(),
curDateTime.getMonth(),
curDateTime.getDate() + daysTillClose,
timeInParts[0],
timeInParts[1],
timeInParts[2]);
return (closeTime - curDateTime) / 60000;
};
/**
* Calculates and formats the time until the facility open or closes.
*
* @param facility The facility to determine the message for.
* @returns {string} The formatted message of how long until a facility open or closes.
*/
const timeTillMessage = facility => {
const schedule = facility.main_schedule;
//TODO: Logic for "Special Schedule". I have no idea what this is.
//Facility is open 24/7
if (schedule.twenty_four_hours) {
return "24/7"
}
let time = facility.isOpen ? calcTimeTillClose(schedule) : calcTimeTillOpen(schedule);
//TODO: May want to use Math.ceil instead of Math.round
if (time < 60) { //Under one hour
const roundedMins = Math.round(time);
return `${roundedMins} ${roundedMins === 1 ? "min" : "mins"}`;
} else if (time < 1440) { //Under one day
const roundedHrs = Math.round(time / 60);
return `${roundedHrs} ${roundedHrs === 1 ? "hr" : "hrs"}`;
} else { //Over a day
const roundedDays = Math.round(time / 1440);
return `${roundedDays} ${roundedDays === 1 ? "day" : "days"}`;
}
};
return (
<Chip label={
<div>
<Typography type={'caption'} className={classes.isOpenText}>OPEN</Typography>
<Typography type={'caption'} className={classes.timeText}>~18 Hrs</Typography>
<Typography type={'caption'} className={classes.isOpenText}>
{facility.isOpen ? "OPEN" : "CLOSED"}
</Typography>
<Typography type={'caption'} className={classes.timeText}>
{timeTillMessage(facility)}
</Typography>
</div>
} className={classes.chip} style={{backgroundColor: isOpen(facility) ? green[500] : red[500]}}/>
} className={classes.chip} style={{backgroundColor: facility.isOpen ? green[500] : red[500]}}/>
)
};
const styleSheet = {
......
import React from 'react'
import {withStyles} from 'material-ui/styles';
import yellow from 'material-ui/colors/yellow';
import {compose} from 'redux'
import {connect} from 'react-redux'
import {addFavoriteFacility, removeFavoriteFacility} from "../actions/ui";
import StarIcon from 'material-ui-icons/Star';
import StarBorderIcon from 'material-ui-icons/StarBorder';
import PropTypes from 'prop-types';
class FavoriteButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
this.setState({
isFavorite: this.props.favorites.includes(this.props.favorites.slug)
});
}
componentWillReceiveProps(nextProps) {
this.setState({
isFavorite: nextProps.favorites.includes(nextProps.facility.slug)
});
}
handleClick() {
if (this.state.isFavorite) {
this.props.removeFavoriteFacility(this.props.facility.slug);
} else {
this.props.addFavoriteFacility(this.props.facility.slug);
}
}
render() {
if (this.state.isFavorite) {
return (<StarIcon onClick={this.handleClick} className={this.props.classes.star}/>);
}
return (<StarBorderIcon onClick={this.handleClick} className={this.props.classes.star}/>);
}
}
FavoriteButton.propTypes = {
classes: PropTypes.object.isRequired,
facility: PropTypes.object.isRequired,
favorites: PropTypes.array,
addFavoriteFacility: PropTypes.func.isRequired,
removeFavoriteFacility: PropTypes.func.isRequired,
};
const styleSheet = {
star: {
position: 'absolute',
top: '0px',
right: '0px',
color: yellow[600],
height: '20px',
width: '20px',
cursor: 'pointer',
}
};
const mapStateToProps = state => ({
favorites: state.ui.favorites
});
export default compose(connect(mapStateToProps, {
addFavoriteFacility,
removeFavoriteFacility
}), withStyles(styleSheet))(FavoriteButton);
\ No newline at end of file
......@@ -3,20 +3,20 @@ import { SET_FACILITIES,GET_FACILITIES } from '../actions/action-types'
const defaultState = {
isLoading:false,
data:[]
}
};
export const facilities = (state = defaultState, action) => {
switch(action.type){
case GET_FACILITIES:
return Object.assign({},state,{
isLoading:true,
})
});
case SET_FACILITIES:
return {
data:action.facilities,
isLoading:false
}
};
default:
return state
}
}
};
......@@ -6,7 +6,12 @@ import {facilities} from './api'
const reducers = combineReducers({
router:routerReducer,
ui,
<<<<<<< HEAD
facilities,
})
=======
facilities
});
>>>>>>> c3abe9389ebadac8437111d3fa63f4ac278bb160
expo