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: refacto useSearchOperationalPoints and get all ch options when selecting a op in stdcm #10782

Merged
merged 4 commits into from
Feb 13, 2025
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
Expand Up @@ -44,8 +44,9 @@ const StdcmOperationalPoint = ({ location, pathStepId, disabled }: StdcmOperatio
const {
searchTerm,
setSearchTerm,
sortedSearchResults: searchResults,
searchResults,
setSearchResults,
searchOperationalPointsByTrigram,
} = useSearchOperationalPoint({
initialSearchTerm: location?.name,
initialChCodeFilter: location?.secondary_code,
Expand Down Expand Up @@ -103,14 +104,17 @@ const StdcmOperationalPoint = ({ location, pathStepId, disabled }: StdcmOperatio
[searchResults]
);

const handleCiSelect = (selectedSuggestion?: CIOption) => {
const handleCiSelect = async (selectedSuggestion?: CIOption) => {
dispatch(updateStdcmPathStep({ id: pathStepId, updates: { location: selectedSuggestion } }));
if (selectedSuggestion) {
const newChSuggestions = extractChCodes(searchResults, selectedSuggestion);
const operationalPointParts = await searchOperationalPointsByTrigram(
selectedSuggestion.trigram
);
const newChSuggestions = extractChCodes(operationalPointParts, selectedSuggestion);
setChSuggestions(newChSuggestions);
} else {
setChSuggestions([]);
}
dispatch(updateStdcmPathStep({ id: pathStepId, updates: { location: selectedSuggestion } }));
};

const handleChSelect = (selectedChCode?: CHOption) => {
Expand Down
18 changes: 9 additions & 9 deletions front/src/common/Map/Search/MapSearchOperationalPoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const MapSearchOperationalPoint = ({
const {
searchTerm,
chCodeFilter,
sortedSearchResults,
filteredAndSortedSearchResults,
searchResults,
searchResultsFilteredByCh,
mainOperationalPointsOnly,
setSearchTerm,
setChCodeFilter,
Expand Down Expand Up @@ -57,7 +57,7 @@ const MapSearchOperationalPoint = ({
switch (event.key) {
case 'ArrowUp':
setSelectedResultIndex((prevIndex) => {
const newIndex = prevIndex > 0 ? prevIndex - 1 : sortedSearchResults.length - 1;
const newIndex = prevIndex > 0 ? prevIndex - 1 : searchResults.length - 1;
const element = document.getElementById(`result-${newIndex}`);
if (element) {
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
Expand All @@ -67,7 +67,7 @@ const MapSearchOperationalPoint = ({
break;
case 'ArrowDown':
setSelectedResultIndex((prevIndex) => {
const newIndex = prevIndex < sortedSearchResults.length - 1 ? prevIndex + 1 : 0;
const newIndex = prevIndex < searchResults.length - 1 ? prevIndex + 1 : 0;
const element = document.getElementById(`result-${newIndex}`);
if (element) {
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
Expand Down Expand Up @@ -133,16 +133,16 @@ const MapSearchOperationalPoint = ({
</span>
</div>
<h2 className="text-center mt-3">
{sortedSearchResults.length > 100
{searchResults.length > 100
? t('resultsCountTooMuch')
: t('resultsCount', {
count: filteredAndSortedSearchResults.length,
count: searchResultsFilteredByCh.length,
})}
</h2>
<div className="search-results">
{sortedSearchResults.length > 0 &&
sortedSearchResults.length <= 100 &&
filteredAndSortedSearchResults.map((searchResult, index) => (
{searchResults.length > 0 &&
searchResults.length <= 100 &&
searchResultsFilteredByCh.map((searchResult, index) => (
<button
id={`result-${index}`}
type="button"
Expand Down
254 changes: 127 additions & 127 deletions front/src/common/Map/Search/useSearchOperationalPoint.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect, useMemo, useCallback } from 'react';

import { isEmpty } from 'lodash';
import { useSelector } from 'react-redux';

import DPY_TO_MAS_OPERATIONAL_POINTS from 'assets/operationStudies/DPYToMASOperationalPoints';
import {
type SearchQuery,
type SearchResultItemOperationalPoint,
osrdEditoastApi,
} from 'common/api/osrdEditoastApi';
import { type SearchResultItemOperationalPoint, osrdEditoastApi } from 'common/api/osrdEditoastApi';
import { useInfraID } from 'common/osrdContext';
import { setFailure } from 'reducers/main';
import { getIsSuperUser } from 'reducers/user/userSelectors';
Expand Down Expand Up @@ -44,135 +40,139 @@ export default function useSearchOperationalPoint({
const debouncedSearchTerm = useDebounce(searchTerm, debounceDelay);
const [postSearch] = osrdEditoastApi.endpoints.postSearch.useMutation();

const searchOperationalPointsByTrigram = async (
infraId: number,
dpyToMasOperationalpointsFilter: SearchQuery
) => {
const shouldSearchByTrigram =
!Number.isInteger(+debouncedSearchTerm) && debouncedSearchTerm.length < 4;

if (!shouldSearchByTrigram) return [];

const payload = {
object: 'operationalpoint',
query: [
'and',
['=i', ['trigram'], debouncedSearchTerm],
['=', ['infra_id'], infraId],
dpyToMasOperationalpointsFilter,
],
const sortOperationalPoints =
(searchQuery: string) =>
(a: SearchResultItemOperationalPoint, b: SearchResultItemOperationalPoint) => {
const upperCaseSearchTerm = searchQuery.toUpperCase();
const lowerCaseSearchTerm = searchQuery.toLowerCase();

// ops with trigram match first
if (a.trigram === upperCaseSearchTerm && b.trigram !== upperCaseSearchTerm) {
return -1;
}
if (b.trigram === upperCaseSearchTerm && a.trigram !== upperCaseSearchTerm) {
return 1;
}

// ops whose name starts by the searchTerm
const aStartsWithSearchTerm = a.name.toLowerCase().startsWith(lowerCaseSearchTerm);
const bStartsWithSearchTerm = b.name.toLowerCase().startsWith(lowerCaseSearchTerm);

if (aStartsWithSearchTerm && !bStartsWithSearchTerm) {
return -1;
}
if (!aStartsWithSearchTerm && bStartsWithSearchTerm) {
return 1;
}

// other matching ops alphabetically ordered
const nameComparison = a.name.localeCompare(b.name);
if (nameComparison !== 0) {
return nameComparison;
}

const chA = a.ch ?? '';
const chB = b.ch ?? '';

if (MAIN_OP_CH_CODES.includes(chA)) {
return -1;
}
if (MAIN_OP_CH_CODES.includes(chB)) {
return 1;
}
return chA.localeCompare(chB);
};
try {
const results = (await postSearch({
searchPayload: payload,
pageSize: 101,
}).unwrap()) as SearchResultItemOperationalPoint[];
return results;
} catch (error) {
setFailure(castErrorToFailure(error));
return [];
}
};

const searchOperationalPoints = async () => {
if (infraID === undefined) return;

const dpyToMasOperationalpointsFilter = isStdcm && !isSuperUser ? DPY_TO_MAS_FILTER : true;

const trigramResults = await searchOperationalPointsByTrigram(
infraID,
dpyToMasOperationalpointsFilter
);

try {
const results = (await postSearch({
searchPayload: {
object: 'operationalpoint',
query: [
'and',
[
'or',
['search', ['name'], debouncedSearchTerm],
['like', ['to_string', ['uic']], `%${debouncedSearchTerm}%`],
],
['=', ['infra_id'], infraID],
dpyToMasOperationalpointsFilter,
],
},
pageSize: 101,
}).unwrap()) as SearchResultItemOperationalPoint[];
setSearchResults([...trigramResults, ...results]);
} catch (error) {
setFailure(castErrorToFailure(error));
setSearchResults([]);
}
};

const sortOperationalPoints = (
a: SearchResultItemOperationalPoint,
b: SearchResultItemOperationalPoint
) => {
const upperCaseSearchTerm = debouncedSearchTerm.toUpperCase();
const lowerCaseSearchTerm = debouncedSearchTerm.toLowerCase();

// ops with trigram match first
if (a.trigram === upperCaseSearchTerm && b.trigram !== upperCaseSearchTerm) {
return -1;
}
if (b.trigram === upperCaseSearchTerm && a.trigram !== upperCaseSearchTerm) {
return 1;
}

// ops whose name starts by the searchTerm
const aStartsWithSearchTerm = a.name.toLowerCase().startsWith(lowerCaseSearchTerm);
const bStartsWithSearchTerm = b.name.toLowerCase().startsWith(lowerCaseSearchTerm);

if (aStartsWithSearchTerm && !bStartsWithSearchTerm) {
return -1;
}
if (!aStartsWithSearchTerm && bStartsWithSearchTerm) {
return 1;
}

// other matching ops alphabetically ordered
const nameComparison = a.name.localeCompare(b.name);
if (nameComparison !== 0) {
return nameComparison;
}

const chA = a.ch ?? '';
const chB = b.ch ?? '';

if (MAIN_OP_CH_CODES.includes(chA)) {
return -1;
}
if (MAIN_OP_CH_CODES.includes(chB)) {
return 1;
}
return chA.localeCompare(chB);
};
const searchOperationalPointsByTrigram = useCallback(
async (searchQuery: string) => {
const shouldSearchByTrigram = !Number.isInteger(+searchQuery) && searchQuery.length < 4;

if (!shouldSearchByTrigram || !infraID) return [];

const dpyToMasOperationalpointsFilter = isStdcm && !isSuperUser ? DPY_TO_MAS_FILTER : true;

const payload = {
object: 'operationalpoint',
query: [
'and',
['=i', ['trigram'], searchQuery],
['=', ['infra_id'], infraID],
dpyToMasOperationalpointsFilter,
],
};
try {
const results = (await postSearch({
searchPayload: payload,
pageSize: 101,
}).unwrap()) as SearchResultItemOperationalPoint[];
const sortedResults = [...results];
sortedResults.sort(sortOperationalPoints(searchQuery));
return sortedResults;
} catch (error) {
setFailure(castErrorToFailure(error));
return [];
}
},
[infraID, isStdcm, isSuperUser]
);

const sortedSearchResults = useMemo(
() => [...searchResults].sort(sortOperationalPoints),
[searchResults]
/** Search for operational points by name or UIC code (primary code) */
const searchOperationalPoints = useCallback(
async (searchQuery: string) => {
if (infraID === undefined) return [];

const trigramResults = await searchOperationalPointsByTrigram(searchQuery);

const dpyToMasOperationalpointsFilter = isStdcm && !isSuperUser ? DPY_TO_MAS_FILTER : true;

try {
const results = (await postSearch({
searchPayload: {
object: 'operationalpoint',
query: [
'and',
[
'or',
['search', ['name'], searchQuery],
['like', ['to_string', ['uic']], `%${searchQuery}%`],
],
['=', ['infra_id'], infraID],
dpyToMasOperationalpointsFilter,
],
},
pageSize: 101,
}).unwrap()) as SearchResultItemOperationalPoint[];

const allResults = [...trigramResults, ...results];
allResults.sort(sortOperationalPoints(searchQuery));
return allResults;
} catch (error) {
setFailure(castErrorToFailure(error));
return [];
}
},
[infraID, isStdcm, isSuperUser]
);

const filteredAndSortedSearchResults = useMemo(
() =>
sortedSearchResults.filter((result) => {
if (mainOperationalPointsOnly || (chCodeFilter && MAIN_OP_CH_CODES.includes(chCodeFilter)))
return MAIN_OP_CH_CODES.includes(result.ch);
/** Filter operational points on secondary code (ch), if provided */
const searchResultsFilteredByCh = useMemo(() => {
if (
mainOperationalPointsOnly ||
(chCodeFilter !== undefined && MAIN_OP_CH_CODES.includes(chCodeFilter))
)
return searchResults.filter((result) => MAIN_OP_CH_CODES.includes(result.ch));

if (chCodeFilter === undefined) return true;
if (!chCodeFilter) return searchResults;

return result.ch.toLocaleLowerCase().includes(chCodeFilter.trim().toLowerCase());
}),
[sortedSearchResults, chCodeFilter, mainOperationalPointsOnly]
);
const chFilter = chCodeFilter.trim().toLowerCase();
return searchResults.filter((result) => result.ch.toLocaleLowerCase().includes(chFilter));
}, [searchResults, chCodeFilter, mainOperationalPointsOnly]);

useEffect(() => {
if (debouncedSearchTerm) {
searchOperationalPoints();
searchOperationalPoints(debouncedSearchTerm).then((results) => {
setSearchResults(results);
});
} else if (searchResults.length !== 0) {
setSearchResults([]);
}
Expand All @@ -185,11 +185,11 @@ export default function useSearchOperationalPoint({
return {
searchTerm,
chCodeFilter,
sortedSearchResults,
filteredAndSortedSearchResults,
searchResultsFilteredByCh,
mainOperationalPointsOnly,
searchResults,
searchOperationalPoints,
searchOperationalPointsByTrigram,
setSearchTerm,
setChCodeFilter,
setSearchResults,
Expand Down
Loading