Commit d0a6debc authored by Andrew Hrdy's avatar Andrew Hrdy
Browse files

Merge master into new facility card.

parents 626b36f0 27ca9d4e
......@@ -9,7 +9,7 @@
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.40.1/mapbox-gl.css' rel='stylesheet' />
<!--
......@@ -17,11 +17,11 @@
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
Unlike "/favicon.png" or "favicon.png", "%PUBLIC_URL%/favicon.png" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>What's Open</title>
</head>
<body>
<noscript>
......
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "What's Open",
"name": "What's Open",
"icons": [
{
"src": "favicon.ico",
"src": "favicon.png",
"sizes": "192x192",
"type": "image/png"
}
],
"related_applications": [
{
"platform": "play",
"id": "srct.whatsopen"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
}
\ No newline at end of file
export const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR';
export const TOGGLE_SIDEBAR_MAP = 'TOGGLE_SIDEBAR_MAP';
export const SET_FACILITIES = 'SET_FACILITIES';
export const GET_FACILITIES = 'GET_FACILITIES';
export const SET_SIDEBAR = 'SET_SIDEBAR';
......
......@@ -18,53 +18,20 @@ export const getFacilities = () => dispatch => {
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', {
const request = new Request('https://api.srct.gmu.edu/whatsopen/v2/facilities/', {
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]]
return fetch(request)
.then(res => {
if (res.status < 200 || res.status >= 300) {
throw new Error(res.statusText);
}
/**
* 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)));
return res.json();
})
.then(json => {
dispatch(setFacilities(JSON.stringify(json)));
});
};
......
......@@ -4,6 +4,7 @@ import {
SET_SEARCH_TERM,
SET_SIDEBAR,
TOGGLE_SIDEBAR,
TOGGLE_SIDEBAR_MAP,
SET_ALL_FAVORITES
} from './action-types';
......@@ -11,6 +12,10 @@ export const toggleSidebar = () => ({
type: TOGGLE_SIDEBAR,
});
export const toggleSidebarMap = () => ({
type: TOGGLE_SIDEBAR_MAP,
});
export const setSidebar = (facility) => ({
type: SET_SIDEBAR,
facility,
......
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles} from 'material-ui/styles';
import {withStyles, withTheme} from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { compose } from 'redux';
import { withTheme } from 'material-ui/styles';
import classNames from 'classnames';
function customAppBar({ classes, handleMenuClick, isOpen}) {
return (
<div>
<AppBar position="absolute" >
<Toolbar>
{/* <IconButton onClick={handleMenuClick} color="contrast" aria-label="Menu">
<MenuIcon />
</IconButton> */}
<Typography type="title" color="inherit" className={classes.title}>
Title
</Typography>
{/* <Button color="contrast">Login</Button> */}
</Toolbar>
</AppBar>
</div>
);
import {compose} from 'redux';
import classNames from 'classnames'
class CustomAppBar extends React.Component {
constructor(props) {
super();
this.state = {
isAppBarExpanded: false
};
this.toggleExpand = this.toggleExpand.bind(this);
}
toggleExpand() {
this.setState({
isAppBarExpanded: !this.state.isAppBarExpanded
})
};
render() {
return (<div>
<AppBar position="absolute" className={this.props.classes.appBar}>
<Toolbar className={this.props.classes.toolBar}>
<img src={require('../images/SRCT_square.svg')} className={this.props.classes.navbarLogo}/>
<Typography type="title"
className={classNames(this.props.classes.title, this.props.classes.navbarTextColor)}>
What's Open
</Typography>
<IconButton onClick={this.toggleExpand} aria-label="Menu"
className={classNames(this.props.classes.appBarMenuButton, this.props.classes.navbarTextColor)}>
<MenuIcon/>
</IconButton>
<div
className={classNames(this.props.classes.linkContainer, !this.state.isAppBarExpanded && this.props.classes.hide)}>
<Button
className={classNames(this.props.classes.appBarLinkButton, this.props.classes.navbarTextColor)}>
About
</Button>
<Button
className={classNames(this.props.classes.appBarLinkButton, this.props.classes.navbarTextColor)}>
Feedback
</Button>
</div>
</Toolbar>
</AppBar>
</div>);
};
}
customAppBar.propTypes = {
classes: PropTypes.object.isRequired,
handleMenuClick: PropTypes.func.isRequired,
CustomAppBar.propTypes = {
classes: PropTypes.object.isRequired,
};
const styleSheet = {
title: {
marginRight: 'auto',
},
drawerOpen: {
paddingLeft: 400,
transition: 'padding 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms',
height: '100%'
const styleSheet = {
'@media screen and (max-width: 600px)': {
appBarLinkButton: {
display: 'block',
padding: 0,
textAlign: 'left'
},
appBarMenuButton: {
display: 'inherit !important'
},
hide: {
maxHeight: '0 !important',
overflow: 'hidden'
},
toolBar: {
flexWrap: 'wrap',
},
linkContainer: {
display: 'block',
flexBasis: '100%',
transition: 'ease-in-out 2s'
}
},
appBar: {
backgroundColor: 'white',
boxShadow: '0px 1px 0px 0px rgba(0, 0, 0, 0.2)'
},
title: {
marginRight: 'auto',
},
appBarMenuButton: {
display: 'none'
},
navbarLogo: {
width: '30px',
height: '30px',
marginRight: '5px'
},
drawerClosed: {
paddingLeft: 0,
transition: 'padding 255ms cubic-bezier(0, 0, 0.2, 1) 0ms',
height: '100%'
navbarTextColor: {
color: '#354052'
},
};
export default compose(withStyles(styleSheet),withTheme)(customAppBar);
export default compose(withStyles(styleSheet), withTheme)(CustomAppBar);
......@@ -2,28 +2,29 @@ import React from 'react'
import {withStyles} from 'material-ui/styles';
import FacilityCard from '../containers/FacilityCard'
import Grid from 'material-ui/Grid';
import fuzzysearch from 'fuzzysearch';
const CardContainer = ({classes, searchTerm,facilities,favorites}) => {
const CardContainer = ({classes, searchTerm, facilities}) => {
const filterCards = (facility) => {
const name = facility.facility_name.toLowerCase()
return name.includes(searchTerm.toLowerCase())
}
return (
<Grid container className={classes.root} spacing={24} justify={'center'} align={'flex-end'}>
{facilities.filter(filterCards).map(item =>{
return(<Grid key={item.slug} item>
<FacilityCard facility={item}/>
</Grid>)
{facilities.filter(filterCards).map(item => {
return (
<Grid key={item.slug} item>
<FacilityCard facility={item}/>
</Grid>
)
})}
</Grid>
)
}
const styleSheet = {
root:{
root: {
// backgroundColor:'red',
margin:0,
margin: 0,
width: '100%',
display: 'flex',
flexWrap: 'wrap',
......
......@@ -93,11 +93,11 @@ class FacilitiesMap extends React.Component {
render (){
const {facilities,facility,classes} = this.props
const {facilities,facility,classes,isMapOpen} = this.props
const {position,positionReady,fitBounds,maxBounds,mappedRoute,fitBoundsOptions} = this.state
console.log(fitBounds)
return(
<div>
<div className={classes.mapContainer} style={{'height': isMapOpen ? '400px' : 0}}>
<Map
onStyleLoad={(map,e)=>{
map.addControl(new mapboxgl.GeolocateControl({
......@@ -110,8 +110,8 @@ class FacilitiesMap extends React.Component {
style="mapbox://styles/mapbox/streets-v9"
movingMethod={'easeTo'}
containerStyle={{
height: "400px",
width: "400px"
height: "100%",
width: "100%"
}}
fitBounds={fitBounds}
fitBoundsOptions={fitBoundsOptions}
......@@ -135,8 +135,10 @@ class FacilitiesMap extends React.Component {
}
}
const styleSheet = {
}
mapContainer: {
transition: '250ms ease-in-out'
}
};
export default withStyles(styleSheet)(FacilitiesMap)
......
......@@ -3,206 +3,27 @@ import {withStyles} from 'material-ui/styles';
import Typography from 'material-ui/Typography';
import DoneIcon from 'material-ui-icons/Done';
import CloseIcon from 'material-ui-icons/Close';
import AlarmIcon from 'material-ui-icons/Alarm';
import {blue, green, orange, red} from 'material-ui/colors'
import {green, red} from 'material-ui/colors'
import FacilityUtils from '../utils/facilityUtils';
const FacilityStatus = ({classes, facility}) => {
/**
* 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 (entryTillOpen > 0 && (!timeTillOpen || (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;
// return 6000
};
/**
* Determines how long until a facility open / closes.
*
* @param facility
* @returns {number} The time in minutes until a facility open / closes. -1 if 24/7.
*/
const timeTill = facility => {
let schedule = facility.main_schedule;
const curDateTime = new Date();
for (let i = 0; i < facility.special_schedules; i++) {
const specialSchedule = facility.special_schedules[i];
const startInParts = specialSchedule.valid_start.split('-');
const endInParts = specialSchedule.valid_end.split('-');
const startDate = new Date(startInParts[0], startInParts[1], startInParts[2]);
const endDate = new Date(endInParts[0], endInParts[1], endInParts[2]);
/*
TODO: Possible issues may arise by only checking date and not time
valid_start and valid_end come as dates without times. If a facility,
such as Southside, closes are 2 am the day a special schedule is in use,
a user checking between 12am and 2am would receive incorrect information.
Possible solutions:
- API valid_start and valid_end are in the format yyyy-mm-dd-hh-mm-ss (preferred)
- Iterate over all schedules, find active schedule for current day of week,
then add the time to startDate and endDate before the date checking.
*/
if (startDate < curDateTime && endDate > curDateTime) {
schedule = specialSchedule;
break;
}
}
//Facility is open 24/7
if (schedule.twenty_four_hours) {
return -1;
}
return facility.isOpen ? calcTimeTillClose(schedule) : calcTimeTillOpen(schedule);
};
/**
* Generates information about the facility's status.
*
* @param isOpen True if the facility is open, otherwise false.
* @param time The time in minutes until the facility opens / closes.
* @param facility The facility to get generate information about.
* @returns {{label: string, color: *, icon: *}} Information about the facility.
*/
const generateStatusInfo = (isOpen, time) => {
const generateStatusInfo = facility => {
let label;
let color;
let icon;
if (time === -1) {
if (FacilityUtils.getFacilityActiveSchedule(facility).twenty_four_hours) {
label = 'OPEN 24/7';
color = green[500];
icon = <DoneIcon/>;
} else if (isOpen) {
} else if (FacilityUtils.isFacilityOpen(facility)) {
label = 'OPEN';
color = green[500];
icon = <DoneIcon/>;
......@@ -219,7 +40,7 @@ const FacilityStatus = ({classes, facility}) => {
}
};
const statusInfo = generateStatusInfo(facility.isOpen, timeTill(facility));
const statusInfo = generateStatusInfo(facility);
return (
<Typography type={'caption'} className={classes.statusText} style={{color: statusInfo.color}}>
......
......@@ -6,50 +6,57 @@ import Typography from 'material-ui/Typography'
import Divider from 'material-ui/Divider';
import TextwTitle from '../components/TextwTitle'
import FacilitiesMap from '../components/FacilitiesMap'
import classNames from 'classnames'
import classNames from 'classnames';
import Button from 'material-ui/Button';
const Sidebar = ({classes,facility,isSidebarOpen,facilities}) => {
const Sidebar = ({classes,facility,isSidebarOpen,isSidebarMapOpen,toggleSidebarMap,facilities}) => {
const removeBrackets = (name) => {
if(typeof(name) === "undefined"){
return ""
}
}
const openBracket = name.indexOf('[')