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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | 4x 12x 12x 12x 12x 12x 12x 12x 1x 11x 11x | import { ReactNode } from "react";
import Table from "componentsV2/Table/Table";
import { useTable } from "componentsV2/Table/hooks/useTable";
import { QKEY_EVENT_TICKETS } from "@constants/queryKeys";
import { FilterPagesEnum } from "@shared/GiveFilter/constants";
import { useFilterButton } from "@shared/GiveFilter/hooks/useFilterButton";
import { EVENT_TICKET_DETAIL_MODAL } from "modals/modal_names";
import {
EVENT_BANNER_ANIMATION_PAGE,
STORED_EVENT_TICKETS_ROWS_PER_PAGE,
} from "../constants";
import TabErrorState from "../TabErrorState";
import { useEventTicketColumns } from "./useEventTicketColumns";
import { TICKET_SORT_KEYS, useEventTickets } from "./useEventTickets";
import { useTicketPickup } from "./useTicketPickup";
type Props = {
eventId?: string;
/**
* The shared banner head (breadcrumb offset, cards, tabs, search). A render
* function because only this tab knows whether its list is empty, and the
* empty state is drawn without the search / Export row.
*/
renderHead: (options?: { hideToolbar?: boolean }) => ReactNode;
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
searchQuery?: string;
handleSearchChange?: (value: string) => void;
/**
* The event offers printed tickets, so the "Ticket Picked Up" column applies
* (frame 7594-66313). Read from the event, not a feature flag.
*/
showPickupColumn?: boolean;
/**
* The collapsed header revealed once the banner scrolls away (frame
* 8122-61150) — built by the page so both tabs share one instance.
*/
compactHead?: ReactNode;
/** The event name, shown in the mobile top bar while scrolled. */
compactTitle?: ReactNode;
/** Told when the collapsed header crosses its reveal threshold. */
onCompactVisibleChange?: (isVisible: boolean) => void;
/** Page gutters, so the collapsed header's labels align to the columns. */
tablePadding?: string;
};
/**
* PayBuilder 032 — the Tickets tab (frames 7397-90412/90421/90442): the
* per-ticket list with check-in, on the same V2 table the Transactions tab
* uses so skeleton, empty state (frame 7397-90358) and pagination match.
*/
const EventTicketsTab = ({
eventId,
renderHead,
onScroll,
searchQuery,
handleSearchChange,
showPickupColumn,
compactHead,
compactTitle,
onCompactVisibleChange,
tablePadding,
}: Props) => {
const {
allRows,
totalRows,
rowsPerPage,
page,
setPage,
setPageDispatcher,
isLoading,
isFetching,
isError,
checkIn,
checkingInId,
} = useEventTickets(eventId, showPickupColumn);
const { setPickupStatus, savingId } = useTicketPickup(eventId);
// The same panel the toolbar's Filter button opens, read here for its count:
// the tab is the only place that knows both how many filters are applied and
// whether the list came back empty, which together decide the empty state.
const { filtersAmount, handleResetFilters } = useFilterButton({
filterPage: FilterPagesEnum.EVENT_TICKETS,
productId: eventId,
});
const hasFilters = filtersAmount > 0;
const { columns } = useEventTicketColumns({
onCheckIn: checkIn,
checkingInId,
sortKeys: TICKET_SORT_KEYS,
showPickupColumn,
onPickupChange: setPickupStatus,
savingPickupId: savingId,
});
// Row click opens the detail panel; `useTable` also drives its up/down
// navigation by moving the selected row (and paging when it runs off the end).
const { setSelectedRowIdx, selectedRowIdx, loadMore, numberOfPages } =
useTable({
openModal: EVENT_TICKET_DETAIL_MODAL,
allRows,
isLoading,
isFetching,
totalRows,
page,
setPage,
setPageDispatcher,
useIdForRows: true,
storageKey: STORED_EVENT_TICKETS_ROWS_PER_PAGE,
});
// The error replaces the list, never the page: the head (and with it the
// tabs) stays mounted so Transactions remains reachable.
if (isError && !isLoading)
return <TabErrorState head={renderHead({ hideToolbar: true })} />;
// Frame 7397-90358: an event with no tickets shows only the banner, tabs and
// the empty state. A search OR a filter that matched nothing keeps the toolbar
// — hiding it would strand the user with no way to clear what narrowed the
// list (frame 8238-62023, "Filter / No results").
const hideToolbar =
!isLoading && !searchQuery && !hasFilters && allRows.length === 0;
return (
<Table
type="event-tickets"
sectionName="Tickets"
allRows={allRows}
totalRows={totalRows}
rowsPerPage={rowsPerPage}
isLoading={isLoading}
isFetching={isFetching}
page={page}
setPage={setPage}
setPageDispatcher={setPageDispatcher}
columns={columns}
openModal={EVENT_TICKET_DETAIL_MODAL}
queryKey={QKEY_EVENT_TICKETS}
sortReduxKey={QKEY_EVENT_TICKETS}
defaultSortKey={TICKET_SORT_KEYS.purchaseDate}
storageKey={STORED_EVENT_TICKETS_ROWS_PER_PAGE}
searchQuery={searchQuery}
handleSearchChange={handleSearchChange}
// Turns the empty state from "No tickets purchased yet" into the filtered
// one — "No results" over a Clear Filters button (frame 8238-62023).
hasFilters={hasFilters}
handleResetFilters={handleResetFilters}
selectedRowIdx={selectedRowIdx}
setSelectedRowIdx={setSelectedRowIdx}
loadMore={loadMore}
numberOfPages={numberOfPages}
Head={renderHead({ hideToolbar })}
onScroll={onScroll}
useIdForRows
// The content sits on the page's animated backdrop, so the column
// header scrolls off with it, transparent (see Table).
scrollingHeader
CompactHead={compactHead}
compactTitle={compactTitle}
onCompactVisibleChange={onCompactVisibleChange}
compactAnimationPage={EVENT_BANNER_ANIMATION_PAGE}
tablePadding={tablePadding}
/>
);
};
export default EventTicketsTab;
|