diff --git a/openbas-front/src/admin/components/Dashboard.tsx b/openbas-front/src/admin/components/Dashboard.tsx index a22e30cdd8..5a216816fa 100644 --- a/openbas-front/src/admin/components/Dashboard.tsx +++ b/openbas-front/src/admin/components/Dashboard.tsx @@ -69,6 +69,7 @@ const Dashboard = () => { const dispatch = useAppDispatch(); // Exercises + const [loadingExercises, setLoadingExercises] = useState(true); const [exercises, setExercises] = useState([]); const searchPaginationInput = { sorts: initSorting('exercise_updated_at', 'DESC'), @@ -76,10 +77,11 @@ const Dashboard = () => { size: 6, }; useEffect(() => { + setLoadingExercises(true); searchExercises(searchPaginationInput).then((result: { data: Page }) => { const { data } = result; setExercises(data.content); - }); + }).finally(() => setLoadingExercises(false)); }, []); // Statistics @@ -278,6 +280,7 @@ const Dashboard = () => { exercises={exercises} hasHeader={false} variant="reduced-view" + loading={loadingExercises} /> diff --git a/openbas-front/src/admin/components/atomic_testings/InjectDtoList.tsx b/openbas-front/src/admin/components/atomic_testings/InjectDtoList.tsx index 48ba77899b..b986b6e9a9 100644 --- a/openbas-front/src/admin/components/atomic_testings/InjectDtoList.tsx +++ b/openbas-front/src/admin/components/atomic_testings/InjectDtoList.tsx @@ -1,3 +1,4 @@ +import { HelpOutlineOutlined } from '@mui/icons-material'; import { List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { CSSProperties, FunctionComponent, useMemo, useState } from 'react'; @@ -12,6 +13,7 @@ import Empty from '../../../components/Empty'; import { useFormatter } from '../../../components/i18n'; import ItemStatus from '../../../components/ItemStatus'; import ItemTargets from '../../../components/ItemTargets'; +import PaginatedListLoader from '../../../components/PaginatedListLoader'; import type { InjectResultDTO, SearchPaginationInput } from '../../../utils/api-types'; import { isNotEmptyField } from '../../../utils/utils'; import InjectIcon from '../common/injects/InjectIcon'; @@ -82,6 +84,8 @@ const InjectDtoList: FunctionComponent = ({ const classes = useStyles(); const { t, fldt, tPick, nsdt } = useFormatter(); + const [loading, setLoading] = useState(true); + // Filter and sort hook const [injects, setInjects] = useState([]); @@ -152,10 +156,15 @@ const InjectDtoList: FunctionComponent = ({ }, ], []); + const search = (input: SearchPaginationInput) => { + setLoading(true); + return fetchInjects(input).finally(() => setLoading(false)); + }; + return ( <> = ({ )} /> - {injects.map((injectDto) => { - return ( - setInjects(injects.filter(e => (e.inject_id !== result)))} - inList - /> - )} - disablePadding - > - - - - - - {headers.map(header => ( -
- {header.value?.(injectDto)} -
- ))} - + { + loading + ? + : injects.map((injectDto, index) => { + return ( + setInjects(injects.filter(e => (e.inject_id !== result)))} + inList + /> )} - /> -
-
- ); - })} + disablePadding + > + + + + + + {headers.map(header => ( +
+ {header.value?.(injectDto)} +
+ ))} + + )} + /> +
+ + ); + }) + } {!injects ? () : null} diff --git a/openbas-front/src/admin/components/scenarios/Scenarios.tsx b/openbas-front/src/admin/components/scenarios/Scenarios.tsx index 7f234ae345..fe59bf321b 100644 --- a/openbas-front/src/admin/components/scenarios/Scenarios.tsx +++ b/openbas-front/src/admin/components/scenarios/Scenarios.tsx @@ -20,9 +20,10 @@ import { useFormatter } from '../../../components/i18n'; import ItemCategory from '../../../components/ItemCategory'; import ItemSeverity from '../../../components/ItemSeverity'; import ItemTags from '../../../components/ItemTags'; +import PaginatedListLoader from '../../../components/PaginatedListLoader'; import PlatformIcon from '../../../components/PlatformIcon'; import { useHelper } from '../../../store'; -import type { FilterGroup } from '../../../utils/api-types'; +import type { FilterGroup, SearchPaginationInput } from '../../../utils/api-types'; import ImportUploaderScenario from './ImportUploaderScenario'; import ScenarioPopover from './scenario/ScenarioPopover'; import ScenarioStatus from './scenario/ScenarioStatus'; @@ -77,6 +78,8 @@ const Scenarios = () => { const classes = useStyles(); const { t, nsdt } = useFormatter(); + const [loading, setLoading] = useState(true); + // Fetching data const { userAdmin } = useHelper((helper: TagHelper & UserHelper) => ({ userAdmin: helper.getMe()?.user_admin ?? false, @@ -192,11 +195,16 @@ const Scenarios = () => { exportFileName: `${t('Scenarios')}.csv`, }; + const search = (input: SearchPaginationInput) => { + setLoading(true); + return searchScenarios(input).finally(() => setLoading(false)); + }; + return ( <> { )} /> - {scenarios.map((scenario: ScenarioStore) => { - return ( - setScenarios(scenarios.filter(e => (e.scenario_id !== result)))} - inList - /> - )} - disablePadding - > - - - - - - {headers.map(header => ( -
- {header.value(scenario)} -
- ))} - + { + loading + ? + : scenarios.map((scenario: ScenarioStore, index) => { + return ( + setScenarios(scenarios.filter(e => (e.scenario_id !== result)))} + inList + /> )} - /> -
-
- ); - })} + disablePadding + > + + + + + + {headers.map(header => ( +
+ {header.value(scenario)} +
+ ))} + + )} + /> +
+ + ); + }) + } {userAdmin && ( diff --git a/openbas-front/src/admin/components/simulations/ExerciseList.tsx b/openbas-front/src/admin/components/simulations/ExerciseList.tsx index 3e6302dcd5..cd1f14bcd1 100644 --- a/openbas-front/src/admin/components/simulations/ExerciseList.tsx +++ b/openbas-front/src/admin/components/simulations/ExerciseList.tsx @@ -12,6 +12,7 @@ import { Header } from '../../../components/common/SortHeadersList'; import { useFormatter } from '../../../components/i18n'; import ItemTags from '../../../components/ItemTags'; import ItemTargets from '../../../components/ItemTargets'; +import PaginatedListLoader from '../../../components/PaginatedListLoader'; import type { ExerciseSimple } from '../../../utils/api-types'; import AtomicTestingResult from '../atomic_testings/atomic_testing/AtomicTestingResult'; import ExerciseStatus from './simulation/ExerciseStatus'; @@ -69,6 +70,7 @@ interface Props { hasHeader?: boolean; variant?: string; secondaryAction?: (exercise: ExerciseStore) => React.ReactNode; + loading: boolean; } const ExerciseList: FunctionComponent = ({ @@ -77,6 +79,7 @@ const ExerciseList: FunctionComponent = ({ hasHeader = true, variant = 'list', secondaryAction, + loading, }) => { // Standard hooks const classes = useStyles(); @@ -161,39 +164,43 @@ const ExerciseList: FunctionComponent = ({ /> )} - {exercises.map((exercise: ExerciseStore, index) => ( - - - - - - - {headers.map(header => ( -
- {header.value?.(exercise)} + { + loading + ? + : exercises.map((exercise: ExerciseStore, index) => ( + + + + + + + {headers.map(header => ( +
+ {header.value?.(exercise)} +
+ ))}
- ))} - - )} - /> -
-
- ))} + )} + /> + + + )) + } ); }; diff --git a/openbas-front/src/admin/components/simulations/Exercises.tsx b/openbas-front/src/admin/components/simulations/Exercises.tsx index c12681f645..a7e7187e14 100644 --- a/openbas-front/src/admin/components/simulations/Exercises.tsx +++ b/openbas-front/src/admin/components/simulations/Exercises.tsx @@ -14,7 +14,7 @@ import { buildSearchPagination } from '../../../components/common/queryable/Quer import { useQueryableWithLocalStorage } from '../../../components/common/queryable/useQueryableWithLocalStorage'; import { useFormatter } from '../../../components/i18n'; import { useHelper } from '../../../store'; -import type { FilterGroup } from '../../../utils/api-types'; +import type { FilterGroup, SearchPaginationInput } from '../../../utils/api-types'; import type { EndpointStore } from '../assets/endpoints/Endpoint'; import ExerciseList from './ExerciseList'; import ImportUploaderExercise from './ImportUploaderExercise'; @@ -30,6 +30,7 @@ const Exercises = () => { userAdmin: helper.getMe()?.user_admin ?? false, })); + const [loading, setLoading] = useState(true); const [exercises, setExercises] = useState([]); // Filters @@ -79,11 +80,18 @@ const Exercises = () => { /> ); + const search = (input: SearchPaginationInput) => { + setLoading(true); + return searchExercises(input).finally(() => { + setLoading(false); + }); + }; + return ( <> { exercises={exercises} queryableHelpers={queryableHelpers} secondaryAction={secondaryAction} + loading={loading} /> {userAdmin && } diff --git a/openbas-front/src/admin/components/simulations/simulation/overview/ExerciseDistribution.tsx b/openbas-front/src/admin/components/simulations/simulation/overview/ExerciseDistribution.tsx index 04622b5a12..b896a7833e 100644 --- a/openbas-front/src/admin/components/simulations/simulation/overview/ExerciseDistribution.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/overview/ExerciseDistribution.tsx @@ -69,10 +69,6 @@ const ExerciseDistribution: FunctionComponent = ({ injectExpectations: helper.getExerciseInjectExpectations(exerciseId), })); - if (loading) { - return ; - } - if (exercise.exercise_status === 'SCHEDULED' && injectExpectations?.length === 0 && !isReport) { return (
@@ -83,9 +79,7 @@ const ExerciseDistribution: FunctionComponent = ({
); } - if (injectExpectations?.length === 0) { - return
; - } + return ( @@ -93,7 +87,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of score by team (in % of expectations)')} - + { + loading + ? + : + } @@ -101,7 +99,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Teams scores over time (in % of expectations)')} - + { + loading + ? + : + } @@ -109,13 +111,21 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of total score by team')} - + { + loading + ? + : + } {t('Teams scores over time')} - + { + loading + ? + : + } @@ -123,7 +133,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of total score by inject type')} - + { + loading + ? + : + } @@ -131,7 +145,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Inject types scores over time')} - + { + loading + ? + : + } @@ -139,7 +157,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of total score by organization')} - + { + loading + ? + : + } @@ -147,7 +169,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of total score by player')} - + { + loading + ? + : + } @@ -155,7 +181,11 @@ const ExerciseDistribution: FunctionComponent = ({ {t('Distribution of total score by inject')} - + { + loading + ? + : + } diff --git a/openbas-front/src/components/PaginatedListLoader.tsx b/openbas-front/src/components/PaginatedListLoader.tsx new file mode 100644 index 0000000000..2fe7f342f5 --- /dev/null +++ b/openbas-front/src/components/PaginatedListLoader.tsx @@ -0,0 +1,71 @@ +import { MoreVert } from '@mui/icons-material'; +import { + IconButton, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Skeleton, + SvgIconProps, +} from '@mui/material'; +import { ComponentType, CSSProperties, FunctionComponent } from 'react'; + +import { Header } from './common/SortHeadersList'; + +interface Props { + headers: Header[]; + headerStyles: Record; + Icon: ComponentType; + height?: number; + number?: number; +} + +const PaginatedListLoader: FunctionComponent = ({ + headers, + headerStyles, + Icon, + height = 50, + number = 21, +}) => { + return ( + [...Array(number)].map((_, key) => ( + + + + )} + > + + + + + + {headers.map(header => ( +
+ +
+ ))} +
+ )} + /> + + + )) + ); +}; + +export default PaginatedListLoader; diff --git a/openbas-front/src/components/common/queryable/filter/FilterChips.tsx b/openbas-front/src/components/common/queryable/filter/FilterChips.tsx index f27a18dcce..bb4c68022d 100644 --- a/openbas-front/src/components/common/queryable/filter/FilterChips.tsx +++ b/openbas-front/src/components/common/queryable/filter/FilterChips.tsx @@ -1,5 +1,5 @@ import { Box } from '@mui/material'; -import { Fragment, FunctionComponent } from 'react'; +import { Fragment, FunctionComponent, useCallback } from 'react'; import type { Filter, FilterGroup, PropertySchemaDTO } from '../../../../utils/api-types'; import ClickableModeChip from '../../chips/ClickableModeChip'; @@ -23,9 +23,9 @@ const FilterChips: FunctionComponent = ({ }) => { const filters = filterGroup?.filters?.filter(f => availableFilterNames.length === 0 || availableFilterNames.includes(f.key)) ?? []; - const propertySchema = (filter: Filter) => { + const propertySchema = useCallback((filter: Filter) => { return propertySchemas.find(p => p.schema_property_name === filter.key); - }; + }, [propertySchemas]); const handleSwitchMode = () => helpers.handleSwitchMode(); @@ -40,6 +40,7 @@ const FilterChips: FunctionComponent = ({ display: 'flex', flexWrap: 'wrap', gap: 1, + minHeight: 56, }} > {filters.map((filter, idx) => {