Commit 264e53bf authored by Florent Chehab's avatar Florent Chehab
Browse files

Updated frontend to more standard REST API

Fixes #48
parent 1ff6c45b
Pipeline #35293 passed with stages
in 4 minutes and 41 seconds
...@@ -71,7 +71,7 @@ export default class CrudReducers { ...@@ -71,7 +71,7 @@ export default class CrudReducers {
return is(state, action, self.types.isReadingAll); return is(state, action, self.types.isReadingAll);
} }
function readAllSucceeded(state = { data: Object(), readAt: 0 }, action) { function readAllSucceeded(state = { data: Array(), readAt: 0 }, action) {
return succeeded(state, action, self.types.readAllSucceeded); return succeeded(state, action, self.types.readAllSucceeded);
} }
...@@ -152,7 +152,7 @@ export default class CrudReducers { ...@@ -152,7 +152,7 @@ export default class CrudReducers {
isInvalidated: isInvalidatedSpecific, isInvalidated: isInvalidatedSpecific,
}; };
// Add only appropriate reducers // Add only appropriate reducers
if (this.readOnly !== true) { if (this.readOnly !== true) {
// Creation // Creation
out.isCreating = isCreating; out.isCreating = isCreating;
...@@ -168,4 +168,4 @@ export default class CrudReducers { ...@@ -168,4 +168,4 @@ export default class CrudReducers {
return combineReducers(out); return combineReducers(out);
} }
} }
\ No newline at end of file
...@@ -186,7 +186,7 @@ App.propTypes = { ...@@ -186,7 +186,7 @@ App.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
}; };
// Already load some of the data even if it's not use here.
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
countries: state.api.countriesAll, countries: state.api.countriesAll,
......
...@@ -224,35 +224,66 @@ class CustomComponentForAPI extends Component { ...@@ -224,35 +224,66 @@ class CustomComponentForAPI extends Component {
return out; return out;
} }
/**
* Access to the read data from the propNames array
* This should be used instead of getAllReadData to optimize things
*
* @param {array} propNames Array of the prop names you want to read data from
* @returns {object}
* @memberof CustomComponentForAPI
*/
getReadDataFor(propNames) {
let out = Object();
propNames.forEach(propName => out[propName] = this.getReadData(propName));
return out;
}
// utilities functions shared by subclasses // utilities functions shared by subclasses
/**
* Function to retrieve all the information relative to a campus
*
* @param {object} campus instance of Campus
* @returns {object} Campus with replaced university, city and country with the matching instance
* @memberof CustomComponentForAPI
*/
joinCampus(campus) { joinCampus(campus) {
const { universities, countries, cities } = this.getAllReadData(); const { universities, countries, cities } = this.getReadDataFor(["universities", "countries", "cities"]);
let res = Object.assign({}, campus); //copy for safety let res = Object.assign({}, campus); //copy for safety
res.university = universities[campus.university]; res.university = universities.find(univ => univ.id = campus.university);
res.city = cities[campus.city]; res.city = cities.find(city => city.id = campus.city);
res.country = countries[res.city.country]; res.country = countries.find(country => country.id = res.city.country);
return res; return res;
} }
/**
* Funciton to get the city and the country of a university given a university id
*
* @param {number} univId
* @returns {object} Object with city and country instance of the university (main campus)
* @memberof CustomComponentForAPI
*/
getUnivCityAndCountry(univId) { getUnivCityAndCountry(univId) {
const univMainCampus = this.findMainCampus(univId); const univMainCampus = this.findMainCampus(univId),
const cities = this.getReadData("cities"); { countries, cities } = this.getReadDataFor(["countries", "cities"]),
const countries = this.getReadData("countries"); city = cities.find(city => city.id == univMainCampus.city),
const city = cities[univMainCampus.city]; country = countries.find(country => country.id == city.country);
const country = countries[city.country];
return { city, country }; return { city, country };
} }
/**
* Function that returns the main campus instance associated with a university
* identified by its id.
*
* @param {number} univId
* @returns {object}
* @memberof CustomComponentForAPI
*/
findMainCampus(univId) { findMainCampus(univId) {
const mainCampuses = this.getReadData("mainCampuses"); const mainCampuses = this.getReadData("mainCampuses");
for (let mainCampusPk in mainCampuses) { return mainCampuses.find(campus => campus.university == univId);
const campus = mainCampuses[mainCampusPk];
if (campus.university == univId) {
return campus;
}
}
return null;
} }
} }
......
...@@ -3,8 +3,6 @@ import DownshiftMultiple from "./DownshiftMultiple"; ...@@ -3,8 +3,6 @@ import DownshiftMultiple from "./DownshiftMultiple";
import CustomComponentForAPI from "../CustomComponentForAPI"; import CustomComponentForAPI from "../CustomComponentForAPI";
import { connect } from "react-redux"; import { connect } from "react-redux";
import __each from "lodash/each";
import __uniq from "lodash/uniq";
import __map from "lodash/map"; import __map from "lodash/map";
import __indexOf from "lodash/indexOf"; import __indexOf from "lodash/indexOf";
import { saveSelectedUniversities, saveFilterConfig } from "../../actions/filter"; import { saveSelectedUniversities, saveFilterConfig } from "../../actions/filter";
...@@ -31,34 +29,44 @@ const styles = theme => ({ ...@@ -31,34 +29,44 @@ const styles = theme => ({
class Filter extends CustomComponentForAPI { class Filter extends CustomComponentForAPI {
saveContriesFilterConfig(state) { saveCountriesFilterConfig(state) {
this.props.saveConfig({ contriesFilter: state }); this.props.saveConfig({ countriesFilter: state });
} }
/**
* Function to get the list of countries where there are universities.
*
* @returns {Array} of the countries instances
* @memberof Filter
*/
getCountriesWhereThereAreUniversities() { getCountriesWhereThereAreUniversities() {
const { mainCampuses } = this.getAllReadData(); const mainCampuses = this.getReadData("mainCampuses");
let res = []; // use of map to get only each country once
__each(mainCampuses, (campus) => { let res = new Map();
const campusFull = this.joinCampus(campus);
res.push(campusFull.country); // TODO optimize: use maps instead of joinCampus
mainCampuses.forEach(campus => {
const country = this.joinCampus(campus).country,
code = country.iso_alpha2_code;
res.set(code, country);
}); });
return __uniq(res, false, (c) => { return c.iso_alpha2_code; }); return [...res.values()];
} }
updateSelectedUniversities(selection) { updateSelectedUniversities(selection) {
const { mainCampuses } = this.getAllReadData(); const mainCampuses = this.getReadData("mainCampuses"),
const listOfCountries = __map(selection, (s) => s.id); listOfCountries = __map(selection, (s) => s.id);
let selected_universities = []; let selectedUniversities = [];
__each(mainCampuses, (campus) => { mainCampuses.forEach(campus => {
const campusFull = this.joinCampus(campus); const campusFull = this.joinCampus(campus);
if (__indexOf(listOfCountries, campusFull.country.iso_alpha2_code) > -1) { if (__indexOf(listOfCountries, campusFull.country.iso_alpha2_code) > -1) {
selected_universities.push(campusFull.university.id); selectedUniversities.push(campusFull.university.id);
} }
}); });
this.props.saveSelection(selected_universities); this.props.saveSelection(selectedUniversities);
} }
customRender() { customRender() {
...@@ -74,8 +82,8 @@ class Filter extends CustomComponentForAPI { ...@@ -74,8 +82,8 @@ class Filter extends CustomComponentForAPI {
<DownshiftMultiple <DownshiftMultiple
options={options} options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)} onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)} onComponentUnmount={(state) => this.saveCountriesFilterConfig(state)}
config={this.props.contriesFilterConfig} config={this.props.countriesFilterConfig}
/> />
</ExpansionPanelDetails> </ExpansionPanelDetails>
</ExpansionPanel> </ExpansionPanel>
...@@ -91,7 +99,7 @@ const mapStateToProps = (state) => { ...@@ -91,7 +99,7 @@ const mapStateToProps = (state) => {
mainCampuses: state.api.mainCampusesAll, mainCampuses: state.api.mainCampusesAll,
cities: state.api.citiesAll, cities: state.api.citiesAll,
countries: state.api.countriesAll, countries: state.api.countriesAll,
contriesFilterConfig: state.app.filter.contriesFilter countriesFilterConfig: state.app.filter.countriesFilter
}; };
}; };
......
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Marker, Popup } from "react-leaflet"; import { Marker, Popup } from "react-leaflet";
// import MarkerClusterGroup from 'react-leaflet-markercluster'; // import MarkerClusterGroup from 'react-leaflet-markercluster';
...@@ -7,44 +8,61 @@ import UnivPopupContent from "./UnivPopupContent"; ...@@ -7,44 +8,61 @@ import UnivPopupContent from "./UnivPopupContent";
import getActions from "../../api/getActions"; import getActions from "../../api/getActions";
import CustomComponentForAPI from "../CustomComponentForAPI"; import CustomComponentForAPI from "../CustomComponentForAPI";
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
/**
* Class that renders the markers for the map of the universities.
*
* Each main campus and associated relevant information is added to the map.
*
* @class UnivMarkers
* @extends {CustomComponentForAPI}
*/
class UnivMarkers extends CustomComponentForAPI { class UnivMarkers extends CustomComponentForAPI {
customRender() { customRender() {
let { universities, const { universities,
mainCampuses, mainCampuses,
countries, countries,
cities } = this.getAllReadData(); cities } = this.getAllReadData();
let selected_main_campus = []; // some conversions for optimization (faster search of element in a map)
for (let main_campus_pk in mainCampuses) { const universitiesMap = arrayOfInstancesToMap(universities),
let campus = mainCampuses[main_campus_pk]; countriesMap = arrayOfInstancesToMap(countries),
let univ = universities[campus.university]; citiesMap = arrayOfInstancesToMap(cities);
if (univ && campus) {
let city = cities[campus.city]; let mainCampusesSelection = [];
let country = countries[city.country]; // Merge the data and add it to the selection
mainCampuses.forEach(campus => {
selected_main_campus.push({ const univ = universitiesMap.get(campus.university);
univ_name: univ.name, if (campus && univ) {
univ_logo: univ.logo, const city = citiesMap.get(campus.city),
univ_city: city.name, country = countriesMap.get(city.country);
univ_country: country.name,
mainCampusesSelection.push({
univName: univ.name,
univLogo: univ.logo,
univCity: city.name,
univCountry: country.name,
lat: campus.lat, lat: campus.lat,
lon: campus.lon, lon: campus.lon,
id: univ.id id: univ.id
}); });
} }
} });
return ( return (
selected_main_campus.map((el) => ( mainCampusesSelection.map((el, idx) => (
<Marker key={el.id} position={[el.lat, el.lon]}> <Marker key={idx} position={[el.lat, el.lon]}>
<Popup closeButton={false} > <Popup closeButton={false} >
<UnivPopupContent <UnivPopupContent
name={el.univ_name} name={el.univName}
logo={el.univ_logo} logo={el.univLogo}
city={el.univ_city} city={el.univCity}
country={el.univ_country} country={el.univCountry}
univId={el.id} univId={el.id}
/> />
</Popup> </Popup>
...@@ -55,6 +73,12 @@ class UnivMarkers extends CustomComponentForAPI { ...@@ -55,6 +73,12 @@ class UnivMarkers extends CustomComponentForAPI {
} }
} }
UnivMarkers.propTypes = {
universities: PropTypes.object.isRequired,
mainCampuses: PropTypes.object.isRequired,
cities: PropTypes.object.isRequired,
countries: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
......
...@@ -74,10 +74,11 @@ class PageUniversity extends CustomComponentForAPI { ...@@ -74,10 +74,11 @@ class PageUniversity extends CustomComponentForAPI {
customComponentDidUpdate() { customComponentDidUpdate() {
if (this.props.universities.readSucceeded.readAt) { if (this.props.universities.readSucceeded.readAt) {
// we have the university data // we have the university data
const { universities } = this.getAllReadData(); const universities = this.getReadData("universities"),
const { match, universityBeingViewed } = this.props; { match, universityBeingViewed } = this.props,
const requestedUniversity = match.params.id; requestedUniversity = match.params.id;
if (requestedUniversity != "undefined" && requestedUniversity in universities && requestedUniversity != universityBeingViewed) {
if (requestedUniversity != "undefined" && universities.find(univ => univ.id == requestedUniversity) && requestedUniversity != universityBeingViewed) {
this.props.saveUniversityInView(requestedUniversity); this.props.saveUniversityInView(requestedUniversity);
} }
} }
...@@ -85,9 +86,9 @@ class PageUniversity extends CustomComponentForAPI { ...@@ -85,9 +86,9 @@ class PageUniversity extends CustomComponentForAPI {
} }
customRender() { customRender() {
const { match, universityBeingViewed } = this.props; const { match, universityBeingViewed } = this.props,
const requestedUnivId = match.params.id; requestedUnivId = match.params.id,
const { universities } = this.getAllReadData(); universities = this.getReadData("universities");
if (requestedUnivId == "undefined") { if (requestedUnivId == "undefined") {
if (universityBeingViewed != null) { if (universityBeingViewed != null) {
...@@ -96,7 +97,7 @@ class PageUniversity extends CustomComponentForAPI { ...@@ -96,7 +97,7 @@ class PageUniversity extends CustomComponentForAPI {
return renderFirstTimeHere(); return renderFirstTimeHere();
} }
} else { } else {
if (requestedUnivId in universities) { if (universities.find(univ => univ.id == requestedUnivId)) {
return renderDefaultView(requestedUnivId); return renderDefaultView(requestedUnivId);
} else { } else {
return renderUniversityNotFound(); return renderUniversityNotFound();
......
...@@ -5,7 +5,6 @@ import CustomComponentForAPI from "../CustomComponentForAPI"; ...@@ -5,7 +5,6 @@ import CustomComponentForAPI from "../CustomComponentForAPI";
import { connect } from "react-redux"; import { connect } from "react-redux";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
import UnivList from "./UnivList"; import UnivList from "./UnivList";
import __map from "lodash/map";
// import { saveSelectedUniversities, saveSearchConfig } from '../../actions/filter'; // import { saveSelectedUniversities, saveSearchConfig } from '../../actions/filter';
import withStyles from "@material-ui/core/styles/withStyles"; import withStyles from "@material-ui/core/styles/withStyles";
...@@ -27,15 +26,14 @@ class Search extends CustomComponentForAPI { ...@@ -27,15 +26,14 @@ class Search extends CustomComponentForAPI {
} }
getSuggestions(value) { getSuggestions(value) {
const { universities } = this.getAllReadData(); const universities = this.getReadData("universities"),
const filter = fuzzysort.go(value, __map(universities, (univ) => univ), { keys: ["name", "acronym"] }); filter = fuzzysort.go(value, universities, { keys: ["name", "acronym"] });
let out;
if (filter.length > 0) { let suggestions = filter.map(item => item.obj);
out = __map(filter, (item) => item.obj); if (suggestions.length == 0) {
} else { suggestions = universities;
out = universities;
} }
this.setState({ suggestions: out }); this.setState({ suggestions });
} }
......
...@@ -126,7 +126,7 @@ class UnivList extends React.Component { ...@@ -126,7 +126,7 @@ class UnivList extends React.Component {
UnivList.propTypes = { UnivList.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
universitiesToList: PropTypes.object.isRequired, // TODO fix type issue, sometimes is array, sometimes is object universitiesToList: PropTypes.array.isRequired,
itemsPerPage: PropTypes.number.isRequired, itemsPerPage: PropTypes.number.isRequired,
}; };
......
...@@ -96,7 +96,7 @@ CountryDri.propTypes = { ...@@ -96,7 +96,7 @@ CountryDri.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
countryId: PropTypes.string.isRequired, countryId: PropTypes.string.isRequired,
countries: PropTypes.object.isRequired, countries: PropTypes.array.isRequired,
}; };
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
......
...@@ -116,8 +116,8 @@ CountryScholarships.propTypes = { ...@@ -116,8 +116,8 @@ CountryScholarships.propTypes = {
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
countryId: PropTypes.string.isRequired, countryId: PropTypes.string.isRequired,
country: PropTypes.object.isRequired, country: PropTypes.object.isRequired,
countries: PropTypes.object.isRequired, countries: PropTypes.array.isRequired,
currencies: PropTypes.object.isRequired, currencies: PropTypes.array.isRequired,
}; };
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
......
...@@ -99,7 +99,7 @@ UniversityDri.propTypes = { ...@@ -99,7 +99,7 @@ UniversityDri.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
univId: PropTypes.string.isRequired, univId: PropTypes.string.isRequired,
universities: PropTypes.object.isRequired, universities: PropTypes.array.isRequired,
}; };
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
......
...@@ -113,8 +113,8 @@ UniversityScholarships.propTypes = { ...@@ -113,8 +113,8 @@ UniversityScholarships.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
univId: PropTypes.string.isRequired, univId: PropTypes.string.isRequired,
universities: PropTypes.object.isRequired, universities: PropTypes.array.isRequired,
currencies: PropTypes.object.isRequired, currencies: PropTypes.array.isRequired,
}; };
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
......
...@@ -52,7 +52,7 @@ class Scholarship extends React.Component { ...@@ -52,7 +52,7 @@ class Scholarship extends React.Component {
getSymbol() { getSymbol() {
const { currencies, currency } = this.props; const { currencies, currency } = this.props;
const currencyInfo = currencies[currency]; const currencyInfo = currencies.find(c => c.id == currency);
const { symbol } = currencyInfo; const { symbol } = currencyInfo;
if (symbol) { if (symbol) {
...@@ -64,7 +64,7 @@ class Scholarship extends React.Component { ...@@ -64,7 +64,7 @@ class Scholarship extends React.Component {
convertAmountToEur(amount) { convertAmountToEur(amount) {
const { currencies, currency } = this.props; const { currencies, currency } = this.props;
const rate = currencies[currency].one_EUR_in_this_currency; const rate = currencies.find(c => c.id == currency).one_EUR_in_this_currency;
return Math.trunc(amount / rate); return Math.trunc(amount / rate);
} }
...@@ -153,7 +153,7 @@ Scholarship.propTypes = { ...@@ -153,7 +153,7 @@ Scholarship.propTypes = {
otherAdvantages: PropTypes.string, otherAdvantages: PropTypes.string,
amountMin: PropTypes.string, amountMin: PropTypes.string,
amountMax: PropTypes.string,