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,
}));
|