All files / src/features/TransactionPanel/components/Customer TransactionCustomFields.tsx

100% Statements 15/15
85.71% Branches 12/14
100% Functions 4/4
100% Lines 11/11

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                                    53x 86x   86x       86x 79x     7x 13x     7x     6x   11x             6x                                                            
import { Stack } from "@mui/material";
import GiveText from "@shared/Text/GiveText";
import { useGetFeatureFlagValues } from "FeatureFlags/useGetFeatureFlagValues";
import { TCustomFieldsSnapshot } from "@components/ManageMoney/TransactionTable/transactions.types";
import GiveCollapsableComponent from "@features/Merchants/MerchantSidePanel/GiveMerchantFile/businessOwners/GiveCollapsableComponent";
import TransactionSummaryDetails from "../Summary/TransactionSummaryDetails";
import { normalizeCustomFieldsSnapshot } from "@features/TransactionPanel/helpers/normalizeCustomFieldsSnapshot";
import { DEFAULT_CUSTOM_FIELDS_SECTION_TITLE } from "@sections/PayBuilder/Checkout/customFields.helpers";
 
type Props = {
  data?: { customFieldsSnapshot?: TCustomFieldsSnapshot | null };
};
 
/**
 * PAY-Builder016 — read-only custom-field answers on the transaction side panel
 * (AC024-AC027). Shown under the Purchaser card only when the transaction
 * captured at least one answered field.
 */
const TransactionCustomFields = ({ data }: Props) => {
  const { isPayBuilderCustomFieldsEnabled } = useGetFeatureFlagValues();
  // The snapshot ships in two shapes — see normalizeCustomFieldsSnapshot (GB-21600).
  const { sectionTitle, answers } = normalizeCustomFieldsSnapshot(
    data?.customFieldsSnapshot,
  );
 
  if (!isPayBuilderCustomFieldsEnabled || answers.length === 0) {
    return null;
  }
 
  const ordered = [...answers].sort((a, b) => a.order - b.order);
  const completed = ordered.filter((f) => (f.value ?? "").trim() !== "").length;
 
  // AC027 — all fields optional and none answered: hide the whole section.
  if (completed === 0) return null;
 
  // The merchant's frozen Section Title, or the default when it captured none.
  const title = sectionTitle.trim() || DEFAULT_CUSTOM_FIELDS_SECTION_TITLE;
 
  const list = ordered.map((f) => ({
    label: f.label,
    // AC026 — optional empty fields render a dash.
    value: (f.value ?? "").trim() || "-",
    disableParseAmount: true,
  }));
 
  return (
    <Stack spacing="21px" data-testid="transaction-custom-fields">
      {/*
        isFirst/isLast make this standalone accordion a self-contained card:
        rounded 20px corners (radius.medium) matching the sibling summary cards
        and no stray bottom border. The 20px inset on both the header and the
        details keeps the left spacing aligned with the other panel sections.
      */}
      <GiveCollapsableComponent
        testId="transaction-custom-fields-accordion"
        isFirst
        isLast
        sx={{ "& .MuiAccordionSummary-root": { px: "20px" } }}
        title={
          <GiveText variant="bodyM">
            {`${title} (${completed}/${ordered.length})`}
          </GiveText>
        }
      >
        <TransactionSummaryDetails
          list={list}
          isHideBorder
          sx={{ px: "20px" }}
        />
      </GiveCollapsableComponent>
    </Stack>
  );
};
 
export default TransactionCustomFields;