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 | 1x 34x 33x 33x 33x 7x 33x 33x 33x 33x | import { PortalRootContainerV2 } from "@components/Layout/LayoutV2";
import TechnicalErrorState from "componentsV2/Table/components/TechnicalErrorState";
import { Stack } from "@mui/material";
import { useGetCurrentMerchantId } from "@hooks/common";
import { StyledTitle } from "@shared/Banner/styles";
import GiveBreadcrumb from "@shared/Breadcrumbs/GiveBreadcrumb";
import { useAppTheme } from "@theme/v2/Provider";
import { useParams } from "react-router-dom";
import moment from "moment";
import { useMemo } from "react";
import { CANARY_TZ } from "@utils/date.helpers";
import CostBreakdownByBrand from "./components/CostBreakdownByBrand";
import HubCashPosition from "./components/HubCashPosition";
import HubExportButton from "./components/HubExportButton";
import HubFilterBar from "./components/HubFilterBar";
import HubFinancialDetails from "./components/HubFinancialDetails";
import HubKpiCards from "./components/HubKpiCards";
import HubSkeleton from "./components/HubSkeleton";
import PerMerchantBreakdown from "./components/PerMerchantBreakdown";
import { useReconciliationHub } from "./hooks/useReconciliationHub";
import { isProcessorResolved, useHubFilters } from "./useHubFilters";
/**
* Reconciliation Hub — the primary acquirer-portal settlement view. Composes
* the filter bar, KPI cards, cash position, financial details, cost-breakdown
* matrix, and per-merchant breakdown around a shared period / processor /
* merchant filter state.
*/
const GiveReconciliationHub = () => {
const theme = useAppTheme();
const { merchantId } = useGetCurrentMerchantId();
// When opened for a settlement date picked from the listing, the route param
// (`/acquirer/settlements/:id`) carries that date as an encoded ISO string —
// seed the hub's anchor to it so the view scopes to the chosen day.
const { id } = useParams();
const initialAnchorDate = useMemo(() => {
Eif (!id) return undefined;
// Resolve the picked settlement date in the app's canonical timezone so the
// day matches how the backend bounds it (see canaryToday in useHubFilters).
const parsed = moment(decodeURIComponent(id)).tz(CANARY_TZ);
return parsed.isValid() ? parsed.format("YYYY-MM-DD") : undefined;
}, [id]);
const { filters, setPeriodType, setAnchorDate, setProcessor, setMerchant } =
useHubFilters({ initialAnchorDate });
const { data, isLoading, isError } = useReconciliationHub(merchantId, filters);
// The data hooks stay disabled until HubProcessorDropdown resolves the
// acquirer's default processor, and a disabled react-query reports `idle`,
// not `loading` — without this the sections would flash empty first.
const isPending = !isProcessorResolved(filters) || isLoading;
return (
<PortalRootContainerV2>
<Stack
gap="20px"
data-testid="reconciliation-hub"
sx={{ padding: { xs: "16px", md: "20px 40px 24px", lg: "20px 64px 24px" } }}
>
<Stack justifyContent="center" gap="24px">
<GiveBreadcrumb
items={[
{ label: "Settlement", path: "/acquirer/settlements" },
{ label: "Reconciliation Hub" },
]}
customStyle={{
activeColor: "primary",
defaultColor: theme.palette.text.secondary,
}}
/>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
flexWrap="wrap"
gap="16px"
>
<StyledTitle
theme={theme}
textColor={theme.palette.gradient?.["ocean-blue"]?.default ?? ""}
variant="h1"
data-testid="hub-page-title"
>
Reconciliation Hub
</StyledTitle>
<HubExportButton merchantId={merchantId} filters={filters} />
</Stack>
</Stack>
{/* Renders unconditionally on purpose: HubProcessorDropdown inside it
resolves the processor that ungates every query below. */}
<HubFilterBar
merchantId={merchantId}
filters={filters}
onPeriodTypeChange={setPeriodType}
onAnchorDateChange={setAnchorDate}
onProcessorChange={setProcessor}
onMerchantChange={setMerchant}
/>
{isPending ? (
<HubSkeleton />
) : isError ? (
<TechnicalErrorState />
) : (
<>
<HubKpiCards data={data} />
<HubCashPosition data={data} />
<HubFinancialDetails data={data} />
<CostBreakdownByBrand data={data} />
<PerMerchantBreakdown merchantId={merchantId} filters={filters} />
</>
)}
</Stack>
</PortalRootContainerV2>
);
};
export default GiveReconciliationHub;
|