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

Updated frontend to more standard REST API

Fixes #48
parent 1ff6c45b
......@@ -71,7 +71,7 @@ export default class CrudReducers {
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);
}
......@@ -152,7 +152,7 @@ export default class CrudReducers {
isInvalidated: isInvalidatedSpecific,
};
// Add only appropriate reducers
// Add only appropriate reducers
if (this.readOnly !== true) {
// Creation
out.isCreating = isCreating;
......@@ -168,4 +168,4 @@ export default class CrudReducers {
return combineReducers(out);
}
}
\ No newline at end of file
}
......@@ -186,7 +186,7 @@ App.propTypes = {
classes: PropTypes.object.isRequired,
};
// Already load some of the data even if it's not use here.
const mapStateToProps = (state) => {
return {
countries: state.api.countriesAll,
......
......@@ -224,35 +224,66 @@ class CustomComponentForAPI extends Component {
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
/**
* 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) {
const { universities, countries, cities } = this.getAllReadData();
const { universities, countries, cities } = this.getReadDataFor(["universities", "countries", "cities"]);
let res = Object.assign({}, campus); //copy for safety
res.university = universities[campus.university];
res.city = cities[campus.city];
res.country = countries[res.city.country];
res.university = universities.find(univ => univ.id = campus.university);
res.city = cities.find(city => city.id = campus.city);
res.country = countries.find(country => country.id = res.city.country);
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) {
const univMainCampus = this.findMainCampus(univId);
const cities = this.getReadData("cities");
const countries = this.getReadData("countries");
const city = cities[univMainCampus.city];
const country = countries[city.country];
const univMainCampus = this.findMainCampus(univId),
{ countries, cities } = this.getReadDataFor(["countries", "cities"]),
city = cities.find(city => city.id == univMainCampus.city),
country = countries.find(country => country.id == 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) {
const mainCampuses = this.getReadData("mainCampuses");
for (let mainCampusPk in mainCampuses) {
const campus = mainCampuses[mainCampusPk];
if (campus.university == univId) {
return campus;
}
}
return null;
return mainCampuses.find(campus => campus.university == univId);
}
}
......
......@@ -3,8 +3,6 @@ import DownshiftMultiple from "./DownshiftMultiple";
import CustomComponentForAPI from "../CustomComponentForAPI";
import { connect } from "react-redux";
import __each from "lodash/each";
import __uniq from "lodash/uniq";
import __map from "lodash/map";
import __indexOf from "lodash/indexOf";
import { saveSelectedUniversities, saveFilterConfig } from "../../actions/filter";
......@@ -31,34 +29,44 @@ const styles = theme => ({
class Filter extends CustomComponentForAPI {
saveContriesFilterConfig(state) {
this.props.saveConfig({ contriesFilter: state });
saveCountriesFilterConfig(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() {
const { mainCampuses } = this.getAllReadData();
const mainCampuses = this.getReadData("mainCampuses");
let res = [];
__each(mainCampuses, (campus) => {
const campusFull = this.joinCampus(campus);
res.push(campusFull.country);
// use of map to get only each country once
let res = new Map();
// 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) {
const { mainCampuses } = this.getAllReadData();
const listOfCountries = __map(selection, (s) => s.id);
const mainCampuses = this.getReadData("mainCampuses"),
listOfCountries = __map(selection, (s) => s.id);
let selected_universities = [];
__each(mainCampuses, (campus) => {
let selectedUniversities = [];
mainCampuses.forEach(campus => {
const campusFull = this.joinCampus(campus);
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() {
......@@ -74,8 +82,8 @@ class Filter extends CustomComponentForAPI {
<DownshiftMultiple
options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)}
config={this.props.contriesFilterConfig}
onComponentUnmount={(state) => this.saveCountriesFilterConfig(state)}
config={this.props.countriesFilterConfig}
/>
</ExpansionPanelDetails>
</ExpansionPanel>
......@@ -91,7 +99,7 @@ const mapStateToProps = (state) => {
mainCampuses: state.api.mainCampusesAll,
cities: state.api.citiesAll,
countries: state.api.countriesAll,
contriesFilterConfig: state.app.filter.contriesFilter
countriesFilterConfig: state.app.filter.countriesFilter
};
};
......
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Marker, Popup } from "react-leaflet";
// import MarkerClusterGroup from 'react-leaflet-markercluster';
......@@ -7,44 +8,61 @@ import UnivPopupContent from "./UnivPopupContent";
import getActions from "../../api/getActions";
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 {
customRender() {
let { universities,
const { universities,
mainCampuses,
countries,
cities } = this.getAllReadData();
let selected_main_campus = [];
for (let main_campus_pk in mainCampuses) {
let campus = mainCampuses[main_campus_pk];
let univ = universities[campus.university];
if (univ && campus) {
let city = cities[campus.city];
let country = countries[city.country];
selected_main_campus.push({
univ_name: univ.name,
univ_logo: univ.logo,
univ_city: city.name,
univ_country: country.name,
// some conversions for optimization (faster search of element in a map)
const universitiesMap = arrayOfInstancesToMap(universities),
countriesMap = arrayOfInstancesToMap(countries),
citiesMap = arrayOfInstancesToMap(cities);
let mainCampusesSelection = [];
// Merge the data and add it to the selection
mainCampuses.forEach(campus => {
const univ = universitiesMap.get(campus.university);
if (campus && univ) {
const city = citiesMap.get(campus.city),
country = countriesMap.get(city.country);
mainCampusesSelection.push({
univName: univ.name,
univLogo: univ.logo,
univCity: city.name,
univCountry: country.name,
lat: campus.lat,
lon: campus.lon,
id: univ.id
});
}
}
});
return (
selected_main_campus.map((el) => (
<Marker key={el.id} position={[el.lat, el.lon]}>
mainCampusesSelection.map((el, idx) => (
<Marker key={idx} position={[el.lat, el.lon]}>
<Popup closeButton={false} >
<UnivPopupContent
name={el.univ_name}
logo={el.univ_logo}
city={el.univ_city}
country={el.univ_country}
name={el.univName}
logo={el.univLogo}
city={el.univCity}
country={el.univCountry}
univId={el.id}
/>
</Popup>
......@@ -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) => {
return {
......
......@@ -74,10 +74,11 @@ class PageUniversity extends CustomComponentForAPI {
customComponentDidUpdate() {
if (this.props.universities.readSucceeded.readAt) {
// we have the university data
const { universities } = this.getAllReadData();
const { match, universityBeingViewed } = this.props;
const requestedUniversity = match.params.id;
if (requestedUniversity != "undefined" && requestedUniversity in universities && requestedUniversity != universityBeingViewed) {
const universities = this.getReadData("universities"),
{ match, universityBeingViewed } = this.props,
requestedUniversity = match.params.id;
if (requestedUniversity != "undefined" && universities.find(univ => univ.id == requestedUniversity) && requestedUniversity != universityBeingViewed) {
this.props.saveUniversityInView(requestedUniversity);
}
}
......@@ -85,9 +86,9 @@ class PageUniversity extends CustomComponentForAPI {
}
customRender() {
const { match, universityBeingViewed } = this.props;
const requestedUnivId = match.params.id;
const { universities } = this.getAllReadData();
const { match, universityBeingViewed } = this.props,
requestedUnivId = match.params.id,
universities = this.getReadData("universities");
if (requestedUnivId == "undefined") {
if (universityBeingViewed != null) {
......@@ -96,7 +97,7 @@ class PageUniversity extends CustomComponentForAPI {
return renderFirstTimeHere();
}
} else {
if (requestedUnivId in universities) {
if (universities.find(univ => univ.id == requestedUnivId)) {
return renderDefaultView(requestedUnivId);
} else {
return renderUniversityNotFound();
......
......@@ -5,7 +5,6 @@ import CustomComponentForAPI from "../CustomComponentForAPI";
import { connect } from "react-redux";
import fuzzysort from "fuzzysort";
import UnivList from "./UnivList";
import __map from "lodash/map";
// import { saveSelectedUniversities, saveSearchConfig } from '../../actions/filter';
import withStyles from "@material-ui/core/styles/withStyles";
......@@ -27,15 +26,14 @@ class Search extends CustomComponentForAPI {
}
getSuggestions(value) {
const { universities } = this.getAllReadData();
const filter = fuzzysort.go(value, __map(universities, (univ) => univ), { keys: ["name", "acronym"] });
let out;
if (filter.length > 0) {
out = __map(filter, (item) => item.obj);
} else {
out = universities;
const universities = this.getReadData("universities"),
filter = fuzzysort.go(value, universities, { keys: ["name", "acronym"] });
let suggestions = filter.map(item => item.obj);
if (suggestions.length == 0) {
suggestions = universities;
}
this.setState({ suggestions: out });
this.setState({ suggestions });
}
......
......@@ -126,7 +126,7 @@ class UnivList extends React.Component {
UnivList.propTypes = {
classes: 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,
};
......
......@@ -96,7 +96,7 @@ CountryDri.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
countryId: PropTypes.string.isRequired,
countries: PropTypes.object.isRequired,
countries: PropTypes.array.isRequired,
};
const mapStateToProps = (state) => {
......
......@@ -116,8 +116,8 @@ CountryScholarships.propTypes = {
theme: PropTypes.object.isRequired,
countryId: PropTypes.string.isRequired,
country: PropTypes.object.isRequired,
countries: PropTypes.object.isRequired,
currencies: PropTypes.object.isRequired,
countries: PropTypes.array.isRequired,
currencies: PropTypes.array.isRequired,
};
const mapStateToProps = (state) => {
......
......@@ -99,7 +99,7 @@ UniversityDri.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
univId: PropTypes.string.isRequired,
universities: PropTypes.object.isRequired,
universities: PropTypes.array.isRequired,
};
const mapStateToProps = (state) => {
......
......@@ -113,8 +113,8 @@ UniversityScholarships.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
univId: PropTypes.string.isRequired,
universities: PropTypes.object.isRequired,
currencies: PropTypes.object.isRequired,
universities: PropTypes.array.isRequired,
currencies: PropTypes.array.isRequired,
};
const mapStateToProps = (state) => {
......
......@@ -52,7 +52,7 @@ class Scholarship extends React.Component {
getSymbol() {
const { currencies, currency } = this.props;
const currencyInfo = currencies[currency];
const currencyInfo = currencies.find(c => c.id == currency);
const { symbol } = currencyInfo;
if (symbol) {
......@@ -64,7 +64,7 @@ class Scholarship extends React.Component {
convertAmountToEur(amount) {
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);
}
......@@ -153,7 +153,7 @@ Scholarship.propTypes = {
otherAdvantages: PropTypes.string,
amountMin: PropTypes.string,
amountMax: PropTypes.string,
currencies: PropTypes.object.isRequired,
currencies: PropTypes.array.isRequired,
};
export default withStyles(styles, { withTheme: true })(Scholarship);
......@@ -12,11 +12,9 @@ import getActions from "../../../api/getActions";
class UnivInfoProvider extends CustomComponentForAPI {
customRender() {
const { city, country } = this.getUnivCityAndCountry(this.props.univId);
const universities = this.getReadData("universities");
const countries = this.getReadData("countries");
const currencies = this.getReadData("currencies");
const { univId } = this.props,
{ city, country } = this.getUnivCityAndCountry(univId),
{ universities, countries, currencies } = this.getReadDataFor(["universities", "countries", "currencies"]),
countryId = country.id,
cityId = city.id;
......
......@@ -12,7 +12,7 @@ export function saveSelectedUniversities(state = [], action) {
}
}
export function saveFilterConfig(state = { contriesFilter: { selectedItems: [], inputValue: "" } }, action) {
export function saveFilterConfig(state = { countriesFilter: { selectedItems: [], inputValue: "" } }, action) {
switch (action.type) {
case SAVE_FILTER_CONFIG:
return Object.assign({}, state, action.config);
......
......@@ -12,7 +12,7 @@ export function saveSelectedUniversities(state = [], action) {
}
}
export function saveFilterConfig(state = { contriesFilter: { selectedItems: [], inputValue: "" } }, action) {
export function saveFilterConfig(state = { countriesFilter: { selectedItems: [], inputValue: "" } }, action) {
switch (action.type) {
case SAVE_FILTER_CONFIG:
return Object.assign({}, state, action.config);
......
/**
* Function that converts an array of instances of a model to a map.
*
* @export
* @param {Array} arr
* @param {string} [attrToUseAsKey="id"]
* @returns {Map}
*/
export default function arrayOfInstancesToMap(arr, attrToUseAsKey = "id") {
let out = new Map;
arr.forEach(instance => out.set(instance[attrToUseAsKey], instance));
return out;
}
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