teamdigitale/italia-app

View on GitHub
ts/features/idpay/details/screens/IdPayOperationsListScreen.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
import {
  ContentWrapper,
  Divider,
  HSpacer,
  IOToast,
  VSpacer
} from "@pagopa/io-app-design-system";
import { useRoute } from "@react-navigation/core";
import { RouteProp } from "@react-navigation/native";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import React from "react";
import { ActivityIndicator, FlatList, StyleSheet } from "react-native";
import Placeholder from "rn-placeholder";
import { OperationListDTO } from "../../../../../definitions/idpay/OperationListDTO";
import { Body } from "../../../../components/core/typography/Body";
import { RNavScreenWithLargeHeader } from "../../../../components/ui/RNavScreenWithLargeHeader";
import I18n from "../../../../i18n";
import customVariables from "../../../../theme/variables";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { localeDateFormat } from "../../../../utils/locale";
import { useTimelineDetailsBottomSheet } from "../../timeline/components/TimelineDetailsBottomSheet";
import { TimelineOperationListItem } from "../components/TimelineOperationListItem";
import { useInitiativeTimelineFetcher } from "../hooks/useInitiativeTimelineFetcher";
import { IDPayDetailsParamsList } from "../navigation";

export type IdPayOperationsListScreenParams = { initiativeId: string };

type IdPayOperationsListScreenRouteProps = RouteProp<
  IDPayDetailsParamsList,
  "IDPAY_DETAILS_TIMELINE"
>;

const TimelineLoader = () => (
  <ActivityIndicator
    animating={true}
    size={"large"}
    style={styles.activityIndicator}
    color={customVariables.brandPrimary}
    accessible={true}
    accessibilityHint={I18n.t("global.accessibility.activityIndicator.hint")}
    accessibilityLabel={I18n.t("global.accessibility.activityIndicator.label")}
    importantForAccessibility={"no-hide-descendants"}
    testID={"activityIndicator"}
  />
);

export const IdPayOperationsListScreen = () => {
  const route = useRoute<IdPayOperationsListScreenRouteProps>();
  const { initiativeId } = route.params;

  const handleOnError = () => {
    IOToast.error("Errore nel caricamento, riprova.");
  };

  const {
    isLoading,
    isUpdating,
    isRefreshing,
    timeline,
    fetchNextPage,
    fetchPage,
    refresh,
    lastUpdate
  } = useInitiativeTimelineFetcher(initiativeId, 10, handleOnError);

  // We need to know if this is the first rendering in order to show the loading spinner overlay
  const isFirstRenderDispatchedRef = React.useRef(false);

  useOnFirstRender(() => {
    fetchPage(0);
    // eslint-disable-next-line functional/immutable-data
    isFirstRenderDispatchedRef.current = true;
  });

  const isFirstLoading = isFirstRenderDispatchedRef.current
    ? timeline.length === 0 && isLoading
    : true;

  const detailsBottomSheet = useTimelineDetailsBottomSheet(initiativeId);

  const showOperationDetailsBottomSheet = (operation: OperationListDTO) =>
    detailsBottomSheet.present(operation);

  const lastUpdateComponent = pipe(
    lastUpdate,
    O.fromNullable,
    O.map(date =>
      localeDateFormat(
        date,
        I18n.t("global.dateFormats.fullFormatFullMonthLiteral")
      )
    ),
    O.fold(
      () => (
        <Placeholder.Box animate="fade" height={18} width={70} radius={4} />
      ),
      dateString => <Body weight="Semibold">{dateString}</Body>
    )
  );

  const operationList = isFirstLoading ? (
    <FlatList
      contentContainerStyle={styles.listContainer}
      data={Array.from({ length: 10 })}
      keyExtractor={(_, index) => `placeholder${index}`}
      renderItem={() => <TimelineOperationListItem isLoading={true} />}
      onRefresh={refresh}
      refreshing={isRefreshing}
    />
  ) : (
    <FlatList
      contentContainerStyle={styles.listContainer}
      data={timeline}
      keyExtractor={item => item.operationId}
      renderItem={({ item }) => (
        <TimelineOperationListItem
          operation={item}
          onPress={() => showOperationDetailsBottomSheet(item)}
        />
      )}
      ItemSeparatorComponent={() => <Divider />}
      onEndReached={fetchNextPage}
      onEndReachedThreshold={0.5}
      onRefresh={refresh}
      refreshing={isRefreshing}
      ListFooterComponent={isUpdating ? <TimelineLoader /> : null}
    />
  );

  return (
    <RNavScreenWithLargeHeader
      title={{
        label: I18n.t(
          "idpay.initiative.details.initiativeDetailsScreen.configured.operationsList.title"
        )
      }}
    >
      <ContentWrapper>
        <VSpacer size={8} />
        <Body>
          {I18n.t(
            "idpay.initiative.details.initiativeDetailsScreen.configured.operationsList.lastUpdated"
          )}
          <HSpacer size={4} />
          {lastUpdateComponent}
        </Body>
      </ContentWrapper>
      <VSpacer size={16} />
      {operationList}
      {detailsBottomSheet.bottomSheet}
    </RNavScreenWithLargeHeader>
  );
};

const styles = StyleSheet.create({
  activityIndicator: {
    padding: 12
  },
  listContainer: {
    paddingBottom: 120,
    paddingHorizontal: customVariables.contentPadding
  }
});