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 | 3x 3x 36x 3x 70x 70x 70x 70x 28x 70x 70x 36x 36x 6x 36x 33x 70x 3x 70x 70x 20x 70x 70x 2x | import { useEffect, useMemo, useRef, useState } from "react";
import GiveThumbnail from "@shared/Thumbnail/GiveThumbnail";
import { addSizeToImage } from "@utils/image.helpers";
import HubDropdown, { HubDropdownOption } from "./HubDropdown";
import { HubFilters } from "../useHubFilters";
import { useReconciliationHubMerchants } from "../hooks/useReconciliationHubMerchants";
import { ReconciliationHubMerchantView } from "../types";
interface Props {
merchantId: number | undefined;
filters: HubFilters;
value?: number;
onChange: (merchantAccId?: number) => void;
disabled?: boolean;
}
const ALL_MERCHANTS: HubDropdownOption = {
value: "all",
label: "All Merchants",
};
const merchantThumb = (imageUrl: string) => (
<GiveThumbnail
imageUrl={addSizeToImage(imageUrl, "thumb")}
size="icon"
type="merchant"
/>
);
/**
* Controlled merchant selector for the Reconciliation Hub. The option list is
* sourced from the SAME processor-scoped endpoint the per-merchant breakdown
* uses (`/reconciliation-hub/merchants?processorName=…`), so it only lists
* merchants that belong to the currently-selected processor (and period) — the
* portfolio-wide `useGetMerchantDropdownOptions` listed every acquirer
* submerchant regardless of processor. The query is shared/deduped with the
* breakdown table via react-query. Search is client-side over the returned
* (already period+processor-scoped) list. `undefined` = all merchants.
*/
const HubMerchantDropdown = ({
merchantId,
filters,
value,
onChange,
disabled,
}: Props) => {
const { data, isLoading } = useReconciliationHubMerchants(merchantId, filters);
const [search, setSearch] = useState("");
// The option list is re-scoped whenever the processor or period changes; a
// term typed against the previous scope would filter the new (unrelated)
// list and could hide every option until manually cleared, so reset it.
const { processorName, periodType, anchorDate } = filters;
useEffect(() => {
setSearch("");
}, [processorName, periodType, anchorDate]);
const merchants = useMemo(() => data?.merchants ?? [], [data]);
const dropdownOptions = useMemo<HubDropdownOption[]>(() => {
const term = search.trim().toLowerCase();
const matches = term
? merchants.filter((m) => m.merchantName.toLowerCase().includes(term))
: merchants;
return [
ALL_MERCHANTS,
...matches.map((m) => ({
value: String(m.merchantAccID),
label: m.merchantName,
id: m.merchantAccID,
Image: merchantThumb(m.merchantImageURL),
})),
];
}, [merchants, search]);
// Resolve the selected label from the UNFILTERED list so an active search
// (which may hide the selected row) does not blank out the button label.
const found =
value != null
? merchants.find((m) => m.merchantAccID === value)
: undefined;
// The KPI queries stay scoped to `value` regardless of what this list
// contains, so when a refetch under a new period/date (or a background
// staleTime/window-focus refetch) no longer includes the selected merchant,
// keep its last-known name — blanking to "All Merchants" would mislabel
// single-merchant data.
const lastResolvedRef = useRef<ReconciliationHubMerchantView>();
useEffect(() => {
if (found) lastResolvedRef.current = found;
}, [found]);
const selected =
found ??
(value != null && lastResolvedRef.current?.merchantAccID === value
? lastResolvedRef.current
: undefined);
return (
<HubDropdown
dataTestId="hub-merchant-dropdown"
selectedValue={selected ? String(selected.merchantAccID) : "all"}
selectedLabel={selected?.merchantName ?? ALL_MERCHANTS.label}
selectedImg={selected ? merchantThumb(selected.merchantImageURL) : undefined}
options={dropdownOptions}
searchBarProps={{
value: search,
handleChange: setSearch,
placeholder: "Search",
}}
isLoading={isLoading}
onSelect={(option) =>
onChange(option.value === "all" ? undefined : option.id)
}
disabled={disabled}
/>
);
};
export default HubMerchantDropdown;
|