All files / src/features/MerchantPortal/ManageMoney/hooks useListManageMoneyTransactionsV2.tsx

88.88% Statements 48/54
66.03% Branches 35/53
91.66% Functions 11/12
90.19% Lines 46/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202                                                6x       6x                                 201x 201x 201x   201x 342x   342x 201x   201x 201x 201x 201x 201x 201x 201x 201x   201x         201x   99x   99x             99x               99x       99x     201x                                                                 201x   79x 33x     500x       201x 79x   79x         201x 4x       4x 4x 4x       4x       201x               201x 59x 59x 59x             201x 59x     201x                                    
import { useMemo, useRef, useState } from "react";
// hooks
import { ROWS_PER_PAGE } from "@hooks/common/usePagination";
// redux
import { useAppSelector } from "@redux/hooks";
import {
  sortingKey,
  sorting as sortingReducer,
} from "@redux/slices/fundraisers";
import { selectQueryString } from "@redux/slices/search";
import { QKEY_MANAGE_MONEY_TRANSACTIONS_LIST } from "@constants/queryKeys";
import { useAppDispatch } from "@redux/hooks";
import { parseRow } from "@components/ManageMoney/TransactionTable/transactions.helpers";
import { useStateEffect } from "@hooks/customReactCore";
import { useFormatManageMoneyFilter } from "./useFormatManageMoneyFilters";
import { resetFilterByKey } from "@redux/slices/dynamicFilterSlice";
import { useGetInfiniteTransactionsV2 } from "@components/VirtualList/hooks/queries";
import { useFiltersRepository } from "@components/VirtualList/hooks";
import { useGetCurrentMerchantId } from "@hooks/common";
import { PaginationMethodType, CursorValue } from "@components/VirtualList/api";
import useCheckLastUnseen from "@components/VirtualList/hooks/useBlockedTransactions";
import { checkPortals } from "@utils/routing";
import { useCustomThemeV2 } from "@theme/hooks/useCustomThemeV2";
 
export const TRANSACTION_PATHS = {
  BASIC: "transactions-basic",
  FULL: "transactions",
};
export const useListManageMoneyTransactionsV2 = ({
  path = TRANSACTION_PATHS.FULL,
  queryKey = QKEY_MANAGE_MONEY_TRANSACTIONS_LIST,
  paginationMethod = "offset",
  customFilterString = "",
  sortReduxKey = "",
  paginationCursorPropertyName = "id",
}: {
  path?:
    | (typeof TRANSACTION_PATHS)["BASIC"]
    | (typeof TRANSACTION_PATHS)["FULL"];
  queryKey?: string;
  paginationMethod?: PaginationMethodType;
  customFilterString?: string;
  sortReduxKey?: any;
  paginationCursorPropertyName?: string;
} = {}) => {
  const { formattedFilterString } = useFormatManageMoneyFilter(queryKey);
  const dispatch = useAppDispatch();
  const sorting = useAppSelector(sortingReducer);
 
  const searchQuery = useAppSelector((state) =>
    selectQueryString(state, queryKey),
  );
  const sortingV2 = useAppSelector((state) => sortingKey(state, queryKey));
  const usedSorting = sortReduxKey ? sortingV2 : sorting;
 
  const { merchantId } = useGetCurrentMerchantId();
  const { formattedFilter, queryString } = useFiltersRepository(queryKey);
  const [page, setPage] = useState(1);
  const nextCursorValue = useRef<null | CursorValue>(null);
  const pageRef = useRef<number | null>(1);
  const { isAcquirerPortal } = checkPortals();
  const { isDesktopView } = useCustomThemeV2();
  const isAcquirerOrProviderManageMoney = path === TRANSACTION_PATHS.BASIC;
 
  useCheckLastUnseen(
    merchantId,
    isAcquirerPortal && isDesktopView && !isAcquirerOrProviderManageMoney,
  );
 
  const { usedQueryString, usedFilter } = useMemo(() => {
    let usedQueryString =
      customFilterString || formattedFilterString || queryString;
 
    Iif (
      queryString &&
      customFilterString &&
      customFilterString === usedQueryString
    )
      usedQueryString = `${queryString}%3B${usedQueryString}`;
 
    Iif (
      queryString &&
      formattedFilterString &&
      formattedFilterString === usedQueryString
    )
      usedQueryString = `${queryString}%3B${formattedFilterString}`;
 
    // if we apply declined filter from filter panel, we remove !declined default filter
    const usedFilter = usedQueryString?.includes("declined")
      ? formattedFilter?.replace("(processingState%3A!%22declined%22)", "")
      : formattedFilter;
 
    return { usedQueryString, usedFilter };
  }, [customFilterString, formattedFilterString, queryString, formattedFilter]);
  const { data, fetchNextPage, isLoading, isFetching, isFetchingNextPage } =
    useGetInfiniteTransactionsV2(
      {
        queryString: usedQueryString,
        path: path,
        rowsPerPage: 20,
        sorting: usedSorting,
        searchQuery,
        merchantId,
        filter: usedFilter,
        cursorPropertyName: paginationCursorPropertyName,
        sortPropertyName: usedSorting.replace("-", ""),
        nextCursorValue: nextCursorValue,
        pageRef,
        paginationMethod,
      },
      {
        queryKey: [
          queryKey,
          usedQueryString,
          usedSorting,
          searchQuery,
          merchantId,
          usedFilter,
        ],
        enabled: true,
        refetchOnWindowFocus: false,
        refetchOnReconnect: false,
        refetchOnMount: false,
        staleTime: 5 * 60 * 1000, // 5 min – avoid background refetches that wipe cache
      },
      false,
    );
 
  const usedData = useMemo(() => {
    const allData =
      data?.pages
        ?.map((row: any) => row.data)
        .flat()
        .filter(Boolean) ?? [];
    return data?.pages?.[0]?.data ? allData.map((row) => parseRow(row)) : [];
  }, [data]);
 
  // Derive nextCursor from data to ensure it updates when query refetches
  const nextCursor = useMemo(() => {
    Eif (paginationMethod === "cursor") {
      // Read from ref, but memoize based on data so it updates when data changes
      return nextCursorValue.current;
    }
    return null;
  }, [data, paginationMethod]);
 
  const handlePageChange = () => {
    const hasNext = Boolean(
      paginationMethod === "cursor" ? nextCursor : pageRef.current,
    );
 
    Eif (!isFetchingNextPage && hasNext) {
      const newPage = (pageRef.current ?? 0) + 1;
      fetchNextPage({
        pageParam: paginationMethod === "offset" ? newPage : nextCursor,
        cancelRefetch: false, // prevent duplicate pages if load-more fires again before first request resolves
      });
      Iif (paginationMethod === "offset") pageRef.current = newPage;
    }
  };
 
  const handleResetFilters = () => {
    dispatch(
      resetFilterByKey({
        filterKey: queryKey,
      }),
    );
  };
 
  const resetPagination = () => {
    Eif (paginationMethod === "cursor") {
      nextCursorValue.current = null;
      return;
    }
    pageRef.current = 1;
  };
 
  // Reset pagination whenever the effective query identity changes so we don't use a stale cursor
  // with a new query (which can cause overlapping/duplicate results).
  useStateEffect(() => {
    resetPagination();
  }, [usedQueryString, usedSorting, searchQuery, usedFilter]);
 
  return {
    isLoading,
    isFetching,
    isFetchingNextPage,
    page,
    rowsPerPage: ROWS_PER_PAGE,
    totalRows:
      paginationMethod === "offset"
        ? data?.pages?.[0]?.total ?? 0
        : usedData.length,
    hasFilters: Boolean(formattedFilterString || customFilterString),
    handleResetFilters,
    setPage: handlePageChange,
    allRows: usedData,
    setPageDispatcher: setPage,
    nextCursor,
  };
};