Commit ccd63b4c authored by Florent Chehab's avatar Florent Chehab

Comments added in all but university generally speaking + code cleaning

parent ebe42bc4
Pipeline #35761 passed with stages
in 9 minutes and 32 seconds
...@@ -60,8 +60,7 @@ class App extends CustomComponentForAPI { ...@@ -60,8 +60,7 @@ class App extends CustomComponentForAPI {
const { classes } = this.props; const { classes } = this.props;
return ( return (
<React.Fragment> <>
<CssBaseline /> <CssBaseline />
<div className={classes.root}> <div className={classes.root}>
<Drawer <Drawer
...@@ -133,7 +132,7 @@ class App extends CustomComponentForAPI { ...@@ -133,7 +132,7 @@ class App extends CustomComponentForAPI {
</main> </main>
</div> </div>
</React.Fragment> </>
); );
} }
} }
......
...@@ -6,7 +6,7 @@ import { connect } from "react-redux"; ...@@ -6,7 +6,7 @@ import { connect } from "react-redux";
/** /**
* Function to enable the FullScreenDialog to have nice transitions * Component to enable the FullScreenDialog to have nice transitions
* @returns * @returns
*/ */
function Transition(props) { function Transition(props) {
...@@ -37,8 +37,8 @@ class FullScreenDialog extends Component { ...@@ -37,8 +37,8 @@ class FullScreenDialog extends Component {
FullScreenDialog.propTypes = { FullScreenDialog.propTypes = {
open: PropTypes.bool.isRequired, open: PropTypes.bool.isRequired, // Is the full screen dialog open
innerNodes: PropTypes.node.isRequired, innerNodes: PropTypes.node.isRequired, // content of the fullScreen Dialog
}; };
// Get the props from the redux store. // Get the props from the redux store.
......
/** /**
* This file contains the general site template * This file contains elements of the site template
*/ */
import React from "react"; import React from "react";
......
...@@ -11,53 +11,61 @@ import withStyles from "@material-ui/core/styles/withStyles"; ...@@ -11,53 +11,61 @@ import withStyles from "@material-ui/core/styles/withStyles";
import compose from "recompose/compose"; import compose from "recompose/compose";
/**
* Component to render an "alert" that prevents all other interaction on the site.
*
* @class Alert
* @extends {React.Component}
*/
class Alert extends React.Component { class Alert extends React.Component {
render() { render() {
const { classes } = this.props; const { classes } = this.props;
if (!this.props.open) {
return <></>;
}
return ( return (
<Dialog <Dialog
open={this.props.open} open={true}
> >
{ {
this.props.open ? <>
<> <DialogTitle>{this.props.title}</DialogTitle>
<DialogTitle>{this.props.title}</DialogTitle> <DialogContent>
<DialogContent> <DialogContentText style={{ whiteSpace: "pre-wrap" }}>
<DialogContentText style={{ whiteSpace: "pre-wrap" }}> {this.props.description}
{this.props.description} </DialogContentText>
</DialogContentText> </DialogContent>
</DialogContent> <DialogActions>
<DialogActions> {
{ this.props.info ?
this.props.info ? <Button onClick={() => { this.props.handleClose(); this.props.handleResponse(); }} color="primary">
<Button onClick={() => { this.props.handleClose(); this.props.handleResponse(); }} color="primary"> {this.props.infoText}
{this.props.infoText} </Button>
:
<div>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(false); }}
color='secondary'
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
>
{this.props.disagreeText}
</Button>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(true); }}
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
variant='outlined'
color="primary"
autoFocus
>
{this.props.agreeText}
</Button> </Button>
: </div>
<div> }
<Button </DialogActions>
onClick={() => { this.props.handleClose(); this.props.handleResponse(false); }} </>
color='secondary'
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
>
{this.props.disagreeText}
</Button>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(true); }}
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
variant='outlined'
color="primary"
autoFocus
>
{this.props.agreeText}
</Button>
</div>
}
</DialogActions>
</>
:
<></>
} }
</Dialog > </Dialog >
); );
...@@ -66,16 +74,16 @@ class Alert extends React.Component { ...@@ -66,16 +74,16 @@ class Alert extends React.Component {
Alert.propTypes = { Alert.propTypes = {
open: PropTypes.bool.isRequired, open: PropTypes.bool.isRequired, // is the alert open
info: PropTypes.bool.isRequired, title: PropTypes.string.isRequired, // Title display on the vindow
title: PropTypes.string.isRequired, description: PropTypes.string, // textual description (below the titile)
agreeText: PropTypes.string.isRequired, info: PropTypes.bool.isRequired, // If it's just un info alert with one button
disagreeText: PropTypes.string.isRequired, infoText: PropTypes.string.isRequired, // content of the alert when it's an info
infoText: PropTypes.string.isRequired, agreeText: PropTypes.string.isRequired, // Text display in the "agree" button
description: PropTypes.string, disagreeText: PropTypes.string.isRequired, // Text display in the "disagree" button
handleClose: PropTypes.func.isRequired, multilineButtons: PropTypes.bool.isRequired, // should the agree/disagree button displayed on multiple lines
handleResponse: PropTypes.func.isRequired, handleClose: PropTypes.func.isRequired, // function called when the alert is closed
multilineButtons: PropTypes.bool.isRequired, handleResponse: PropTypes.func.isRequired, // function called allong the previous one with false if the user disagreed and true otherwise. Or no parameters if info.
classes: PropTypes.object.isRequired classes: PropTypes.object.isRequired
}; };
......
...@@ -3,6 +3,44 @@ import Loading from "./Loading"; ...@@ -3,6 +3,44 @@ import Loading from "./Loading";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { successActionsWithReads, getLatestRead } from "../../redux/api/utils"; import { successActionsWithReads, getLatestRead } from "../../redux/api/utils";
/**
* Custom react component to be used when called to the api are required to display data of the component.
*
* When extending, you shouldn't use the `render` function but the `customRender` (that will be called once all the data is ready)
*
* Also, when connecting your component to redux, you should use a `mapDispatchToProps` such as this one.
* What is important is that "api" related elements are under the `api` key for optimization purposes.
* NB: if you don't do it this way, the data won't be fetched.
*
```JS
const mapDispatchToProps = (dispatch) => {
return {
api: {
universities: () => dispatch(getActions("universities").readAll()),
},
saveUniversityInView: (univId) => dispatch(saveUniversityBeingViewed(univId))
};
};
```
*
* IMPORTANT: to use some of the function contained in this class, you need to have a matching `mapStateToProps` function for read
* elements: the key of the object should match the one in `api`.
*
* Here `universities` is both in the `api` of `mapDispatchToProps` and in `mapStateToProps`.
```JS
const mapStateToProps = (state) => {
return {
universities: state.api.universitiesAll,
universityBeingViewed: state.app.universityBeingViewed
};
};
```
*
*
* @abstract
* @class CustomComponentForAPI
* @extends {Component}
*/
class CustomComponentForAPI extends Component { class CustomComponentForAPI extends Component {
customErrorHandlers = {} customErrorHandlers = {}
// __apiAttr should be an object // __apiAttr should be an object
...@@ -52,11 +90,12 @@ class CustomComponentForAPI extends Component { ...@@ -52,11 +90,12 @@ class CustomComponentForAPI extends Component {
/** /**
* Function to use instead of `render`. * Function to use instead of `render`.
* *
* @virtual
* @memberof CustomComponentForAPI * @memberof CustomComponentForAPI
*/ */
customRender() { customRender() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Dev: you forget to define the `customRender` function that is used when rendering within a subClass of CustomComponentForAPI"); throw new Error("Dev: you forget to define the `customRender` function that is used when rendering within a subClass of CustomComponentForAPI");
} }
// End of react functions override // End of react functions override
...@@ -270,7 +309,7 @@ class CustomComponentForAPI extends Component { ...@@ -270,7 +309,7 @@ class CustomComponentForAPI extends Component {
} }
CustomComponentForAPI.propTypes = { CustomComponentForAPI.propTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired // should be given through redux connect of `mapDispatchToProps`
}; };
export default CustomComponentForAPI; export default CustomComponentForAPI;
...@@ -188,8 +188,10 @@ const renderers = { ...@@ -188,8 +188,10 @@ const renderers = {
/** /**
* Custom Markdown component renderer to make use of material UI * Custom Markdown component renderer to make use of material UI
* *
* We don't make use of Custom Component for API since currencies should be loaded at app startup. * A custom tag for currency was adde: `:100.2CHF:` would be understood as `100 CHF (~88€)` for instance.
* We don't need to fetch them here *
* NB: We don't make use of Custom Component for API since currencies should be loaded at app startup.
* We don't need to fetch them here.
* *
* @class Markdown * @class Markdown
* @extends {Component} * @extends {Component}
......
...@@ -3,7 +3,7 @@ import Badge from "@material-ui/core/Badge"; ...@@ -3,7 +3,7 @@ import Badge from "@material-ui/core/Badge";
/** /**
* Custom component to render a badge only if there is something interesting to display * Custom component to render a badge (little circle with a number in it) only if there is something interesting to display
* *
* @export * @export
* @param {object} props * @param {object} props
......
...@@ -22,7 +22,7 @@ const styles = theme => { ...@@ -22,7 +22,7 @@ const styles = theme => {
/** /**
* Component to render the links in custom manner * Component to render the links in custom manner
* *
*/ */
export default withStyles(styles, { withTheme: true })(({ classes, ...props }) => ( export default withStyles(styles, { withTheme: true })(({ classes, ...props }) => (
<a href={props.href} className={classes.link} target="_blank" rel="noopener noreferrer"> <a href={props.href} className={classes.link} target="_blank" rel="noopener noreferrer">
{props.children} {props.children}
......
...@@ -34,12 +34,12 @@ class ThemeProvider extends CustomComponentForAPI { ...@@ -34,12 +34,12 @@ class ThemeProvider extends CustomComponentForAPI {
return; return;
} }
const currentTheme = this.state.theme; const currentTheme = this.state.theme,
const themeSavedInTheApp = this.props.themeSavedInTheApp.theme; themeSavedInTheApp = this.props.themeSavedInTheApp.theme,
const themeSavedInTheAppAt = this.props.themeSavedInTheApp.savedAt; themeSavedInTheAppAt = this.props.themeSavedInTheApp.savedAt,
const userDataAndTime = this.getReadDataAndTime("userData"); userDataAndTime = this.getReadDataAndTime("userData"),
const themeOnServer = userDataAndTime.data.config.theme; themeOnServer = userDataAndTime.data.config.theme,
const themeOnServerRetrievedAt = userDataAndTime.readAt; themeOnServerRetrievedAt = userDataAndTime.readAt;
if (!themeOnServer) { if (!themeOnServer) {
// config on server is raw, we need to add the default one. // config on server is raw, we need to add the default one.
......
/**
* React context to hold visibility information for optimization purposes.
*/
import React from "react"; import React from "react";
const VisibilityContext = React.createContext(); const VisibilityContext = React.createContext();
export default VisibilityContext; export default VisibilityContext;
\ No newline at end of file
...@@ -105,11 +105,13 @@ class Form extends Component { ...@@ -105,11 +105,13 @@ class Form extends Component {
.combine(this.hasFormLevelErrors()); .combine(this.hasFormLevelErrors());
} }
/** /**
* Function to check for errors in the form * Function to check for errors in the form
* *
* Can be override in sub classes * Can be override in sub classes
* *
* @virtual
* @returns {CustomError} * @returns {CustomError}
* @memberof Form * @memberof Form
*/ */
......
...@@ -26,12 +26,29 @@ class LocalizedUtils extends DateFnsUtils { ...@@ -26,12 +26,29 @@ class LocalizedUtils extends DateFnsUtils {
} }
} }
/**
* Form field for dates
*
* @class DateField
* @extends {Field}
*/
class DateField extends Field { class DateField extends Field {
/**
* @override
* @returns
* @memberof DateField
*/
serializeFromField() { serializeFromField() {
return dateToDateStr(this.state.value); return dateToDateStr(this.state.value);
} }
/**
* Specific error detection for this field
* @override
* @returns {CustomError}
* @memberof MarkdownField
*/
getError() { getError() {
const date = this.state.value; const date = this.state.value;
let messages = Array(); let messages = Array();
...@@ -42,12 +59,20 @@ class DateField extends Field { ...@@ -42,12 +59,20 @@ class DateField extends Field {
return new CustomError(messages); return new CustomError(messages);
} }
handleDateChange = (date) => { /**
* @param {Date} date
* @memberof DateField
*/
handleDateChange(date) {
this.setState({ this.setState({
value: date, value: date,
}); });
} }
/**
* @override
* @memberof DateField
*/
renderField() { renderField() {
return ( return (
<MuiPickersUtilsProvider utils={LocalizedUtils} locale={frLocale}> <MuiPickersUtilsProvider utils={LocalizedUtils} locale={frLocale}>
...@@ -55,7 +80,7 @@ class DateField extends Field { ...@@ -55,7 +80,7 @@ class DateField extends Field {
clearable clearable
format="d MMM yyyy" format="d MMM yyyy"
value={this.state.value} value={this.state.value}
onChange={this.handleDateChange} onChange={(d) => this.handleDateChange(d)}
clearLabel="vider" clearLabel="vider"
cancelLabel="annuler" cancelLabel="annuler"
leftArrowIcon={<KeyboardArrowLeftIcon />} leftArrowIcon={<KeyboardArrowLeftIcon />}
......
...@@ -36,6 +36,13 @@ class Field extends PureComponent { ...@@ -36,6 +36,13 @@ class Field extends PureComponent {
}; };
} }
/**
* Sets the new state. Before it's done it replaces the value by null if appropriate.
*
* @override
* @param {object} newState
* @memberof Field
*/
setState(newState) { setState(newState) {
let state = Object.assign({}, newState); let state = Object.assign({}, newState);
if (typeof this.defaultNullValue !== "undefined") { if (typeof this.defaultNullValue !== "undefined") {
...@@ -45,6 +52,9 @@ class Field extends PureComponent { ...@@ -45,6 +52,9 @@ class Field extends PureComponent {
} }
/** /**
* What is error state of the field.
*
* @virtual
* @returns {CustomError} * @returns {CustomError}
* @memberof Field * @memberof Field
*/ */
...@@ -79,12 +89,19 @@ class Field extends PureComponent { ...@@ -79,12 +89,19 @@ class Field extends PureComponent {
* *
* MUST BE OVERRIDE * MUST BE OVERRIDE
* *
* @virtual
* @memberof Field * @memberof Field
*/ */
renderField() { renderField() {
throw new Error("This method should be override in subclass of Field"); throw new Error("This method should be override in subclass of Field");
} }
/**
* Default react render function
*
* @returns
* @memberof Field
*/
render() { render() {
return ( return (
<FieldWrapper <FieldWrapper
...@@ -105,11 +122,11 @@ Field.defaultProps = { ...@@ -105,11 +122,11 @@ Field.defaultProps = {
}; };
Field.propTypes = { Field.propTypes = {
required: PropTypes.bool.isRequired, required: PropTypes.bool.isRequired, // is the field required ?
label: PropTypes.string, label: PropTypes.string, // text to go along the field
value: PropTypes.isRequired, value: PropTypes.isRequired, // value of the field
form: PropTypes.oneOf([Form]).isRequired, form: PropTypes.instanceOf(Form).isRequired, // (reference of to the) form containing the field
fieldMapping: PropTypes.string.isRequired, fieldMapping: PropTypes.string.isRequired, // name of the field in the data
}; };
export default Field; export default Field;
...@@ -11,7 +11,7 @@ import CustomError from "../../common/CustomError"; ...@@ -11,7 +11,7 @@ import CustomError from "../../common/CustomError";
/** /**
* Wrapper for fields in form to handle labels and visual feedback * Wrapper for fields in forms to handle labels and visual feedback
* *
* @class FieldWrapper * @class FieldWrapper
* @extends {PureComponent} * @extends {PureComponent}
...@@ -42,10 +42,10 @@ FieldWrapper.defaultProps = { ...@@ -42,10 +42,10 @@ FieldWrapper.defaultProps = {
FieldWrapper.propTypes = { FieldWrapper.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired,
classes: PropTypes.object.isRequired,
required: PropTypes.bool.isRequired, required: PropTypes.bool.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired, // Error state of the field
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
classes: PropTypes.object.isRequired,
}; };
......
...@@ -13,9 +13,24 @@ import TextLink from "../../common/TextLink"; ...@@ -13,9 +13,24 @@ import TextLink from "../../common/TextLink";
import CustomError from "../../common/CustomError"; import CustomError from "../../common/CustomError";
/**
* Form field for markdown elements.
*
* A `maxLength` can be set to limit the length of the text.
*
* @class MarkdownField
* @extends {Field}
*/
class MarkdownField extends Field { class MarkdownField extends Field {
defaultNullValue = ""; defaultNullValue = "";
/**
* Specific error detection for this field
*
* @override
* @returns {CustomError}
* @memberof MarkdownField
*/
getError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
...@@ -29,6 +44,9 @@ class MarkdownField extends Field { ...@@ -29,6 +44,9 @@ class MarkdownField extends Field {
return new CustomError(messages); return new CustomError(messages);
} }
/**
* @memberof MarkdownField
*/
handleChangeValue = (val) => { handleChangeValue = (val) => {
let value = val; let value = val;
const { maxLength } = this.props; const { maxLength } = this.props;
...@@ -40,6 +58,11 @@ class MarkdownField extends Field { ...@@ -40,6 +58,11 @@ class MarkdownField extends Field {
} }
/**
* @override
* @returns
* @memberof MarkdownField
*/
renderField() { renderField() {
const valFromState = this.state.value, const valFromState = this.state.value,
......
...@@ -11,6 +11,13 @@ import Field from "./Field"; ...@@ -11,6 +11,13 @@ import Field from "./Field";
import CustomError from "../../common/CustomError"; import CustomError from "../../common/CustomError";
/**
* Form field for multi selects.
*
*
* @class MultiSelectField
* @extends {Field}
*/
class MultiSelectField extends Field { class MultiSelectField extends Field {
constructor(props) { constructor(props) {
...@@ -19,6 +26,11 @@ class MultiSelectField extends Field { ...@@ -19,6 +26,11 @@ class MultiSelectField extends Field {
props.options.map((opt) => this.optionsByValue[opt.value] = opt.label); props.options.map((opt) => this.optionsByValue[opt.value] = opt.label);
} }
/**
* @override
* @returns
* @memberof MultiSelectField
*/
getError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
...@@ -28,10 +40,15 @@ class MultiSelectField extends Field { ...@@ -28,10 +40,15 @@ class MultiSelectField extends Field {
return new CustomError(messages); return new CustomError(messages);
} }
handleChangeValue = (value) => { handleChangeValue(value) {
this.setState({ value }); this.setState({ value });
} }