Commit 341eb442 authored by Andrew J Hrdy's avatar Andrew J Hrdy
Browse files

Merge branch 'facility-cards' into 'master'

Updated facility card design

See merge request !1
parents 24c1e999 5f9a4504
import React from 'react';
import {withStyles} from 'material-ui/styles';
import Avatar from 'material-ui/Avatar';
import Typography from 'material-ui/Typography';
import RestaurantIcon from 'material-ui-icons/Restaurant';
import StoreIcon from 'material-ui-icons/Store';
import LocalCafeIcon from 'material-ui-icons/LocalCafe';
import LocalPrintShopIcon from 'material-ui-icons/LocalPrintshop';
import LocalPostOfficeIcon from 'material-ui-icons/LocalPostOffice';
import FitnessCenterIcon from 'material-ui-icons/FitnessCenter';
import ShoppingCartIcon from 'material-ui-icons/ShoppingCart'
import {red, blue, brown, grey, teal, deepOrange, lime} from 'material-ui/colors';
const FacilityCategory = ({classes, category}) => {
const generateAvatar = () => {
let color;
let icon;
/*
TODO: May not want to hardcode the id's. Can be dynamically retrieved from /api/categories.
this wouldn't be of any use unless the API returns something to indicate the icon / color.
*/
/*
Proposed Category Types:
dining hall
convenience store
cafe
restaurant
food truck ???
athletic
mailroom
print services
retail
school offices
student centers
*/
switch (category.id) {
case 1: //Dining Hall
case 2: //Restaurant
case 5: //Take out dining hall
color = red[400];
icon = <RestaurantIcon className={classes.categoryIcon}/>;
break;
case 3: //Convenience Store
color = blue[500];
icon = <StoreIcon className={classes.categoryIcon}/>;
break;
case 4: //Cafe
color = brown[500];
icon = <LocalCafeIcon className={classes.categoryIcon}/>;
break;
case 6: //Athletic Facility
color = teal[500];
icon = <FitnessCenterIcon className={classes.categoryIcon}/>;
break;
case 7: //TODO: Print Services - NOT IN API
color = grey[500];
icon = <LocalPrintShopIcon className={classes.categoryIcon} />;
break;
case 8: //TODO Mailroom - NOT IN API
color = deepOrange[500];
icon = <LocalPostOfficeIcon className={classes.categoryIcon}/>;
break;
default:
color = lime[500];
icon = <ShoppingCartIcon className={classes.categoryIcon}/>
}
return (
<Avatar className={classes.avatar} style={{backgroundColor: color}}>
{icon}
</Avatar>
)
};
return (
<div className={classes.categoryWrapper}>
{generateAvatar()}
<Typography type={'body1'} noWrap>
{category.name}
</Typography>
</div>
)
};
const styleSheet = {
categoryWrapper: {
display: 'flex',
alignItems: 'center'
},
categoryIcon: {
width: '14px !important',
height: '14px !important',
padding: '4px !important',
},
avatar: {
width: 'auto !important',
height: 'auto !important',
marginRight: '8px'
},
};
export default withStyles(styleSheet)(FacilityCategory);
\ No newline at end of file
import React from 'react';
import {withStyles} from 'material-ui/styles';
import Chip from 'material-ui/Chip';
import Typography from 'material-ui/Typography';
import {green, red} from 'material-ui/colors'
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'
const FacilityStatus = ({classes, facility}) => {
......@@ -56,7 +58,7 @@ const FacilityStatus = ({classes, facility}) => {
timeInParts[2]);
const entryTillOpen = openTime - curDateTime;
if (!timeTillOpen || (entryTillOpen > 0 && entryTillOpen < timeTillOpen)) {
if (entryTillOpen > 0 && (!timeTillOpen || (entryTillOpen < timeTillOpen))) {
timeTillOpen = entryTillOpen;
}
}
......@@ -142,67 +144,102 @@ const FacilityStatus = ({classes, facility}) => {
};
/**
* Calculates and formats the time until the facility open or closes.
* Determines how long until a facility open / closes.
*
* @param facility The facility to determine the message for.
* @returns {string} The formatted message of how long until a facility open or closes.
* @param facility
* @returns {number} The time in minutes until a facility open / closes. -1 if 24/7.
*/
const timeTillMessage = facility => {
const schedule = facility.main_schedule;
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('-');
//TODO: Logic for "Special Schedule". I have no idea what this is.
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 "24/7"
return -1;
}
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 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.
* @returns {{label: string, color: *, icon: *}} Information about the facility.
*/
const generateStatusInfo = (isOpen, time) => {
let label;
let color;
let icon;
if (time === -1) {
label = 'OPEN 24/7';
color = green[500];
icon = <DoneIcon/>;
} else if (isOpen) {
label = 'OPEN';
color = green[500];
icon = <DoneIcon/>;
}else {
label = 'CLOSED';
color = red[500];
icon = <CloseIcon/>
}
return {
label: label,
color: color,
icon: icon,
}
};
const statusInfo = generateStatusInfo(facility.isOpen, timeTill(facility));
return (
<Chip label={
<div>
<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: facility.isOpen ? green[500] : red[500]}}/>
<Typography type={'caption'} className={classes.statusText} style={{color: statusInfo.color}}>
{statusInfo.icon}
{statusInfo.label}
</Typography>
)
};
const styleSheet = {
statusText: {
display: 'flex',
alignItems: 'center'
},
chip: {
margin: 'auto',
height: '24px',
borderRadius: '5px',
height: '28px',
borderRadius: '4px',
},
isOpenText: {
borderRight: '1px solid white',
paddingRight: '5px',
color: 'white',
fontFamily: 'Nunito',
display: 'inline'
},
timeText: {
paddingLeft: '5px',
color: 'white',
fontFamily: 'Nunito',
display: 'inline'
display: 'inline',
}
};
......
import React from 'react'
import {withStyles} from 'material-ui/styles';
import yellow from 'material-ui/colors/yellow';
import StarIcon from 'material-ui-icons/Star';
import StarBorderIcon from 'material-ui-icons/StarBorder';
import pink from 'material-ui/colors/pink';
import FavoriteBorderIcon from 'material-ui-icons/FavoriteBorder';
import FavoriteIcon from 'material-ui-icons/Favorite';
import PropTypes from 'prop-types';
class FavoriteButton extends React.Component {
......@@ -25,10 +25,10 @@ class FavoriteButton extends React.Component {
render() {
if (this.props.isFavorite) {
return (<StarIcon onClick={this.handleClick} className={this.props.classes.star}/>);
return (<FavoriteIcon onClick={this.handleClick} className={this.props.classes.heart}/>);
}
return (<StarBorderIcon onClick={this.handleClick} className={this.props.classes.star}/>);
return (<FavoriteBorderIcon onClick={this.handleClick} className={this.props.classes.heart}/>);
}
}
......@@ -41,14 +41,15 @@ FavoriteButton.propTypes = {
};
const styleSheet = {
star: {
heart: {
position: 'absolute',
top: '0px',
right: '0px',
color: yellow[600],
height: '20px',
width: '20px',
height: '24px',
width: '24px',
padding: '5px',
cursor: 'pointer',
color: pink[500]
}
};
......
import React from 'react'
import {withStyles} from 'material-ui/styles';
import Card, {CardContent} from 'material-ui/Card';
import Card, {CardActions, CardContent, CardMedia} from 'material-ui/Card';
import Typography from 'material-ui/Typography';
import Grid from 'material-ui/Grid';
import Avatar from 'material-ui/Avatar';
import {compose} from 'redux'
import {connect} from 'react-redux'
import {addFavoriteFacility, removeFavoriteFacility, setSidebar} from '../actions/ui'
import FacilityStatus from '../components/FacilityStatus';
import FavoriteButton from '../components/FavoriteButton';
import FacilityCategory from '../components/FacilityCategory';
import {compose} from 'redux';
import {connect} from 'react-redux';
import {addFavoriteFacility, removeFavoriteFacility, setSidebar} from '../actions/ui';
import DirectionsWalkIcon from 'material-ui-icons/DirectionsWalk';
import LocationOnIcon from 'material-ui-icons/LocationOn';
import {removeBrackets} from '../utils/nameUtils';
import classnames from 'classnames'
import {
amber,
......@@ -48,6 +51,7 @@ const FacilityCard = ({classes, facility, favorites, addFavoriteFacility, remove
*
* @param name The facility name to find the initials for.
* @returns {string} The initials.
* @deprecated
*/
const getInitials = name => {
//TODO: May want to allow initials to be more than 2 characters or use a different strategy to decide which characters to use.
......@@ -77,6 +81,7 @@ const FacilityCard = ({classes, facility, favorites, addFavoriteFacility, remove
*
* @param slug The slug of the facility to generate the material color from.
* @return {string} The color code (in hex format) of a material color.
* @deprecated
*/
const materialColorFromSlug = slug => {
......@@ -106,37 +111,56 @@ const FacilityCard = ({classes, facility, favorites, addFavoriteFacility, remove
return materialColors[Math.abs(hash) % 19][((Math.abs(hash) % 7) + 3) * 100];
};
/**
* By adding this property to an element, the text will not exceed 2 lines. On webkit browsers,
* -webkit-line-clamp will show ellipsis. This checks to see if the browser is webkit and uses
* an appropriate class.
*/
const twoLineEllipsis = CSS.supports('-webkit-line-clamp', 2) ? classes.twoLineEllipsisWebkit : classes.twoLineEllipsis;
return (
<Card onClick={handleClick} className={classes.root} raised>
{/*<CardMedia className={classes.media} image={require('../images/chipotleLogo.png')}/>*/}
<CardMedia className={classes.media}
image={'https://gmucampus.files.wordpress.com/2010/09/00sothside2.jpg'}/>
<div className={classes.logoContainer}>
<CardMedia className={classes.logo}
image={'https://upload.wikimedia.org/wikipedia/en/d/d3/Starbucks_Corporation_Logo_2011.svg'}/>
</div>
<FavoriteButton facility={facility} isFavorite={favorites.includes(facility.slug)}
addFavoriteFacility={addFavoriteFacility} removeFavoriteFacility={removeFavoriteFacility}/>
<CardContent className={classes.cardContent}>
<Grid container>
<Grid item xs={4} className={classes.avatarContainer}>
<Avatar className={classes.avatar}
style={{backgroundColor: materialColorFromSlug(facility.slug)}}>{getInitials(facility.facility_name)}</Avatar>
<Grid container align={'center'} direction={'column'} className={classes.smallGridContainerSpacing}>
<Grid item className={classes.smallGridItemSpacing}>
<Typography type={'title'} align={'center'} className={twoLineEllipsis}>
{removeBrackets(facility.facility_name)}
</Typography>
</Grid>
<Grid item xs={8}>
<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 item className={classes.smallGridItemSpacing}>
<FacilityCategory category={facility.facility_category} />
</Grid>
</Grid>
</CardContent>
<CardActions>
<Grid container justify={'space-around'}>
<Grid item className={classes.extraInfoWrapper}>
<FacilityStatus facility={facility}/>
</Grid>
<Grid item className={classes.extraInfoWrapper}>
<Typography type={'caption'}>
<LocationOnIcon/>
</Typography>
<Typography type={'caption'} align={'center'} className={twoLineEllipsis}>
{facility.facility_location.building}
</Typography>
</Grid>
</Grid>
</CardActions>
</Card>
)
};
......@@ -147,32 +171,51 @@ const styleSheet = {
position: 'relative'
},
cardContent: {
paddingBottom: '16px !important'
padding: '8px 4px 0 4px !important'
},
smallGridContainerSpacing: {
margin: '-2px -8px !important'
},
smallGridItemSpacing: {
padding: '2px !important'
padding: '3px 8px !important'
},
/**media: {
media: {
flex: 1,
width: 200,
height: 100,
resizeMode: 'cover',
},**/
avatarContainer: {
display: 'flex',
height: '115px',
},
logoContainer: {
width: '100px',
height: '100px',
margin: 'auto',
marginTop: '-60px',
borderRadius: '90px',
border: '5px solid white',
},
avatar: {
logo: {
width: '100px',
height: '100px',
margin: 'auto',
width: '50px',
height: '50px'
borderRadius: '90px',
boxShadow: '0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12)',
},
title: { //TODO: Should the fonts be added here or in the muitheme (index.js)?
fontFamily: 'Nunito',
fontWeight: 'Bold'
extraInfoWrapper: {
display: 'flex',
alignItems: 'center',
maxWidth: '50%'
},
location: {
fontFamily: 'Nunito'
twoLineEllipsis: {
position: 'relative',
lineHeight: '1em',
maxHeight: '2em',
overflow: 'hidden',
},
twoLineEllipsisWebkit: {
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}
};
const mapStateToProps = state => ({
......
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import {applyMiddleware, compose, createStore} from 'redux';
import './index.css';
import Layout from './containers/Layout';
import registerServiceWorker from './registerServiceWorker';
import createHistory from 'history/createBrowserHistory'
import { ConnectedRouter, routerMiddleware} from 'react-router-redux'
import { Provider } from 'react-redux';
import {ConnectedRouter, routerMiddleware} from 'react-router-redux'
import {Provider} from 'react-redux';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers/index';
import { MuiThemeProvider,createMuiTheme,createPalette } from 'material-ui/styles';
import blue from 'material-ui/colors/blue';
// import fullWhite from 'material-ui/colors/common';
// import grey from 'material-ui/colors/grey';
import amber from 'material-ui/colors/amber';
import red from 'material-ui/colors/red';
import green from 'material-ui/colors/green';
import {MuiThemeProvider} from 'material-ui/styles';
import theme from './theme';
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory()
const history = createHistory();
const extension = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
if(extension){
const enhance = compose(
applyMiddleware(ReduxThunk,routerMiddleware(history))
,extension)
let enhance;
if (extension) {
enhance = compose(
applyMiddleware(ReduxThunk, routerMiddleware(history))
, extension);
} else {
enhance = compose(
applyMiddleware(ReduxThunk, routerMiddleware(history)));
}
const enhance = compose(
applyMiddleware(ReduxThunk,routerMiddleware(history))
,extension)
const store = createStore(reducers,enhance)
const theme = createMuiTheme({
palette: {primary:blue,secondary:green,warn:amber,error:red,type:'light'}
});
const store = createStore(reducers, enhance);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<MuiThemeProvider theme={theme}>
<Layout />
<Layout/>
</MuiThemeProvider>
</ConnectedRouter>
</Provider>
......
This diff is collapsed.
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