Skip to content

Commit

Permalink
Pfn/front/electrification error handler (#3935)
Browse files Browse the repository at this point in the history
close #3835

This PR adds an error message when selecting a power restriction code
than cannot be applied by the rolling stock given the electrification on
the chosen path.
  • Loading branch information
SharglutDev committed Apr 25, 2023
1 parent 582656e commit 8f8a608
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"deleteVias": "Supprimer les étapes",
"destination": "Destination",
"errorMessages": {
"error": "Une erreur est survenue",
"mandatoryField": "Champ obligatoire",
"noDelta": "L'intervalle ente deux trains doit être d'au moins une minute",
"noDeltaInput": "Doit être > 0",
Expand All @@ -26,6 +27,7 @@
"noTrainCountInput": "Doit être > 0",
"noTrainStep": "Le pas d'incrément du nom du train doit être supérieur à zéro",
"noTrainStepInput": "Doit être > 0",
"powerRestrictionInvalidCombination": "Code {{powerRestrictionCode}} incompatible avec l'électrification à {{electrification}} V de l'itinéraire",
"stdcmError": "Erreur de calcul Sillon de dernière minute",
"The requested train couldn't reach its destination": "Le train ne peut pas atteindre sa destination",
"trainScheduleTitle": "Une erreur est survenue",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
updateDestination,
updateDestinationDate,
updateDestinationTime,
updatePathfindingID,
updateStdcmMode,
} from 'reducers/osrdconf';
import { makeEnumBooleans } from 'utils/constants';

import InputSNCF from 'common/BootstrapSNCF/InputSNCF';
import { store } from 'Store';
import { MODES, STDCM_MODES } from 'applications/operationalStudies/consts';

interface DestinationProps {
Expand Down Expand Up @@ -61,7 +61,10 @@ function Destination(props: DestinationProps) {
<button
className="btn btn-sm btn-only-icon btn-white"
type="button"
onClick={() => store.dispatch(updateDestination(undefined))}
onClick={() => {
dispatch(updateDestination(undefined));
dispatch(updatePathfindingID(undefined));
}}
>
<i className="icons-circle-delete" />
<span className="sr-only" aria-hidden="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
updateOriginUpperBoundTime,
updateStdcmMode,
toggleOriginLinkedBounds,
updatePathfindingID,
} from 'reducers/osrdconf';
import {
getStdcmMode,
Expand Down Expand Up @@ -105,6 +106,7 @@ function Origin(props: OriginProps) {
type="button"
onClick={() => {
dispatch(updateOrigin(undefined));
dispatch(updatePathfindingID(undefined));
}}
>
<i className="icons-circle-delete" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable no-restricted-syntax */
import React, { useState, useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { updatePowerRestriction } from 'reducers/osrdconf';
Expand All @@ -10,67 +9,125 @@ import {
getPowerRestriction,
getPathfindingID,
} from 'reducers/osrdconf/selectors';
import { osrdEditoastApi, LightRollingStock } from 'common/api/osrdEditoastApi';
import { osrdEditoastApi, RollingStock } from 'common/api/osrdEditoastApi';
import { osrdMiddlewareApi, PowerRestrictionRange } from 'common/api/osrdMiddlewareApi';
import { lengthFromLineCoordinates } from 'utils/geometry';
import { setFailure } from 'reducers/main';
import { compact, reduce, uniq } from 'lodash';

type selectorOption = { key: string | undefined; value: string | undefined };

type ElectrificationPR = {
[key: string]: string[] | string[];
};

export default function PowerRestrictionSelector() {
const { t } = useTranslation(['operationalStudies/manageTrainSchedule']);
const dispatch = useDispatch();
const rollingStockID: number | undefined = useSelector(getRollingStockID);
const pathFindingID = useSelector(getPathfindingID);
const powerRestriction = useSelector(getPowerRestriction);
const [powerRestrictions, setPowerRestrictions] = useState<selectorOption[] | undefined>();
const [getRollingStockById] = osrdEditoastApi.endpoints.getLightRollingStockById.useLazyQuery({});
const [getPathFindingById] = osrdMiddlewareApi.endpoints.getPathfindingById.useLazyQuery({});

const getPowerRestrictionsList = async (rollingStockInfo: LightRollingStock) => {
if (rollingStockInfo.power_restrictions) {
const powerRestrictionsList = Object.entries(rollingStockInfo.power_restrictions).map(
const { data: pathFinding } = osrdMiddlewareApi.useGetPathfindingByIdQuery(
{ id: pathFindingID as number },
{ skip: !pathFindingID }
);

const { data: pathWithCatenaries } = osrdEditoastApi.useGetPathfindingByPathIdCatenariesQuery(
{ pathId: pathFindingID as number },
{ skip: !pathFindingID }
);

const { data: rollingStock } = osrdEditoastApi.useGetRollingStockByIdQuery(
{ id: rollingStockID as number },
{ skip: !rollingStockID }
);

const powerRestrictions = useMemo(() => {
let powerRestrictionsList: selectorOption[] = [];
if (rollingStock) {
powerRestrictionsList = Object.entries(rollingStock.power_restrictions).map(
([key, _]: [string | undefined, string]) => ({ key, value: key })
);
if (powerRestrictionsList.length > 0) {
powerRestrictionsList.unshift({ key: undefined, value: t('noPowerRestriction') });
}
setPowerRestrictions(powerRestrictionsList);
powerRestrictionsList.unshift({ key: undefined, value: t('noPowerRestriction') });
}
return powerRestrictionsList;
}, [rollingStock]);

// Extract unique rollingstock's power restriction codes allowed by each electrification mode
const cleanConditionalEffortCurves = (rollingStockToClean: RollingStock) => {
const curvesMode = rollingStockToClean.effort_curves.modes;
const curvesModesKey = Object.keys(curvesMode);

const parsedElectrification: ElectrificationPR = reduce(
curvesModesKey,
(result, mode) => {
const powerCodes = curvesMode[mode].curves.map(
(curve) => curve.cond?.power_restriction_code
);
compact(uniq(powerCodes));
return {
...result,
[mode]: powerCodes,
};
},
{}
);

return parsedElectrification;
};

const definePowerRestrictionRange = (powerRestrictionCode?: string) => {
if (powerRestrictionCode && pathFindingID) {
getPathFindingById({ id: pathFindingID })
.unwrap()
.then((pathFinding) => {
const pathLength = Math.round(
lengthFromLineCoordinates(pathFinding?.geographic?.coordinates) * 1000
);
const powerRestrictionRange: PowerRestrictionRange[] = [
{
begin_position: 0,
end_position: pathLength,
power_restriction_code: powerRestrictionCode,
},
];
dispatch(updatePowerRestriction(powerRestrictionRange));
});
const definePowerRestrictionRange = async (powerRestrictionCode?: string) => {
if (powerRestrictionCode && pathFinding) {
const pathLength = Math.round(
lengthFromLineCoordinates(pathFinding.geographic?.coordinates) * 1000
);
const powerRestrictionRange: PowerRestrictionRange = {
begin_position: 0,
end_position: pathLength,
power_restriction_code: powerRestrictionCode,
};

dispatch(updatePowerRestriction(powerRestrictionRange));
} else dispatch(updatePowerRestriction(undefined));
};

useEffect(() => {
if (rollingStockID) {
dispatch(updatePowerRestriction(undefined));
setPowerRestrictions(undefined);
getRollingStockById({ id: rollingStockID })
.unwrap()
.then((rollingStockInfo) => {
getPowerRestrictionsList(rollingStockInfo);
if (powerRestriction && rollingStock && pathWithCatenaries) {
const parsedElectrification = cleanConditionalEffortCurves(rollingStock);

const powerRestrictionCode = powerRestriction.power_restriction_code;
const pathCatenaryRanges = pathWithCatenaries.catenary_ranges;

if (pathCatenaryRanges && powerRestrictionCode) {
// Extract path electrification mode and check compatibility
const pathElectrification = compact(pathCatenaryRanges.map((range) => range.mode));

// Display an error when the first incompatibility is encountered
pathElectrification.some((electrification) => {
const isInvalid =
!parsedElectrification[electrification as keyof ElectrificationPR].includes(
powerRestrictionCode
);
if (isInvalid) {
dispatch(
setFailure({
name: t('errorMessages.error'),
message: t('errorMessages.powerRestrictionInvalidCombination', {
powerRestrictionCode,
electrification,
}),
})
);
}

return isInvalid;
});
}
}
}, [rollingStockID]);
}, [powerRestriction]);

return powerRestrictions && powerRestrictions.length > 0 && pathFindingID ? (
return powerRestrictions.length > 1 && pathFindingID ? (
<div className="osrd-config-item mb-2">
<div className="osrd-config-item-container">
<img width="32px" className="mr-2" src={icon} alt="PowerRestrictionIcon" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
updateRollingStockComfort,
updateRollingStockID,
updateShouldRunPathfinding,
updatePowerRestriction,
} from 'reducers/osrdconf';
import { getRollingStockComfort } from 'reducers/osrdconf/selectors';
import { ModalContext } from 'common/BootstrapSNCF/ModalSNCF/ModalProvider';
Expand Down Expand Up @@ -36,6 +37,7 @@ export default function RollingStockCardButtons(props) {
dispatch(updateRollingStockComfort(comfort));
dispatch(updateShouldRunPathfinding(true));
dispatch(updateRollingStockID(id));
dispatch(updatePowerRestriction(undefined));
closeModal();
};

Expand Down
2 changes: 1 addition & 1 deletion front/src/reducers/osrdconf/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ export function deleteItinerary() {
});
};
}
export function updatePowerRestriction(powerRestriction?: PowerRestrictionRange[]) {
export function updatePowerRestriction(powerRestriction?: PowerRestrictionRange) {
return (dispatch: Dispatch) => {
dispatch({
type: UPDATE_POWER_RESTRICTION,
Expand Down

0 comments on commit 8f8a608

Please sign in to comment.