Commit 550c12ff authored by Andrew Hrdy's avatar Andrew Hrdy
Browse files

WIP: Started work on updating mobile

parent 03afc1e8
...@@ -9424,6 +9424,14 @@ ...@@ -9424,6 +9424,14 @@
} }
} }
}, },
"react-device-detect": {
"version": "1.11.14",
"resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-1.11.14.tgz",
"integrity": "sha512-WSjch241xI+rXHVtJaSYxNUT2WAykzfJgMI2Hg9xjNNTlIZdJu/fmWf4iedNH7qzFq+JaJ6fDJu3mrKFLerKBw==",
"requires": {
"ua-parser-js": "^0.7.20"
}
},
"react-dom": { "react-dom": {
"version": "16.12.0", "version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz",
...@@ -12018,6 +12026,11 @@ ...@@ -12018,6 +12026,11 @@
"typescript-compare": "^0.0.2" "typescript-compare": "^0.0.2"
} }
}, },
"ua-parser-js": {
"version": "0.7.21",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
},
"uglify-js": { "uglify-js": {
"version": "3.3.28", "version": "3.3.28",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz",
......
...@@ -11,52 +11,16 @@ class CardContainer extends React.Component<CardContainerProps> { ...@@ -11,52 +11,16 @@ class CardContainer extends React.Component<CardContainerProps> {
super(props); super(props);
} }
/**
* A filtering function for facilities. Returns true
* if the facility should be shown, otherwise false.
*
* Note: Filtering is done based on the current search term.
*
* @memberof CardContainer
*/
filterCards = (facility: IFacility): boolean => {
if (facility.facility_location.campus_region.toLowerCase() !== this.props.campusRegion.toLowerCase()) {
return false;
}
const lSearchTerm = this.props.searchTerm.toLowerCase();
const facilityName = facility.facility_name.toLowerCase();
const facilityLocation = facility.facility_location.building.toLowerCase();
const facilityCategory = facility.facility_category.name.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const facilityTags = facility.facility_product_tags;
const friendlyName = facility.facility_location.friendly_building.toLowerCase();
facilityTags.forEach((tag) => {
return tag.toLowerCase();
});
const index = facilityTags.findIndex((tag) => {
return tag.includes(lSearchTerm);
});
const hasTag = index !== -1;
return facilityName.includes(lSearchTerm) || facilityLocation.includes(lSearchTerm) ||
facilityCategory.includes(lSearchTerm) || hasTag || friendlyName.includes(lSearchTerm);
}
render() { render() {
const {facilities} = this.props; const {facilities, showFavoriteIcons} = this.props;
return ( return (
<Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}> <Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}>
{facilities.filter(this.filterCards).map((item) => { {facilities.map(item => (
return ( <Grid key={item.slug} item={true}>
<Grid key={item.slug} item={true}> <FacilityCard facility={item} showFavoriteIcon={showFavoriteIcons} />
<FacilityCard facility={item} facilities={facilities}/> </Grid>
</Grid> ))}
);
})}
</Grid> </Grid>
); );
} }
...@@ -64,8 +28,7 @@ class CardContainer extends React.Component<CardContainerProps> { ...@@ -64,8 +28,7 @@ class CardContainer extends React.Component<CardContainerProps> {
export interface CardContainerProps { export interface CardContainerProps {
facilities: IFacility[]; facilities: IFacility[];
searchTerm: string; showFavoriteIcons: boolean;
campusRegion: CampusRegion;
} }
export default CardContainer; export default CardContainer;
\ No newline at end of file
import * as React from 'react';
import { removeBrackets } from '../utils/nameUtils';
import { IFacility } from '../models/facility.model';
import WeekHours from './WeekHours';
import Dialog from '@material-ui/core/Dialog';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import LocationOnIcon from '@material-ui/icons/LocationOn';
class FacilityDialog extends React.Component<FacilityDialogProps> {
constructor(props: FacilityDialogProps) {
super(props);
}
render() {
const {facility, facilities, isOpen, onClose} = this.props;
return (
<Dialog classes={{
root: 'fd-dialog-root',
paper: 'fd-dialog-paper'
}} open={isOpen} onClose={onClose}>
<IconButton className={'fd-close-btn'} onClick={onClose}>
<CloseIcon />
</IconButton>
<Grid container={true} className={'fd-container'} justify={'center'}>
<Grid item={true} className={'fd-header-container'}>
<Grid container={true} className={'fd-header'}>
<Grid item={true}>
<Avatar className={'fd-avatar'} src={facility.logo} />
</Grid>
<Grid item={true} className={'fd-header-text-container'}>
<Typography className={'fd-header-text'} variant={'h5'}>
{removeBrackets(facility.facility_name)}
</Typography>
</Grid>
</Grid>
</Grid>
<Grid item={true} className={'fd-location-wrapper'}>
<Typography variant={'caption'}>
<LocationOnIcon />
</Typography>
<Typography title={facility.facility_location.building} variant={'caption'} align={'center'}>
{facility.facility_location.building}
</Typography>
</Grid>
<Grid item={true} className={'fd-week-hours'}>
<WeekHours facility={facility} />
</Grid>
</Grid>
</Dialog>
);
}
}
export interface FacilityDialogProps {
facility: IFacility;
facilities: IFacility[];
isOpen: boolean;
onClose: () => void;
}
export default FacilityDialog;
\ No newline at end of file
import * as React from 'react';
import * as classNames from 'classnames';
import AlertContainer from '../containers/AlertContainer';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
const MobileAppBar = () => {
return (
<div>
<AppBar position="absolute" className={classNames('mobile-app-bar')}>
<Toolbar>
<div className={'app-bar-logo-name'}>
<img src={require('../images/SRCT_square.svg')} className={'app-bar-logo'}/>
<Typography variant="h6" className={classNames('app-bar-title', 'app-bar-text-color')}>
What's Open
</Typography>
</div>
<div className={'app-bar-right-section'}>
<div className={'app-bar-alert-container'}>
<AlertContainer />
</div>
</div>
</Toolbar>
</AppBar>
</div>
);
};
export default MobileAppBar;
\ No newline at end of file
...@@ -14,7 +14,6 @@ import Divider from '@material-ui/core/Divider'; ...@@ -14,7 +14,6 @@ import Divider from '@material-ui/core/Divider';
import CloseIcon from '@material-ui/icons/Close'; import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import FacilityDialog from './FacilityDialog'; import FacilityDialog from './FacilityDialog';
import { History } from 'history';
class Sidebar extends React.Component<SidebarProps> { class Sidebar extends React.Component<SidebarProps> {
...@@ -23,16 +22,14 @@ class Sidebar extends React.Component<SidebarProps> { ...@@ -23,16 +22,14 @@ class Sidebar extends React.Component<SidebarProps> {
} }
render() { render() {
const {facility, facilities, history} = this.props; const {facility, closeSidebar} = this.props;
return ( return (
<div className={'sidebar-container'}> <div className={'sidebar-container'}>
<Paper className={'sidebar-root'}> <Paper className={'sidebar-root'}>
<Link to="/"> <IconButton onClick={closeSidebar} className={'sidebar-close-btn'}>
<IconButton className={'sidebar-close-btn'}> <CloseIcon />
<CloseIcon /> </IconButton>
</IconButton>
</Link>
<div className={'sidebar-row1'}> <div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility.logo} /> <Avatar className={'sidebar-avatar'} src={facility.logo} />
...@@ -56,17 +53,14 @@ class Sidebar extends React.Component<SidebarProps> { ...@@ -56,17 +53,14 @@ class Sidebar extends React.Component<SidebarProps> {
</div> </div>
</div> </div>
</Paper> </Paper>
<FacilityDialog facility={facility} facilities={facilities} isOpen={true} onClose={() => history.push('/')} />
</div> </div>
); );
} }
} }
export interface SidebarProps { export interface SidebarProps {
history: History;
facility: IFacility; facility: IFacility;
facilities: IFacility[]; closeSidebar: () => void;
} }
export default Sidebar; export default Sidebar;
\ No newline at end of file
...@@ -36,7 +36,7 @@ export class WeekHours extends React.Component<WeekHoursProps> { ...@@ -36,7 +36,7 @@ export class WeekHours extends React.Component<WeekHoursProps> {
for (let i = 0; i < todaysHours.length; i++) { for (let i = 0; i < todaysHours.length; i++) {
output[index] = ( output[index] = (
<Grid container={true} spacing={0} key={this.props.facility.slug + index} className="week-hours-row"> <Grid container={true} spacing={0} key={this.props.facility.slug + index} className="week-hours-row">
<Grid item={true} xs={2}> <Grid item={true} xs={4}>
<Typography variant={'body1'}>{weekDays[dayOfWeek]}</Typography> <Typography variant={'body1'}>{weekDays[dayOfWeek]}</Typography>
</Grid> </Grid>
......
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { IFacility, CampusRegion } from '../models/facility.model'; import { IFacility, CampusRegion } from '../models/facility.model';
import { IAlert } from '../models/alert.model';
import { ApplicationState } from '../store'; import { ApplicationState } from '../store';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { fetchFacilities } from '../store/facility/facility.actions'; import { fetchFacilities } from '../store/facility/facility.actions';
import { fetchAlerts } from '../store/alert/alert.actions'; import { fetchAlerts } from '../store/alert/alert.actions';
import { Route, withRouter, RouteComponentProps } from 'react-router-dom'; import facilityUtils from '../utils/facilityUtils';
import CardContainer from '../components/CardContainer'; import CardContainer from '../components/CardContainer';
import AppBar from '../components/AppBar'; import AppBar from '../components/AppBar';
import Sidebar from '../components/Sidebar'; import Sidebar from '../components/Sidebar';
import { setSelectedFacility } from '../store/ui/ui.actions';
class Layout extends React.Component<LayoutProps> { class DesktopLayout extends React.Component<DesktopLayoutProps> {
constructor(props: LayoutProps) { constructor(props: DesktopLayoutProps) {
super(props); super(props);
} }
...@@ -23,7 +23,7 @@ class Layout extends React.Component<LayoutProps> { ...@@ -23,7 +23,7 @@ class Layout extends React.Component<LayoutProps> {
} }
render() { render() {
const {facilities, searchTerm, campusRegion, history} = this.props; const {facilities, selectedFacility, searchTerm, campusRegion, closeSidebar} = this.props;
return ( return (
<div className={'layout-root'}> <div className={'layout-root'}>
...@@ -32,41 +32,40 @@ class Layout extends React.Component<LayoutProps> { ...@@ -32,41 +32,40 @@ class Layout extends React.Component<LayoutProps> {
<div className={'layout-container'}> <div className={'layout-container'}>
<div className={'layout-main-content'}> <div className={'layout-main-content'}>
<div className={'layout-card-container'}> <div className={'layout-card-container'}>
<CardContainer searchTerm={searchTerm} <CardContainer facilities={facilityUtils.filterFacilities(facilities, searchTerm, campusRegion)} showFavoriteIcons={true} />
campusRegion={campusRegion} facilities={facilities}/>
</div> </div>
</div> </div>
<Route path="/facility/:slug" render={(props) => { {
return <Sidebar facility={facilities.find(facility => facility.slug === props.match.params.slug)} facilities={facilities} history={history} />; selectedFacility !== '' && <Sidebar facility={facilities.find(facility => facility.slug === selectedFacility)} closeSidebar={closeSidebar} />
}} /> }
</div> </div>
</div> </div>
); );
} }
} }
export interface LayoutProps extends RouteComponentProps { export interface DesktopLayoutProps {
facilities: IFacility[]; facilities: IFacility[];
alerts: IAlert[]; selectedFacility: string;
favorites: string[];
searchTerm: string; searchTerm: string;
campusRegion: CampusRegion; campusRegion: CampusRegion;
fetchFacilities: () => any; fetchFacilities: () => any;
fetchAlerts: () => any; fetchAlerts: () => any;
closeSidebar: () => void;
} }
const mapStateToProps = (state: ApplicationState) => ({ const mapStateToProps = (state: ApplicationState) => ({
facilities: state.facilities.facilities, facilities: state.facilities.facilities,
alerts: state.alerts.alerts, selectedFacility: state.ui.selectedFacility,
favorites: state.ui.favorites,
searchTerm: state.ui.search.searchTerm, searchTerm: state.ui.search.searchTerm,
campusRegion: state.ui.search.campusRegion campusRegion: state.ui.search.campusRegion
}); });
const mapDispatchToProps = (dispatch: Dispatch) => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
fetchFacilities: () => dispatch(fetchFacilities()), fetchFacilities: () => dispatch(fetchFacilities()),
fetchAlerts: () => dispatch(fetchAlerts()) fetchAlerts: () => dispatch(fetchAlerts()),
closeSidebar: () => dispatch(setSelectedFacility(''))
}); });
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Layout)); export default connect(mapStateToProps, mapDispatchToProps)(DesktopLayout);
...@@ -6,9 +6,8 @@ import FacilityUtils from '../utils/facilityUtils'; ...@@ -6,9 +6,8 @@ import FacilityUtils from '../utils/facilityUtils';
import { IFacility } from '../models/facility.model'; import { IFacility } from '../models/facility.model';
import { ApplicationState } from '../store'; import { ApplicationState } from '../store';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { addFavoriteFacility, removeFavoriteFacility } from '../store/ui/ui.actions'; import { addFavoriteFacility, removeFavoriteFacility, setSelectedFacility } from '../store/ui/ui.actions';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { withRouter, RouteComponentProps } from 'react-router';
import { Location } from 'history'; import { Location } from 'history';
import FacilityStatus from '../components/FacilityStatus'; import FacilityStatus from '../components/FacilityStatus';
...@@ -31,13 +30,7 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState> ...@@ -31,13 +30,7 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
}; };
} }
getSlugFromLocation = (location: Location) => location.pathname.substring(10);
shouldComponentUpdate = (nextProps: FacilityCardProps) => { shouldComponentUpdate = (nextProps: FacilityCardProps) => {
if (this.isFacilitySelected() !== (this.getSlugFromLocation(nextProps.location) === this.props.facility.slug)) {
return true;
}
const dateModified = nextProps.facility.modified; const dateModified = nextProps.facility.modified;
if (dateModified !== this.state.modified) { if (dateModified !== this.state.modified) {
...@@ -51,10 +44,12 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState> ...@@ -51,10 +44,12 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
return false; return false;
} }
isFacilitySelected = (): boolean => this.getSlugFromLocation(this.props.location) === this.props.facility.slug; isFacilitySelected = (): boolean => this.props.selectedFacility === this.props.facility.slug;
selectFacility = () => this.props.selectFacility(this.isFacilitySelected() ? '' : this.props.facility.slug);
render() { render() {
const {facility, favorites, addFavoriteFacility, removeFavoriteFacility} = this.props; const {facility, favorites, showFavoriteIcon, addFavoriteFacility, removeFavoriteFacility} = this.props;
const dayOfWeek = [6, 0, 1, 2, 3, 4, 5][new Date().getDay()]; const dayOfWeek = [6, 0, 1, 2, 3, 4, 5][new Date().getDay()];
...@@ -79,68 +74,70 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState> ...@@ -79,68 +74,70 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
facility.facility_location.building; facility.facility_location.building;
return ( return (
<NavLink to={this.isFacilitySelected() ? '/' : `/facility/${facility.slug}`} style={{textDecoration: 'none', color: 'black'}}> <Card onClick={this.selectFacility} className={classNames('fc-root', this.isFacilitySelected() && 'fc-selected')}
<Card className={classNames('fc-root', this.isFacilitySelected() && 'fc-selected')} elevation={3}>
elevation={3}> <div className={'fc-logo-container'}>
<div className={'fc-logo-container'}> <img className={'fc-logo'}
<img className={'fc-logo'} alt={facility.slug} src={facility.logo} />
alt={facility.slug} src={facility.logo} /> </div>
</div>
{
<FavoriteButton slug={facility.slug} initialState={favorites.includes(facility.slug)} showFavoriteIcon && <FavoriteButton slug={facility.slug} initialState={favorites.includes(facility.slug)}
addFavoriteFacility={addFavoriteFacility} addFavoriteFacility={addFavoriteFacility}
removeFavoriteFacility={removeFavoriteFacility} /> removeFavoriteFacility={removeFavoriteFacility} />
}
<CardContent className={'fc-card-content'}> <CardContent className={'fc-card-content'}>
<Grid container={true} alignItems={'center'} direction={'column'}> <Grid container={true} alignItems={'center'} direction={'column'}>
<Grid item={true} <Grid item={true}
className={classNames('fc-small-grid-item-spacing', 'fc-ellipsis-container', 'fc-title-container')}> className={classNames('fc-small-grid-item-spacing', 'fc-ellipsis-container', 'fc-title-container')}>
<Typography variant={'subtitle1'} align={'center'} <Typography variant={'subtitle1'} align={'center'}
className={classNames('fc-title', 'fc-one-line-ellipsis')}> className={classNames('fc-title', 'fc-one-line-ellipsis')}>
{removeBrackets(facility.facility_name)} {removeBrackets(facility.facility_name)}
</Typography> </Typography>
</Grid>
<Grid item={true} className={'fc-small-grid-item-spacing'}>
<FacilityCategory category={facility.facility_category} />
</Grid>
<Grid item={true} className={'fc-small-grid-item-spacing fc-display-hours'}>
<Typography variant={'body1'}>
{`Today: ${getDisplayHours()}`}
</Typography>
</Grid>
</Grid> </Grid>
<Grid container={true} justify={'space-around'}> <Grid item={true} className={'fc-small-grid-item-spacing'}>
<Grid item={true} className={'fc-extra-info'}> <FacilityCategory category={facility.facility_category} />
<FacilityStatus facility={facility} /> </Grid>
</Grid>
<Grid item={true} className={'fc-small-grid-item-spacing fc-display-hours'}>
<Grid item={true} className={'fc-extra-info'}> <Typography variant={'body1'}>
<Typography variant={'caption'}> {`Today: ${getDisplayHours()}`}
<LocationOnIcon className={'fc-card-map-marker-icon'} /> </Typography>
</Typography> </Grid>
</Grid>
<Typography title={buildingName} variant={'caption'} align={'center'}
className={'fc-two-line-e