All files / src/features/Events/EventDetail/host useEventHostColumns.tsx

100% Statements 13/13
100% Branches 2/2
100% Functions 6/6
100% Lines 13/13

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                                                                                        7x                         7x                       7x   7x 122x 180x   122x             90x                                     90x                 13x 13x   90x             122x    
import { Stack } from "@mui/material";
import { DotsThreeIcon } from "@phosphor-icons/react";
import { ActionsComponent } from "@common/Table/components/ActionsComponent";
import { GiveMemberNameCell } from "@features/Settings/Team/components/GiveMemberNameCell";
import {
  ColumnBaseType,
  TableColumnType,
} from "componentsV2/Table/Table.types";
import { EventHostRow } from "./host.types";
 
type Params = {
  onOpenMenu: (event: React.MouseEvent<HTMLElement>, row: EventHostRow) => void;
  /** The row with a write in flight, if any — see PENDING_ROW_SX. */
  pendingId?: number | null;
};
 
/**
 * Columns for the two host sections (frames 8119-110578 / 8119-109601).
 *
 * Both sections draw the same row: the identity cell and an overflow menu. What
 * differs is what the menu offers — an assigned host can be unassigned or
 * revoked, a pending one re-sent or withdrawn — and that is decided from the
 * row when the menu opens, not by the column. So one set of columns serves both,
 * and the identity cell tells them apart itself through `row.status`.
 *
 * The assigned row's inline "Revoke access" button is gone: with a second
 * destructive-adjacent action (Unassign) the pair belongs behind the same kebab
 * the pending rows already use, which is what the mockups now draw.
 *
 * `GiveMemberNameCell` renders the empty name as a blank line, which is what an
 * invited host's row wants anyway — the email carries the row.
 */
/**
 * The mockups have no column-header strip — rows sit directly under each
 * section title. The V2 table renders its header row unconditionally, and with
 * empty titles that is a blank padded band with a bottom border (the stray rule
 * under "Assigned Host"). Zero the header cells out rather than `display:
 * none`-ing them: a fixed-layout table reads its column widths from the first
 * row's cells, and removing them from the box tree entirely makes the browser
 * fall back to equal column widths — which parked the actions column (and its
 * button) in the middle of the row. The border override needs `!important`
 * because the header row styles its cells through an `& th` rule, which
 * outranks a plain cell-level declaration.
 */
const HIDE_HEADER_CELL = {
  padding: 0,
  height: 0,
  borderBottom: "none !important",
  "& > *": { display: "none" },
};
 
/**
 * A definite width for the trailing action cell. Left as the numeric default,
 * `getWidth` resolves the actions column to ~5% of the table — too narrow for
 * even the 32px control. Strings bypass that formula; the identity column has no
 * width and takes the rest, which is what lets a long email truncate late.
 */
const ACTION_COLUMN_WIDTH = "72px";
 
/**
 * The row's only feedback while a write is in flight.
 *
 * Both actions live behind a kebab that closes itself on click, so without this
 * the sequence a merchant sees for Unassign is: menu closes → nothing → the row
 * disappears when the refetch lands. Unassign is two round trips (read the
 * member's product set, then rewrite it), so "nothing" is long enough to read
 * as a dead click. `pointerEvents: none` also keeps the kebab from being
 * reopened, which is the case `isRowPending` guards from the other side.
 */
const PENDING_ROW_SX = { opacity: 0.5, pointerEvents: "none" } as const;
 
export const useEventHostColumns = ({ onOpenMenu, pendingId }: Params) => {
  const pendingSx = (row: EventHostRow) =>
    pendingId === row.id ? PENDING_ROW_SX : undefined;
 
  const columns: TableColumnType[] = [
    {
      key: "name",
      title: "",
      columnType: "avatar-name" as ColumnBaseType,
      sx: HIDE_HEADER_CELL,
      renderColumn: (row: EventHostRow) => (
        <Stack direction="row" flex={1} minWidth={0} sx={pendingSx(row)}>
          <GiveMemberNameCell
            imageURL={row.imageURL}
            firstName={row.name}
            email={row.email}
          />
        </Stack>
      ),
    },
    {
      key: "actions",
      title: "",
      columnType: "actions" as ColumnBaseType,
      columnWidth: ACTION_COLUMN_WIDTH,
      sx: HIDE_HEADER_CELL,
      renderColumn: (row: EventHostRow) => (
        // Full-width: the cell's flex wrapper sizes children to their content,
        // so without it `justifyContent` has no room to push the control to the
        // row's right edge.
        <Stack
          direction="row"
          justifyContent="flex-end"
          width="100%"
          sx={pendingSx(row)}
        >
          <ActionsComponent
            title="More"
            onClick={(e) => {
              e.stopPropagation();
              onOpenMenu(e, row);
            }}
            icon={() => <DotsThreeIcon fill="regular" size={20} />}
          />
        </Stack>
      ),
    },
  ];
 
  return { columns };
};