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

89.47% Statements 17/19
84.61% Branches 11/13
100% Functions 4/4
100% Lines 17/17

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            46x 46x 46x 46x                         46x 23x                     46x             11x 11x   11x     2x 2x 2x 2x 2x     11x                                                 17x                                                                          
import { Box, Stack } from "@mui/material";
import GiveText from "@shared/Text/GiveText";
import { groupSeatingRowsByLabel } from "../seating.helpers";
import type { SeatingRow } from "../types";
import SeatMapRow from "./SeatMapRow";
 
const SEAT_AVAILABLE = "#666665";
const SEAT_SELECTED = "#088750";
const SEAT_TAKEN = "rgba(0, 0, 0, 0.25)";
const BORDER = "#E6E6E3";
 
type Props = {
  seatingRows: SeatingRow[];
  value: number[];
  onChange: (ids: number[]) => void;
  canEdit: boolean;
  // rowID → owning ticket name for rows already assigned to OTHER tickets. Those rows
  // render disabled so one seating row can only ever belong to one ticket (PAY Builder 031).
  takenRows?: Map<number, string>;
};
 
// Legend item: a 10×10 swatch above its label (Figma Frame 1321317240/241).
const LegendItem = ({ color, label }: { color: string; label: string }) => (
  <Stack alignItems="center" gap="4px">
    <Box sx={{ width: 10, height: 10, borderRadius: "2px", background: color }} />
    <GiveText variant="bodyXS" sx={{ color: SEAT_AVAILABLE }}>
      {label}
    </GiveText>
  </Stack>
);
 
// AC006/AC007 — restrict this ticket to specific seating rows, rendered as the seat map
// (Figma node 6722-8421). Presentational: the parent owns the form state. Selection is per
// range (each range = one seatingRowID); the seat squares are the visual.
const SeatingRowsField = ({
  seatingRows,
  value,
  onChange,
  canEdit,
  takenRows,
}: Props) => {
  const groups = groupSeatingRowsByLabel(seatingRows);
  const hasTakenRows = !!takenRows && takenRows.size > 0;
 
  const toggle = (id: number) => {
    // A row owned by another ticket is not selectable (defense — the control is
    // already disabled); never let it enter this ticket's selection.
    Iif (takenRows?.has(id)) return;
    const next = new Set<number>(value);
    Iif (next.has(id)) next.delete(id);
    else next.add(id);
    onChange([...next]);
  };
 
  return (
    <Stack gap="8px">
      {/* Section label — Body S #292928 (matches the other field labels). */}
      <GiveText variant="bodyS" sx={{ color: "#292928" }}>
        Assign rows for this ticket
      </GiveText>
 
      {groups.length === 0 ? (
        <GiveText variant="bodyXS" color="secondary">
          Add and save seating rows in the event details first.
        </GiveText>
      ) : (
        <Stack
          sx={{
            alignItems: "center",
            gap: "12px",
            p: "12px",
            background: "rgba(245, 245, 243, 0.7)",
            border: `1px solid ${BORDER}`,
            borderRadius: "8px",
          }}
        >
          {/* Rows wrapper — centered content block (Figma Frame 1321317244). */}
          <Stack alignItems="center" gap="4px">
            {groups.map((group) => (
              <SeatMapRow
                key={group.ranges[0].id}
                group={group}
                value={value}
                canEdit={canEdit}
                onToggle={toggle}
                takenRows={takenRows}
              />
            ))}
          </Stack>
 
          {/* Legend — full-width, top border, items centered (Figma Frame 1321317242). */}
          <Stack
            direction="row"
            justifyContent="center"
            gap="16px"
            sx={{ alignSelf: "stretch", pt: "12px", borderTop: `1px solid ${BORDER}` }}
          >
            <LegendItem color={SEAT_AVAILABLE} label="Available" />
            <LegendItem color={SEAT_SELECTED} label="Selected" />
            {hasTakenRows && (
              <LegendItem color={SEAT_TAKEN} label="Assigned" />
            )}
          </Stack>
        </Stack>
      )}
 
      {groups.length > 0 && value.length === 0 && (
        <GiveText variant="bodyXS" color="error1">
          Select at least one seating row for this ticket.
        </GiveText>
      )}
    </Stack>
  );
};
 
export default SeatingRowsField;