(null);
+ const { t } = useTranslation('stdcm');
+
+ const dispatch = useAppDispatch();
+ const { updateGridMarginAfter, updateGridMarginBefore, updateStdcmStandardAllowance } =
+ useOsrdConfActions() as StdcmConfSliceActions;
+
+ useEffect(() => {
+ if (isPending) {
+ loaderRef?.current?.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, [isPending]);
+
+ useEffect(() => {
+ dispatch(updateGridMarginAfter(35));
+ dispatch(updateGridMarginBefore(35));
+ dispatch(updateStdcmStandardAllowance({ type: 'time_per_distance', value: 4.5 }));
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ {scenarioID &&
}
+
+ {scenarioID && (
+ <>
+
+
+ {/* //TODO: rename StdcmDefaultCard */}
+ {/* } /> */}
+
+ {/* } /> */}
+
+ {/* } /> */}
+
+ >
+ )}
+
+ {scenarioID && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default StdcmViewV2;
diff --git a/front/src/assets/pictures/views/stdcm_v2_loader.jpg b/front/src/assets/pictures/views/stdcm_v2_loader.jpg
new file mode 100644
index 00000000000..13aed63a1f6
Binary files /dev/null and b/front/src/assets/pictures/views/stdcm_v2_loader.jpg differ
diff --git a/front/src/common/BootstrapSNCF/NavBarSNCF.scss b/front/src/common/BootstrapSNCF/NavBarSNCF.scss
new file mode 100644
index 00000000000..06e9474bed3
--- /dev/null
+++ b/front/src/common/BootstrapSNCF/NavBarSNCF.scss
@@ -0,0 +1,5 @@
+.user-settings-btn:disabled {
+ cursor: not-allowed;
+ color: #c0c0c0 !important;
+ pointer-events: auto;
+}
diff --git a/front/src/common/BootstrapSNCF/NavBarSNCF.tsx b/front/src/common/BootstrapSNCF/NavBarSNCF.tsx
index ae88c538fb6..70325bf7b63 100644
--- a/front/src/common/BootstrapSNCF/NavBarSNCF.tsx
+++ b/front/src/common/BootstrapSNCF/NavBarSNCF.tsx
@@ -5,7 +5,7 @@ import getUnicodeFlagIcon from 'country-flag-icons/unicode';
import i18n from 'i18next';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
-import { Link } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
import logoOSRD from 'assets/fav-osrd-color.svg';
import ChangeLanguageModal from 'common/ChangeLanguageModal';
@@ -28,6 +28,7 @@ export default function LegacyNavBarSNCF({ appName, logo = logoOSRD }: Props) {
const safeWord = useSelector(getUserSafeWord);
const { t } = useTranslation('home/navbar');
const { logout, username } = useAuth();
+ const { pathname } = useLocation();
return (
@@ -91,9 +92,11 @@ export default function LegacyNavBarSNCF({ appName, logo = logoOSRD }: Props) {
,
- {isOpen && (
+ {!disableShadow && isOpen && (
({
aria-label="close selection display"
tabIndex={0}
onClick={() => {
+ if (disabled) return;
+
setIsOpen(false);
if (setSelectVisibility) setSelectVisibility(false);
}}
diff --git a/front/src/common/BootstrapSNCF/SelectSNCF.tsx b/front/src/common/BootstrapSNCF/SelectSNCF.tsx
index ff890703aaf..d543a8798c9 100644
--- a/front/src/common/BootstrapSNCF/SelectSNCF.tsx
+++ b/front/src/common/BootstrapSNCF/SelectSNCF.tsx
@@ -24,6 +24,7 @@ interface SelectProps {
onChange: (e?: T) => void;
className?: string;
sm?: boolean;
+ disabled?: boolean;
}
function SelectSNCF({
@@ -35,6 +36,7 @@ function SelectSNCF({
className,
sm,
value,
+ disabled,
}: SelectProps) {
return (
<>
@@ -49,6 +51,7 @@ function SelectSNCF({
}}
className={cx(className, sm && 'sm')}
value={isString(value) ? value : value?.id}
+ disabled={disabled}
>
{options.map((option) => (
- {searchResults.length > 100
+ {sortedSearchResults.length > 100
? t('resultsCountTooMuch')
: t('resultsCount', {
- count: sortedResults.length,
+ count: filteredAndSortedSearchResults.length,
})}
- {searchResults.length > 0 &&
- searchResults.length <= 100 &&
- sortedResults.map((searchResult, index) => (
+ {sortedSearchResults.length > 0 &&
+ sortedSearchResults.length <= 100 &&
+ filteredAndSortedSearchResults.map((searchResult, index) => (
{searchResult.trigram}
{searchResult.name}
- {searchResult.ch}
+ {!MAIN_OP_CH_CODES.includes(searchResult.ch) && (
+ {searchResult.ch ?? ''}
+ )}
{searchResult.ci}
diff --git a/front/src/common/Map/Search/useSearchOperationalPoint.tsx b/front/src/common/Map/Search/useSearchOperationalPoint.tsx
new file mode 100644
index 00000000000..b3664ba4fc5
--- /dev/null
+++ b/front/src/common/Map/Search/useSearchOperationalPoint.tsx
@@ -0,0 +1,116 @@
+import { useState, useEffect, useMemo } from 'react';
+
+import { isEmpty } from 'lodash';
+
+import { type SearchResultItemOperationalPoint, osrdEditoastApi } from 'common/api/osrdEditoastApi';
+import { useInfraID } from 'common/osrdContext';
+import { useDebounce } from 'utils/helpers';
+
+export const MAIN_OP_CH_CODES = ['', '00', 'BV'];
+
+type SearchOperationalPoint = {
+ debounceDelay?: number;
+ initialSearchTerm?: string;
+ initialChCodeFilter?: string;
+};
+
+export default function useSearchOperationalPoint({
+ debounceDelay = 150,
+ initialSearchTerm = '',
+ initialChCodeFilter,
+}: SearchOperationalPoint = {}) {
+ const infraID = useInfraID();
+ const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
+ const [chCodeFilter, setChCodeFilter] = useState(initialChCodeFilter);
+ const [searchResults, setSearchResults] = useState
([]);
+ const [mainOperationalPointsOnly, setMainOperationalPointsOnly] = useState(false);
+
+ const debouncedSearchTerm = useDebounce(searchTerm, debounceDelay);
+ const [postSearch] = osrdEditoastApi.endpoints.postSearch.useMutation();
+
+ const searchOperationalPoints = async () => {
+ const isSearchingByTrigram =
+ !Number.isInteger(+debouncedSearchTerm) && debouncedSearchTerm.length < 4;
+ const searchQuery = isSearchingByTrigram
+ ? // We have to test for op names that goes under 4 letters too
+ ['or', ['=i', ['trigram'], debouncedSearchTerm], ['search', ['name'], debouncedSearchTerm]]
+ : [
+ 'or',
+ ['search', ['name'], debouncedSearchTerm],
+ ['like', ['to_string', ['uic']], `%${debouncedSearchTerm}%`],
+ ];
+ const payload = {
+ object: 'operationalpoint',
+ query: ['and', searchQuery, infraID !== undefined ? ['=', ['infra_id'], infraID] : true],
+ };
+
+ try {
+ const results = await postSearch({
+ searchPayload: payload,
+ pageSize: 101,
+ }).unwrap();
+ setSearchResults(results as SearchResultItemOperationalPoint[]);
+ } catch (error) {
+ setSearchResults([]);
+ }
+ };
+
+ const sortByMainOperationalPoints = (
+ a: SearchResultItemOperationalPoint,
+ b: SearchResultItemOperationalPoint
+ ) => {
+ const nameComparison = a.name.localeCompare(b.name);
+ if (nameComparison !== 0) {
+ return nameComparison;
+ }
+ if (MAIN_OP_CH_CODES.includes(a.ch)) {
+ return -1;
+ }
+ if (MAIN_OP_CH_CODES.includes(b.ch)) {
+ return 1;
+ }
+ return a.ch.localeCompare(b.ch);
+ };
+
+ const sortedSearchResults = useMemo(
+ () => [...searchResults].sort(sortByMainOperationalPoints),
+ [searchResults]
+ );
+
+ const filteredAndSortedSearchResults = useMemo(
+ () =>
+ sortedSearchResults.filter((result) => {
+ if (mainOperationalPointsOnly || (chCodeFilter && MAIN_OP_CH_CODES.includes(chCodeFilter)))
+ return MAIN_OP_CH_CODES.includes(result.ch);
+
+ if (chCodeFilter === undefined) return true;
+
+ return result.ch.toLocaleLowerCase().includes(chCodeFilter.trim().toLowerCase());
+ }),
+ [sortedSearchResults, chCodeFilter, mainOperationalPointsOnly]
+ );
+
+ useEffect(() => {
+ if (debouncedSearchTerm) {
+ searchOperationalPoints();
+ } else if (searchResults.length !== 0) {
+ setSearchResults([]);
+ }
+ }, [debouncedSearchTerm]);
+
+ useEffect(() => {
+ if (isEmpty(searchResults)) setChCodeFilter(undefined);
+ }, [searchResults]);
+
+ return {
+ searchTerm,
+ chCodeFilter,
+ sortedSearchResults,
+ filteredAndSortedSearchResults,
+ mainOperationalPointsOnly,
+ setSearchTerm,
+ setChCodeFilter,
+ setSearchResults,
+ setMainOperationalPointsOnly,
+ };
+}
diff --git a/front/src/common/SpeedLimitByTagSelector/SpeedLimitByTagSelector.tsx b/front/src/common/SpeedLimitByTagSelector/SpeedLimitByTagSelector.tsx
index 0d65df7d877..e9076b70f67 100644
--- a/front/src/common/SpeedLimitByTagSelector/SpeedLimitByTagSelector.tsx
+++ b/front/src/common/SpeedLimitByTagSelector/SpeedLimitByTagSelector.tsx
@@ -9,6 +9,7 @@ import SelectImprovedSNCF from 'common/BootstrapSNCF/SelectImprovedSNCF';
type SpeedLimitByTagSelectorProps = {
condensed?: boolean;
+ disabled?: boolean;
selectedSpeedLimitByTag?: string;
speedLimitsByTags: string[];
dispatchUpdateSpeedLimitByTag: (newTag: string | null) => void;
@@ -16,6 +17,7 @@ type SpeedLimitByTagSelectorProps = {
export default function SpeedLimitByTagSelector({
condensed = false,
+ disabled = false,
selectedSpeedLimitByTag: speedLimitByTag,
speedLimitsByTags,
dispatchUpdateSpeedLimitByTag,
@@ -42,6 +44,7 @@ export default function SpeedLimitByTagSelector({
{t('speedLimitByTag')}
{
dispatch(updateUserPreferences({ ...userPreferences, safeWord: debouncedSafeWord }));
}, [debouncedSafeWord]);
- const { t } = useTranslation('home/navbar');
+ const { t } = useTranslation(['home/navbar']);
return (
<>
@@ -70,12 +80,29 @@ export default function UserSettings() {
dispatch(switchTrainScheduleV2Activated());
dispatch(updateScenarioID(undefined));
dispatch(updateTimetableID(undefined));
+ dispatch(updateInfraID(undefined));
}}
checked={trainScheduleV2Activated}
/>
TrainSchedule V2