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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | 14x 4x 4x 4x 4x 4x 4x 4x 14x 14x 14x 4x 4x 14x 12x 14x | import { UseQueryOptions, useMutation, useQuery } from "react-query";
import { customInstance } from "..";
import { buildMerchantEndpoints } from "../utils.api";
/**
* `GET /merchants/{mid}/products/{pid}/ticket-holders` —
* `product.EventTicketHolderView`, the Tickets tab's list.
*
* Deliberately NOT `.../tickets`: that endpoint is the mobile scanner's check-in
* list and filters to `usable`/`consumed` server-side, so the tickets a reversal
* cancelled — the mockup's "Do not admit" rows — never appear in it.
*/
export type EventTicketStatus = "usable" | "consumed" | "expired" | "cancelled";
export type EventTicketPickupStatus = "pending" | "collected" | "delivered";
/** Why a `cancelled` ticket is invalid; `""` when the reason isn't on record. */
export type EventTicketPaymentStatus =
| ""
| "voided"
| "refunded"
| "chargeback";
export type EventTicketApi = {
id: number;
/** Server-owned display identity, e.g. "GCS-0004327823". */
code: string;
/** Purchase date. Unix SECONDS; null when unset. */
createdAt: number | null;
userAccID: number | null;
orderID: number;
/** The transaction's OBJECT id (`GS_TXN_…`); null when there is no purchase. */
transactionID: string | null;
holderName: string;
holderEmail: string;
/** The variant name — the mockup's "Ticket Name". */
ticketType: string;
/** What the holder paid for this one ticket, in MINOR units (25000 = 250.00). */
ticketPrice: number;
seat: string;
rowLabel: string;
/** Composed seat line for seated events, e.g. "A1 - Early Bird"; "" when none. */
seatLabel: string;
status: EventTicketStatus;
/**
* Physical-ticket fulfilment — the "Ticket Picked Up" column. `""` for a
* ticket with nothing to pick up; the column is only shown for events that
* offer printed tickets. `pickupState` on the wire (the view's json name).
*/
pickupState: "" | EventTicketPickupStatus;
paymentStatus: EventTicketPaymentStatus;
/** Unix SECONDS; null while not checked in. */
consumedAt: number | null;
/**
* How the ticket was admitted — the detail panel's "Check-in Method". `""` when
* it never was, and for tickets consumed before the server recorded a method.
* `consumptionMethod` on the wire (the view's json name).
*/
consumptionMethod: "" | "scan" | "manual";
/** The staff member who admitted the holder; `""` when none is on record. */
consumedBy: string;
consumedByEmail: string;
};
export type EventTicketListResponse = {
data: EventTicketApi[] | null;
total: number;
};
type ListParams = {
page?: number;
sorting?: string;
searchQuery?: string;
maxRowsPerPage?: number;
/**
* The filter panel's `frk/fql` string, ALREADY url-encoded — `useGeneralFilters`
* stores it that way in redux (`urlFilterFormat`), so it is appended raw.
*/
filter?: string;
};
/** Shared by the list and the export so they always agree on the result set. */
const buildTicketQuery = ({
page,
sorting,
searchQuery,
maxRowsPerPage,
filter,
}: ListParams) => {
const parts: string[] = [];
Iif (page) parts.push(`page=${page}`);
Iif (maxRowsPerPage) parts.push(`max=${maxRowsPerPage}`);
Eif (sorting) parts.push(`sort=${sorting}`);
Iif (searchQuery) parts.push(`q=${encodeURIComponent(searchQuery)}`);
Iif (filter) parts.push(`filter=${filter}`);
return parts.join("&");
};
export const EVENT_TICKETS_PATH = "ticket-holders";
export const useGetEventTickets =
(productId: string, queryKey: string) =>
(
{
page = 1,
sorting,
searchQuery,
maxRowsPerPage = 100,
filter,
}: ListParams,
options: Omit<
UseQueryOptions<
EventTicketListResponse,
any,
EventTicketListResponse,
any
>,
"queryKey" | "queryFn"
> = {},
) =>
useQuery<EventTicketListResponse>(
[queryKey, productId, page, sorting, searchQuery, maxRowsPerPage, filter],
async () =>
customInstance({
url: buildMerchantEndpoints(
`products/${productId}/${EVENT_TICKETS_PATH}?${buildTicketQuery({
page,
sorting,
searchQuery,
maxRowsPerPage,
filter,
})}`,
),
method: "GET",
}),
options,
);
/**
* The Export button. Same filter + search as the list, streamed as CSV — no
* `page`/`max`, the server raises the limit to the full matching count.
*/
export const buildEventTicketsExportUrl = (
productId: string,
{
sorting,
searchQuery,
filter,
}: Pick<ListParams, "sorting" | "searchQuery" | "filter">,
) => {
const query = buildTicketQuery({ sorting, searchQuery, filter });
return buildMerchantEndpoints(
`products/${productId}/${EVENT_TICKETS_PATH}.csv${
query ? `?${query}` : ""
}`,
);
};
/**
* Records whether a printed ticket was handed over — the "Ticket Picked Up"
* column's dropdown. A per-ticket PATCH, separate from `/status`: pickup is a
* fulfilment record and never decides admission.
*/
export const useUpdateTicketPickup = (productId?: string) =>
useMutation(
({
ticketId,
pickupStatus,
}: {
ticketId: number | string;
pickupStatus: EventTicketPickupStatus;
}) =>
customInstance({
url: buildMerchantEndpoints(
`products/${productId}/tickets/${ticketId}/pickup`,
),
method: "PATCH",
// `TicketPickupUpdateParams` names the field pickupState, not
// pickupStatus — the wrong name 422s with "pickupState is required".
data: { pickupState: pickupStatus },
}),
);
/**
* Check-in. Deliberately the BULK route with a ticketIDs array — the
* per-ticket `PATCH .../tickets/{id}/status` cannot set `consumed`
* (`IsManuallySettable` excludes it); this mirrors the mobile app's
* `useCheckInTickets`.
*/
export const useCheckInEventTickets = (productId?: string) =>
useMutation((ticketIDs: Array<number | string>) =>
customInstance({
url: buildMerchantEndpoints(`products/${productId}/tickets/status`),
method: "PATCH",
data: { status: "consumed", ticketIDs },
}),
);
|