All files / src/sections/PayBuilder/items SeatMapRange.tsx

94.11% Statements 16/17
91.66% Branches 22/24
100% Functions 3/3
100% Lines 16/16

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;