kupos-ui-components-lib 10.4.59 → 10.4.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,11 +10,6 @@ let SEAT_ORDER = [
10
10
  "Premium",
11
11
  "Premium (I)",
12
12
  ];
13
- function addSeatDegree(label) {
14
- if (!label)
15
- return label;
16
- return String(label).replace(/\b(120|140|160|180)\b(?!°)/g, "$1°");
17
- }
18
13
  class ServiceFilter extends React.Component {
19
14
  constructor(props) {
20
15
  var _a, _b, _c, _d, _e, _f, _g;
@@ -230,6 +225,9 @@ class ServiceFilter extends React.Component {
230
225
  ? val.spText
231
226
  : val.label;
232
227
  }
228
+ const seatDegreeIcon = option.type === "seat_types" && this.props.isShowSeatDegree
229
+ ? commonService.getSeatDegreeIcon(label, this.props.icons)
230
+ : null;
233
231
  return (React.createElement("div", { key: i, onClick: () => onClick(val.originalOptionIndex !== undefined
234
232
  ? val.originalOptionIndex
235
233
  : i), style: {
@@ -245,13 +243,14 @@ class ServiceFilter extends React.Component {
245
243
  ? `text-[${this.props.colors.selectedTextColor}]`
246
244
  : ""} ${val.active ? "bold-text" : ""}` },
247
245
  val.icon && this.props.icons && (React.createElement("img", { src: iconKey, alt: val.icon, className: "w-[20px] h-[20px] mr-[10px] " })),
246
+ seatDegreeIcon && (React.createElement("img", { src: seatDegreeIcon, alt: "seattype", className: "w-[20px] h-[20px] mr-[10px]" })),
248
247
  React.createElement("span", { style: {
249
248
  textTransform: option.type !== "time" ? "capitalize" : "none",
250
249
  } }, option.type === "time"
251
250
  ? commonService.formatTimeLabel(label)
252
251
  : option.type === "seat_types" &&
253
252
  this.props.isShowSeatDegree
254
- ? addSeatDegree(commonService.formatDefaultLabel(label))
253
+ ? commonService.addSeatDegree(commonService.formatDefaultLabel(label))
255
254
  : commonService.formatDefaultLabel(label))));
256
255
  }))));
257
256
  }
@@ -28,6 +28,11 @@ export interface FilterBarProps {
28
28
  iconSunsetOrange?: string;
29
29
  iconNight?: string;
30
30
  iconNightOrange?: string;
31
+ seat120?: string;
32
+ seat140?: string;
33
+ seat160?: string;
34
+ seat180?: string;
35
+ fallBackSeatIcon?: string;
31
36
  };
32
37
  translation?: {
33
38
  [key: string]: string;
@@ -6,5 +6,5 @@ import { FilterSortBarMobileProps } from "./types";
6
6
  * consumer's list — sees a change until Aplicar commits the draft via
7
7
  * onApply. Closing the sheet without applying discards it.
8
8
  */
9
- declare const FilterSortBarMobile: ({ categories, onApply, onClearFilter, sortOptions, onSortChange, currencySign, colors, icons, }: FilterSortBarMobileProps) => React.JSX.Element;
9
+ declare const FilterSortBarMobile: ({ categories, onApply, onClearFilter, sortOptions, onSortChange, currencySign, colors, icons, isShowSeatDegree, }: FilterSortBarMobileProps) => React.JSX.Element;
10
10
  export default FilterSortBarMobile;
@@ -45,7 +45,7 @@ const isCategoryActive = (category) => getCategoryActiveCount(category) > 0;
45
45
  * consumer's list — sees a change until Aplicar commits the draft via
46
46
  * onApply. Closing the sheet without applying discards it.
47
47
  */
48
- const FilterSortBarMobile = ({ categories, onApply, onClearFilter, sortOptions, onSortChange, currencySign = "$", colors, icons, }) => {
48
+ const FilterSortBarMobile = ({ categories, onApply, onClearFilter, sortOptions, onSortChange, currencySign = "$", colors, icons, isShowSeatDegree = false, }) => {
49
49
  const [openSheet, setOpenSheet] = useState(null);
50
50
  const [isScrolled, setIsScrolled] = useState(false);
51
51
  const [draftCategories, setDraftCategories] = useState(categories);
@@ -164,6 +164,6 @@ const FilterSortBarMobile = ({ categories, onApply, onClearFilter, sortOptions,
164
164
  var _a;
165
165
  return (React.createElement(FilterChip, { key: `${category.key}:${option.id}`, label: option.label, active: option.selected, isOpen: false, onClick: () => handleInlineOptionToggle(category.key, option.id), iconSrc: option.icon ? (_a = resolvedIcons.timeOfDayIcons) === null || _a === void 0 ? void 0 : _a[option.icon] : undefined, colors: resolvedColors, icons: resolvedIcons }));
166
166
  })))),
167
- React.createElement(FiltersMasterSheet, { isOpen: isMasterOpen, initialCategoryKey: initialCategoryKey, categories: draftCategories, onClose: closeSheet, onToggleOption: handleDraftToggleOption, onPriceRangeChange: handleDraftPriceRangeChange, onClearAll: handleClearAll, onApplyAll: handleApplyAll, currencySign: currencySign, colors: resolvedColors, icons: resolvedIcons })));
167
+ React.createElement(FiltersMasterSheet, { isOpen: isMasterOpen, initialCategoryKey: initialCategoryKey, categories: draftCategories, onClose: closeSheet, onToggleOption: handleDraftToggleOption, onPriceRangeChange: handleDraftPriceRangeChange, onClearAll: handleClearAll, onApplyAll: handleApplyAll, currencySign: currencySign, colors: resolvedColors, icons: resolvedIcons, isShowSeatDegree: isShowSeatDegree })));
168
168
  };
169
169
  export default FilterSortBarMobile;
@@ -13,6 +13,7 @@ interface FiltersMasterSheetProps {
13
13
  currencySign: string;
14
14
  colors: Required<FilterSortBarColors>;
15
15
  icons?: FilterSortBarIcons;
16
+ isShowSeatDegree?: boolean;
16
17
  }
17
- declare const FiltersMasterSheet: ({ isOpen, initialCategoryKey, categories, onClose, onToggleOption, onPriceRangeChange, onClearAll, onApplyAll, currencySign, colors, icons, }: FiltersMasterSheetProps) => React.JSX.Element;
18
+ declare const FiltersMasterSheet: ({ isOpen, initialCategoryKey, categories, onClose, onToggleOption, onPriceRangeChange, onClearAll, onApplyAll, currencySign, colors, icons, isShowSeatDegree, }: FiltersMasterSheetProps) => React.JSX.Element;
18
19
  export default FiltersMasterSheet;
@@ -4,7 +4,7 @@ import { CloseIcon } from "./icons";
4
4
  import OptionCheckboxList from "./OptionCheckboxList";
5
5
  import PriceRangeSlider from "./PriceRangeSlider";
6
6
  import SheetFooter from "./SheetFooter";
7
- const FiltersMasterSheet = ({ isOpen, initialCategoryKey, categories, onClose, onToggleOption, onPriceRangeChange, onClearAll, onApplyAll, currencySign, colors, icons, }) => {
7
+ const FiltersMasterSheet = ({ isOpen, initialCategoryKey, categories, onClose, onToggleOption, onPriceRangeChange, onClearAll, onApplyAll, currencySign, colors, icons, isShowSeatDegree, }) => {
8
8
  var _a, _b;
9
9
  const [activeKey, setActiveKey] = useState((_b = initialCategoryKey !== null && initialCategoryKey !== void 0 ? initialCategoryKey : (_a = categories[0]) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : "time");
10
10
  // Reset the pre-selected tab every time the sheet is (re)opened, whether
@@ -35,7 +35,7 @@ const FiltersMasterSheet = ({ isOpen, initialCategoryKey, categories, onClose, o
35
35
  })),
36
36
  React.createElement("div", { className: "flex-1 overflow-y-auto pl-[16px] pr-[20px] relative z-[1] bg-white" },
37
37
  activeCategory && (React.createElement("div", { className: "text-[11px] font-bold text-[#9B9B9B] uppercase tracking-[0.4px] mb-[5px] mt-[15px]" }, activeCategory.sectionLabel || activeCategory.label)),
38
- (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.kind) === "price" && activeCategory.priceRange ? (React.createElement(PriceRangeSlider, { value: activeCategory.priceRange, onChange: (min, max) => onPriceRangeChange(activeCategory.key, min, max), currencySign: currencySign, primaryColor: colors.primaryColor, minLabel: "M\u00EDnimo", maxLabel: "M\u00E1ximo" })) : (React.createElement(OptionCheckboxList, { options: (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.options) || [], kind: (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.kind) || "checkbox", onToggle: (optionId) => onToggleOption(activeCategory.key, optionId), primaryColor: colors.primaryColor, starColor: colors.buttonColor || colors.primaryColor, icons: icons })))),
38
+ (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.kind) === "price" && activeCategory.priceRange ? (React.createElement(PriceRangeSlider, { value: activeCategory.priceRange, onChange: (min, max) => onPriceRangeChange(activeCategory.key, min, max), currencySign: currencySign, primaryColor: colors.primaryColor, minLabel: "M\u00EDnimo", maxLabel: "M\u00E1ximo" })) : (React.createElement(OptionCheckboxList, { options: (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.options) || [], kind: (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.kind) || "checkbox", onToggle: (optionId) => onToggleOption(activeCategory.key, optionId), primaryColor: colors.primaryColor, starColor: colors.buttonColor || colors.primaryColor, icons: icons, showSeatDegree: !!isShowSeatDegree && (activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.key) === "seatType" })))),
39
39
  React.createElement("div", { className: "px-[20px]" },
40
40
  React.createElement(SheetFooter, { onClear: onClearAll, onApply: onApplyAll, colors: colors })))));
41
41
  };
@@ -8,6 +8,8 @@ interface OptionCheckboxListProps {
8
8
  /** Filled-star color for rating options — the app's button/CTA color, not the chip accent. */
9
9
  starColor: string;
10
10
  icons?: FilterSortBarIcons;
11
+ /** For options whose label contains 120/140/160/180: add "°" and show icons.seat120/140/160/180. */
12
+ showSeatDegree?: boolean;
11
13
  }
12
- declare const OptionCheckboxList: ({ options, kind, onToggle, primaryColor, starColor, icons }: OptionCheckboxListProps) => React.JSX.Element;
14
+ declare const OptionCheckboxList: ({ options, kind, onToggle, primaryColor, starColor, icons, showSeatDegree, }: OptionCheckboxListProps) => React.JSX.Element;
13
15
  export default OptionCheckboxList;
@@ -4,16 +4,20 @@ import CommonService from "../../utils/CommonService";
4
4
  const RatingLabel = ({ stars, label, starColor, icons, }) => (React.createElement("div", { className: "flex items-center gap-[1px] whitespace-nowrap" },
5
5
  Array.from({ length: 5 }, (_, i) => (React.createElement(StarIcon, { key: i, size: 10, filled: stars != null && i < stars, color: starColor, filledSrc: icons === null || icons === void 0 ? void 0 : icons.starFilledIcon, emptySrc: icons === null || icons === void 0 ? void 0 : icons.starEmptyIcon }))),
6
6
  React.createElement("span", { className: "ml-[6px]" }, label)));
7
- const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor, icons }) => {
7
+ const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor, icons, showSeatDegree, }) => {
8
8
  if (!(options === null || options === void 0 ? void 0 : options.length)) {
9
9
  return (React.createElement("div", { className: "py-[24px] text-center text-[#9B9B9B] text-[13px]" }, "No hay opciones disponibles"));
10
10
  }
11
11
  return (React.createElement("div", null, options.map((option) => {
12
12
  var _a;
13
+ const seatIcon = showSeatDegree ? CommonService.getSeatDegreeIcon(option.label, icons) : null;
14
+ const label = CommonService.capitalizeFirstLetter(showSeatDegree ? CommonService.addSeatDegree(option.label) : option.label);
13
15
  return (React.createElement("div", { key: option.id, onClick: () => onToggle(option.id), className: "flex items-center justify-between py-[12px] px-[2px] cursor-pointer text-[13px] text-[#464647] select-none [-webkit-tap-highlight-color:transparent]" },
14
16
  kind === "rating" ? (React.createElement(RatingLabel, { stars: option.stars, label: option.label, starColor: starColor, icons: icons })) : option.icon ? (React.createElement("div", { className: "flex items-center gap-[10px]" },
15
17
  React.createElement(TimeOfDayIcon, { variant: option.icon, size: 16, src: (_a = icons === null || icons === void 0 ? void 0 : icons.timeOfDayIcons) === null || _a === void 0 ? void 0 : _a[option.icon] }),
16
- React.createElement("span", null, option.label))) : (React.createElement("span", null, CommonService.capitalizeFirstLetter(option.label))),
18
+ React.createElement("span", null, option.label))) : seatIcon ? (React.createElement("div", { className: "flex items-center gap-[10px]" },
19
+ React.createElement("img", { src: seatIcon, alt: "seattype", className: "w-[16px] h-[16px]" }),
20
+ React.createElement("span", null, label))) : (React.createElement("span", null, label)),
17
21
  React.createElement(CheckboxIcon, { checked: option.selected, color: primaryColor, spriteSrc: icons === null || icons === void 0 ? void 0 : icons.checkBoxIcon })));
18
22
  })));
19
23
  };
@@ -77,6 +77,11 @@ export interface FilterSortBarIcons {
77
77
  timeOfDayIcons?: Partial<Record<TimeOfDayVariant, string>>;
78
78
  /** Icons for sort chips whose key doesn't already map to a category/time-of-day icon (e.g. "duration" for a Rápido chip). Keyed by SortOption.key. */
79
79
  sortIcons?: Partial<Record<string, string>>;
80
+ /** Seat-type icons shown next to "Tipo de asiento" options whose label contains 120/140/160/180, only when isShowSeatDegree is true. */
81
+ seat120?: string;
82
+ seat140?: string;
83
+ seat160?: string;
84
+ seat180?: string;
80
85
  }
81
86
  export interface FilterSortBarMobileProps {
82
87
  categories: FilterCategory[];
@@ -88,4 +93,6 @@ export interface FilterSortBarMobileProps {
88
93
  currencySign?: string;
89
94
  colors?: FilterSortBarColors;
90
95
  icons?: FilterSortBarIcons;
96
+ /** In the Filtros sheet's seat-type tab, adds "°" to 120/140/160/180 labels and shows icons.seat120/140/160/180 next to them. */
97
+ isShowSeatDegree?: boolean;
91
98
  }
@@ -349,11 +349,20 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
349
349
  }
350
350
  }
351
351
  if (hasDiscount && discountSeat) {
352
+ const discountSeatIcon = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 && isShowSeatIcon
353
+ ? getSeatLabelIcon(discountSeat.label, serviceItem)
354
+ : null;
352
355
  return (React.createElement("div", { className: "grid grid-cols-2 items-center text-[13.33px] relative", style: { opacity: isSoldOut ? 0.5 : 1 } },
353
356
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 ? null : (React.createElement("div", { className: "col-start-1 row-start-2 flex items-center" },
354
357
  React.createElement("span", { className: "text-[13.33px] font-normal leading-[22px] text-[#ccc]" }, "Antes"))),
355
358
  React.createElement("div", { className: "col-start-1 row-start-3 flex h-[20px] items-end" },
356
- React.createElement("span", { className: "text-[13.33px] font-normal leading-[20px] text-[#464647] whitespace-nowrap" }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 ? discountSeat.label : "Desde")),
359
+ React.createElement("span", { className: "text-[13.33px] font-normal leading-[20px] text-[#464647] whitespace-nowrap flex items-center" },
360
+ discountSeatIcon && (React.createElement("img", { src: discountSeatIcon, alt: "seattype", style: {
361
+ width: "20px",
362
+ height: "20px",
363
+ marginRight: "5px",
364
+ } })),
365
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 ? discountSeat.label : "Desde")),
357
366
  React.createElement("div", { className: "col-start-2 row-start-1 flex items-center justify-center absolute", style: { top: "-22px", left: "50%", transform: "translateX(-50%)" } },
358
367
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 &&
359
368
  !isPeru &&
@@ -104,8 +104,19 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
104
104
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] absolute", style: { color: "#6a6a6a", top: -20, right: 0 } }, "Desde"),
105
105
  uniqueSeats.map((seat, key) => {
106
106
  const { originalPrice, discountedPrice } = commonService.calculateDiscountedPrice(Number(seat.fare), serviceItem);
107
+ // Picked from the full label, since truncation can cut off the angle
108
+ const rowSeatIcon = isShowSeatIcon
109
+ ? getSeatLabelIcon(seat.label, serviceItem)
110
+ : null;
107
111
  return (React.createElement("div", { key: key, className: "w-[100%] flex flex-row justify-between items-center", style: { marginTop: key > 0 ? "8px" : "0" } },
108
- React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text whitespace-nowrap", style: { color: isSoldOut ? "#bbb" : "#464647" } }, commonService.truncateSeatLabel(seat.label)),
112
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text whitespace-nowrap flex items-center", style: { color: isSoldOut ? "#bbb" : "#464647" } },
113
+ rowSeatIcon && (React.createElement("img", { src: rowSeatIcon, alt: "seattype", style: {
114
+ width: "16px",
115
+ height: "16px",
116
+ marginRight: "4px",
117
+ filter: isSoldOut ? "grayscale(1)" : "",
118
+ } })),
119
+ commonService.truncateSeatLabel(seat.label)),
109
120
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: priceColor } }, commonService.currency(showOriginalFare ? originalPrice : discountedPrice, currencySign))));
110
121
  })));
111
122
  }
@@ -14,6 +14,8 @@ declare const commonService: {
14
14
  capitalizeFirstLetter: (str: any) => string;
15
15
  formatTimeLabel: (label: string) => string;
16
16
  formatDefaultLabel: (label: string) => string;
17
+ addSeatDegree: (label: string) => string;
18
+ getSeatDegreeIcon: (label: string, icons: any) => string | null;
17
19
  calculateDiscountedPrice: (originalPrice: number | string, serviceItem?: {
18
20
  discount_type?: string;
19
21
  discount_value?: number;
@@ -323,6 +323,15 @@ const commonService = {
323
323
  return "";
324
324
  return label.toLowerCase();
325
325
  },
326
+ addSeatDegree: (label) => {
327
+ if (!label)
328
+ return label;
329
+ return String(label).replace(/\b(120|140|160|180)\b(?!°)/g, "$1°");
330
+ },
331
+ getSeatDegreeIcon: (label, icons) => {
332
+ const match = String(label !== null && label !== void 0 ? label : "").match(/\b(120|140|160|180)\b/);
333
+ return match ? (icons === null || icons === void 0 ? void 0 : icons[`seat${match[1]}`]) || null : null;
334
+ },
326
335
  calculateDiscountedPrice: (originalPrice, serviceItem) => {
327
336
  var _a;
328
337
  const price = typeof originalPrice === "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "10.4.59",
3
+ "version": "10.4.61",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -14,11 +14,6 @@ let SEAT_ORDER = [
14
14
  "Premium (I)",
15
15
  ];
16
16
 
17
- function addSeatDegree(label: string) {
18
- if (!label) return label;
19
- return String(label).replace(/\b(120|140|160|180)\b(?!°)/g, "$1°");
20
- }
21
-
22
17
  interface ServiceFilterBarDesktopState {
23
18
  showOperators: boolean;
24
19
  showTimeOptions: boolean;
@@ -307,6 +302,11 @@ class ServiceFilter extends React.Component<
307
302
  : val.label;
308
303
  }
309
304
 
305
+ const seatDegreeIcon =
306
+ option.type === "seat_types" && this.props.isShowSeatDegree
307
+ ? commonService.getSeatDegreeIcon(label, this.props.icons)
308
+ : null;
309
+
310
310
  return (
311
311
  <div
312
312
  key={i}
@@ -342,6 +342,13 @@ class ServiceFilter extends React.Component<
342
342
  className="w-[20px] h-[20px] mr-[10px] "
343
343
  />
344
344
  )}
345
+ {seatDegreeIcon && (
346
+ <img
347
+ src={seatDegreeIcon}
348
+ alt="seattype"
349
+ className="w-[20px] h-[20px] mr-[10px]"
350
+ />
351
+ )}
345
352
  <span
346
353
  style={{
347
354
  textTransform:
@@ -352,7 +359,9 @@ class ServiceFilter extends React.Component<
352
359
  ? commonService.formatTimeLabel(label)
353
360
  : option.type === "seat_types" &&
354
361
  this.props.isShowSeatDegree
355
- ? addSeatDegree(commonService.formatDefaultLabel(label))
362
+ ? commonService.addSeatDegree(
363
+ commonService.formatDefaultLabel(label),
364
+ )
356
365
  : commonService.formatDefaultLabel(label)}
357
366
  </span>
358
367
  </div>
@@ -29,6 +29,11 @@ export interface FilterBarProps {
29
29
  iconSunsetOrange?: string;
30
30
  iconNight?: string;
31
31
  iconNightOrange?: string;
32
+ seat120?: string;
33
+ seat140?: string;
34
+ seat160?: string;
35
+ seat180?: string;
36
+ fallBackSeatIcon?: string;
32
37
  };
33
38
  translation?: { [key: string]: string };
34
39
  isTrain?: boolean;
@@ -59,6 +59,7 @@ const FilterSortBarMobile = ({
59
59
  currencySign = "$",
60
60
  colors,
61
61
  icons,
62
+ isShowSeatDegree = false,
62
63
  }: FilterSortBarMobileProps) => {
63
64
  const [openSheet, setOpenSheet] = useState<FilterCategoryKey | "all" | null>(null);
64
65
  const [isScrolled, setIsScrolled] = useState(false);
@@ -284,6 +285,7 @@ const FilterSortBarMobile = ({
284
285
  currencySign={currencySign}
285
286
  colors={resolvedColors}
286
287
  icons={resolvedIcons}
288
+ isShowSeatDegree={isShowSeatDegree}
287
289
  />
288
290
  </div>
289
291
  );
@@ -19,6 +19,7 @@ interface FiltersMasterSheetProps {
19
19
  currencySign: string;
20
20
  colors: Required<FilterSortBarColors>;
21
21
  icons?: FilterSortBarIcons;
22
+ isShowSeatDegree?: boolean;
22
23
  }
23
24
 
24
25
  const FiltersMasterSheet = ({
@@ -33,6 +34,7 @@ const FiltersMasterSheet = ({
33
34
  currencySign,
34
35
  colors,
35
36
  icons,
37
+ isShowSeatDegree,
36
38
  }: FiltersMasterSheetProps) => {
37
39
  const [activeKey, setActiveKey] = useState<FilterCategoryKey>(initialCategoryKey ?? categories[0]?.key ?? "time");
38
40
 
@@ -108,6 +110,7 @@ const FiltersMasterSheet = ({
108
110
  primaryColor={colors.primaryColor}
109
111
  starColor={colors.buttonColor || colors.primaryColor}
110
112
  icons={icons}
113
+ showSeatDegree={!!isShowSeatDegree && activeCategory?.key === "seatType"}
111
114
  />
112
115
  )}
113
116
  </div>
@@ -11,6 +11,8 @@ interface OptionCheckboxListProps {
11
11
  /** Filled-star color for rating options — the app's button/CTA color, not the chip accent. */
12
12
  starColor: string;
13
13
  icons?: FilterSortBarIcons;
14
+ /** For options whose label contains 120/140/160/180: add "°" and show icons.seat120/140/160/180. */
15
+ showSeatDegree?: boolean;
14
16
  }
15
17
 
16
18
  const RatingLabel = ({
@@ -39,7 +41,15 @@ const RatingLabel = ({
39
41
  </div>
40
42
  );
41
43
 
42
- const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor, icons }: OptionCheckboxListProps) => {
44
+ const OptionCheckboxList = ({
45
+ options,
46
+ kind,
47
+ onToggle,
48
+ primaryColor,
49
+ starColor,
50
+ icons,
51
+ showSeatDegree,
52
+ }: OptionCheckboxListProps) => {
43
53
  if (!options?.length) {
44
54
  return (
45
55
  <div className="py-[24px] text-center text-[#9B9B9B] text-[13px]">
@@ -50,25 +60,37 @@ const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor,
50
60
 
51
61
  return (
52
62
  <div>
53
- {options.map((option) => (
54
- <div
55
- key={option.id}
56
- onClick={() => onToggle(option.id)}
57
- className="flex items-center justify-between py-[12px] px-[2px] cursor-pointer text-[13px] text-[#464647] select-none [-webkit-tap-highlight-color:transparent]"
58
- >
59
- {kind === "rating" ? (
60
- <RatingLabel stars={option.stars} label={option.label} starColor={starColor} icons={icons} />
61
- ) : option.icon ? (
62
- <div className="flex items-center gap-[10px]">
63
- <TimeOfDayIcon variant={option.icon} size={16} src={icons?.timeOfDayIcons?.[option.icon]} />
64
- <span>{option.label}</span>
65
- </div>
66
- ) : (
67
- <span>{CommonService.capitalizeFirstLetter(option.label)}</span>
68
- )}
69
- <CheckboxIcon checked={option.selected} color={primaryColor} spriteSrc={icons?.checkBoxIcon} />
70
- </div>
71
- ))}
63
+ {options.map((option) => {
64
+ const seatIcon = showSeatDegree ? CommonService.getSeatDegreeIcon(option.label, icons) : null;
65
+ const label = CommonService.capitalizeFirstLetter(
66
+ showSeatDegree ? CommonService.addSeatDegree(option.label) : option.label,
67
+ );
68
+
69
+ return (
70
+ <div
71
+ key={option.id}
72
+ onClick={() => onToggle(option.id)}
73
+ className="flex items-center justify-between py-[12px] px-[2px] cursor-pointer text-[13px] text-[#464647] select-none [-webkit-tap-highlight-color:transparent]"
74
+ >
75
+ {kind === "rating" ? (
76
+ <RatingLabel stars={option.stars} label={option.label} starColor={starColor} icons={icons} />
77
+ ) : option.icon ? (
78
+ <div className="flex items-center gap-[10px]">
79
+ <TimeOfDayIcon variant={option.icon} size={16} src={icons?.timeOfDayIcons?.[option.icon]} />
80
+ <span>{option.label}</span>
81
+ </div>
82
+ ) : seatIcon ? (
83
+ <div className="flex items-center gap-[10px]">
84
+ <img src={seatIcon} alt="seattype" className="w-[16px] h-[16px]" />
85
+ <span>{label}</span>
86
+ </div>
87
+ ) : (
88
+ <span>{label}</span>
89
+ )}
90
+ <CheckboxIcon checked={option.selected} color={primaryColor} spriteSrc={icons?.checkBoxIcon} />
91
+ </div>
92
+ );
93
+ })}
72
94
  </div>
73
95
  );
74
96
  };
@@ -100,6 +100,11 @@ export interface FilterSortBarIcons {
100
100
  timeOfDayIcons?: Partial<Record<TimeOfDayVariant, string>>;
101
101
  /** Icons for sort chips whose key doesn't already map to a category/time-of-day icon (e.g. "duration" for a Rápido chip). Keyed by SortOption.key. */
102
102
  sortIcons?: Partial<Record<string, string>>;
103
+ /** Seat-type icons shown next to "Tipo de asiento" options whose label contains 120/140/160/180, only when isShowSeatDegree is true. */
104
+ seat120?: string;
105
+ seat140?: string;
106
+ seat160?: string;
107
+ seat180?: string;
103
108
  }
104
109
 
105
110
  export interface FilterSortBarMobileProps {
@@ -112,4 +117,6 @@ export interface FilterSortBarMobileProps {
112
117
  currencySign?: string;
113
118
  colors?: FilterSortBarColors;
114
119
  icons?: FilterSortBarIcons;
120
+ /** In the Filtros sheet's seat-type tab, adds "°" to 120/140/160/180 labels and shows icons.seat120/140/160/180 next to them. */
121
+ isShowSeatDegree?: boolean;
115
122
  }
@@ -661,6 +661,11 @@ function SeatSection({
661
661
  }
662
662
 
663
663
  if (hasDiscount && discountSeat) {
664
+ const discountSeatIcon =
665
+ serviceItem?.ui_type === 1 && isShowSeatIcon
666
+ ? getSeatLabelIcon(discountSeat.label, serviceItem)
667
+ : null;
668
+
664
669
  return (
665
670
  <div
666
671
  className="grid grid-cols-2 items-center text-[13.33px] relative"
@@ -675,7 +680,18 @@ function SeatSection({
675
680
  )}
676
681
 
677
682
  <div className="col-start-1 row-start-3 flex h-[20px] items-end">
678
- <span className="text-[13.33px] font-normal leading-[20px] text-[#464647] whitespace-nowrap">
683
+ <span className="text-[13.33px] font-normal leading-[20px] text-[#464647] whitespace-nowrap flex items-center">
684
+ {discountSeatIcon && (
685
+ <img
686
+ src={discountSeatIcon}
687
+ alt="seattype"
688
+ style={{
689
+ width: "20px",
690
+ height: "20px",
691
+ marginRight: "5px",
692
+ }}
693
+ />
694
+ )}
679
695
  {serviceItem?.ui_type === 1 ? discountSeat.label : "Desde"}
680
696
  </span>
681
697
  </div>
@@ -218,6 +218,10 @@ function SeatSectionMobile({
218
218
  Number(seat.fare),
219
219
  serviceItem,
220
220
  );
221
+ // Picked from the full label, since truncation can cut off the angle
222
+ const rowSeatIcon = isShowSeatIcon
223
+ ? getSeatLabelIcon(seat.label, serviceItem)
224
+ : null;
221
225
  return (
222
226
  <div
223
227
  key={key}
@@ -225,9 +229,21 @@ function SeatSectionMobile({
225
229
  style={{ marginTop: key > 0 ? "8px" : "0" }}
226
230
  >
227
231
  <span
228
- className="min-[420]:text-[13px] text-[12px] bold-text whitespace-nowrap"
232
+ className="min-[420]:text-[13px] text-[12px] bold-text whitespace-nowrap flex items-center"
229
233
  style={{ color: isSoldOut ? "#bbb" : "#464647" }}
230
234
  >
235
+ {rowSeatIcon && (
236
+ <img
237
+ src={rowSeatIcon}
238
+ alt="seattype"
239
+ style={{
240
+ width: "16px",
241
+ height: "16px",
242
+ marginRight: "4px",
243
+ filter: isSoldOut ? "grayscale(1)" : "",
244
+ }}
245
+ />
246
+ )}
231
247
  {commonService.truncateSeatLabel(seat.label)}
232
248
  </span>
233
249
  <span
@@ -339,6 +339,16 @@ const commonService = {
339
339
  return label.toLowerCase();
340
340
  },
341
341
 
342
+ addSeatDegree: (label: string) => {
343
+ if (!label) return label;
344
+ return String(label).replace(/\b(120|140|160|180)\b(?!°)/g, "$1°");
345
+ },
346
+
347
+ getSeatDegreeIcon: (label: string, icons: any): string | null => {
348
+ const match = String(label ?? "").match(/\b(120|140|160|180)\b/);
349
+ return match ? icons?.[`seat${match[1]}`] || null : null;
350
+ },
351
+
342
352
  calculateDiscountedPrice: (
343
353
  originalPrice: number | string,
344
354
  serviceItem?: {