Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: split the ScenarioView into 2 components #8866

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { useState, useEffect, useCallback } from 'react';

import { ChevronRight } from '@osrd-project/ui-icons';
import cx from 'classnames';
import { useTranslation } from 'react-i18next';
import { GiElectric } from 'react-icons/gi';
import { useSelector } from 'react-redux';

import handleOperation from 'applications/operationalStudies/components/MacroEditor/ngeToOsrd';
import importTimetableToNGE from 'applications/operationalStudies/components/MacroEditor/osrdToNge';
import MicroMacroSwitch from 'applications/operationalStudies/components/MicroMacroSwitch';
import NGE from 'applications/operationalStudies/components/NGE/NGE';
import type { NetzgrafikDto, NGEEvent } from 'applications/operationalStudies/components/NGE/types';
import { MANAGE_TRAIN_SCHEDULE_TYPES } from 'applications/operationalStudies/consts';
import useScenarioData from 'applications/operationalStudies/hooks/useScenarioData';
import ImportTrainSchedule from 'applications/operationalStudies/views/ImportTrainSchedule';
import ManageTrainSchedule from 'applications/operationalStudies/views/ManageTrainSchedule';
import SimulationResults from 'applications/operationalStudies/views/SimulationResults';
import infraLogo from 'assets/pictures/components/tracks.svg';
import type {
InfraWithState,
ScenarioResponse,
TimetableDetailedResult,
TrainScheduleResult,
} from 'common/api/osrdEditoastApi';
import ScenarioLoaderMessage from 'modules/scenario/components/ScenarioLoaderMessage';
import TimetableManageTrainSchedule from 'modules/trainschedule/components/ManageTrainSchedule/TimetableManageTrainSchedule';
import Timetable from 'modules/trainschedule/components/Timetable/Timetable';
import type { RootState } from 'reducers';
import { useAppDispatch } from 'store';
import { concatMap, mapBy } from 'utils/types';

import ScenarioDescription from './ScenarioDescription';

type ScenarioDescriptionProps = {
scenario: ScenarioResponse;
timetable: TimetableDetailedResult;
infra: InfraWithState;
infraMetadata: { isInfraLoaded: boolean; reloadCount: number };
};

const ScenarioContent = ({
scenario,
timetable,
infra,
infraMetadata: { isInfraLoaded, reloadCount },
}: ScenarioDescriptionProps) => {
const { t } = useTranslation('operationalStudies/scenario');
const dispatch = useAppDispatch();
const isUpdating = useSelector((state: RootState) => state.osrdsimulation.isUpdating);

const [displayTrainScheduleManagement, setDisplayTrainScheduleManagement] = useState<string>(
MANAGE_TRAIN_SCHEDULE_TYPES.none
);
const [showTrainDetails, setShowTrainDetails] = useState(false);
const [collapsedTimetable, setCollapsedTimetable] = useState(false);
const [trainIdToEdit, setTrainIdToEdit] = useState<number>();
const [isMacro, setIsMacro] = useState(false);

const {
selectedTrainId,
trainScheduleSummaries,
trainSchedules,
trainScheduleUsedForProjection,
trainIdUsedForProjection,
projectedTrains,
simulationResults,
conflicts,
upsertTrainSchedules,
removeTrains,
} = useScenarioData(scenario, timetable, infra);

const toggleMicroMacroButton = useCallback(
(isMacroMode: boolean) => {
setIsMacro(isMacroMode);
setCollapsedTimetable(isMacroMode);
},
[setIsMacro, setCollapsedTimetable]
);

const [ngeDto, setNgeDto] = useState<NetzgrafikDto>();
const [ngeUpsertedTrainSchedules, setNgeUpsertedTrainSchedules] = useState<
Map<number, TrainScheduleResult>
>(new Map());
const [ngeDeletedTrainIds, setNgeDeletedTrainIds] = useState<number[]>([]);

useEffect(() => {
if (!isMacro || (isMacro && ngeDto)) {
return;
}

const doImport = async () => {
const dto = await importTimetableToNGE(scenario.infra_id, scenario.timetable_id, dispatch);
setNgeDto(dto);
};
doImport();
}, [scenario, isMacro]);

useEffect(() => {
if (isMacro) {
return;
}

if (ngeDto) {
setNgeDto(undefined);
}
upsertTrainSchedules(Array.from(ngeUpsertedTrainSchedules.values()));
removeTrains(ngeDeletedTrainIds);
}, [isMacro]);

const handleNGEOperation = (event: NGEEvent, netzgrafikDto: NetzgrafikDto) =>
handleOperation({
event,
dispatch,
infraId: infra.id,
timeTableId: scenario.timetable_id,
netzgrafikDto,
addUpsertedTrainSchedules: (upsertedTrainSchedules: TrainScheduleResult[]) => {
setNgeUpsertedTrainSchedules((prev) =>
concatMap(prev, mapBy(upsertedTrainSchedules, 'id'))
);
},
addDeletedTrainIds: (trainIds: number[]) => {
setNgeDeletedTrainIds((prev) => [...prev, ...trainIds]);
},
});

return (
<main className="mastcontainer mastcontainer-no-mastnav">
<div className="scenario">
<div className="row scenario-container">
<div
className={`scenario-sidemenu ${collapsedTimetable ? 'd-none' : 'col-hdp-3 col-xl-4 col-lg-5 col-md-6'}`}
>
<div className="scenario-sidemenu">
<ScenarioDescription
scenario={scenario}
infra={infra}
infraReloadCount={reloadCount}
showTrainDetails={showTrainDetails}
toggleTrainDetails={() => setShowTrainDetails(!showTrainDetails)}
collapseTimetable={() => setCollapsedTimetable(true)}
/>
<MicroMacroSwitch isMacro={isMacro} setIsMacro={toggleMicroMacroButton} />
{!isMacro && infra && (
<>
{displayTrainScheduleManagement !== MANAGE_TRAIN_SCHEDULE_TYPES.none && (
<TimetableManageTrainSchedule
displayTrainScheduleManagement={displayTrainScheduleManagement}
setDisplayTrainScheduleManagement={setDisplayTrainScheduleManagement}
upsertTrainSchedules={upsertTrainSchedules}
trainIdToEdit={trainIdToEdit}
setTrainIdToEdit={setTrainIdToEdit}
infraState={infra.state}
/>
)}
<Timetable
setDisplayTrainScheduleManagement={setDisplayTrainScheduleManagement}
trainsWithDetails={showTrainDetails}
infraState={infra.state}
trainIds={timetable.train_ids}
selectedTrainId={selectedTrainId}
conflicts={conflicts}
upsertTrainSchedules={upsertTrainSchedules}
removeTrains={removeTrains}
setTrainIdToEdit={setTrainIdToEdit}
trainIdToEdit={trainIdToEdit}
trainSchedules={trainSchedules}
trainSchedulesWithDetails={trainScheduleSummaries}
/>
</>
)}
</div>
</div>

<div className={collapsedTimetable ? 'col-12' : 'col-hdp-9 col-xl-8 col-lg-7 col-md-6'}>
{(!isInfraLoaded || isUpdating) &&
displayTrainScheduleManagement !== MANAGE_TRAIN_SCHEDULE_TYPES.add &&
displayTrainScheduleManagement !== MANAGE_TRAIN_SCHEDULE_TYPES.edit && (
<ScenarioLoaderMessage infraState={infra?.state} />
)}
{(displayTrainScheduleManagement === MANAGE_TRAIN_SCHEDULE_TYPES.add ||
displayTrainScheduleManagement === MANAGE_TRAIN_SCHEDULE_TYPES.edit) && (
<div className="scenario-managetrainschedule">
<ManageTrainSchedule trainIdToEdit={trainIdToEdit} />
</div>
)}
{displayTrainScheduleManagement === MANAGE_TRAIN_SCHEDULE_TYPES.import && (
<div className="scenario-managetrainschedule">
<ImportTrainSchedule timetableId={scenario.timetable_id} />
</div>
)}
<div className="scenario-results">
{collapsedTimetable && (
<>
<div className="scenario-timetable-collapsed">
<button
className="timetable-collapse-button"
type="button"
aria-label={t('toggleTimetable')}
onClick={() => setCollapsedTimetable(false)}
>
<ChevronRight />
</button>
<div className="lead ml-2">{scenario.name}</div>
<div className="d-flex align-items-center ml-auto">
<img src={infraLogo} alt="Infra logo" className="infra-logo mr-2" />
{scenario.infra_name}
</div>
<div className="d-flex align-items-center ml-4">
<span className="mr-1">
<GiElectric />
</span>
{scenario.electrical_profile_set_id
? scenario.electrical_profile_set_id
: t('noElectricalProfileSet')}
</div>
</div>
<MicroMacroSwitch isMacro={isMacro} setIsMacro={toggleMicroMacroButton} />
</>
)}
{isMacro ? (
<div className={cx(collapsedTimetable ? 'macro-container' : 'h-100')}>
<NGE dto={ngeDto} onOperation={handleNGEOperation} />
</div>
) : (
isInfraLoaded &&
infra && (
<SimulationResults
collapsedTimetable={collapsedTimetable}
spaceTimeData={projectedTrains}
simulationResults={simulationResults}
trainScheduleUsedForProjection={trainScheduleUsedForProjection}
trainIdUsedForProjection={trainIdUsedForProjection}
infraId={infra.id}
timetableTrainNb={timetable.train_ids.length}
/>
)
)}
</div>
</div>
</div>
</div>
</main>
);
};

export default ScenarioContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { ChevronLeft, Eye, EyeClosed, Pencil } from '@osrd-project/ui-icons';
import { useTranslation } from 'react-i18next';
import { GiElectric } from 'react-icons/gi';

import InfraLoadingState from 'applications/operationalStudies/components/Scenario/InfraLoadingState';
import infraLogo from 'assets/pictures/components/tracks.svg';
import type { InfraWithState, ScenarioResponse } from 'common/api/osrdEditoastApi';
import { useModal } from 'common/BootstrapSNCF/ModalSNCF';
import AddAndEditScenarioModal from 'modules/scenario/components/AddOrEditScenarioModal';

type ScenarioDescriptionProps = {
scenario: ScenarioResponse;
infra?: InfraWithState;
infraReloadCount: number;
showTrainDetails: boolean;
toggleTrainDetails: () => void;
collapseTimetable: () => void;
};

const ScenarioDescription = ({
scenario,
infra,
infraReloadCount,
showTrainDetails,
toggleTrainDetails,
collapseTimetable,
}: ScenarioDescriptionProps) => {
const { t } = useTranslation('operationalStudies/scenario');
const { openModal } = useModal();

return (
<div className="scenario-details">
<div className="scenario-details-name">
<span className="flex-grow-1 scenario-name text-truncate" title={scenario.name}>
{scenario.name}
</span>
<button
data-testid="editScenario"
className="scenario-details-modify-button"
type="button"
aria-label={t('editScenario')}
onClick={() =>
openModal(
<AddAndEditScenarioModal editionMode scenario={scenario} />,
'xl',
'no-close-modal'
)
}
title={t('editScenario')}
>
<Pencil />
</button>
<button
type="button"
className="scenario-details-modify-button"
onClick={toggleTrainDetails}
title={t('displayTrainsWithDetails')}
>
{showTrainDetails ? <EyeClosed /> : <Eye />}
</button>
<button
type="button"
className="scenario-details-modify-button"
aria-label={t('toggleTimetable')}
onClick={() => collapseTimetable()}
>
<ChevronLeft />
</button>
</div>
<div className="row">
<div className="col-md-6">
<div className="scenario-details-infra-name">
<img src={infraLogo} alt="Infra logo" className="infra-logo mr-2" />
{infra && <InfraLoadingState infra={infra} />}
<span className="scenario-infra-name">{scenario.infra_name}</span>
<small className="ml-auto text-muted">ID {scenario.infra_id}</small>
</div>
</div>
<div className="col-md-6">
<div className="scenario-details-electrical-profile-set">
<span className="mr-2">
<GiElectric />
</span>
{scenario.electrical_profile_set_id
? scenario.electrical_profile_set_id
: t('noElectricalProfileSet')}
</div>
</div>
</div>
{infra &&
infra.state === 'TRANSIENT_ERROR' &&
(infraReloadCount <= 5 ? (
<div className="scenario-details-infra-error mt-1">
{t('errorMessages.unableToLoadInfra', { infraReloadCount })}
</div>
) : (
<div className="scenario-details-infra-error mt-1">
{t('errorMessages.softErrorInfra')}
</div>
))}
{infra && infra.state === 'ERROR' && (
<div className="scenario-details-infra-error mt-1">{t('errorMessages.hardErrorInfra')}</div>
)}
<div className="scenario-details-description">{scenario.description}</div>
</div>
);
};

export default ScenarioDescription;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { useEffect, useMemo } from 'react';

import { useParams } from 'react-router-dom';
Expand Down Expand Up @@ -48,6 +49,13 @@ const useScenario = () => {
}
);

const { data: timetable } = osrdEditoastApi.endpoints.getTimetableById.useQuery(
{ id: scenario?.timetable_id! },
{
skip: !scenario,
}
);

useEffect(() => {
if (scenario) {
dispatch(updateTimetableID(scenario.timetable_id));
Expand All @@ -71,7 +79,7 @@ const useScenario = () => {
}
}, [projectId, studyId, scenarioId]);

return scenario;
return { scenario, timetable };
};

export default useScenario;
Loading
Loading