Commit 8bc54c74 authored by Florent Chehab's avatar Florent Chehab

refacto(front): more switch from custom component to hook & tweaks & bug fix

parent db52ddd0
......@@ -34,13 +34,15 @@ import CityService from "../../services/data/CityService";
import CountryService from "../../services/data/CountryService";
import CurrencyService from "../../services/data/CurrencyService";
import LanguageService from "../../services/data/LanguageService";
import FilterService from "../../services/FilterService";
const SERVICES_TO_INITIALIZE = [
UniversityService,
CityService,
CountryService,
CurrencyService,
LanguageService
LanguageService,
FilterService
];
// import PageFiles from "../pages/PageFiles";
......
This diff is collapsed.
import InfoIcon from "@material-ui/icons/InfoOutlined";
import Typography from "@material-ui/core/Typography";
import React from "react";
import { makeStyles } from "@material-ui/styles";
import { useSelector } from "react-redux";
const useStyles = makeStyles(theme => ({
infoFilter: {
marginLeft: theme.spacing(2),
display: "inherit"
}
}));
function getMessage(selectedUniversities) {
const hasSelection = selectedUniversities !== null;
if (!hasSelection) return "(Aucun filtre est actif)";
const base = "(Un filtre est actif — ";
const nbSelection = selectedUniversities.length;
if (nbSelection === 0) return `${base}aucune université ne correspond)`;
if (nbSelection === 1) return `${base}1 université correspond)`;
return `${base}${nbSelection} universités correspondent)`;
}
function FilterStatus() {
const classes = useStyles();
const selectedUniversities = useSelector(
state => state.app.selectedUniversities
);
const hasSelection = selectedUniversities !== null;
return (
<div className={classes.infoFilter}>
<InfoIcon color={hasSelection ? "primary" : "disabled"} />
&nbsp;
<Typography
className={classes.caption}
color={hasSelection ? "primary" : "textSecondary"}
>
<em>{getMessage(selectedUniversities)}</em>
</Typography>
</div>
);
}
export default React.memo(FilterStatus);
import React from "react";
import React, { useEffect } from "react";
import { compose } from "recompose";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Typography from "@material-ui/core/Typography";
import { withStyles } from "@material-ui/styles";
import { makeStyles } from "@material-ui/styles";
import { withErrorBoundary } from "../common/ErrorBoundary";
import { withPaddedPaper } from "./shared";
import EditModuleGeneralPreviousExchangeFeedback from "../university/modules/previousExchangeFeedback/edit/EditModuleGeneralFeedback";
import CustomComponentForAPI from "../common/CustomComponentForAPI";
import getActions from "../../redux/api/getActions";
import RequestParams from "../../redux/api/RequestParams";
import { getUnivName } from "./PageMyExchanges";
import EditModuleCoursesFeedback from "../university/modules/previousExchangeFeedback/edit/EditModuleCoursesFeedback";
import CustomLink from "../common/CustomLink";
import APP_ROUTES from "../../config/appRoutes";
import UniversityService from "../../services/data/UniversityService";
import withNetworkWrapper, {
getApiPropTypes,
NetWrapParam
} from "../../hoc/withNetworkWrapper";
function getExchangeId(props) {
return parseInt(props.match.params.exchangeId, 10);
function getExchangeId(match) {
return parseInt(match.params.exchangeId, 10);
}
const useStyles = makeStyles(theme => ({
spacer: {
marginBottom: theme.spacing(4)
},
container: {
maxWidth: 1200,
margin: "0 auto"
}
}));
const buildParams = match =>
RequestParams.Builder.withId(getExchangeId(match)).build();
/**
* Page that handle the modification of exchange feedbacks.
*/
class PageEditExchangeFeedbacks extends CustomComponentForAPI {
/**
* @override
*/
enableSmartDataRefreshOnComponentDidUpdate = true;
function PageEditExchangeFeedbacks({ match, exchange, api }) {
const classes = useStyles();
const id = getExchangeId(match);
apiParams = {
exchange: ({ props }) =>
RequestParams.Builder.withId(getExchangeId(props)).build()
};
useEffect(() => {
api.exchange.setParams(buildParams(match));
}, [id]);
customRender() {
const { classes } = this.props;
const id = getExchangeId(this.props);
const exchange = this.getLatestReadData("exchange");
const univId = exchange.university;
const univName = getUnivName(univId);
const univId = exchange.university;
const univName = UniversityService.getUnivName(univId);
if (id) {
return (
<>
<Typography variant="h4">
{univId ? (
<CustomLink to={APP_ROUTES.forUniversity(univId)}>
{univName}
</CustomLink>
) : (
univName
)}
&nbsp; ({exchange.semester.toUpperCase()}
{exchange.year})
</Typography>
<Typography variant="h5">
Page d'édition du feedback concernant votre échange.
</Typography>
<Typography>
<em>
Les informations issues de l'ENT (ECTS, etc.) sont modifiables
uniquement depuis l'ENT.
</em>
</Typography>
if (id) {
return (
<>
<Typography variant="h4">
{univId ? (
<CustomLink to={APP_ROUTES.forUniversity(univId)}>
{univName}
</CustomLink>
) : (
univName
)}
&nbsp; ({exchange.semester.toUpperCase()}
{exchange.year})
</Typography>
<Typography variant="h5">
Page d'édition du feedback concernant votre échange.
</Typography>
<Typography>
<em>
Les informations issues de l'ENT (ECTS, etc.) sont modifiables
uniquement depuis l'ENT.
</em>
</Typography>
<div className={classes.spacer} />
<div className={classes.container}>
<EditModuleGeneralPreviousExchangeFeedback exchangeId={id} />
<div className={classes.spacer} />
<div className={classes.container}>
<EditModuleGeneralPreviousExchangeFeedback exchangeId={id} />
<div className={classes.spacer} />
<EditModuleCoursesFeedback exchangeId={id} />
</div>
</>
);
}
return <></>;
<EditModuleCoursesFeedback exchangeId={id} />
</div>
</>
);
}
return <></>;
}
PageEditExchangeFeedbacks.propTypes = {
......@@ -83,35 +89,16 @@ PageEditExchangeFeedbacks.propTypes = {
exchangeId: PropTypes.string.isRequired
})
}).isRequired,
classes: PropTypes.object.isRequired
exchange: PropTypes.object.isRequired,
api: getApiPropTypes("exchange").isRequired
};
const mapStateToProps = state => ({
exchange: state.api.exchangesOne
});
const mapDispatchToProps = dispatch => ({
api: {
exchange: params => dispatch(getActions("exchanges").readOne(params))
}
});
const styles = theme => ({
spacer: {
marginBottom: theme.spacing(4)
},
container: {
maxWidth: 1200,
margin: "0 auto"
}
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withNetworkWrapper([
new NetWrapParam("exchanges", "one", "exchange", props =>
buildParams(props.match)
)
]),
withPaddedPaper(),
withStyles(styles, { withTheme: true }),
withErrorBoundary()
)(PageEditExchangeFeedbacks);
......@@ -16,12 +16,13 @@ import { withPaddedPaper } from "./shared";
*/
function PageLists(props) {
const { listId } = props.match.params;
const parsedListId = parseInt(listId, 10);
return (
<>
{typeof listId === "undefined" ? (
{isNaN(parsedListId) ? (
<SelectList />
) : (
<ViewList listId={listId} />
<ViewList listId={parsedListId} />
)}
</>
);
......
import React from "react";
import React, { useCallback } from "react";
import { compose } from "recompose";
import { connect } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import Typography from "@material-ui/core/Typography";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemIcon from "@material-ui/core/ListItemIcon";
......@@ -8,10 +8,8 @@ import ListItem from "@material-ui/core/ListItem";
import List from "@material-ui/core/List";
import SunnyIcon from "@material-ui/icons/WbSunny";
import WinterIcon from "@material-ui/icons/AcUnit";
import { withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import Button from "@material-ui/core/Button";
import { getLatestReadDataFromStore } from "../../redux/api/utils";
import APP_ROUTES from "../../config/appRoutes";
import Loading from "../common/Loading";
import { withErrorBoundary } from "../common/ErrorBoundary";
......@@ -20,160 +18,130 @@ import { CURRENT_USER_ID } from "../../config/user";
import RequestParams from "../../redux/api/RequestParams";
import getActions from "../../redux/api/getActions";
import compareSemesters from "../../utils/compareSemesters";
import CustomComponentForAPI from "../common/CustomComponentForAPI";
import TextLink from "../common/TextLink";
import UniversityService from "../../services/data/UniversityService";
import useInvalidateAll from "../../hooks/useInvalidateAll";
import NavigationService from "../../services/NavigationService";
import withNetworkWrapper, { NetWrapParam } from "../../hoc/withNetworkWrapper";
/**
*
* @param {number} univId
* @returns {string} the university name from the redux store
*/
export function getUnivName(univId) {
const univ = getLatestReadDataFromStore("universitiesAll").find(
u => u.id === univId
);
function Introduction() {
const dispatch = useDispatch();
const invalidateData = useInvalidateAll("exchanges");
const requestReload = useCallback(() => {
dispatch(
getActions("updateStudentExchanges").readAll(
RequestParams.Builder.withOnSuccessCallback(() =>
invalidateData()
).build()
)
);
}, []);
if (typeof univ === "undefined")
return "(Université pas encore répertoriée sur REX-DRI)";
return univ.name;
return (
<>
<div>
<Typography>
Vous avez effectué un échange qui n'est pas listé ci-dessous ?
Vérifier que vous avez&nbsp;
<TextLink href="https://webapplis.utc.fr/etudiant/suiviOutgoing/autorisationsREX.faces">
donné les autorisations de partage d'informations (
<b>cours et login</b>) avec la plateforme REX-DRI depuis l'ENT.
</TextLink>
&nbsp;Puis, cliquez sur le bouton ci-dessous.
</Typography>
</div>
<div
style={{
margin: "0 auto",
width: "fit-content"
}}
>
<Button variant="outlined" color="secondary" onClick={requestReload}>
J'ai donné toutes les autorisations, recharger mes échanges depuis
l'ENT
</Button>
</div>
</>
);
}
/**
* Page that lists the previous exchange of a user
*/
class PageMyExchanges extends CustomComponentForAPI {
requestReload() {
this.props.requestUpdateFromEnt(
RequestParams.Builder.withOnSuccessCallback(() =>
this.props.invalidateExchanges()
).build()
);
}
function PageMyExchanges({ exchanges }) {
const isReloadingFromEnt = useSelector(
state => state.api.updateStudentExchangesAll.isReading
);
renderReloadInformation() {
exchanges.sort(
(a, b) => -compareSemesters(a.year, a.semester, b.year, b.semester)
);
if (isReloadingFromEnt) {
return (
<>
<div>
<Typography>
Vous avez effectué un échange qui n'est pas listé ci-dessous ?
Vérifier que vous avez&nbsp;
<TextLink href="https://webapplis.utc.fr/etudiant/suiviOutgoing/autorisationsREX.faces">
donné les autorisations de partage d'informations (
<b>cours et login</b>) avec la plateforme REX-DRI depuis l'ENT.
</TextLink>
&nbsp;Puis, cliquez sur le bouton ci-dessous.
</Typography>
</div>
<div
style={{
margin: "0 auto",
width: "fit-content"
}}
>
<Button
variant="outlined"
color="secondary"
onClick={() => this.requestReload()}
>
J'ai donné toutes les autorisations, recharger mes échanges depuis
l'ENT
</Button>
</div>
<Typography variant="h4">Rechargement en cours</Typography>
<Loading />
</>
);
}
customRender() {
const { isReloadingFromEnt } = this.props;
const exchanges = this.getLatestReadData("exchanges");
exchanges.sort(
(a, b) => -compareSemesters(a.year, a.semester, b.year, b.semester)
);
if (isReloadingFromEnt) {
return (
<>
<Typography variant="h4">Rechargement en cours</Typography>
<Loading />
</>
);
}
if (exchanges.length === 0) {
return (
<>
{this.renderReloadInformation()}
<Typography variant="h4">
Aucun échange n'est associé à votre compte.
</Typography>
</>
);
}
if (exchanges.length === 0) {
return (
<>
{this.renderReloadInformation()}
<Typography variant="h4">Les échanges que j'ai effectué</Typography>
<Typography variant="caption">
(cliquer sur un départ pour éditer votre feedback sur ce dernier)
<Introduction />
<Typography variant="h4">
Aucun échange n'est associé à votre compte.
</Typography>
<List component="nav" aria-label="Liste des échanges réalisés">
{exchanges.map(el => (
<ListItem
button
key={el.id}
onClick={() =>
this.props.history.push(
APP_ROUTES.editForPreviousExchange(el.id)
)
}
>
<ListItemIcon>
{el.semester === "a" ? <WinterIcon /> : <SunnyIcon />}
</ListItemIcon>
<ListItemText
primary={`${el.semester.toUpperCase()}${
el.year
} | ${getUnivName(el.university)}`}
/>
</ListItem>
))}
</List>
</>
);
}
return (
<>
<Introduction />
<Typography variant="h4">Les échanges que j'ai effectué</Typography>
<Typography variant="caption">
(cliquer sur un départ pour éditer votre feedback sur ce dernier)
</Typography>
<List component="nav" aria-label="Liste des échanges réalisés">
{exchanges.map(el => (
<ListItem
button
key={el.id}
onClick={() =>
NavigationService.goToRoute(
APP_ROUTES.editForPreviousExchange(el.id)
)
}
>
<ListItemIcon>
{el.semester === "a" ? <WinterIcon /> : <SunnyIcon />}
</ListItemIcon>
<ListItemText
primary={`${el.semester.toUpperCase()}${
el.year
} | ${UniversityService.getUnivName(el.university)}`}
/>
</ListItem>
))}
</List>
</>
);
}
PageMyExchanges.propTypes = {
isReloadingFromEnt: PropTypes.bool.isRequired
exchanges: PropTypes.array.isRequired
};
const mapStateToProps = state => ({
exchanges: state.api.exchangesAll,
isReloadingFromEnt: state.api.updateStudentExchangesAll.isReading
});
const mapDispatchToProps = dispatch => ({
api: {
exchanges: () =>
dispatch(
getActions("exchanges").readAll(
RequestParams.Builder.withQueryParam(
"student",
CURRENT_USER_ID
).build()
)
)
},
invalidateExchanges: () => dispatch(getActions("exchanges").invalidateAll()),
requestUpdateFromEnt: params =>
dispatch(getActions("updateStudentExchanges").readAll(params))
});
export default compose(
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
),
withNetworkWrapper([
new NetWrapParam(
"exchanges",
"all",
"exchanges",
RequestParams.Builder.withQueryParam("student", CURRENT_USER_ID).build()
)
]),
withPaddedPaper(),
withErrorBoundary()
)(PageMyExchanges);
import React from "react";
import PropTypes from "prop-types";
import Typography from "@material-ui/core/Typography";
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 { Redirect } from "react-router-dom";
import compose from "recompose/compose";
import UniversityTemplate from "../university/UniversityTemplate";
import UnivInfoProvider from "../university/common/UnivInfoProvider";
import getActions from "../../redux/api/getActions";
import CustomComponentForAPI from "../common/CustomComponentForAPI";
import { withErrorBoundary } from "../common/ErrorBoundary";
import APP_ROUTES from "../../config/appRoutes";
import CustomNavLink from "../common/CustomNavLink";
import CustomLink from "../common/CustomLink";
import { PaddedPaper } from "./shared";
let previousUnivId = -1;
import UniversityService from "../../services/data/UniversityService";
function UniversityNotFound() {
return (
<Dialog
open
// 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>
<CustomNavLink to={APP_ROUTES.university}>
<Button variant="contained" color="primary">
C'est noté, ramenez-moi sur le droit chemin.
</Button>
</CustomNavLink>
</DialogActions>
</Dialog>
);
}
/**
* Component holding the page with the university details
*
* @class PageUniversity
* @extends {CustomComponentForAPI}
* @extends React.Component
*/
class PageUniversity extends CustomComponentForAPI {
componentDidMount() {
super.componentDidMount();
const requestedUnivId = this.getUnivIdFromProps();
if (requestedUnivId && requestedUnivId !== "previousOne") {
previousUnivId = requestedUnivId;
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
super.componentDidUpdate(prevProps, prevState, snapshot);
}
getUnivIdFromProps() {
return this.props.match.params.univId;
}
customRender() {
const { match } = this.props;
const requestedUnivId = match.params.univId;
const { tabName } = match.params;
const universities = this.getLatestReadData("universities");
if (
requestedUnivId === "previousOne" ||
typeof requestedUnivId === "undefined"
) {
if (previousUnivId !== -1) {
return this.renderUniversityUndefinedButHavePrevious(previousUnivId);
}
return this.renderFirstTimeHere();
}
if (
universities.find(
univ => parseInt(univ.id, 10) === parseInt(requestedUnivId, 10),
10
)
) {
return this.renderDefaultView(requestedUnivId, tabName);
}
return this.renderUniversityNotFound();
}
function PageUniversity({ match }) {
const { univId, tabName } = match.params;
renderUniversityNotFound() {
const parsedUnivId = parseInt(univId, 10);
if (UniversityService.hasUniversity(parsedUnivId)) {
return (
<Dialog
open
// 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>
<CustomNavLink to={APP_ROUTES.university}>
<Button variant="contained" color="primary">
C'est noté, ramenez-moi sur le droit chemin.
</Button>
</CustomNavLink>
</DialogActions>
</Dialog>
);
}
renderUniversityUndefinedButHavePrevious(univId) {
return <Redirect to={APP_ROUTES.forUniversity(univId)} />;
}
renderFirstTimeHere() {
return (
<PaddedPaper>
<Typography>
C'est la première fois que vous consultez cet onglet. Nous vous
invitons à &nbsp;
<CustomLink to={APP_ROUTES.map}>parcourir les universités</CustomLink>
&nbsp; dans un premier temps. 😁
</Typography>
</PaddedPaper>
);
}
renderDefaultView(univId, tabName) {
return (
<UnivInfoProvider univId={parseInt(univId, 10)}>
<UniversityTemplate tabName={tabName} univId={univId} />
<UnivInfoProvider univId={parsedUnivId}>
<UniversityTemplate tabName={tabName}