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.
- package/dist/components/FilterBar/ServiceFilter.js +5 -6
- package/dist/components/FilterBar/tyoes.d.ts +5 -0
- package/dist/components/FilterSortBar/FilterSortBarMobile.d.ts +1 -1
- package/dist/components/FilterSortBar/FilterSortBarMobile.js +2 -2
- package/dist/components/FilterSortBar/FiltersMasterSheet.d.ts +2 -1
- package/dist/components/FilterSortBar/FiltersMasterSheet.js +2 -2
- package/dist/components/FilterSortBar/OptionCheckboxList.d.ts +3 -1
- package/dist/components/FilterSortBar/OptionCheckboxList.js +6 -2
- package/dist/components/FilterSortBar/types.d.ts +7 -0
- package/dist/ui/SeatSection/SeatSection.js +10 -1
- package/dist/ui/mobileweb/SeatSectionMobile.js +12 -1
- package/dist/utils/CommonService.d.ts +2 -0
- package/dist/utils/CommonService.js +9 -0
- package/package.json +1 -1
- package/src/components/FilterBar/ServiceFilter.tsx +15 -6
- package/src/components/FilterBar/tyoes.ts +5 -0
- package/src/components/FilterSortBar/FilterSortBarMobile.tsx +2 -0
- package/src/components/FilterSortBar/FiltersMasterSheet.tsx +3 -0
- package/src/components/FilterSortBar/OptionCheckboxList.tsx +42 -20
- package/src/components/FilterSortBar/types.ts +7 -0
- package/src/ui/SeatSection/SeatSection.tsx +17 -1
- package/src/ui/mobileweb/SeatSectionMobile.tsx +17 -1
- package/src/utils/CommonService.ts +10 -0
|
@@ -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("
|
|
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" },
|
|
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" } },
|
|
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
|
@@ -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
|
-
?
|
|
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 = ({
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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?: {
|