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 | 6x 6x 14x 14x 14x 27x 14x 6x 6x 14x 4x 4x 3x 14x 2x 42x 5x 2x 2x 2x | import { MouseEvent, useState } from "react";
import { CaretDownIcon, CaretUpIcon, CheckIcon } from "@phosphor-icons/react";
import ContextualMenu from "@shared/ContextualMenu/ContextualMenu";
import GiveChip from "@shared/Chip/GiveChip";
import { useAppTheme } from "@theme/v2/Provider";
import {
EventTicketRow,
TICKET_PICKUP_LABELS,
TICKET_PICKUP_OPTIONS,
TicketPickupStatus,
} from "./ticket.types";
type Props = {
row: EventTicketRow;
onChange: (row: EventTicketRow, status: TicketPickupStatus) => void;
isSaving?: boolean;
};
/**
* The pill the three states share (frame 7594-65104): a 132px-wide 40px-radius
* chip, 6px/16px padding at 12px/14px with a 4px icon gap. The theme's
* `size="large"` rule drops to 4px/8px padding once `onDelete` is set — that
* shape is for a dismissible chip, not this one — and `GiveChip` sets a 12px
* gap, so both are restated here, along with the icon margins MUI adds. Same
* `!important`/specificity reason as `TicketSummaryCard`'s CHIP_SX.
*/
const CHIP_SX = {
height: "28px",
width: "132px",
maxWidth: "100%",
fontSize: "12px",
borderRadius: "40px",
padding: "6px 16px",
gap: "4px !important",
"& .MuiChip-deleteIcon": { margin: 0 },
};
/**
* The "Ticket Picked Up" cell (frame 7594-66313): a status chip that opens a
* three-option menu — Pending / Collected / Delivered, a tick against the current
* one.
*
* Editable in place because it records something that happened at a desk: the
* merchant marks a ticket handed over while the holder is standing there, and
* making them open the detail panel for it would be a step too many. The panel
* shows the same value read-only.
*/
const TicketPickupCell = ({ row, onChange, isSaving }: Props) => {
const { palette } = useAppTheme();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const current = row.pickupStatus ?? "pending";
const option =
TICKET_PICKUP_OPTIONS.find((o) => o.value === current) ??
TICKET_PICKUP_OPTIONS[0];
// Rows open the detail panel on click — the chip and its menu must not.
const stop = (event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
event.preventDefault();
};
const handleOpen = (event: MouseEvent<HTMLElement>) => {
stop(event);
if (isSaving) return;
setAnchorEl(event.currentTarget);
};
return (
<>
<GiveChip
label={TICKET_PICKUP_LABELS[option.value]}
color={option.color}
// Each state is the tinted treatment — surface at the `25`/`10` token
// (the neutral one at `surface.tertiary`), label and caret at `100` —
// which the theme only applies to `variant="light"`. Without it MUI
// falls back to its own solid `filled` palette, which is what made
// "Delivered" read as a solid green badge and "Collected" a solid blue
// one.
variant="light"
size="large"
onClick={handleOpen}
disabled={isSaving}
data-testid={`ticket-pickup-${row.id}`}
// The caret points at where the menu is, per the open-state chip in the
// frame.
deleteIcon={
anchorEl ? <CaretUpIcon size={16} /> : <CaretDownIcon size={16} />
}
// MUI only renders the trailing icon when onDelete is set; the chip as a
// whole opens the menu, so the caret is decorative and repeats the click.
onDelete={handleOpen as unknown as () => void}
sx={{ ...CHIP_SX, cursor: isSaving ? "default" : "pointer" }}
/>
<ContextualMenu
anchorEl={anchorEl}
handleClose={() => setAnchorEl(null)}
menuWidth={180}
color="primary"
texture="solid"
options={TICKET_PICKUP_OPTIONS.map(({ value, label }) => ({
text: label,
// The tick marks what is already recorded (frame 7594-66313).
IconRight:
value === current
? (iconProps: object) => (
<CheckIcon
{...iconProps}
size={16}
color={palette.text?.primary}
/>
)
: undefined,
onClick: (event: MouseEvent<HTMLDivElement>) => {
stop(event);
setAnchorEl(null);
if (value !== current) onChange(row, value);
},
}))}
/>
</>
);
};
export default TicketPickupCell;
|