All files / src/features/Events/EventDetail/inventory EventInventoryTab.tsx

100% Statements 18/18
100% Branches 2/2
100% Functions 8/8
100% Lines 14/14

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                            5x                     5x 24x   24x 24x 14x     42x                 10x                     10x             24x                             24x                   24x                                 22x                           42x          
import { ReactNode } from "react";
import { Box } from "@mui/material";
import GiveEmptyStateWrapper from "@shared/EmptyState/GiveEmptyStateWrapper";
import { styled } from "@theme/v2/Provider";
import EventInventoryCard from "./EventInventoryCard";
import { useEventInventory } from "./useEventInventory";
 
type Props = {
  eventId?: string;
  /** The shared banner head — see EventTicketsTab for why this is a render fn. */
  renderHead: (options?: { hideToolbar?: boolean }) => ReactNode;
  onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
};
 
const SKELETON_CARDS = [0, 1, 2];
 
/**
 * PayBuilder — the Inventory tab (frames 7278-37144, 7278-37114, 7278-37177,
 * 7278-37230): capacity per ticket type.
 *
 * The only tab that isn't a table, so it doesn't go through componentsV2/Table
 * — it owns its own scroller instead, which is also what drives the page's
 * sticky breadcrumb via `onScroll`. None of the four frames draw a search or
 * filter row, so the head is always rendered without its toolbar.
 */
const EventInventoryTab = ({ eventId, renderHead, onScroll }: Props) => {
  const { rows, isLoading } = useEventInventory(eventId);
 
  const content = (() => {
    if (isLoading) {
      return (
        <Grid>
          {SKELETON_CARDS.map((key) => (
            <SkeletonCard key={key} data-testid="event-inventory-skeleton" />
          ))}
        </Grid>
      );
    }
    // A failed read falls through to the empty state rather than a technical
    // error panel: the frames give this tab no error state, and `rows` is
    // already empty on failure, so "This event has no tickets" is what shows —
    // the tab stays usable instead of replacing the whole page.
    return (
      <GiveEmptyStateWrapper
        isEmpty={rows.length === 0}
        section="event-inventory"
        // Only the empty state (frame 7278-37114) sits 64px down; the populated
        // grid keeps its own 24px below the tabs (frame 7278-37177). The plain
        // `sx` prop would pad the populated branch too.
        giveEmptyStateSx={{ paddingTop: "64px" }}
      >
        <Grid>
          {rows.map((row) => (
            <EventInventoryCard key={row.id} row={row} />
          ))}
        </Grid>
      </GiveEmptyStateWrapper>
    );
  })();
 
  return (
    <Scroller onScroll={onScroll} data-testid="event-inventory-tab">
      <Inner>
        {renderHead({ hideToolbar: true })}
        {content}
      </Inner>
    </Scroller>
  );
};
 
export default EventInventoryTab;
 
// Mirrors what componentsV2/Table gives the other tabs: the page's own scroll
// container, centred, capped, with the same gutters so the banner head lines up
// across tabs.
const Scroller = styled(Box)(() => ({
  position: "relative",
  zIndex: 100,
  width: "100%",
  height: "100%",
  overflowY: "auto",
  display: "flex",
  justifyContent: "center",
}));
 
const Inner = styled(Box)(({ theme }) => ({
  width: "100%",
  maxWidth: "1920px",
  height: "fit-content",
  padding: "0 40px",
  paddingBottom: "40px",
  [theme.breakpoints.up("v2_lg")]: {
    padding: "0 64px 40px",
  },
  [theme.breakpoints.down("v2_sm")]: {
    padding: "0 20px 40px",
  },
}));
 
// Frame 7288-38102: three equal columns with 24px gutters. Fixed at three
// rather than auto-fitting so a stack of one or two keeps the column width
// instead of stretching across the page (annotation on frame 7278-37177).
const Grid = styled(Box)(({ theme }) => ({
  display: "grid",
  gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
  gap: "24px",
  paddingTop: "24px",
  width: "100%",
  [theme.breakpoints.down("v2_md")]: {
    gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
  },
  [theme.breakpoints.down("v2_sm")]: {
    gridTemplateColumns: "minmax(0, 1fr)",
  },
}));
 
const SkeletonCard = styled(Box)(({ theme }) => ({
  height: "159px",
  borderRadius: "16px",
  backgroundColor: theme.palette.surface?.tertiary,
}));