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 | 46x 46x 46x 46x 46x 46x 20x 20x 20x 20x 20x 20x 20x 2x 2x 135x | import { Box, ButtonBase, Stack } from "@mui/material";
import GiveText from "@shared/Text/GiveText";
import type { PersistedSeatingRow } from "../seating.helpers";
const SEAT_AVAILABLE = "#666665";
const SEAT_SELECTED = "#088750";
// A row already assigned to another ticket: faded and non-selectable (matches the
// customer SeatPicker "taken" treatment).
const SEAT_TAKEN = "rgba(0, 0, 0, 0.25)";
const SEAT_SIZE = 16;
// Cap rendered squares so a large range (e.g. 1–500) can't blow up the DOM; the overflow
// count keeps the real seat total visible instead of silently truncating it.
const MAX_VISIBLE_SEATS = 40;
type Props = {
range: PersistedSeatingRow;
selected: boolean;
canEdit: boolean;
onToggle: (id: number) => void;
// Name of another ticket this row is already assigned to. When set, the range is
// faded and non-selectable (one row → one ticket, PAY Builder 031).
takenBy?: string;
};
// PAY Builder 031 — one seat range, rendered as a block of seat squares with its optional
// category label below. The whole block is a single multi-select control: selecting it
// assigns this range (a seatingRowID) to the ticket and turns its seats green. Seats are
// visual only — selection is per range, matching the seatingRowIDs data model. A range
// already assigned to another ticket is faded, non-clickable, and labelled with its owner.
const SeatMapRange = ({ range, selected, canEdit, onToggle, takenBy }: Props) => {
const total = Math.max(
0,
Number(range.lastSeat) - Number(range.firstSeat) + 1,
);
const visible = Math.min(total, MAX_VISIBLE_SEATS);
const overflow = total - visible;
const isTaken = !!takenBy;
const seatColor = isTaken
? SEAT_TAKEN
: selected
? SEAT_SELECTED
: SEAT_AVAILABLE;
const ariaLabel = `Row ${range.rowLabel} seats ${range.firstSeat}–${range.lastSeat}${
range.label ? `, ${range.label}` : ""
}${isTaken ? `, assigned to ${takenBy}` : ""}`;
return (
<ButtonBase
role="checkbox"
aria-checked={selected && !isTaken}
aria-label={ariaLabel}
disabled={!canEdit || isTaken}
onClick={() => {
Iif (isTaken) return;
onToggle(range.id);
}}
sx={{
flexDirection: "column",
alignItems: "center",
gap: "4px",
borderRadius: "4px",
p: "4px",
cursor: canEdit && !isTaken ? "pointer" : "default",
}}
>
<Stack
direction="row"
alignItems="center"
justifyContent="center"
gap="4px"
flexWrap="wrap"
>
{Array.from({ length: visible }).map((_, i) => (
<Box
key={i}
className="seat-square"
sx={{
width: SEAT_SIZE,
height: SEAT_SIZE,
borderRadius: "2px",
background: seatColor,
transition: "opacity 120ms ease",
}}
/>
))}
{overflow > 0 && (
<GiveText variant="bodyXXS" sx={{ color: SEAT_AVAILABLE, ml: "2px" }}>
{`+${overflow}`}
</GiveText>
)}
</Stack>
{range.label ? (
<GiveText variant="bodyXXS" sx={{ color: SEAT_AVAILABLE, textAlign: "center" }}>
{range.label}
</GiveText>
) : null}
{isTaken ? (
<GiveText variant="bodyXXS" sx={{ color: SEAT_AVAILABLE, textAlign: "center" }}>
{`Assigned to ${takenBy}`}
</GiveText>
) : null}
</ButtonBase>
);
};
export default SeatMapRange;
|