Commit 9c099cdf authored by Florent Chehab's avatar Florent Chehab

Merge branch 'university_page' into 'master'

University page

See merge request chehabfl/outgoing_rex!33
parents a065c242 606cb6d7
Pipeline #27072 passed with stages
in 2 minutes and 47 seconds
{
"plugins": {
"autoprefixer": {
"browsers": ["last 2 versions"]
}
}
}
......@@ -3,3 +3,4 @@ export const SAVE_SELECTED_UNIVERSITIES = 'SAVE_SELECTED_UNIVERSITIES';
export const SAVE_FILTER_CONFIG = 'SAVE_FILTER_CONFIG';
export const SAVE_APP_THEME = 'SAVE_APP_THEME';
export const SAVE_APP_COLOR_PICKER = 'SAVE_APP_COLOR_PICKER';
export const SAVE_UNIVERSITY_BEING_VIEWED = 'SAVE_UNIVERSITY_BEING_VIEWED';
import {
SAVE_UNIVERSITY_BEING_VIEWED
} from "./action-types";
export function saveUniversityBeingViewed(univId) {
return {
type: SAVE_UNIVERSITY_BEING_VIEWED,
univId
};
}
......@@ -14,15 +14,15 @@ import Chip from '@material-ui/core/Chip';
import Avatar from '@material-ui/core/Avatar';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import SchoolIcon from '@material-ui/icons/School';
import { mainListItems, secondaryListItems } from './template/listItems';
import { mainListItems, secondaryListItems, thirdListItems } from './template/listItems';
import { connect } from "react-redux";
import MyComponent from './MyComponent'
// import route Components here
import {
Route,
Redirect
} from 'react-router-dom';
import {
......@@ -33,7 +33,7 @@ import {
import PageMap from './pages/PageMap';
import PageHome from './pages/PageHome';
import PageFilter from './pages/PageFilter';
import PageUniversity from './pages/PageUniversity';
import PageSearch from './pages/PageSearch';
import PageSettings from './pages/PageSettings';
......@@ -90,6 +90,10 @@ const styles = theme => ({
padding: theme.spacing.unit * 3,
height: '100vh',
overflow: 'auto',
paddingTop: "0px"
},
paddingTop: {
paddingTop: "24px"
},
chartContainer: {
marginLeft: -22,
......@@ -156,14 +160,25 @@ class App extends MyComponent {
<List>{mainListItems}</List>
<Divider />
<List>{secondaryListItems}</List>
<Divider />
<List>{thirdListItems}</List>
</Drawer>
<main className={classes.content}>
<Route path="/app/" exact={true} component={PageHome} />
<Route path="/app/search" component={PageSearch} />
<Route path="/app/map" component={PageMap} />
<Route path="/app/filter" component={PageFilter} />
<Route path="/app/settings" component={PageSettings} />
<main className={classNames(classes.content, classes.noPaddingTop)}>
<div className={classes.paddingTop}>
<Route path="/app/" exact={true} component={PageHome} />
<Route path="/app/search" component={PageSearch} />
<Route path="/app/map" component={PageMap} />
<Route path="/app/settings" component={PageSettings} />
<Route
exact
path="/app/university/"
render={() => (<Redirect to="/app/university/undefined" />)}
/>
</div>
<div >
<Route path="/app/university/:id" component={PageUniversity} />
</div>
</main>
</div>
</React.Fragment>
......
......@@ -35,10 +35,10 @@ class MyComponent extends Component {
const { props } = this;
for (let prop_key in props) {
let prop = props[prop_key];
if (typeof prop == 'boolean') {
continue;
}
if (prop && 'fetched' in prop) {
// if (typeof prop == 'boolean') {
// continue;
// }
if (prop === Object(prop) && 'fetched' in prop) {
out[prop_key] = prop.fetched.data;
}
}
......@@ -58,10 +58,10 @@ class MyComponent extends Component {
const { props } = this;
for (let el in props) {
let prop = props[el];
if (typeof prop == 'boolean') {
continue;
}
if (prop && val in prop && prop[val]) {
// if (typeof prop == 'boolean') {
// continue;
// }
if (prop === Object(prop) && val in prop && prop[val]) {
return true;
}
}
......@@ -75,7 +75,7 @@ class MyComponent extends Component {
if (typeof prop == 'boolean') {
continue;
}
if (prop && 'fetchHasError' in prop) {
if (prop === Object(prop) && 'fetchHasError' in prop) {
if (prop.fetchHasError.status) {
if (prop_key in this.customErrorHandlers) {
console.log("icicicicici");
......@@ -110,7 +110,7 @@ class MyComponent extends Component {
if (typeof prop == 'boolean') {
continue;
}
if (prop && 'fetched' in prop) {
if (prop === Object(prop) && 'fetched' in prop) {
if ((!prop.fetched.fetchedAt) || prop.invalidated) {
if (!prop.isLoading) {
props.fetchData[prop_key]();
......
......@@ -5,6 +5,7 @@ import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
import { connect } from "react-redux";
import { BrowserRouter as Router, Route } from 'react-router-dom';
import MyComponent from './MyComponent';
import 'typeface-roboto';
import areSameThemes from '../utils/areSameThemes';
......@@ -43,10 +44,17 @@ class ThemeProvider extends MyComponent {
}
}
myRender() {
const font = {typography: {
fontSize: 14,
htmlFontSize: 14
}}
const theme = Object.assign({}, this.state.theme, font)
return (
<div>
<MuiThemeProvider theme={createMuiTheme(this.state.theme)}>
<MuiThemeProvider theme={createMuiTheme(theme)}>
<Router>
{this.props.children}
</Router>
......
......@@ -5,6 +5,13 @@ import MyComponent from '../MyComponent'
import { connect } from "react-redux";
import _ from 'underscore';
import { saveSelectedUniversities, saveFilterConfig } from '../../actions/filter';
import ExpansionPanel from '@material-ui/core/ExpansionPanel';
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import Typography from '@material-ui/core/Typography';
import { withStyles } from '@material-ui/core/styles';
import {
universitiesFetchData,
......@@ -13,25 +20,24 @@ import {
countriesFetchData
} from '../../generated/actions';
const styles = theme => ({
root: {
width: '100%',
},
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
});
class Filter extends MyComponent {
saveContriesFilterConfig(state) {
this.props.saveConfig({ contriesFilter: state })
}
// getUnivFromCampus(campus) {
// const { universities } = this.props;
// return universities[campus.univ]
// }
// getCountryFromUniversity(univ) {
// const { countries } = this.props;
// return countries[univ.country]
// }
// getCountryFromCampus(campus) {
// const univ = this.getUnivFromCampus(campus);
// return this.getCountryFromUniversity(univ);
// }
getCountriesWhereThereAreUniversities() {
const { mainCampuses } = this.getAllFetchedData();
......@@ -62,14 +68,21 @@ class Filter extends MyComponent {
myRender() {
const options = _.map(this.getCountriesWhereThereAreUniversities(),
(c) => { return { id: c.iso_alpha2_code, label: c.name } })
const { classes } = this.props;
return (
<DownshiftMultiple
options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)}
config={this.props.contriesFilterConfig}
/>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<Typography className={classes.heading}>Appliquer des filtres</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<DownshiftMultiple
options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)}
config={this.props.contriesFilterConfig}
/>
</ExpansionPanelDetails>
</ExpansionPanel>
);
}
}
......@@ -100,4 +113,4 @@ const mapDispatchToProps = (dispatch) => {
};
export default connect(mapStateToProps, mapDispatchToProps)(Filter);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Filter));
......@@ -49,6 +49,7 @@ class UnivMarkers extends MyComponent {
logo={el.univ_logo}
city={el.univ_city}
country={el.univ_country}
univId={el.id}
/>
</Popup>
</Marker>
......
......@@ -10,67 +10,70 @@ import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import IconAdd from '@material-ui/icons/Add';
import IconClose from '@material-ui/icons/Close';
import { Link } from 'react-router-dom';
import MyCardMedia from './MyCardMedia';
import { withLeaflet } from 'react-leaflet';
const styles = {
card: {
maxWidth: 345,
},
media: {
// ⚠️ object-fit is not supported by IE11.
objectFit: 'cover',
},
hide: {
display: 'none',
}
card: {
maxWidth: 345,
},
media: {
// ⚠️ object-fit is not supported by IE11.
objectFit: 'cover',
},
hide: {
display: 'none',
}
};
class UnivPopupContent extends Component {
render() {
const { classes, logo, name, city, country, leaflet } = this.props;
let height = (this.props.logo !== "" ? "140" : "0");
let closeIt = () => leaflet.map.closePopup();
return (
<Card className={classes.card}>
<CardActionArea>
<MyCardMedia
className={classes.media}
height={height}
url={logo}
title={name}
/>
<Divider />
<CardContent>
<Typography variant="subheading">
{city}, {country}
</Typography>
<Divider />
<Typography gutterBottom variant="headline">
{name}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button variant="contained" size="small" color="primary">
< IconAdd />
En savoir plus
</Button>
<Button size="small" color="secondary" onClick={() => closeIt()}>
< IconClose />
Fermer
</Button>
</CardActions>
</Card>
);
}
render() {
const { classes, logo, name, city, country, leaflet, univId } = this.props;
let height = (this.props.logo !== "" ? "140" : "0");
let closeIt = () => leaflet.map.closePopup();
return (
<Card className={classes.card}>
<CardActionArea>
<MyCardMedia
className={classes.media}
height={height}
url={logo}
title={name}
/>
<Divider />
<CardContent>
<Typography variant="subheading">
{city}, {country}
</Typography>
<Divider />
<Typography gutterBottom variant="headline">
{name}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link to={"/app/university/" + univId} style={{ textDecoration: 'none' }}>
<Button variant="contained" size="small" color="primary">
< IconAdd />
En savoir plus
</Button>
</Link>
<Button size="small" color="secondary" onClick={() => closeIt()}>
< IconClose />
Fermer
</Button>
</CardActions>
</Card>
);
}
}
UnivPopupContent.propTypes = {
classes: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
};
export default withLeaflet(withStyles(styles)(UnivPopupContent));
\ No newline at end of file
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import { lighten, darken } from '@material-ui/core/styles/colorManipulator';
const styles = (theme) => {
const { palette } = theme;
const linkColor = palette.type == 'dark' ? lighten(palette.secondary.main, 0.8) : darken(palette.secondary.main, 0.3);
return {
link: {
color: linkColor,
textDecoration: "none",
borderBottom: "1px dotted"
},
}
}
export default withStyles(styles, { withTheme: true })(({ classes, ...props }) => (
<a href={props.href} className={classes.link} target="_blank">
{props.children}
</a>
))
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Filter from '../filter/Filter';
import Paper from '@material-ui/core/Paper';
const styles = theme => ({
myPaper: {
padding: 16
}
});
class PageFilter extends React.Component {
render() {
const { classes } = this.props;
return (
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
Filtrer
</Typography>
</Grid>
{/* <Grid item xs={1}>
<UnivMapReloadButton />
</Grid> */}
</Grid>
<Filter />
</Paper>
);
}
}
PageFilter.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(PageFilter);
......@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import Markdown from '../shared/Markdown';
const styles = theme => ({
myPaper: {
......@@ -10,15 +11,35 @@ const styles = theme => ({
}
});
const source = `
Les objectifs de ce service sont :
- 1
- 2
- 3
Âge des données de l'UTC :
| **Feature** | **Support** |
| ------ | ----------- |
| Ancien départs | ✔ |
| Départs possibles | ✔ |
| Informatio sur les universités | ✔ |
[Rendez-vous sur le GitLab de l'UTC !](https://gitlab.utc.fr)
`;
class PageHome extends React.Component {
render() {
const { classes } = this.props;
return (
<Paper className={classes.myPaper}>
<Typography variant="display1">
Accueil du site, coucou :)
<Typography variant="display2">
Bienvenue sur <i>Outgoing REX</i>
</Typography>
<Markdown source={source} />
</Paper>
);
......
......@@ -6,36 +6,38 @@ import Typography from '@material-ui/core/Typography';
import UnivMap from '../map/UnivMap';
import UnivMapReloadButton from '../map/UnivMapReloadButton';
import Paper from '@material-ui/core/Paper';
import Filter from '../filter/Filter';
const styles = theme => ({
myPaper: {
padding: 16
}
myPaper: {
padding: 16
}
});
class PageMap extends React.Component {
render() {
const { classes } = this.props;
return (
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
Exploration Cartographique
</Typography>
</Grid>
<Grid item xs={1}>
<UnivMapReloadButton />
</Grid>
</Grid>
<UnivMap />
</Paper>
);
}
render() {
const { classes } = this.props;
return (
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
Exploration Cartographique
</Typography>
</Grid>
<Grid item xs={1}>
<UnivMapReloadButton />
</Grid>
</Grid>
<Filter />
<UnivMap />
</Paper>
);
}
}
PageMap.propTypes = {
classes: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(PageMap);
......@@ -5,6 +5,7 @@ import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Search from '../search/Search';
import Paper from '@material-ui/core/Paper';
import Filter from '../filter/Filter';
const styles = theme => ({
myPaper: {
......@@ -23,10 +24,8 @@ class PageSearch extends React.Component {
Recherche d'une université
</Typography>
</Grid>
{/* <Grid item xs={1}>
<UnivMapReloadButton />
</Grid> */}
</Grid>
<Filter />
<Search />
</Paper>
);
......
import React from 'react';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import MyComponent from '../MyComponent';
import { connect } from "react-redux";
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
import UniversityTemplate from '../university/UniversityTemplate';
import {
NavLink,
Redirect
} from 'react-router-dom';
import {
universitiesFetchData,
} from '../../generated/actions';
import {
saveUniversityBeingViewed
} from '../../actions/universityPage';
function renderUniversityNotFound() {
return (
<Dialog
open={true}
//onClose={this.handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"L'université demandée n'est pas reconnue !"}</DialogTitle>
<DialogActions>
<NavLink to="/app/university" style={{ textDecoration: 'none' }}>
<Button variant="contained" color="primary">
C'est noté, ramenez-moi sur le droit chemin.
</Button>
</NavLink>
</DialogActions>
</Dialog>
)
}
function renderUniversityUndefinedButHavePrevious(univId) {
return (
<Redirect to={"/app/university/" + univId} />
)
}
function renderFirstTimeHere() {
return (
<Paper>
<Typography >
C'est la première fois que vous utilisé cet onglet et vous n'avez pas encore util....
</Typography>
</Paper>
)
}
function renderDefaultView(univId) {
return (
<UniversityTemplate univId={univId} />
);
}
class PageUniversity extends MyComponent {
myComponentDidUpdate() {
if (this.props.universities.fetched.fetchedAt) {
// we have the university data
const { universities } = this.getAllFetchedData();
const { match, universityBeingViewed } = this.props;
const requestedUniversity = match.params.id;
if (requestedUniversity != 'undefined' && requestedUniversity in universities && requestedUniversity != universityBeingViewed) {
this.props.saveUniversityInView(requestedUniversity);
}
}
}
myRender() {
const { match, universityBeingViewed } = this.props;
const requestedUnivId = match.params.id;
const { universities } = this.getAllFetchedData();
if (requestedUnivId == 'undefined') {
if (universityBeingViewed != null) {
return renderUniversityUndefinedButHavePrevious(universityBeingViewed);
} else {
return renderFirstTimeHere();
}
} else {
if (requestedUnivId in universities) {
return renderDefaultView(requestedUnivId);
} else {
return renderUniversityNotFound();
}
}
}
}
PageUniversity.propTypes = {
// classes: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => {
return {
universities: state.universities,