diff --git a/assets/translations/en.json b/assets/translations/en.json index 0dae4556c..25d3a8aef 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -131,7 +131,7 @@ "common_refresh_error": "The applets list was not refreshed. Please try again." }, "activity_list_component": { - "no_activities_yet": "No activities specified yet", + "no_activities": "No activities are available for you to complete right now", "insufficient_data_error": "This applet was not refreshed. Please try to refresh again.", "other_error": "Undefined error occurred" }, diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 51a702162..2fcaf8828 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -131,7 +131,7 @@ "common_refresh_error": "La liste des applets n'a pas été actualisée. Veuillez réessayer." }, "activity_list_component": { - "no_activities_yet": "Aucune activité spécifiée pour le moment", + "no_activities": "Aucune activité n'est disponible pour le moment", "insufficient_data_error": "Cette applet n'a pas été actualisée. Veuillez réessayer d'actualiser.", "other_error": "Une erreur non définie s'est produite" }, diff --git a/src/shared/ui/icons/ChecklistIcon.tsx b/src/shared/ui/icons/ChecklistIcon.tsx new file mode 100644 index 000000000..f453cb2b4 --- /dev/null +++ b/src/shared/ui/icons/ChecklistIcon.tsx @@ -0,0 +1,10 @@ +import Svg, { Path, SvgProps } from 'react-native-svg'; + +export const ChecklistIcon = (props: SvgProps) => ( + + + + + + +); diff --git a/src/widgets/activity-group/ui/ActivityGroups.tsx b/src/widgets/activity-group/ui/ActivityGroups.tsx index d04511d8e..425d52c10 100644 --- a/src/widgets/activity-group/ui/ActivityGroups.tsx +++ b/src/widgets/activity-group/ui/ActivityGroups.tsx @@ -1,6 +1,7 @@ -import { FC } from 'react'; +import { FC, useMemo } from 'react'; import { useIsFetching } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; import { CheckAvailability, @@ -9,10 +10,12 @@ import { import { getAppletCompletedEntitiesKey } from '@app/shared/lib/utils/reactQueryHelpers'; import { ActivityIndicator } from '@app/shared/ui/ActivityIndicator'; import { Box, BoxProps, XStack, YStack } from '@app/shared/ui/base'; +import { ChecklistIcon } from '@app/shared/ui/icons/ChecklistIcon'; import { LoadListError } from '@app/shared/ui/LoadListError'; -import { NoListItemsYet } from '@app/shared/ui/NoListItemsYet'; import { ActivitySectionList } from './ActivitySectionList'; +import { EmptyState } from './EmptyState'; +import { ActivityGroupType } from '../lib/types/activityGroup'; import { useActivityGroups } from '../model/hooks/useActivityGroups'; import { useBaseInfo } from '../model/hooks/useBaseInfo'; @@ -23,6 +26,8 @@ type Props = { } & BoxProps; export const ActivityGroups: FC = props => { + const { t } = useTranslation(); + const isLoadingCompletedEntities = useIsFetching({ exact: true, @@ -34,6 +39,26 @@ export const ActivityGroups: FC = props => { const { responseTypes } = data || {}; const hasError = !isSuccess || !!baseInfoError; + const renderedGroups = useMemo(() => { + const hasActivities = groups.some(g => g.activities.length); + + if (hasActivities) { + // Only show empty available group if there are no in-progress activities + const showAvailableGroup = !groups.some( + g => g.type === ActivityGroupType.InProgress && g.activities.length, + ); + + // Filter out empty groups, but show the available group based on above logic + return groups.filter( + g => + g.activities.length || + (g.type === ActivityGroupType.Available && showAvailableGroup), + ); + } else { + return null; + } + }, [groups]); + if (isLoadingCompletedEntities || isLoading) { return ( = props => { ); } - if (isSuccess && !groups?.length) { - return ( - - - - ); - } - return ( - + {renderedGroups ? ( + + ) : ( + } + description={t('activity_list_component:no_activities')} + /> + )} ); diff --git a/src/widgets/activity-group/ui/ActivitySectionList.tsx b/src/widgets/activity-group/ui/ActivitySectionList.tsx index b01d49c65..9a98e838c 100644 --- a/src/widgets/activity-group/ui/ActivitySectionList.tsx +++ b/src/widgets/activity-group/ui/ActivitySectionList.tsx @@ -28,8 +28,10 @@ import { getSupportsMobile, } from '@app/shared/lib/utils/responseTypes'; import { Box, YStack } from '@app/shared/ui/base'; +import { ChecklistIcon } from '@app/shared/ui/icons/ChecklistIcon'; import { Text } from '@app/shared/ui/Text'; +import { EmptyState } from './EmptyState'; import { ActivityListGroup } from '../lib/types/activityGroup'; import { useAvailabilityEvaluator } from '../model/hooks/useAvailabilityEvaluator'; @@ -54,16 +56,10 @@ export function ActivitySectionList({ const { isUploading } = useUploadObservable(); - const sections = useMemo(() => { - return groups - .filter(g => g.activities.length) - .map(group => { - return { - data: group.activities, - key: t(group.name), - }; - }); - }, [t, groups]); + const sections = useMemo( + () => groups.map(group => ({ data: group.activities, key: t(group.name) })), + [t, groups], + ); const { startFlow, startActivity } = useStartEntity({ hasMediaReferences: getDefaultMediaLookupService().hasMediaReferences, @@ -200,6 +196,16 @@ export function ActivitySectionList({ /> ); }} + // SectionList doesn't provide a prop for section empty components, so we use + // renderSectionFooter to conditionally render any empty sections. + renderSectionFooter={({ section }) => + section.data.length ? null : ( + } + description={t('activity_list_component:no_activities')} + /> + ) + } ItemSeparatorComponent={ItemSeparator} stickySectionHeadersEnabled={false} contentContainerStyle={styles.sectionList} diff --git a/src/widgets/activity-group/ui/EmptyState.tsx b/src/widgets/activity-group/ui/EmptyState.tsx new file mode 100644 index 000000000..080b1f3c6 --- /dev/null +++ b/src/widgets/activity-group/ui/EmptyState.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from 'react'; + +import { BoxProps, YStack } from '@app/shared/ui/base'; +import { Text } from '@app/shared/ui/Text'; + +type Props = BoxProps & { + icon: ReactNode; + description: string; +}; + +export const EmptyState = ({ icon, description, ...rest }: Props) => { + return ( + + {icon} + + {description} + + + ); +};