kupos-ui-components-lib 9.9.10 → 9.10.0

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.
Files changed (35) hide show
  1. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  2. package/dist/components/ServiceItem/ServiceItemDesktop.js +20 -2
  3. package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
  4. package/dist/components/ServiceItem/ServiceItemMobile.js +1 -1
  5. package/dist/components/ServiceItem/mobileTypes.d.ts +2 -0
  6. package/dist/components/ServiceItem/types.d.ts +7 -0
  7. package/dist/components/Survey/SurveyDesktop.d.ts +1 -1
  8. package/dist/components/Survey/SurveyDesktop.js +18 -24
  9. package/dist/components/Survey/SurveyMobile.d.ts +1 -1
  10. package/dist/components/Survey/SurveyMobile.js +16 -25
  11. package/dist/components/Survey/types.d.ts +4 -0
  12. package/dist/styles.css +3 -0
  13. package/dist/ui/SeatSection/SeatSection.d.ts +7 -1
  14. package/dist/ui/SeatSection/SeatSection.js +38 -9
  15. package/dist/ui/Survey/FeedbackTextarea.d.ts +4 -1
  16. package/dist/ui/Survey/FeedbackTextarea.js +19 -21
  17. package/dist/ui/Survey/ScoreButtons.d.ts +5 -1
  18. package/dist/ui/Survey/ScoreButtons.js +13 -9
  19. package/dist/ui/Survey/constants.d.ts +1 -1
  20. package/dist/ui/Survey/constants.js +1 -1
  21. package/dist/ui/mobileweb/DateTimeSectionMobile.d.ts +2 -1
  22. package/dist/ui/mobileweb/DateTimeSectionMobile.js +12 -6
  23. package/dist/ui/mobileweb/SeatSectionMobile.d.ts +2 -1
  24. package/dist/ui/mobileweb/SeatSectionMobile.js +21 -14
  25. package/dist/utils/CommonService.d.ts +1 -1
  26. package/dist/utils/CommonService.js +5 -1
  27. package/package.json +1 -1
  28. package/src/components/ServiceItem/ServiceItemDesktop.tsx +43 -0
  29. package/src/components/ServiceItem/ServiceItemMobile.tsx +2 -0
  30. package/src/components/ServiceItem/mobileTypes.ts +32 -26
  31. package/src/components/ServiceItem/types.ts +12 -0
  32. package/src/ui/SeatSection/SeatSection.tsx +86 -18
  33. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +44 -35
  34. package/src/ui/mobileweb/SeatSectionMobile.tsx +26 -11
  35. package/src/utils/CommonService.ts +7 -1
@@ -11,7 +11,7 @@ const EXCEPTIONS = [
11
11
  "blanco",
12
12
  "asiento_mascota",
13
13
  ];
14
- const SeatRow = ({ type, index, displayLabel, fare, isSoldOut, seatPriceColor, hasMultipleTypes, textSize, }) => {
14
+ const SeatRow = ({ type, index, displayLabel, fare, isSoldOut, seatPriceColor, hasMultipleTypes, textSize, isTrain, }) => {
15
15
  if (EXCEPTIONS.includes(type.label))
16
16
  return null;
17
17
  const rowClass = hasMultipleTypes
@@ -20,7 +20,9 @@ const SeatRow = ({ type, index, displayLabel, fare, isSoldOut, seatPriceColor, h
20
20
  const labelColor = isSoldOut ? "#bbb" : "#464647";
21
21
  const priceColor = isSoldOut ? "#bbb" : seatPriceColor;
22
22
  return (React.createElement("div", { className: rowClass, key: index },
23
- React.createElement("span", { className: `min-[420]:text-[13px] ${textSize} `, style: { color: labelColor } }, displayLabel),
23
+ React.createElement("span", { className: `min-[420]:text-[13px] ${textSize} `, style: { color: labelColor } }, isTrain
24
+ ? commonService.truncateSeatLabel(commonService.capitalize(displayLabel), 8)
25
+ : displayLabel),
24
26
  React.createElement("span", { className: `min-[420]:text-[13px] ${textSize} bold-text`, style: { color: priceColor } }, fare)));
25
27
  };
26
28
  const getFilteredSeats = (item) => {
@@ -42,8 +44,8 @@ const getUniqueSeats = (data, limit) => {
42
44
  .sort((a, b) => a.fare - b.fare)
43
45
  .slice(0, limit);
44
46
  };
45
- function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, showLastSeats, discountSeatPriceColor, }) {
46
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
47
+ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, showLastSeats, discountSeatPriceColor, isTrain, }) {
48
+ var _a, _b, _c, _d, _e, _f, _g;
47
49
  const hasMultipleTypes = ((_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.length) !== null && _a !== void 0 ? _a : 0) > 2;
48
50
  const getFare = (fare) => {
49
51
  if (removeDuplicateSeats && availableSeats <= 0 && !isPeru) {
@@ -101,19 +103,24 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
101
103
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] text-[#464647]", style: { opacity: isSoldOut ? 0.5 : 1 } }, "Agotado"))) : null));
102
104
  };
103
105
  const renderSeats = () => {
104
- var _a, _b, _c;
106
+ var _a, _b;
105
107
  if (isPeru) {
106
108
  return renderPeruSeats();
107
109
  }
108
110
  if (removeDuplicateSeats) {
109
111
  const uniqueSeats = getUniqueSeats(seatTypesData, 3);
110
- return uniqueSeats.map((type, i) => (React.createElement(SeatRow, { key: i, type: type, index: i, displayLabel: commonService.truncateSeatLabel(type.label), fare: getFare(type.fare), isSoldOut: isSoldOut, seatPriceColor: seatPriceColor, hasMultipleTypes: hasMultipleTypes, textSize: "text-[11px]" })));
112
+ return uniqueSeats.map((type, i) => (React.createElement(SeatRow, { key: i, type: type, index: i, displayLabel: commonService.truncateSeatLabel(type.label), fare: getFare(type.fare), isSoldOut: isSoldOut, seatPriceColor: seatPriceColor, hasMultipleTypes: hasMultipleTypes, textSize: "text-[11px]", isTrain: isTrain })));
111
113
  }
112
- return (_c = (_b = (_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label))) === null || _a === void 0 ? void 0 : _a.sort((a, b) => a.fare - b.fare)) === null || _b === void 0 ? void 0 : _b.slice(0, 2)) === null || _c === void 0 ? void 0 : _c.map((type, i) => (React.createElement(SeatRow, { key: i, type: type, index: i, displayLabel: type.label, fare: getFare(type.fare), isSoldOut: isSoldOut, seatPriceColor: seatPriceColor, hasMultipleTypes: hasMultipleTypes, textSize: "text-[12px]" })));
114
+ const filteredSeats = (_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label))) === null || _a === void 0 ? void 0 : _a.sort((a, b) => a.fare - b.fare);
115
+ return (_b = (isTrain ? filteredSeats : filteredSeats === null || filteredSeats === void 0 ? void 0 : filteredSeats.slice(0, 2))) === null || _b === void 0 ? void 0 : _b.map((type, i) => (React.createElement(SeatRow, { key: i, type: type, index: i, displayLabel: type.label, fare: getFare(type.fare), isSoldOut: isSoldOut, seatPriceColor: seatPriceColor, hasMultipleTypes: hasMultipleTypes, textSize: "text-[12px]", isTrain: isTrain })));
113
116
  };
114
117
  const seats = removeDuplicateSeats
115
118
  ? getUniqueSeats(seatTypesData, 3)
116
- : (_c = (_b = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label))) === null || _b === void 0 ? void 0 : _b.sort((a, b) => a.fare - b.fare)) === null || _c === void 0 ? void 0 : _c.slice(0, 2);
119
+ : (() => {
120
+ var _a;
121
+ const filtered = (_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label))) === null || _a === void 0 ? void 0 : _a.sort((a, b) => a.fare - b.fare);
122
+ return isTrain ? filtered : filtered === null || filtered === void 0 ? void 0 : filtered.slice(0, 2);
123
+ })();
117
124
  const discountedSeats = seats === null || seats === void 0 ? void 0 : seats.map((seat) => (Object.assign(Object.assign({}, seat), commonService.calculateDiscountedPrice(seat.fare, serviceItem))));
118
125
  const peruLowestFare = isPeru ? getLowestFare() : null;
119
126
  const peruDiscountCalc = isPeru && peruLowestFare != null
@@ -124,7 +131,7 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
124
131
  peruDiscountCalc.originalPrice !== peruDiscountCalc.discountedPrice
125
132
  : discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.some((s) => s.originalPrice !== s.discountedPrice);
126
133
  const discountSeat = isPeru && peruDiscountCalc
127
- ? Object.assign({ label: "", fare: peruLowestFare }, peruDiscountCalc) : (_d = discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.filter((seat) => !EXCEPTIONS.includes(seat.label))) === null || _d === void 0 ? void 0 : _d.sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
134
+ ? Object.assign({ label: "", fare: peruLowestFare }, peruDiscountCalc) : (_b = discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.filter((seat) => !EXCEPTIONS.includes(seat.label))) === null || _b === void 0 ? void 0 : _b.sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
128
135
  const discountValue = (() => {
129
136
  if ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_type) === "percentage" &&
130
137
  typeof (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_value) === "number") {
@@ -147,14 +154,14 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
147
154
  };
148
155
  const originalDpPrice = getMinValue(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.original_dp_price);
149
156
  const dpDiscountPercent = getMinValue(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents);
150
- const firstSeatFare = (_f = (_e = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label))) === null || _e === void 0 ? void 0 : _e.sort((a, b) => a.fare - b.fare)[0]) === null || _f === void 0 ? void 0 : _f.fare;
157
+ const firstSeatFare = (_d = (_c = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label))) === null || _c === void 0 ? void 0 : _c.sort((a, b) => a.fare - b.fare)[0]) === null || _d === void 0 ? void 0 : _d.fare;
151
158
  const hasDpDiscount = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) &&
152
159
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) &&
153
160
  originalDpPrice != null &&
154
161
  dpDiscountPercent != null &&
155
162
  firstSeatFare != null;
156
163
  return (React.createElement("div", { className: "content-center relative", style: { width: "40%" } }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) &&
157
- !((_g = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) === null || _g === void 0 ? void 0 : _g.length) &&
164
+ !((_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) === null || _e === void 0 ? void 0 : _e.length) &&
158
165
  !dpDiscountPercent ? (React.createElement("div", { className: "flex flex-col justify-between h-[2.5rem]", style: { gap: isSoldOut ? "0px" : "5px" } }, renderDpSeats())) : hasDpDiscount ? (React.createElement("div", { className: "relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] " },
159
166
  !isNaN(Number(dpDiscountPercent)) &&
160
167
  Number(dpDiscountPercent) > 0 && (React.createElement("div", { className: "absolute -top-[18px] right-[0px]", style: {
@@ -186,7 +193,7 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
186
193
  React.createElement("span", { className: "flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]", style: {
187
194
  color: isSoldOut ? "#bbb" : discountSeatPriceColor || "#ff5964",
188
195
  } },
189
- ((_h = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _h === void 0 ? void 0 : _h.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
196
+ ((_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
190
197
  commonService.discountedCurrency(Number(firstSeatFare), currencySign)),
191
198
  isSoldOut ? (React.createElement("span", { className: "col-span-2 min-[420]:text-[13px] text-right text-[12px] text-[#ccc]" }, "Agotado")) : null)) : hasDiscount && discountSeat ? (React.createElement("div", null,
192
199
  React.createElement("div", { className: "relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] " },
@@ -220,8 +227,8 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
220
227
  React.createElement("span", { className: "flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]", style: {
221
228
  color: isSoldOut ? "#bbb" : discountSeatPriceColor || "#ff5964",
222
229
  } },
223
- ((_j = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _j === void 0 ? void 0 : _j.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { opacity: isSoldOut ? 0.5 : 1 } })) : null,
224
- commonService.discountedCurrency(discountSeat.discountedPrice, currencySign))))) : (React.createElement("div", { className: "flex flex-col justify-between h-[2.5rem] ", style: {
230
+ ((_g = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _g === void 0 ? void 0 : _g.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { opacity: isSoldOut ? 0.5 : 1 } })) : null,
231
+ commonService.discountedCurrency(discountSeat.discountedPrice, currencySign))))) : (React.createElement("div", { className: `flex flex-col justify-between ${isTrain ? "" : "h-[2.5rem]"} `, style: {
225
232
  gap: isSoldOut ? "0px" : "5px",
226
233
  justifyContent: hasMultipleTypes ? "space-between" : "center",
227
234
  } },
@@ -3,7 +3,7 @@ declare const commonService: {
3
3
  discountedCurrency(amount: number, currencySign?: string): string;
4
4
  copyObject: (ob: any) => any;
5
5
  getServiceTypeLabelForFilters: (service_type: any) => "Tipo de servicio" | "Punto de embarque" | "Tipo de asiento" | "SERVICIOS" | "";
6
- truncateSeatLabel: (label: string | number) => string;
6
+ truncateSeatLabel: (label: string | number, maxLength?: number) => string;
7
7
  getAmenitiesImage: (name: string, serviceItem: any) => string;
8
8
  getAmenityName: (rawAmenity: string) => string;
9
9
  getSeatNameForFilters: (rawSeat: any) => any;
@@ -34,11 +34,15 @@ const commonService = {
34
34
  return "";
35
35
  }
36
36
  },
37
- truncateSeatLabel: (label) => {
37
+ truncateSeatLabel: (label, maxLength) => {
38
38
  if (typeof label !== "string")
39
39
  return String(label);
40
40
  if (label.includes("("))
41
41
  return label;
42
+ // If maxLength provided, hard-truncate regardless of word count
43
+ if (maxLength != null && label.length > maxLength) {
44
+ return label.slice(0, maxLength) + "...";
45
+ }
42
46
  const words = label.trim().split(/\s+/);
43
47
  const truncateWord = (word) => word.length > 5 ? word.slice(0, 3) + "..." : word;
44
48
  if (words.length === 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.9.10",
3
+ "version": "9.10.0",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -131,6 +131,13 @@ function ServiceItemPB({
131
131
  showLoginModal,
132
132
  isLoggedIn,
133
133
  showLoginOption,
134
+ isTrain,
135
+ selectedSeatKey,
136
+ onSeatSelect,
137
+ onTrainButtonClick,
138
+ showSeatSelectionError,
139
+ onShowSeatSelectionError,
140
+ onClearSeatSelectionError,
134
141
  isFeatureDropDownExpand,
135
142
  setIsFeatureDropDownExpand,
136
143
  ticketQuantity,
@@ -144,6 +151,15 @@ function ServiceItemPB({
144
151
  wowDealData,
145
152
  isFlores,
146
153
  }: ServiceItemProps & { currencySign?: string }): React.ReactElement {
154
+ const handleSeatSelect = (
155
+ key: any,
156
+ price: number,
157
+ seatKey: string,
158
+ apiSeatType?: string,
159
+ ) => {
160
+ onClearSeatSelectionError?.();
161
+ onSeatSelect?.(key, price, seatKey, apiSeatType);
162
+ };
147
163
  const getAnimationIcon = (icon: string) => {
148
164
  const animation = ANIMATION_MAP[icon];
149
165
  if (!animation) return null;
@@ -344,6 +360,17 @@ function ServiceItemPB({
344
360
  return;
345
361
  }
346
362
 
363
+ if (isTrain) {
364
+ if (!selectedSeatKey) {
365
+ onShowSeatSelectionError?.(serviceItem.id);
366
+ return;
367
+ }
368
+ if (onTrainButtonClick) {
369
+ onTrainButtonClick();
370
+ return;
371
+ }
372
+ }
373
+
347
374
  onBookButtonPress();
348
375
  };
349
376
 
@@ -597,6 +624,10 @@ function ServiceItemPB({
597
624
  isPeru={isPeru}
598
625
  renderIcon={renderIcon}
599
626
  discountSeatPriceColor={colors.discountSeatPriceColor}
627
+ isTrain={isTrain}
628
+ selectedSeatKey={selectedSeatKey}
629
+ onSeatSelect={handleSeatSelect}
630
+ topLabelColor={colors.topLabelColor}
600
631
  />
601
632
  </div>
602
633
 
@@ -612,6 +643,18 @@ function ServiceItemPB({
612
643
  soldOutIcon={renderIcon("soldOutIcon", "14px")}
613
644
  onClick={checkMidnight}
614
645
  />
646
+ {showSeatSelectionError === serviceItem.id && isTrain && (
647
+ <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]">
648
+ <div
649
+ className="text-[9px] text-center whitespace-nowrap"
650
+ style={{
651
+ color: colors.seatPriceColor,
652
+ }}
653
+ >
654
+ Selecciona el tipo de servicio
655
+ </div>
656
+ </div>
657
+ )}
615
658
  {showLastSeats ? (
616
659
  <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]">
617
660
  {serviceItem?.available_seats < 10 &&
@@ -46,6 +46,8 @@ function ServiceItemMobile({
46
46
  removeDuplicateSeats,
47
47
  isLinatal,
48
48
  viewersConfig,
49
+ operatorLabel,
50
+ isTrain,
49
51
  isFeatureDropDownExpand,
50
52
  setIsFeatureDropDownExpand,
51
53
  ticketQuantity,
@@ -135,36 +135,36 @@ export interface MobileServiceItemProps {
135
135
  bombAnim?: string;
136
136
  whiteBoardingIcon?: string;
137
137
  downArrow?: string;
138
- personIcon?: string
138
+ personIcon?: string;
139
139
  specialDeparture?: string;
140
140
  fireIcon?: string;
141
141
  directoIcon?: string;
142
- whiteFireIcon?: string
143
- femaleAnim?:string
144
- thunderAnim?: string
145
- personsAnim?: string
146
- whiteOrigin?: string,
147
- whiteDestination?: string,
148
- userIcon?: string,
149
-
150
- sheildIcon?: string,
151
- busIcon?: string,
152
- whiteDownArrow?: string,
153
- empressaIcon?: string,
154
- flexibleIcon?: string,
155
- listoIcon?: string,
156
- precioIcon?: string,
157
- confirmarIcon?: string
142
+ whiteFireIcon?: string;
143
+ femaleAnim?: string;
144
+ thunderAnim?: string;
145
+ personsAnim?: string;
146
+ whiteOrigin?: string;
147
+ whiteDestination?: string;
148
+ userIcon?: string;
149
+
150
+ sheildIcon?: string;
151
+ busIcon?: string;
152
+ whiteDownArrow?: string;
153
+ empressaIcon?: string;
154
+ flexibleIcon?: string;
155
+ listoIcon?: string;
156
+ precioIcon?: string;
157
+ confirmarIcon?: string;
158
158
  cancelTicketIcon?: string;
159
159
  changeTicketIcon?: string;
160
160
  petFriendlyIcon?: string;
161
- womenSeatIcon?: string
161
+ womenSeatIcon?: string;
162
162
  [key: string]: string | Record<string, string | undefined> | undefined;
163
163
  };
164
164
  useLottieFor?: string[];
165
165
  };
166
166
  onBookButtonPress?: () => void;
167
- onRemateUiButtonClick?: ()=> void;
167
+ onRemateUiButtonClick?: () => void;
168
168
  terminals?: any[];
169
169
  showDropdown?: boolean;
170
170
  setShowDropdown?: (value: boolean) => void;
@@ -208,7 +208,7 @@ export interface MobileServiceItemProps {
208
208
  seatPriceColor?: string;
209
209
  rightGradiantColor?: string;
210
210
  leftGradiantColor?: string;
211
- discountSeatPriceColor?: string
211
+ discountSeatPriceColor?: string;
212
212
  };
213
213
  isCiva?: boolean;
214
214
  currencySign?: string;
@@ -221,23 +221,29 @@ export interface MobileServiceItemProps {
221
221
  showLastSeats?: boolean;
222
222
  removeDuplicateSeats?: boolean;
223
223
  isLinatal?: boolean;
224
- viewersConfig?: {
224
+ viewersConfig?: {
225
225
  min: number;
226
226
  max: number;
227
227
  interval?: number; // ms, default 5000
228
228
  label?: string; // e.g. "personas están viendo este viaje"
229
229
  icon?: string; // optional icon URL
230
230
  };
231
- isFeatureDropDownExpand?: any;
231
+ operatorLabel?: string;
232
+ isTrain?: boolean;
233
+ isFeatureDropDownExpand?: any;
232
234
  setIsFeatureDropDownExpand?: (value: any) => void;
233
235
  ticketQuantity?: number;
234
- onIncreaseTicketQuantity?: (serviceItem: MobileServiceItemProps["serviceItem"]) => void;
235
- onDecreaseTicketQuantity?: (serviceItem: MobileServiceItemProps["serviceItem"]) => void;
236
+ onIncreaseTicketQuantity?: (
237
+ serviceItem: MobileServiceItemProps["serviceItem"],
238
+ ) => void;
239
+ onDecreaseTicketQuantity?: (
240
+ serviceItem: MobileServiceItemProps["serviceItem"],
241
+ ) => void;
236
242
  cityOrigin?: { value: number; label: string };
237
243
  cityDestination?: { value: number; label: string };
238
- isNewUi?: boolean
244
+ isNewUi?: boolean;
239
245
 
240
- selectedTimeSlot?: string;
246
+ selectedTimeSlot?: string;
241
247
  onTimeSlotChange?: (slot: string) => void;
242
248
  isTimeDropdownOpen?: string | number | null;
243
249
  onTimeDropdownToggle?: (id?: string | number | null) => void;
@@ -252,6 +252,18 @@ export interface ServiceItemProps {
252
252
  showLoginModal?: any;
253
253
  isLoggedIn?: any;
254
254
  showLoginOption?: boolean;
255
+ isTrain?: boolean;
256
+ selectedSeatKey?: any;
257
+ onSeatSelect?: (
258
+ key: any,
259
+ price: number,
260
+ seatKey: string,
261
+ apiSeatType?: string,
262
+ ) => void;
263
+ onTrainButtonClick?: any;
264
+ showSeatSelectionError?: string | null;
265
+ onShowSeatSelectionError?: (serviceId: string) => void;
266
+ onClearSeatSelectionError?: () => void;
255
267
  selectedTimeSlot?: string;
256
268
  onTimeSlotChange?: (slot: string) => void;
257
269
  isTimeDropdownOpen?: string | number | null;
@@ -7,6 +7,8 @@ interface SeatType {
7
7
  label: string;
8
8
  fare: number;
9
9
  key: any;
10
+ apiSeatType?: string;
11
+ api_seat_type?: string;
10
12
  }
11
13
 
12
14
  interface SeatSectionProps {
@@ -21,6 +23,15 @@ interface SeatSectionProps {
21
23
  serviceItem?: any;
22
24
  renderIcon?: (iconKey: string, size?: string) => React.ReactNode;
23
25
  discountSeatPriceColor?: string;
26
+ isTrain?: boolean;
27
+ selectedSeatKey?: any;
28
+ onSeatSelect?: (
29
+ key: any,
30
+ price: number,
31
+ seatKey: string,
32
+ apiSeatType?: string,
33
+ ) => void;
34
+ topLabelColor?: string;
24
35
  }
25
36
 
26
37
  function getAllSeatTypes(seatTypes: SeatType[]) {
@@ -31,6 +42,8 @@ function getAllSeatTypes(seatTypes: SeatType[]) {
31
42
  let seatTypesWithPrices = seatTypes.filter(Boolean).map((val) => ({
32
43
  label: val?.label,
33
44
  price: val?.fare,
45
+ key: val?.key,
46
+ apiSeatType: val?.apiSeatType || val?.api_seat_type,
34
47
  }));
35
48
 
36
49
  seatTypesWithPrices.sort((a, b) => a.price - b.price);
@@ -38,7 +51,7 @@ function getAllSeatTypes(seatTypes: SeatType[]) {
38
51
  return seatTypesWithPrices;
39
52
  }
40
53
 
41
- function getSortedSeatTypes(seatTypes: SeatType[]) {
54
+ function getSortedSeatTypes(seatTypes: SeatType[], isTrain: any) {
42
55
  if (!seatTypes?.length) {
43
56
  return [{ label: "Salon cama", price: 0 }];
44
57
  }
@@ -52,7 +65,9 @@ function getSortedSeatTypes(seatTypes: SeatType[]) {
52
65
  seatTypesWithPrices[2] = seatTypesWithPrices[premiumIndex];
53
66
  }
54
67
 
55
- seatTypesWithPrices = seatTypesWithPrices.slice(0, 2);
68
+ if (!isTrain) {
69
+ seatTypesWithPrices = seatTypesWithPrices.slice(0, 2);
70
+ }
56
71
 
57
72
  const seenPrices = new Set<number>();
58
73
  seatTypesWithPrices = seatTypesWithPrices.filter((seat) => {
@@ -97,14 +112,18 @@ function SeatSection({
97
112
  priceColor,
98
113
  currencySign,
99
114
  removeDuplicateSeats,
115
+ selectedSeatKey,
116
+ onSeatSelect,
100
117
  isPeru,
101
118
  serviceItem,
102
119
  renderIcon,
103
120
  dpSeatColor,
104
121
  discountSeatPriceColor,
122
+ isTrain,
123
+ topLabelColor,
105
124
  }: SeatSectionProps): React.ReactElement {
106
125
  const uniqueSeats = getUniqueSeats(seatTypes);
107
- const sortedSeatTypes = getSortedSeatTypes(seatTypes);
126
+ const sortedSeatTypes = getSortedSeatTypes(seatTypes, isTrain);
108
127
  const numberOfSeats = getNumberOfSeats(seatTypes);
109
128
  const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
110
129
 
@@ -116,22 +135,71 @@ function SeatSection({
116
135
  const renderSeatNames = () => {
117
136
  const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
118
137
 
119
- return seats.map((val, key: number) =>
120
- SEAT_EXCEPTIONS.includes(val.label) ? null : (
121
- <span
122
- key={key}
123
- className={`flex items-center justify-between text-[13.33px] ${
124
- isSoldOut ? "text-[#c0c0c0]" : ""
125
- }`}
138
+ return seats.map((val, key: number) => {
139
+ return SEAT_EXCEPTIONS.includes(val.label) ? null : (
140
+ <div
141
+ className="flex items-center"
142
+ style={isTrain ? { cursor: "pointer" } : undefined}
143
+ onClick={
144
+ isTrain && !isSoldOut
145
+ ? () =>
146
+ val.label === selectedSeatKey
147
+ ? onSeatSelect?.(null, 0, "", "")
148
+ : onSeatSelect?.(
149
+ val.label,
150
+ val.price,
151
+ val.key,
152
+ (val as any).apiSeatType,
153
+ )
154
+ : undefined
155
+ }
126
156
  >
127
- {typeof val.label === "string" || typeof val.label === "number"
128
- ? removeDuplicateSeats && isPeru
129
- ? CommonService.truncateSeatLabel(val.label)
130
- : val.label
131
- : null}
132
- </span>
133
- ),
134
- );
157
+ {isTrain && (
158
+ <div
159
+ style={{
160
+ border: `1px solid ${val.label === selectedSeatKey ? topLabelColor : "#ccc"}`,
161
+ borderRadius: "50%",
162
+ width: "14px",
163
+ height: "14px",
164
+ minWidth: "14px",
165
+ marginRight: "10px",
166
+ display: "flex",
167
+ alignItems: "center",
168
+ justifyContent: "center",
169
+ }}
170
+ >
171
+ {val.label === selectedSeatKey && (
172
+ <div
173
+ style={{
174
+ backgroundColor: topLabelColor,
175
+ borderRadius: "50%",
176
+ width: "7px",
177
+ height: "7px",
178
+ }}
179
+ />
180
+ )}
181
+ </div>
182
+ )}
183
+ <span
184
+ key={key}
185
+ className={`flex items-center justify-between text-[13.33px] ${
186
+ isSoldOut ? "text-[#c0c0c0]" : ""
187
+ }`}
188
+ >
189
+ {typeof val.label === "string" || typeof val.label === "number"
190
+ ? removeDuplicateSeats && isPeru
191
+ ? CommonService.truncateSeatLabel(val.label)
192
+ : isTrain
193
+ ? CommonService.truncateSeatLabel(
194
+ CommonService.capitalize(String(val.label)),
195
+ 8,
196
+ )
197
+ : val.label
198
+ : null}
199
+ </span>
200
+ </div>
201
+ );
202
+ });
135
203
  };
136
204
 
137
205
  const renderSeatPrices = () => {
@@ -25,6 +25,7 @@ interface DateTimeSectionMobileProps {
25
25
  tooltipBgColor?: string;
26
26
  showLastSeats?: boolean;
27
27
  discountSeatPriceColor?: string;
28
+ isTrain?: boolean;
28
29
  }
29
30
 
30
31
  const pad = (n: number) => (n < 10 ? "0" + n : String(n));
@@ -67,41 +68,43 @@ const TimeRow: React.FC<TimeRowProps> = ({
67
68
  isSoldOut,
68
69
  }) => {
69
70
  const formattedDate = DateService.getServiceItemDate(date);
70
- const dotPositionClass = formattedDate.includes("dom") ? "max-[399px]:left-[53%]" : "";
71
- return <div
72
- className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
73
- isSoldOut ? "text-[#c0c0c0]" : ""
74
- }`}
75
- >
76
- <div className="flex items-center" style={{ flex: 1 }}>
77
- <div>
78
- {" "}
79
- {label ? (
80
- <div className="w-[60px]">{label}</div>
81
- ) : (
82
- <div className="w-[12px] h-auto mr-[5px]">
83
- <img
84
- src={icon}
85
- alt={alt}
86
- className={`w-[12px] h-auto mr-[5px] ${
87
- isSoldOut ? "grayscale" : ""
88
- }`}
89
- />
90
- </div>
91
- )}
92
- </div>
93
- <div
94
- className="flex items-center relative capitalize justify-between"
95
- style={{ flex: 1 }}
96
- >
97
- <span className="cursor-pointer black-text">
98
- {formattedDate}
99
- </span>
100
- <div className={`absolute left-[50%] ${dotPositionClass}`}>•</div>
101
- <div className="font-[900] relative black-text">{timeContent}</div>
71
+ const dotPositionClass = formattedDate.includes("dom")
72
+ ? "max-[399px]:left-[53%]"
73
+ : "";
74
+ return (
75
+ <div
76
+ className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
77
+ isSoldOut ? "text-[#c0c0c0]" : ""
78
+ }`}
79
+ >
80
+ <div className="flex items-center" style={{ flex: 1 }}>
81
+ <div>
82
+ {" "}
83
+ {label ? (
84
+ <div className="w-[60px]">{label}</div>
85
+ ) : (
86
+ <div className="w-[12px] h-auto mr-[5px]">
87
+ <img
88
+ src={icon}
89
+ alt={alt}
90
+ className={`w-[12px] h-auto mr-[5px] ${
91
+ isSoldOut ? "grayscale" : ""
92
+ }`}
93
+ />
94
+ </div>
95
+ )}
96
+ </div>
97
+ <div
98
+ className="flex items-center relative capitalize justify-between"
99
+ style={{ flex: 1 }}
100
+ >
101
+ <span className="cursor-pointer black-text">{formattedDate}</span>
102
+ <div className={`absolute left-[50%] ${dotPositionClass}`}>•</div>
103
+ <div className="font-[900] relative black-text">{timeContent}</div>
104
+ </div>
102
105
  </div>
103
106
  </div>
104
- </div>;
107
+ );
105
108
  };
106
109
 
107
110
  function DateTimeSectionMobile({
@@ -127,6 +130,7 @@ function DateTimeSectionMobile({
127
130
  tooltipBgColor,
128
131
  showLastSeats,
129
132
  discountSeatPriceColor,
133
+ isTrain,
130
134
  }: DateTimeSectionMobileProps): React.ReactElement {
131
135
  const { cleaned: cleanedDepTime, hasAM, hasPM } = getCleanedDepTime(depTime);
132
136
 
@@ -153,8 +157,12 @@ function DateTimeSectionMobile({
153
157
  >
154
158
  {/* DATE AND TIME */}
155
159
  <div
156
- className="min-h-[2.5rem] flex flex-col justify-between gap-[4px] w-[50%] "
157
- style={{ justifyContent: isCiva && "center" }}
160
+ className={`flex flex-col gap-[4px] w-[50%] ${isTrain ? "justify-center" : "justify-between"}`}
161
+ style={{
162
+ justifyContent: isCiva && "center",
163
+ minHeight: isTrain ? undefined : "2.5rem",
164
+ alignSelf: isTrain ? "stretch" : undefined,
165
+ }}
158
166
  >
159
167
  <TimeRow
160
168
  label={orignLabel}
@@ -198,6 +206,7 @@ function DateTimeSectionMobile({
198
206
  tooltipBgColor={tooltipBgColor}
199
207
  showLastSeats={showLastSeats}
200
208
  discountSeatPriceColor={discountSeatPriceColor}
209
+ isTrain={isTrain}
201
210
  />
202
211
  </div>
203
212
  );