Skip to content

Commit 8f8a608

Browse files
authored
Pfn/front/electrification error handler (#3935)
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.
1 parent 582656e commit 8f8a608

File tree

6 files changed

+108
-42
lines changed

6 files changed

+108
-42
lines changed

front/public/locales/fr/operationalStudies/manageTrainSchedule.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"deleteVias": "Supprimer les étapes",
1313
"destination": "Destination",
1414
"errorMessages": {
15+
"error": "Une erreur est survenue",
1516
"mandatoryField": "Champ obligatoire",
1617
"noDelta": "L'intervalle ente deux trains doit être d'au moins une minute",
1718
"noDeltaInput": "Doit être > 0",
@@ -26,6 +27,7 @@
2627
"noTrainCountInput": "Doit être > 0",
2728
"noTrainStep": "Le pas d'incrément du nom du train doit être supérieur à zéro",
2829
"noTrainStepInput": "Doit être > 0",
30+
"powerRestrictionInvalidCombination": "Code {{powerRestrictionCode}} incompatible avec l'électrification à {{electrification}} V de l'itinéraire",
2931
"stdcmError": "Erreur de calcul Sillon de dernière minute",
3032
"The requested train couldn't reach its destination": "Le train ne peut pas atteindre sa destination",
3133
"trainScheduleTitle": "Une erreur est survenue",

front/src/applications/operationalStudies/components/ManageTrainSchedule/Itinerary/DisplayItinerary/Destination.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import {
1515
updateDestination,
1616
updateDestinationDate,
1717
updateDestinationTime,
18+
updatePathfindingID,
1819
updateStdcmMode,
1920
} from 'reducers/osrdconf';
2021
import { makeEnumBooleans } from 'utils/constants';
2122

2223
import InputSNCF from 'common/BootstrapSNCF/InputSNCF';
23-
import { store } from 'Store';
2424
import { MODES, STDCM_MODES } from 'applications/operationalStudies/consts';
2525

2626
interface DestinationProps {
@@ -61,7 +61,10 @@ function Destination(props: DestinationProps) {
6161
<button
6262
className="btn btn-sm btn-only-icon btn-white"
6363
type="button"
64-
onClick={() => store.dispatch(updateDestination(undefined))}
64+
onClick={() => {
65+
dispatch(updateDestination(undefined));
66+
dispatch(updatePathfindingID(undefined));
67+
}}
6568
>
6669
<i className="icons-circle-delete" />
6770
<span className="sr-only" aria-hidden="true">

front/src/applications/operationalStudies/components/ManageTrainSchedule/Itinerary/DisplayItinerary/Origin.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
updateOriginUpperBoundTime,
1414
updateStdcmMode,
1515
toggleOriginLinkedBounds,
16+
updatePathfindingID,
1617
} from 'reducers/osrdconf';
1718
import {
1819
getStdcmMode,
@@ -105,6 +106,7 @@ function Origin(props: OriginProps) {
105106
type="button"
106107
onClick={() => {
107108
dispatch(updateOrigin(undefined));
109+
dispatch(updatePathfindingID(undefined));
108110
}}
109111
>
110112
<i className="icons-circle-delete" />

front/src/applications/operationalStudies/components/ManageTrainSchedule/PowerRestrictionSelector.tsx

Lines changed: 96 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
/* eslint-disable no-restricted-syntax */
2-
import React, { useState, useEffect } from 'react';
1+
import React, { useEffect, useMemo } from 'react';
32
import { useDispatch, useSelector } from 'react-redux';
43
import { useTranslation } from 'react-i18next';
54
import { updatePowerRestriction } from 'reducers/osrdconf';
@@ -10,67 +9,125 @@ import {
109
getPowerRestriction,
1110
getPathfindingID,
1211
} from 'reducers/osrdconf/selectors';
13-
import { osrdEditoastApi, LightRollingStock } from 'common/api/osrdEditoastApi';
12+
import { osrdEditoastApi, RollingStock } from 'common/api/osrdEditoastApi';
1413
import { osrdMiddlewareApi, PowerRestrictionRange } from 'common/api/osrdMiddlewareApi';
1514
import { lengthFromLineCoordinates } from 'utils/geometry';
15+
import { setFailure } from 'reducers/main';
16+
import { compact, reduce, uniq } from 'lodash';
1617

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

20+
type ElectrificationPR = {
21+
[key: string]: string[] | string[];
22+
};
23+
1924
export default function PowerRestrictionSelector() {
2025
const { t } = useTranslation(['operationalStudies/manageTrainSchedule']);
2126
const dispatch = useDispatch();
2227
const rollingStockID: number | undefined = useSelector(getRollingStockID);
2328
const pathFindingID = useSelector(getPathfindingID);
2429
const powerRestriction = useSelector(getPowerRestriction);
25-
const [powerRestrictions, setPowerRestrictions] = useState<selectorOption[] | undefined>();
26-
const [getRollingStockById] = osrdEditoastApi.endpoints.getLightRollingStockById.useLazyQuery({});
27-
const [getPathFindingById] = osrdMiddlewareApi.endpoints.getPathfindingById.useLazyQuery({});
2830

29-
const getPowerRestrictionsList = async (rollingStockInfo: LightRollingStock) => {
30-
if (rollingStockInfo.power_restrictions) {
31-
const powerRestrictionsList = Object.entries(rollingStockInfo.power_restrictions).map(
31+
const { data: pathFinding } = osrdMiddlewareApi.useGetPathfindingByIdQuery(
32+
{ id: pathFindingID as number },
33+
{ skip: !pathFindingID }
34+
);
35+
36+
const { data: pathWithCatenaries } = osrdEditoastApi.useGetPathfindingByPathIdCatenariesQuery(
37+
{ pathId: pathFindingID as number },
38+
{ skip: !pathFindingID }
39+
);
40+
41+
const { data: rollingStock } = osrdEditoastApi.useGetRollingStockByIdQuery(
42+
{ id: rollingStockID as number },
43+
{ skip: !rollingStockID }
44+
);
45+
46+
const powerRestrictions = useMemo(() => {
47+
let powerRestrictionsList: selectorOption[] = [];
48+
if (rollingStock) {
49+
powerRestrictionsList = Object.entries(rollingStock.power_restrictions).map(
3250
([key, _]: [string | undefined, string]) => ({ key, value: key })
3351
);
34-
if (powerRestrictionsList.length > 0) {
35-
powerRestrictionsList.unshift({ key: undefined, value: t('noPowerRestriction') });
36-
}
37-
setPowerRestrictions(powerRestrictionsList);
52+
powerRestrictionsList.unshift({ key: undefined, value: t('noPowerRestriction') });
3853
}
54+
return powerRestrictionsList;
55+
}, [rollingStock]);
56+
57+
// Extract unique rollingstock's power restriction codes allowed by each electrification mode
58+
const cleanConditionalEffortCurves = (rollingStockToClean: RollingStock) => {
59+
const curvesMode = rollingStockToClean.effort_curves.modes;
60+
const curvesModesKey = Object.keys(curvesMode);
61+
62+
const parsedElectrification: ElectrificationPR = reduce(
63+
curvesModesKey,
64+
(result, mode) => {
65+
const powerCodes = curvesMode[mode].curves.map(
66+
(curve) => curve.cond?.power_restriction_code
67+
);
68+
compact(uniq(powerCodes));
69+
return {
70+
...result,
71+
[mode]: powerCodes,
72+
};
73+
},
74+
{}
75+
);
76+
77+
return parsedElectrification;
3978
};
4079

41-
const definePowerRestrictionRange = (powerRestrictionCode?: string) => {
42-
if (powerRestrictionCode && pathFindingID) {
43-
getPathFindingById({ id: pathFindingID })
44-
.unwrap()
45-
.then((pathFinding) => {
46-
const pathLength = Math.round(
47-
lengthFromLineCoordinates(pathFinding?.geographic?.coordinates) * 1000
48-
);
49-
const powerRestrictionRange: PowerRestrictionRange[] = [
50-
{
51-
begin_position: 0,
52-
end_position: pathLength,
53-
power_restriction_code: powerRestrictionCode,
54-
},
55-
];
56-
dispatch(updatePowerRestriction(powerRestrictionRange));
57-
});
80+
const definePowerRestrictionRange = async (powerRestrictionCode?: string) => {
81+
if (powerRestrictionCode && pathFinding) {
82+
const pathLength = Math.round(
83+
lengthFromLineCoordinates(pathFinding.geographic?.coordinates) * 1000
84+
);
85+
const powerRestrictionRange: PowerRestrictionRange = {
86+
begin_position: 0,
87+
end_position: pathLength,
88+
power_restriction_code: powerRestrictionCode,
89+
};
90+
91+
dispatch(updatePowerRestriction(powerRestrictionRange));
5892
} else dispatch(updatePowerRestriction(undefined));
5993
};
6094

6195
useEffect(() => {
62-
if (rollingStockID) {
63-
dispatch(updatePowerRestriction(undefined));
64-
setPowerRestrictions(undefined);
65-
getRollingStockById({ id: rollingStockID })
66-
.unwrap()
67-
.then((rollingStockInfo) => {
68-
getPowerRestrictionsList(rollingStockInfo);
96+
if (powerRestriction && rollingStock && pathWithCatenaries) {
97+
const parsedElectrification = cleanConditionalEffortCurves(rollingStock);
98+
99+
const powerRestrictionCode = powerRestriction.power_restriction_code;
100+
const pathCatenaryRanges = pathWithCatenaries.catenary_ranges;
101+
102+
if (pathCatenaryRanges && powerRestrictionCode) {
103+
// Extract path electrification mode and check compatibility
104+
const pathElectrification = compact(pathCatenaryRanges.map((range) => range.mode));
105+
106+
// Display an error when the first incompatibility is encountered
107+
pathElectrification.some((electrification) => {
108+
const isInvalid =
109+
!parsedElectrification[electrification as keyof ElectrificationPR].includes(
110+
powerRestrictionCode
111+
);
112+
if (isInvalid) {
113+
dispatch(
114+
setFailure({
115+
name: t('errorMessages.error'),
116+
message: t('errorMessages.powerRestrictionInvalidCombination', {
117+
powerRestrictionCode,
118+
electrification,
119+
}),
120+
})
121+
);
122+
}
123+
124+
return isInvalid;
69125
});
126+
}
70127
}
71-
}, [rollingStockID]);
128+
}, [powerRestriction]);
72129

73-
return powerRestrictions && powerRestrictions.length > 0 && pathFindingID ? (
130+
return powerRestrictions.length > 1 && pathFindingID ? (
74131
<div className="osrd-config-item mb-2">
75132
<div className="osrd-config-item-container">
76133
<img width="32px" className="mr-2" src={icon} alt="PowerRestrictionIcon" />

front/src/common/RollingStockSelector/RollingStockCardButtons.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
updateRollingStockComfort,
88
updateRollingStockID,
99
updateShouldRunPathfinding,
10+
updatePowerRestriction,
1011
} from 'reducers/osrdconf';
1112
import { getRollingStockComfort } from 'reducers/osrdconf/selectors';
1213
import { ModalContext } from 'common/BootstrapSNCF/ModalSNCF/ModalProvider';
@@ -36,6 +37,7 @@ export default function RollingStockCardButtons(props) {
3637
dispatch(updateRollingStockComfort(comfort));
3738
dispatch(updateShouldRunPathfinding(true));
3839
dispatch(updateRollingStockID(id));
40+
dispatch(updatePowerRestriction(undefined));
3941
closeModal();
4042
};
4143

front/src/reducers/osrdconf/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ export function deleteItinerary() {
669669
});
670670
};
671671
}
672-
export function updatePowerRestriction(powerRestriction?: PowerRestrictionRange[]) {
672+
export function updatePowerRestriction(powerRestriction?: PowerRestrictionRange) {
673673
return (dispatch: Dispatch) => {
674674
dispatch({
675675
type: UPDATE_POWER_RESTRICTION,

0 commit comments

Comments
 (0)