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

WIP: Started work on updating mobile

parent 03afc1e8
......@@ -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": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz",
......@@ -12018,6 +12026,11 @@
"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": {
"version": "3.3.28",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz",
......
......@@ -11,52 +11,16 @@ class CardContainer extends React.Component<CardContainerProps> {
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() {
const {facilities} = this.props;
const {facilities, showFavoriteIcons} = this.props;
return (
<Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}>
{facilities.filter(this.filterCards).map((item) => {
return (
<Grid key={item.slug} item={true}>
<FacilityCard facility={item} facilities={facilities}/>
</Grid>
);
})}
{facilities.map(item => (
<Grid key={item.slug} item={true}>
<FacilityCard facility={item} showFavoriteIcon={showFavoriteIcons} />
</Grid>
))}
</Grid>
);
}
......@@ -64,8 +28,7 @@ class CardContainer extends React.Component<CardContainerProps> {
export interface CardContainerProps {
facilities: IFacility[];
searchTerm: string;
campusRegion: CampusRegion;
showFavoriteIcons: boolean;
}
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';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import FacilityDialog from './FacilityDialog';
import { History } from 'history';
class Sidebar extends React.Component<SidebarProps> {
......@@ -23,16 +22,14 @@ class Sidebar extends React.Component<SidebarProps> {
}
render() {
const {facility, facilities, history} = this.props;
const {facility, closeSidebar} = this.props;
return (
<div className={'sidebar-container'}>
<Paper className={'sidebar-root'}>
<Link to="/">
<IconButton className={'sidebar-close-btn'}>
<CloseIcon />
</IconButton>
</Link>
<IconButton onClick={closeSidebar} className={'sidebar-close-btn'}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility.logo} />
......@@ -56,17 +53,14 @@ class Sidebar extends React.Component<SidebarProps> {
</div>
</div>
</Paper>
<FacilityDialog facility={facility} facilities={facilities} isOpen={true} onClose={() => history.push('/')} />
</div>
);
}
}
export interface SidebarProps {
history: History;
facility: IFacility;
facilities: IFacility[];
closeSidebar: () => void;
}
export default Sidebar;
\ No newline at end of file
......@@ -36,7 +36,7 @@ export class WeekHours extends React.Component<WeekHoursProps> {
for (let i = 0; i < todaysHours.length; i++) {
output[index] = (
<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>
</Grid>
......
import * as React from 'react';
import { connect } from 'react-redux';
import { IFacility, CampusRegion } from '../models/facility.model';
import { IAlert } from '../models/alert.model';
import { ApplicationState } from '../store';
import { Dispatch } from 'redux';
import { fetchFacilities } from '../store/facility/facility.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 AppBar from '../components/AppBar';
import Sidebar from '../components/Sidebar';
import { setSelectedFacility } from '../store/ui/ui.actions';
class Layout extends React.Component<LayoutProps> {
constructor(props: LayoutProps) {
class DesktopLayout extends React.Component<DesktopLayoutProps> {
constructor(props: DesktopLayoutProps) {
super(props);
}
......@@ -23,7 +23,7 @@ class Layout extends React.Component<LayoutProps> {
}
render() {
const {facilities, searchTerm, campusRegion, history} = this.props;
const {facilities, selectedFacility, searchTerm, campusRegion, closeSidebar} = this.props;
return (
<div className={'layout-root'}>
......@@ -32,41 +32,40 @@ class Layout extends React.Component<LayoutProps> {
<div className={'layout-container'}>
<div className={'layout-main-content'}>
<div className={'layout-card-container'}>
<CardContainer searchTerm={searchTerm}
campusRegion={campusRegion} facilities={facilities}/>
<CardContainer facilities={facilityUtils.filterFacilities(facilities, searchTerm, campusRegion)} showFavoriteIcons={true} />
</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>
);
}
}
export interface LayoutProps extends RouteComponentProps {
export interface DesktopLayoutProps {
facilities: IFacility[];
alerts: IAlert[];
favorites: string[];
selectedFacility: string;
searchTerm: string;
campusRegion: CampusRegion;
fetchFacilities: () => any;
fetchAlerts: () => any;
closeSidebar: () => void;
}
const mapStateToProps = (state: ApplicationState) => ({
facilities: state.facilities.facilities,
alerts: state.alerts.alerts,
favorites: state.ui.favorites,
selectedFacility: state.ui.selectedFacility,
searchTerm: state.ui.search.searchTerm,
campusRegion: state.ui.search.campusRegion
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
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';
import { IFacility } from '../models/facility.model';
import { ApplicationState } from '../store';
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 { withRouter, RouteComponentProps } from 'react-router';
import { Location } from 'history';
import FacilityStatus from '../components/FacilityStatus';
......@@ -31,13 +30,7 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
};
}
getSlugFromLocation = (location: Location) => location.pathname.substring(10);
shouldComponentUpdate = (nextProps: FacilityCardProps) => {
if (this.isFacilitySelected() !== (this.getSlugFromLocation(nextProps.location) === this.props.facility.slug)) {
return true;
}
const dateModified = nextProps.facility.modified;
if (dateModified !== this.state.modified) {
......@@ -51,10 +44,12 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
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() {
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()];
......@@ -79,68 +74,70 @@ class FacilityCard extends React.Component<FacilityCardProps, FacilityCardState>
facility.facility_location.building;
return (
<NavLink to={this.isFacilitySelected() ? '/' : `/facility/${facility.slug}`} style={{textDecoration: 'none', color: 'black'}}>
<Card className={classNames('fc-root', this.isFacilitySelected() && 'fc-selected')}
elevation={3}>
<div className={'fc-logo-container'}>
<img className={'fc-logo'}
alt={facility.slug} src={facility.logo} />
</div>
<FavoriteButton slug={facility.slug} initialState={favorites.includes(facility.slug)}
<Card onClick={this.selectFacility} className={classNames('fc-root', this.isFacilitySelected() && 'fc-selected')}
elevation={3}>
<div className={'fc-logo-container'}>
<img className={'fc-logo'}
alt={facility.slug} src={facility.logo} />
</div>
{
showFavoriteIcon && <FavoriteButton slug={facility.slug} initialState={favorites.includes(facility.slug)}
addFavoriteFacility={addFavoriteFacility}
removeFavoriteFacility={removeFavoriteFacility} />
}
<CardContent className={'fc-card-content'}>
<Grid container={true} alignItems={'center'} direction={'column'}>
<Grid item={true}
className={classNames('fc-small-grid-item-spacing', 'fc-ellipsis-container', 'fc-title-container')}>
<Typography variant={'subtitle1'} align={'center'}
className={classNames('fc-title', 'fc-one-line-ellipsis')}>
{removeBrackets(facility.facility_name)}
</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>
<CardContent className={'fc-card-content'}>
<Grid container={true} alignItems={'center'} direction={'column'}>
<Grid item={true}
className={classNames('fc-small-grid-item-spacing', 'fc-ellipsis-container', 'fc-title-container')}>
<Typography variant={'subtitle1'} align={'center'}
className={classNames('fc-title', 'fc-one-line-ellipsis')}>
{removeBrackets(facility.facility_name)}
</Typography>
</Grid>
<Grid container={true} justify={'space-around'}>
<Grid item={true} className={'fc-extra-info'}>
<FacilityStatus facility={facility} />
</Grid>
<Grid item={true} className={'fc-extra-info'}>
<Typography variant={'caption'}>
<LocationOnIcon className={'fc-card-map-marker-icon'} />
</Typography>
<Typography title={buildingName} variant={'caption'} align={'center'}
className={'fc-two-line-ellipsis'}>
{buildingName}
</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 container={true} justify={'space-around'}>
<Grid item={true} className={'fc-extra-info'}>
<FacilityStatus facility={facility} />
</Grid>
<Grid item={true} className={'fc-extra-info'}>
<Typography variant={'caption'}>
<LocationOnIcon className={'fc-card-map-marker-icon'} />
</Typography>
<Typography title={buildingName} variant={'caption'} align={'center'}
className={'fc-two-line-ellipsis'}>
{buildingName}
</Typography>
</Grid>
</CardContent>
</Card>
</NavLink>
</Grid>
</CardContent>
</Card>
);
}
}
export interface FacilityCardProps extends RouteComponentProps<{slug: string}> {
export interface FacilityCardProps {
facility: IFacility;
facilities: IFacility[];
favorites: string[];
showFavoriteIcon: boolean;
selectedFacility: string;
addFavoriteFacility: (slug: string) => void;
removeFavoriteFacility: (slug: string) => void;
selectFacility: (slug: string) => void;
}
export interface FacilityCardState {
......@@ -148,12 +145,14 @@ export interface FacilityCardState {
}
const mapStateToProps = (state: ApplicationState) => ({
favorites: state.ui.favorites
favorites: state.ui.favorites,
selectedFacility: state.ui.selectedFacility
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
addFavoriteFacility: (slug: string) => dispatch(addFavoriteFacility(slug)),
removeFavoriteFacility: (slug: string) => dispatch(removeFavoriteFacility(slug))
removeFavoriteFacility: (slug: string) => dispatch(removeFavoriteFacility(slug)),
selectFacility: (slug: string) => dispatch(setSelectedFacility(slug))
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(FacilityCard));
\ No newline at end of file
export default connect(mapStateToProps, mapDispatchToProps)(FacilityCard);
\ No newline at end of file
import * as React from 'react';
import { IFacility } from '../models/facility.model';
import { Paper, Typography, IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import { removeBrackets } from '../utils/nameUtils';
import * as phoneFormatter from 'phone-formatter';
import WeekHours from '../components/WeekHours';
import { ApplicationState } from '../store';
import { addFavoriteFacility, removeFavoriteFacility } from '../store/ui/ui.actions';
import { useSelector, useDispatch } from 'react-redux';
import FavoriteButton from '../components/FavoriteButton';
import TextwTitle from '../components/TextwTitle';
const FacilityDetail = (props: FacilityDetailProps) => {
const isFavorite = useSelector((state: ApplicationState) => state.ui.favorites).includes(props.facility.slug);
const dispatch = useDispatch();
const addFavorite = (slug: string) => dispatch(addFavoriteFacility(slug));
const removeFavorite = (slug: string) => dispatch(removeFavoriteFacility(slug));
return (
<Paper className={'detail-paper'}>
<FavoriteButton slug={props.facility.slug} initialState={isFavorite}
addFavoriteFacility={addFavorite}
removeFavoriteFacility={removeFavorite} />
<IconButton className={'detail-close-btn'} onClick={props.onClose}>
<CloseIcon />
</IconButton>
<div className={'detail-logo-container'}>
<img className={'detail-logo'} src={props.facility.logo} />
</div>
<div className={'detail-content'}>
<Typography className={'detail-title'} variant={'h6'}>
{removeBrackets(props.facility.facility_name)}
</Typography>
<div className={'detail-label-holder'}>
<div className={'detail-label-holder-left'}>
<TextwTitle label="Building"
content={props.facility.facility_location && props.facility.facility_location.building} />
<TextwTitle label="Address"
content={props.facility.facility_location && props.facility.facility_location.address} />
<TextwTitle label="Phone Number"
content={props.facility.phone_number ? phoneFormatter.format(props.facility.phone_number, '(NNN) NNN-NNNN') : 'Unknown'} />
</div>
<div>
<TextwTitle label="Hours" content={<WeekHours facility={props.facility} />} />
</div>
</div>
</div>
</Paper>
);
};
export interface FacilityDetailProps {
facility: IFacility;