kupos-ui-components-lib 9.6.2 → 9.6.4

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 (41) hide show
  1. package/dist/assets/images/anims/service_list/female_anim.json +1 -0
  2. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  3. package/dist/components/ServiceItem/ServiceItemDesktop.js +7 -3
  4. package/dist/components/ServiceItem/ServiceItemMobile.js +9 -6
  5. package/dist/components/ServiceItem/mobileTypes.d.ts +5 -0
  6. package/dist/components/ServiceItem/types.d.ts +7 -3
  7. package/dist/styles.css +2 -2
  8. package/dist/types.d.ts +1 -0
  9. package/dist/ui/BottomAmenities/BottomAmenities.d.ts +2 -0
  10. package/dist/ui/BottomAmenities/BottomAmenities.js +4 -0
  11. package/dist/ui/FemaleBlock.d.ts +9 -0
  12. package/dist/ui/FemaleBlock.js +19 -0
  13. package/dist/ui/OfferBanner.d.ts +2 -1
  14. package/dist/ui/OfferBanner.js +17 -6
  15. package/dist/ui/SeatSection/SeatSection.d.ts +2 -1
  16. package/dist/ui/SeatSection/SeatSection.js +33 -15
  17. package/dist/ui/mobileweb/BottomAmenitiesMobile.d.ts +4 -1
  18. package/dist/ui/mobileweb/BottomAmenitiesMobile.js +8 -1
  19. package/dist/ui/mobileweb/DateTimeSectionMobile.d.ts +2 -1
  20. package/dist/ui/mobileweb/DateTimeSectionMobile.js +2 -2
  21. package/dist/ui/mobileweb/ExpandedDropdownMobile.d.ts +3 -1
  22. package/dist/ui/mobileweb/ExpandedDropdownMobile.js +8 -1
  23. package/dist/ui/mobileweb/SeatSectionMobile.d.ts +2 -1
  24. package/dist/ui/mobileweb/SeatSectionMobile.js +30 -18
  25. package/dist/utils/CommonService.js +4 -2
  26. package/package.json +1 -1
  27. package/src/assets/images/anims/service_list/female_anim.json +1 -0
  28. package/src/components/ServiceItem/ServiceItemDesktop.tsx +7 -0
  29. package/src/components/ServiceItem/ServiceItemMobile.tsx +11 -71
  30. package/src/components/ServiceItem/mobileTypes.ts +5 -0
  31. package/src/components/ServiceItem/types.ts +7 -3
  32. package/src/types.ts +1 -0
  33. package/src/ui/BottomAmenities/BottomAmenities.tsx +14 -0
  34. package/src/ui/FemaleBlock.tsx +45 -0
  35. package/src/ui/OfferBanner.tsx +24 -6
  36. package/src/ui/SeatSection/SeatSection.tsx +38 -14
  37. package/src/ui/mobileweb/BottomAmenitiesMobile.tsx +27 -0
  38. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +3 -0
  39. package/src/ui/mobileweb/ExpandedDropdownMobile.tsx +15 -0
  40. package/src/ui/mobileweb/SeatSectionMobile.tsx +41 -31
  41. package/src/utils/CommonService.ts +7 -2
@@ -20,6 +20,7 @@ interface SeatSectionProps {
20
20
  isPeru?: boolean;
21
21
  serviceItem?: any;
22
22
  renderIcon?: (iconKey: string, size?: string) => React.ReactNode;
23
+ discountSeatPriceColor?: string;
23
24
  }
24
25
 
25
26
  function getAllSeatTypes(seatTypes: SeatType[]) {
@@ -100,6 +101,7 @@ function SeatSection({
100
101
  serviceItem,
101
102
  renderIcon,
102
103
  dpSeatColor,
104
+ discountSeatPriceColor,
103
105
  }: SeatSectionProps): React.ReactElement {
104
106
  const uniqueSeats = getUniqueSeats(seatTypes);
105
107
  const sortedSeatTypes = getSortedSeatTypes(seatTypes);
@@ -193,11 +195,23 @@ function SeatSection({
193
195
  .filter((seat) => !SEAT_EXCEPTIONS.includes(seat.label))
194
196
  .sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
195
197
 
196
- const discountValue =
197
- serviceItem?.discount_type === "percentage" &&
198
- typeof serviceItem?.discount_value === "number"
199
- ? Math.round(serviceItem.discount_value)
200
- : null;
198
+ const discountValue = (() => {
199
+ if (
200
+ serviceItem?.discount_type === "percentage" &&
201
+ typeof serviceItem?.discount_value === "number"
202
+ ) {
203
+ return Math.round(serviceItem.discount_value);
204
+ }
205
+ if (serviceItem?.discount_type === "fixed" && discountSeat) {
206
+ const { originalPrice, discountedPrice } = discountSeat;
207
+ if (originalPrice > 0 && originalPrice !== discountedPrice) {
208
+ return Math.round(
209
+ ((originalPrice - discountedPrice) / originalPrice) * 100,
210
+ );
211
+ }
212
+ }
213
+ return null;
214
+ })();
201
215
 
202
216
  const renderLabels = () => {
203
217
  if (isPeru) {
@@ -227,11 +241,15 @@ function SeatSection({
227
241
  // ).find(([, percent]) => Number(percent));
228
242
  const dpDiscountEntry = Object.entries(
229
243
  serviceItem?.dp_discount_percents || {},
230
- )[0];
244
+ ).sort(([, a], [, b]) => Number(a) - Number(b))[0];
231
245
  const dpDiscountPercent = dpDiscountEntry?.[1];
232
- const getFirstDpValue = (source: any) => {
246
+ const getLowestDpValue = (source: any): number | undefined => {
233
247
  if (!source) return undefined;
234
- return Array.isArray(source) ? source[0] : Object.values(source)[0];
248
+ const values: any[] = Array.isArray(source)
249
+ ? source
250
+ : Object.values(source);
251
+ const nums = values.map(Number).filter((n) => !isNaN(n) && n > 0);
252
+ return nums.length > 0 ? Math.min(...nums) : undefined;
235
253
  };
236
254
 
237
255
  const renderDpDiscountUi = (
@@ -301,7 +319,7 @@ function SeatSection({
301
319
  >
302
320
  <div
303
321
  className="absolute"
304
- style={{ left: isPeru ? "-1px" : "-8px" }}
322
+ style={{ left: isPeru ? "-1px" : "-19px", bottom: "1px" }}
305
323
  >
306
324
  {renderIcon("fireIcon", "16px")}
307
325
  </div>
@@ -339,7 +357,7 @@ function SeatSection({
339
357
  </div>
340
358
  <div>
341
359
  <div className="relative">
342
- <div className="absolute -left-[20px] top-1/2 transform -translate-y-1/2">
360
+ <div className="absolute -left-[20px] top-1/3 transform -translate-y-1/2">
343
361
  {renderIcon("fireIcon", "16px")}
344
362
  </div>
345
363
  <div
@@ -375,8 +393,12 @@ function SeatSection({
375
393
  }
376
394
 
377
395
  if (dpDiscountEntry) {
378
- const originalDpPrice = getFirstDpValue(serviceItem.original_dp_price);
379
- const seatTypeFare = serviceItem.seat_types?.[0]?.fare ?? originalDpPrice;
396
+ const originalDpPrice = getLowestDpValue(serviceItem.original_dp_price);
397
+ const allFares = (serviceItem.seat_types ?? [])
398
+ .map((st: any) => st.fare)
399
+ .filter((f: any) => f != null && !isNaN(Number(f)));
400
+ const seatTypeFare =
401
+ allFares.length > 0 ? Math.min(...allFares) : originalDpPrice;
380
402
 
381
403
  if (originalDpPrice && seatTypeFare) {
382
404
  return renderDpDiscountUi(originalDpPrice, seatTypeFare);
@@ -411,6 +433,7 @@ function SeatSection({
411
433
  style={{
412
434
  animation: "pulse-zoom 2s ease-in-out infinite",
413
435
  whiteSpace: "nowrap",
436
+ backgroundColor: discountSeatPriceColor,
414
437
  }}
415
438
  >
416
439
  {discountValue}% OFF
@@ -447,13 +470,14 @@ function SeatSection({
447
470
  <div className="col-start-2 row-start-3 flex h-[30px] items-end justify-center relative">
448
471
  <span
449
472
  className="flex items-center gap-[6px] text-[22px] bold-text leading-[30px]"
450
- style={{ color: isSoldOut ? "#c0c0c0" : "#ff5964" }}
473
+ style={{ color: isSoldOut ? "#c0c0c0" : discountSeatPriceColor }}
451
474
  >
452
475
  {/* <span className="text-[18px] leading-[24px]">🔥</span> */}
453
476
  <div
454
477
  className="absolute"
455
478
  style={{
456
- left: isPeru ? "-1px" : "-8px",
479
+ left: isPeru ? "-1px" : "-18px",
480
+ bottom:"1px"
457
481
  }}
458
482
  >
459
483
  {renderIcon("fireIcon", "16px")}
@@ -22,6 +22,9 @@ interface BottomAmenitiesMobileProps {
22
22
  onDropdownToggle: () => void;
23
23
  isItemExpanded?: boolean;
24
24
  isPeru?: boolean;
25
+ femaleAnim?: any;
26
+ ladiesBookedSeats?: string;
27
+ isDpEnabled?: boolean;
25
28
  }
26
29
 
27
30
  function BottomAmenitiesMobile({
@@ -45,6 +48,9 @@ function BottomAmenitiesMobile({
45
48
  onDropdownToggle,
46
49
  isItemExpanded,
47
50
  isPeru,
51
+ femaleAnim,
52
+ ladiesBookedSeats,
53
+ isDpEnabled,
48
54
  }: BottomAmenitiesMobileProps): React.ReactElement {
49
55
  return (
50
56
  <div className={`${"flex justify-between items-center items-center "}`}>
@@ -132,6 +138,27 @@ function BottomAmenitiesMobile({
132
138
  </div>
133
139
  )}
134
140
 
141
+ {/* Flexible ticket */}
142
+ {ladiesBookedSeats &&
143
+ String(ladiesBookedSeats).trim() !== "" &&
144
+ isDpEnabled === true && (
145
+ <div className="flex items-center">
146
+ <div className="relative group cursor-default">
147
+ <div className="flex items-center">
148
+ <div
149
+ className={`mr-[5px] ${isSoldOut ? "grayscale" : ""}`}
150
+ >
151
+ <LottiePlayer
152
+ animationData={femaleAnim}
153
+ width="16px"
154
+ height="16px"
155
+ />
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ )}
161
+
135
162
  {isTrackingEnabled && (
136
163
  <div className="flex items-center mr-[10px]">
137
164
  <div
@@ -24,6 +24,7 @@ interface DateTimeSectionMobileProps {
24
24
  serviceItem?: any;
25
25
  tooltipBgColor?: string;
26
26
  showLastSeats?: boolean;
27
+ discountSeatPriceColor?: string;
27
28
  }
28
29
 
29
30
  const pad = (n: number) => (n < 10 ? "0" + n : String(n));
@@ -123,6 +124,7 @@ function DateTimeSectionMobile({
123
124
  serviceItem,
124
125
  tooltipBgColor,
125
126
  showLastSeats,
127
+ discountSeatPriceColor,
126
128
  }: DateTimeSectionMobileProps): React.ReactElement {
127
129
  const { cleaned: cleanedDepTime, hasAM, hasPM } = getCleanedDepTime(depTime);
128
130
 
@@ -193,6 +195,7 @@ function DateTimeSectionMobile({
193
195
  serviceItem={serviceItem}
194
196
  tooltipBgColor={tooltipBgColor}
195
197
  showLastSeats={showLastSeats}
198
+ discountSeatPriceColor={discountSeatPriceColor}
196
199
  />
197
200
  </div>
198
201
  );
@@ -13,6 +13,8 @@ interface ExpandedDropdownMobileProps {
13
13
  petFriendlyAnim?: any;
14
14
  isSoldOut?: boolean;
15
15
  isChangeTicket?: boolean;
16
+ ladiesBookedSeats?: string;
17
+ isDpEnabled?: boolean;
16
18
  }
17
19
 
18
20
  function ExpandedDropdownMobile({
@@ -22,6 +24,8 @@ function ExpandedDropdownMobile({
22
24
  petFriendlyAnim,
23
25
  isSoldOut,
24
26
  isChangeTicket = false,
27
+ ladiesBookedSeats,
28
+ isDpEnabled,
25
29
  }: ExpandedDropdownMobileProps): React.ReactElement {
26
30
  return (
27
31
  <div
@@ -60,6 +64,17 @@ function ExpandedDropdownMobile({
60
64
  </span>
61
65
  </div>
62
66
  )}
67
+ {ladiesBookedSeats &&
68
+ String(ladiesBookedSeats).trim() !== "" &&
69
+ isDpEnabled === true && (
70
+ <div className="flex gap-[6px]">
71
+ <span style={{ marginTop: "2px" }}>•</span>
72
+ <span>
73
+ <span className="bold-text">Asientos para damas:</span> Esta
74
+ empresa cuenta con asientos recomendados para mujeres.
75
+ </span>
76
+ </div>
77
+ )}
63
78
  {petSeatInfo && Object.keys(petSeatInfo).length > 0 ? (
64
79
  <div className="flex items-center">
65
80
  <div className={`relative group cursor-default `}>
@@ -30,6 +30,7 @@ interface SeatSectionMobileProps {
30
30
  serviceItem?: any;
31
31
  tooltipBgColor?: string;
32
32
  showLastSeats?: boolean;
33
+ discountSeatPriceColor?: string;
33
34
  }
34
35
 
35
36
  interface SeatRowProps {
@@ -116,6 +117,7 @@ function SeatSectionMobile({
116
117
  serviceItem,
117
118
  tooltipBgColor,
118
119
  showLastSeats,
120
+ discountSeatPriceColor,
119
121
  }: SeatSectionMobileProps): React.ReactElement {
120
122
  const hasMultipleTypes = (seatTypesData?.length ?? 0) > 2;
121
123
 
@@ -243,21 +245,6 @@ function SeatSectionMobile({
243
245
  {commonService.currency(discountedPrice, currencySign)}
244
246
  </span>
245
247
  </div>
246
- {showLastSeats ? (
247
- <div className="flex justify-end">
248
- {serviceItem?.available_seats < 10 &&
249
- serviceItem?.available_seats > 0 && (
250
- <div
251
- className="text-[10px] text-center mt-[3px]"
252
- style={{
253
- color: tooltipBgColor,
254
- }}
255
- >
256
- ¡Últimos Asientos!
257
- </div>
258
- )}
259
- </div>
260
- ) : null}
261
248
  {isSoldOut ? (
262
249
  <div className="flex justify-end">
263
250
  <span
@@ -343,21 +330,40 @@ function SeatSectionMobile({
343
330
  ?.filter((seat) => !EXCEPTIONS.includes(seat.label))
344
331
  ?.sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
345
332
 
346
- const discountValue =
347
- serviceItem?.discount_type === "percentage" &&
348
- typeof serviceItem?.discount_value === "number"
349
- ? Math.round(serviceItem.discount_value)
350
- : null;
333
+ const discountValue = (() => {
334
+ if (
335
+ serviceItem?.discount_type === "percentage" &&
336
+ typeof serviceItem?.discount_value === "number"
337
+ ) {
338
+ return Math.round(serviceItem.discount_value);
339
+ }
340
+ if (serviceItem?.discount_type === "fixed" && discountSeat) {
341
+ const { originalPrice, discountedPrice } = discountSeat;
342
+ if (originalPrice > 0 && originalPrice !== discountedPrice) {
343
+ return Math.round(
344
+ ((originalPrice - discountedPrice) / originalPrice) * 100,
345
+ );
346
+ }
347
+ }
348
+ return null;
349
+ })();
351
350
 
352
- const originalDpPrice = Array.isArray(serviceItem?.original_dp_price)
353
- ? serviceItem.original_dp_price[0]
354
- : Object.values(serviceItem?.original_dp_price || {})[0];
355
- const dpDiscountPercent = Array.isArray(serviceItem?.dp_discount_percents)
356
- ? serviceItem.dp_discount_percents[0]
357
- : Object.values(serviceItem?.dp_discount_percents || {})[0];
358
- const firstSeatFare = seatTypesData?.filter(
359
- (item) => getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label),
360
- )?.[0]?.fare;
351
+ const getMinValue = (data: any): number | undefined => {
352
+ const vals = (Array.isArray(data) ? data : Object.values(data || {})).map(
353
+ Number,
354
+ );
355
+ if (!vals.length) return undefined;
356
+ const min = Math.min(...vals);
357
+ return isFinite(min) ? min : undefined;
358
+ };
359
+ const originalDpPrice = getMinValue(serviceItem?.original_dp_price);
360
+ const dpDiscountPercent = getMinValue(serviceItem?.dp_discount_percents);
361
+ const firstSeatFare = seatTypesData
362
+ ?.filter(
363
+ (item) =>
364
+ getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label),
365
+ )
366
+ ?.sort((a, b) => a.fare - b.fare)[0]?.fare;
361
367
 
362
368
  const hasDpDiscount =
363
369
  serviceItem?.dp_discounted_seats &&
@@ -432,7 +438,9 @@ function SeatSectionMobile({
432
438
  </span>
433
439
  <span
434
440
  className="flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]"
435
- style={{ color: isSoldOut ? "#bbb" : "#ff5964" }}
441
+ style={{
442
+ color: isSoldOut ? "#bbb" : discountSeatPriceColor || "#ff5964",
443
+ }}
436
444
  >
437
445
  {serviceItem?.icons?.fireIcon ? (
438
446
  <img
@@ -510,7 +518,9 @@ function SeatSectionMobile({
510
518
  </span>
511
519
  <span
512
520
  className="flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]"
513
- style={{ color: isSoldOut ? "#bbb" : "#ff5964" }}
521
+ style={{
522
+ color: isSoldOut ? "#bbb" : discountSeatPriceColor || "#ff5964",
523
+ }}
514
524
  >
515
525
  {serviceItem?.icons?.fireIcon ? (
516
526
  <img
@@ -303,15 +303,20 @@ const commonService = {
303
303
 
304
304
  const { discount_type, discount_value, max_discount } = serviceItem;
305
305
 
306
+ const fixedDiscount =
307
+ discount_type === "fixed" && discount_value != null ? discount_value : 0;
308
+
306
309
  const percentageDiscount =
307
310
  discount_type === "percentage" && discount_value != null
308
311
  ? (price * discount_value) / 100
309
312
  : 0;
310
313
 
314
+ const rawDiscount = fixedDiscount || percentageDiscount;
315
+
311
316
  const finalDiscount =
312
317
  max_discount != null && max_discount > 0
313
- ? Math.min(percentageDiscount, max_discount)
314
- : percentageDiscount;
318
+ ? Math.min(rawDiscount, max_discount)
319
+ : rawDiscount;
315
320
 
316
321
  const discountedPrice = Math.max(0, price - finalDiscount);
317
322