diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index be86cba360e..a62eb35d12d 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -2486,6 +2486,22 @@ paths: description: Check if Editoast is running correctly /infra/: get: + parameters: + - description: Page number + in: query + name: page + schema: + default: 1 + minimum: 1 + type: integer + - description: Number of elements by page + in: query + name: page_size + schema: + default: 25 + maximum: 10000 + minimum: 1 + type: integer responses: '200': content: @@ -2505,8 +2521,8 @@ paths: - next - previous type: object - description: The infra list - summary: List all available infra + description: The infras list + summary: Paginated list of all available infras tags: - infra post: diff --git a/editoast/openapi_legacy.yaml b/editoast/openapi_legacy.yaml index 49e367d48ba..7ddb3c226fa 100644 --- a/editoast/openapi_legacy.yaml +++ b/editoast/openapi_legacy.yaml @@ -173,10 +173,26 @@ paths: get: tags: - infra - summary: List all available infra + summary: Paginated list of all available infras + parameters: + - description: Page number + in: query + name: page + schema: + default: 1 + minimum: 1 + type: integer + - description: Number of elements by page + in: query + name: page_size + schema: + default: 25 + maximum: 10000 + minimum: 1 + type: integer responses: 200: - description: The infra list + description: The infras list content: application/json: schema: diff --git a/front/public/locales/fr/infraManagement.json b/front/public/locales/fr/infraManagement.json index 2e9908802bc..b470e045c64 100644 --- a/front/public/locales/fr/infraManagement.json +++ b/front/public/locales/fr/infraManagement.json @@ -24,6 +24,7 @@ "goToEditionMode": "Éditer", "goToStandardMode": "Retour à la sélection", "infraChoice": "Infrastructures", + "infraDeleted": "L'infrastructure {{name}} a bien été supprimée.", "infraManagement": "Gestion des infrastructures", "infraName": "Nom de l'infrastructure", "infrasFound_zero": "Aucune insfrastructure trouvée", diff --git a/front/src/common/InfraSelector/Consts.jsx b/front/src/common/InfraSelector/Consts.jsx deleted file mode 100644 index a3ca67aaa44..00000000000 --- a/front/src/common/InfraSelector/Consts.jsx +++ /dev/null @@ -1,2 +0,0 @@ -export const INFRA_URL = '/editoast/infra/'; -export const INFRA_URL_OLD = '/infra/'; diff --git a/front/src/common/InfraSelector/InfraSelectorEditionActionsBar.tsx b/front/src/common/InfraSelector/InfraSelectorEditionActionsBar.tsx index a2630becba9..0b0001466f8 100644 --- a/front/src/common/InfraSelector/InfraSelectorEditionActionsBar.tsx +++ b/front/src/common/InfraSelector/InfraSelectorEditionActionsBar.tsx @@ -1,40 +1,51 @@ import React, { useState } from 'react'; import { FaCopy, FaDownload, FaLock, FaLockOpen, FaPencilAlt } from 'react-icons/fa'; import { useTranslation } from 'react-i18next'; -import { post, get, put } from 'common/requests'; import { MdCancel, MdCheck } from 'react-icons/md'; import fileDownload from 'js-file-download'; import { Infra, osrdEditoastApi } from 'common/api/osrdEditoastApi'; -import { INFRA_URL } from './Consts'; +import { useDispatch } from 'react-redux'; +import { setFailure } from 'reducers/main'; +import InfraLockState from './consts'; type ActionBarProps = { infra: Infra; isFocused?: number; setIsFocused: (focus?: number) => void; - getInfrasList: () => void; inputValue: string; }; -export default function ActionsBar({ - infra, - isFocused, - setIsFocused, - getInfrasList, - inputValue, -}: ActionBarProps) { +export default function ActionsBar({ infra, isFocused, setIsFocused, inputValue }: ActionBarProps) { const { t } = useTranslation('infraManagement'); const [isWaiting, setIsWaiting] = useState(false); + const dispatch = useDispatch(); + const [lockInfra] = osrdEditoastApi.usePostInfraByIdLockMutation(); + const [unlockInfra] = osrdEditoastApi.usePostInfraByIdUnlockMutation(); + const [getRailjson] = osrdEditoastApi.useLazyGetInfraByIdRailjsonQuery(); const [cloneInfra] = osrdEditoastApi.usePostInfraByIdCloneMutation(); + const [updateInfra] = osrdEditoastApi.usePutInfraByIdMutation(); - async function handleLockedState(action: string) { + async function handleLockedState(action: InfraLockState) { if (!isWaiting) { setIsWaiting(true); try { - await post(`${INFRA_URL}${infra.id}/${action}/`, {}); - getInfrasList(); - setIsWaiting(false); + if (action === InfraLockState.LOCK) { + await lockInfra({ id: infra.id }); + } + if (action === InfraLockState.UNLOCK) { + await unlockInfra({ id: infra.id }); + } } catch (e) { + if (e instanceof Error) { + dispatch( + setFailure({ + name: e.name, + message: e.message, + }) + ); + } + } finally { setIsWaiting(false); } } @@ -44,10 +55,18 @@ export default function ActionsBar({ if (!isWaiting) { setIsWaiting(true); try { - const railjson = await get(`${INFRA_URL}${infra.id}/railjson/`); + const railjson = await getRailjson({ id: infra.id }); fileDownload(JSON.stringify(railjson), `${infra.name}.id${infra.id}.railjson.json`); - setIsWaiting(false); } catch (e) { + if (e instanceof Error) { + dispatch( + setFailure({ + name: e.name, + message: e.message, + }) + ); + } + } finally { setIsWaiting(false); } } @@ -57,14 +76,19 @@ export default function ActionsBar({ if (!isWaiting) { setIsWaiting(true); try { - await cloneInfra({ id: infra.id, name: `${infra.name}_copy` }).unwrap(); - setIsWaiting(false); + await cloneInfra({ id: infra.id, name: `${infra.name}_copy` }); } catch (e) { + if (e instanceof Error) { + dispatch( + setFailure({ + name: e.name, + message: e.message, + }) + ); + } + } finally { setIsWaiting(false); } - setTimeout(() => { - getInfrasList(); - }, 1000); } } @@ -72,11 +96,18 @@ export default function ActionsBar({ if (!isWaiting) { setIsWaiting(true); try { - await put(`${INFRA_URL}${infra.id}/`, { name: inputValue }); - getInfrasList(); + await updateInfra({ id: infra.id, body: { name: inputValue } }); setIsFocused(undefined); - setIsWaiting(false); } catch (e) { + if (e instanceof Error) { + dispatch( + setFailure({ + name: e.name, + message: e.message, + }) + ); + } + } finally { setIsWaiting(false); } } @@ -122,7 +153,7 @@ export default function ActionsBar({ className="infraslist-item-action unlock" type="button" title={t('infraManagement:actions.unlock')} - onClick={() => handleLockedState('unlock')} + onClick={() => handleLockedState(InfraLockState.UNLOCK)} > @@ -131,7 +162,7 @@ export default function ActionsBar({ className="infraslist-item-action lock" type="button" title={t('infraManagement:actions.lock')} - onClick={() => handleLockedState('lock')} + onClick={() => handleLockedState(InfraLockState.LOCK)} > diff --git a/front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.jsx b/front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.tsx similarity index 53% rename from front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.jsx rename to front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.tsx index 5772f9af00f..1d32117bdd0 100644 --- a/front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.jsx +++ b/front/src/common/InfraSelector/InfraSelectorEditionActionsBarDelete.tsx @@ -1,31 +1,53 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { deleteRequest } from 'common/requests'; import { useTranslation } from 'react-i18next'; import Countdown from 'react-countdown'; -import { INFRA_URL } from './Consts'; +import { Infra, osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import { useDispatch } from 'react-redux'; +import { setFailure, setSuccess } from 'reducers/main'; -export default function InfraSelectorEditionActionsBarDelete(props) { - const { getInfrasList, setRunningDelete, infra } = props; +type InfraSelectorEditionActionsBarDeleteProps = { + infra: Infra; + setRunningDelete: (infraId?: number) => void; +}; + +export default function InfraSelectorEditionActionsBarDelete({ + infra, + setRunningDelete, +}: InfraSelectorEditionActionsBarDeleteProps) { const { t } = useTranslation('infraManagement'); + const dispatch = useDispatch(); + + const [deleteInfra] = osrdEditoastApi.useDeleteInfraByIdMutation(); - async function deleteInfra() { + async function handleDeleteInfra() { try { - await deleteRequest(`${INFRA_URL}${infra.id}/`); + await deleteInfra({ id: infra.id }); setRunningDelete(undefined); - getInfrasList(); + dispatch( + setSuccess({ + title: t('infraDeleted', { name: infra.name }), + text: '', + }) + ); } catch (e) { - /* empty */ + if (e instanceof Error) { + dispatch( + setFailure({ + name: e.name, + message: e.message, + }) + ); + } } } - const countDownDelete = ({ seconds, completed }) => { + const countDownDelete = ({ seconds, completed }: { seconds: number; completed: boolean }) => { if (completed) { return ( @@ -59,9 +81,3 @@ export default function InfraSelectorEditionActionsBarDelete(props) { ); } - -InfraSelectorEditionActionsBarDelete.propTypes = { - getInfrasList: PropTypes.func.isRequired, - infra: PropTypes.object.isRequired, - setRunningDelete: PropTypes.func.isRequired, -}; diff --git a/front/src/common/InfraSelector/InfraSelectorEditionItem.jsx b/front/src/common/InfraSelector/InfraSelectorEditionItem.tsx similarity index 77% rename from front/src/common/InfraSelector/InfraSelectorEditionItem.jsx rename to front/src/common/InfraSelector/InfraSelectorEditionItem.tsx index 626bdd61caf..fadeadc9ec7 100644 --- a/front/src/common/InfraSelector/InfraSelectorEditionItem.jsx +++ b/front/src/common/InfraSelector/InfraSelectorEditionItem.tsx @@ -1,16 +1,26 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; import nextId from 'react-id-generator'; import { FaLock, FaTrash } from 'react-icons/fa'; import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; import { useTranslation } from 'react-i18next'; -import { editoastUpToDateIndicator } from './InfraSelectorModalBodyStandard'; +import { Infra } from 'common/api/osrdEditoastApi'; import ActionsBar from './InfraSelectorEditionActionsBar'; +import { editoastUpToDateIndicator } from './InfraSelectorModalBodyStandard'; import InfraSelectorEditionActionsBarDelete from './InfraSelectorEditionActionsBarDelete'; -export default function InfraSelectorEditionItem(props) { - const { infra, isFocused, setIsFocused, runningDelete, setRunningDelete, getInfrasList } = props; +type InfraSelectorEditionItemProps = { + infra: Infra; + isFocused?: number; + setIsFocused: (infraId?: number) => void; +}; + +export default function InfraSelectorEditionItem({ + infra, + isFocused, + setIsFocused, +}: InfraSelectorEditionItemProps) { const [value, setValue] = useState(infra.name); + const [runningDelete, setRunningDelete] = useState(undefined); const { t } = useTranslation('infraManagement'); const handleRunningDelete = () => { @@ -24,11 +34,7 @@ export default function InfraSelectorEditionItem(props) {
) : null} {runningDelete === infra.id ? ( - + ) : null} {isFocused === infra.id ? null : (
); } - -InfraSelectorEditionItem.defaultProps = { - isFocused: undefined, - runningDelete: undefined, -}; - -InfraSelectorEditionItem.propTypes = { - infra: PropTypes.object.isRequired, - isFocused: PropTypes.number, - setIsFocused: PropTypes.func.isRequired, - runningDelete: PropTypes.number, - setRunningDelete: PropTypes.func.isRequired, - getInfrasList: PropTypes.func.isRequired, -}; diff --git a/front/src/common/InfraSelector/InfraSelectorModal.tsx b/front/src/common/InfraSelector/InfraSelectorModal.tsx index 4a5d50042ae..e8cd1d81735 100644 --- a/front/src/common/InfraSelector/InfraSelectorModal.tsx +++ b/front/src/common/InfraSelector/InfraSelectorModal.tsx @@ -20,52 +20,41 @@ const InfraSelectorModal = ({ onInfraChange, onlySelectionMode = false, }: InfraSelectorModalProps) => { - const [infrasList, setInfrasList] = useState([]); const { t } = useTranslation(['translation', 'infraManagement']); const [filter, setFilter] = useState(''); const [filteredInfrasList, setFilteredInfrasList] = useState([]); const [editionMode, setEditionMode] = useState(false); - const [isFetching, setIsFetching] = useState(false); - const [getInfra] = osrdEditoastApi.endpoints.getInfra.useLazyQuery(); + const { + data: infrasList, + isSuccess, + isLoading, + } = osrdEditoastApi.useGetInfraQuery({ pageSize: 1000 }); const debouncedFilter = useDebounce(filter, 250); function filterInfras(infrasListLocal: Infra[]) { if (debouncedFilter && debouncedFilter !== '') { - infrasListLocal = infrasList.filter((infra) => + infrasListLocal = infrasListLocal.filter((infra) => infra.name.toLowerCase().includes(debouncedFilter.toLowerCase()) ); } const filteredInfrasListLocal = infrasListLocal .slice() .sort((a, b) => a.name.localeCompare(b.name)); - setFilteredInfrasList(filteredInfrasListLocal); } - const getInfrasList = () => { - setIsFetching(true); - getInfra() - .unwrap() - .then(({ results }) => { - setInfrasList(results as Infra[]); - filterInfras(results as Infra[]); - setIsFetching(false); - }) - .catch(() => setIsFetching(false)); - }; - useEffect(() => { - if (infrasList) { - filterInfras(infrasList); + if (infrasList?.results) { + filterInfras(infrasList.results); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedFilter]); useEffect(() => { - getInfrasList(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (isSuccess && infrasList?.results && infrasList.results.length > 0) { + filterInfras(infrasList.results); + } + }, [isSuccess, infrasList]); return ( <> @@ -101,7 +90,7 @@ const InfraSelectorModal = ({ - {isFetching && ( + {isLoading && (
@@ -111,7 +100,6 @@ const InfraSelectorModal = ({ infrasList={filteredInfrasList} setFilter={setFilter} filter={filter} - getInfrasList={getInfrasList} /> ) : ( >; filter: string; - getInfrasList: () => void; }; const InfraSelectorModalBodyEdition = ({ infrasList, setFilter, filter = '', - getInfrasList, }: InfraSelectorModalBodyEditionProps) => { const [isFocused, setIsFocused] = useState(undefined); - const [runningDelete, setRunningDelete] = useState(undefined); const [nameNewInfra, setNameNewInfra] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [selectedFile, setSelectedFile] = useState(undefined); @@ -71,14 +68,12 @@ const InfraSelectorModalBodyEdition = ({ .unwrap() .then(() => { setSelectedFile(undefined); - getInfrasList(); setErrorMessage(undefined); }); } else { postInfra({ body: { name: nameNewInfra } }) .unwrap() .then(() => { - getInfrasList(); setErrorMessage(undefined); }); } @@ -107,10 +102,7 @@ const InfraSelectorModalBodyEdition = ({ infra={infra} key={nextId()} isFocused={isFocused} - runningDelete={runningDelete} - setRunningDelete={setRunningDelete} setIsFocused={setIsFocused} - getInfrasList={getInfrasList} /> ))} diff --git a/front/src/common/InfraSelector/InfraSelectorModalBodyStandard.jsx b/front/src/common/InfraSelector/InfraSelectorModalBodyStandard.tsx similarity index 78% rename from front/src/common/InfraSelector/InfraSelectorModalBodyStandard.jsx rename to front/src/common/InfraSelector/InfraSelectorModalBodyStandard.tsx index ed1d83c53bb..ae41fd7f52e 100644 --- a/front/src/common/InfraSelector/InfraSelectorModalBodyStandard.jsx +++ b/front/src/common/InfraSelector/InfraSelectorModalBodyStandard.tsx @@ -1,5 +1,4 @@ import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; import nextId from 'react-id-generator'; import { FaLock } from 'react-icons/fa'; import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; @@ -8,21 +7,41 @@ import { getInfraID } from 'reducers/osrdconf/selectors'; import { updateInfraID, deleteItinerary } from 'reducers/osrdconf'; import { useTranslation } from 'react-i18next'; import { ModalContext } from 'common/BootstrapSNCF/ModalSNCF/ModalProvider'; +import { Infra } from 'common/api/osrdEditoastApi'; + +type InfraSelectorModalBodyStandardProps = { + filter: string; + setFilter: (filterInput: string) => void; + infrasList: Infra[]; + onlySelectionMode: boolean; + onInfraChange?: (infraId: number) => void; +}; // Test coherence between actual & generated version, eg. if editoast is up to date with data -export function editoastUpToDateIndicator(v, genv) { - return ; +export function editoastUpToDateIndicator( + infraVersion: string, + infraGeneratedVersion: string | null +) { + return ( + + ● + + ); } -export default function InfraSelectorModalBodyStandard(props) { - const { infrasList, filter, setFilter, onlySelectionMode, onInfraChange } = props; - +export default function InfraSelectorModalBodyStandard({ + filter = '', + setFilter, + infrasList, + onlySelectionMode = false, + onInfraChange, +}: InfraSelectorModalBodyStandardProps) { const { t } = useTranslation(['translation', 'infraManagement']); const dispatch = useDispatch(); const infraID = useSelector(getInfraID); const { closeModal } = useContext(ModalContext); - function setInfraID(id) { + function setInfraID(id: number) { dispatch(updateInfraID(id)); if (onInfraChange) onInfraChange(id); dispatch(deleteItinerary()); @@ -83,17 +102,3 @@ export default function InfraSelectorModalBodyStandard(props) { ); } - -InfraSelectorModalBodyStandard.defaultProps = { - filter: '', - onlySelectionMode: false, - onInfraChange: undefined, -}; - -InfraSelectorModalBodyStandard.propTypes = { - filter: PropTypes.string, - infrasList: PropTypes.array.isRequired, - setFilter: PropTypes.func.isRequired, - onlySelectionMode: PropTypes.bool, - onInfraChange: PropTypes.func, -}; diff --git a/front/src/common/InfraSelector/consts.ts b/front/src/common/InfraSelector/consts.ts new file mode 100644 index 00000000000..e5b797fa68a --- /dev/null +++ b/front/src/common/InfraSelector/consts.ts @@ -0,0 +1,6 @@ +enum InfraLockState { + LOCK, + UNLOCK, +} + +export default InfraLockState; diff --git a/front/src/common/api/osrdEditoastApi.ts b/front/src/common/api/osrdEditoastApi.ts index 3ede3e6be0e..ab08574a01b 100644 --- a/front/src/common/api/osrdEditoastApi.ts +++ b/front/src/common/api/osrdEditoastApi.ts @@ -72,7 +72,10 @@ const injectedRtkApi = api query: () => ({ url: `/health/` }), }), getInfra: build.query({ - query: () => ({ url: `/infra/` }), + query: (queryArg) => ({ + url: `/infra/`, + params: { page: queryArg.page, page_size: queryArg.pageSize }, + }), providesTags: ['infra'], }), postInfra: build.mutation({ @@ -660,13 +663,18 @@ export type GetElectricalProfileSetByIdLevelOrderApiArg = { }; export type GetHealthApiResponse = unknown; export type GetHealthApiArg = void; -export type GetInfraApiResponse = /** status 200 The infra list */ { +export type GetInfraApiResponse = /** status 200 The infras list */ { count: number; next: any; previous: any; results?: Infra[]; }; -export type GetInfraApiArg = void; +export type GetInfraApiArg = { + /** Page number */ + page?: number; + /** Number of elements by page */ + pageSize?: number; +}; export type PostInfraApiResponse = /** status 201 The created infra */ Infra; export type PostInfraApiArg = { /** Name of the infra to create */