kupos-ui-components-lib 10.4.62 → 10.4.64

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 (39) hide show
  1. package/dist/components/FilterBar/FilterBarDesktop.js +10 -0
  2. package/dist/components/FilterBar/ServiceFilter.d.ts +1 -0
  3. package/dist/components/FilterBar/ServiceFilter.js +10 -4
  4. package/dist/components/ServiceItem/PeruServiceItemDesktop.js +3 -1
  5. package/dist/components/ServiceItem/PeruServiceItemMobile.js +5 -3
  6. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  7. package/dist/components/ServiceItem/ServiceItemDesktop.js +7 -5
  8. package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
  9. package/dist/components/ServiceItem/ServiceItemMobile.js +14 -16
  10. package/dist/components/ServiceItem/mobileTypes.d.ts +3 -0
  11. package/dist/components/ServiceItem/types.d.ts +3 -0
  12. package/dist/styles.css +3 -0
  13. package/dist/ui/OfferBanner.d.ts +1 -1
  14. package/dist/ui/OfferBanner.js +9 -5
  15. package/dist/ui/PeruOfferBanner.d.ts +1 -1
  16. package/dist/ui/PeruOfferBanner.js +5 -1
  17. package/dist/ui/RatingBlock.d.ts +1 -1
  18. package/dist/ui/RatingBlock.js +7 -3
  19. package/dist/ui/SeatSection/SeatSection.js +28 -13
  20. package/dist/ui/ServiceBadges/ServiceBadges.js +1 -1
  21. package/dist/ui/mobileweb/SeatSectionMobile.js +10 -5
  22. package/dist/utils/CommonService.d.ts +7 -0
  23. package/dist/utils/CommonService.js +6 -0
  24. package/package.json +1 -1
  25. package/src/components/FilterBar/FilterBarDesktop.tsx +16 -0
  26. package/src/components/FilterBar/ServiceFilter.tsx +12 -2
  27. package/src/components/ServiceItem/PeruServiceItemDesktop.tsx +4 -1
  28. package/src/components/ServiceItem/PeruServiceItemMobile.tsx +6 -3
  29. package/src/components/ServiceItem/ServiceItemDesktop.tsx +9 -3
  30. package/src/components/ServiceItem/ServiceItemMobile.tsx +25 -14
  31. package/src/components/ServiceItem/mobileTypes.ts +3 -0
  32. package/src/components/ServiceItem/types.ts +3 -0
  33. package/src/ui/OfferBanner.tsx +10 -5
  34. package/src/ui/PeruOfferBanner.tsx +7 -1
  35. package/src/ui/RatingBlock.tsx +7 -2
  36. package/src/ui/SeatSection/SeatSection.tsx +53 -29
  37. package/src/ui/ServiceBadges/ServiceBadges.tsx +1 -1
  38. package/src/ui/mobileweb/SeatSectionMobile.tsx +30 -20
  39. package/src/utils/CommonService.ts +13 -0
@@ -119,6 +119,13 @@ function getUniqueSeats(seatTypes: SeatType[], isTrain?: boolean) {
119
119
  });
120
120
  }
121
121
 
122
+ // Matches "160°" as well as a bare "160" label
123
+ function hasInclinationSeatTypes(seatTypes: SeatType[]) {
124
+ return (seatTypes ?? []).some((seat) =>
125
+ /\b(120|140|160|180)\b/.test(String(seat?.label ?? "")),
126
+ );
127
+ }
128
+
122
129
  function getNumberOfSeats(seatTypes: SeatType[]) {
123
130
  return seatTypes.filter((val) => !SEAT_EXCEPTIONS.includes(val.label)).length;
124
131
  }
@@ -169,6 +176,14 @@ function SeatSection({
169
176
  const uniqueSeats = getUniqueSeats(seatTypes, isTrain);
170
177
  const sortedSeatTypes = getSortedSeatTypes(seatTypes, isTrain);
171
178
  const numberOfSeats = getNumberOfSeats(seatTypes);
179
+ const isMovilBus =
180
+ serviceItem?.operator_service_name === "MovilBus" ||
181
+ serviceItem?.operator_service_name === "Movil Bus";
182
+ // Peru: list every seat type with its own fare (MovilBus, or inclination
183
+ // seats like 160° / 180°) instead of only the cheapest one.
184
+ const showSeatTypeRows =
185
+ (isMovilBus || hasInclinationSeatTypes(seatTypes)) &&
186
+ uniqueSeats.length > 1;
172
187
  const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
173
188
 
174
189
  const formatPrice = (price: number) =>
@@ -256,12 +271,8 @@ function SeatSection({
256
271
 
257
272
  const renderSeatPrices = () => {
258
273
  if (isPeru) {
259
- const isMovilBus =
260
- serviceItem?.operator_service_name === "MovilBus" ||
261
- serviceItem?.operator_service_name === "Movil Bus";
262
-
263
- // Multiple unique seat types → show a price row for each (MovilBus only)
264
- if (isMovilBus && uniqueSeats.length > 1) {
274
+ // Multiple unique seat types → show a price row for each
275
+ if (showSeatTypeRows) {
265
276
  return uniqueSeats
266
277
  .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
267
278
  .map((val, key) => {
@@ -378,12 +389,8 @@ function SeatSection({
378
389
 
379
390
  const renderLabels = () => {
380
391
  if (isPeru) {
381
- const isMovilBus =
382
- serviceItem?.operator_service_name === "MovilBus" ||
383
- serviceItem?.operator_service_name === "Movil Bus";
384
-
385
- // Multiple unique seat types → show a label row for each (MovilBus only)
386
- if (isMovilBus && uniqueSeats.length > 1) {
392
+ // Multiple unique seat types → show a label row for each
393
+ if (showSeatTypeRows) {
387
394
  return uniqueSeats
388
395
  .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
389
396
  .map((val, key) => (
@@ -404,7 +411,20 @@ function SeatSection({
404
411
  }}
405
412
  />
406
413
  )}
407
- {CommonService.truncateSeatLabel(val.label)}
414
+ {!isMovilBus && isShowSeatIcon && (
415
+ <img
416
+ src={getSeatLabelIcon(val.label, serviceItem)}
417
+ alt="seattype"
418
+ style={{
419
+ width: "20px",
420
+ height: "20px",
421
+ marginRight: "5px",
422
+ }}
423
+ />
424
+ )}
425
+ {isMovilBus
426
+ ? CommonService.truncateSeatLabel(val.label)
427
+ : CommonService.capitalize(addSeatDegree(val.label))}
408
428
  </span>
409
429
  ));
410
430
  }
@@ -496,6 +516,8 @@ function SeatSection({
496
516
  serviceItem?.dp_discount_percents || {},
497
517
  ).sort(([, a], [, b]) => Number(a) - Number(b))[0];
498
518
  const dpDiscountPercent = dpDiscountEntry?.[1];
519
+ // show_dp_ui: false falls through to the regular fare layout
520
+ const isDpUiHidden = CommonService.isDpUiHidden(serviceItem);
499
521
  const getLowestDpValue = (source: any): number | undefined => {
500
522
  if (!source) return undefined;
501
523
  const values: any[] = Array.isArray(source)
@@ -591,7 +613,7 @@ function SeatSection({
591
613
  );
592
614
  };
593
615
 
594
- if (serviceItem?.is_dp_enabled && !dpDiscountEntry) {
616
+ if (serviceItem?.is_dp_enabled && !isDpUiHidden && !dpDiscountEntry) {
595
617
  const dpSeats = (
596
618
  removeDuplicateSeats ? uniqueSeats : sortedSeatTypes
597
619
  ).filter((s) => !SEAT_EXCEPTIONS.includes(s.label));
@@ -616,25 +638,27 @@ function SeatSection({
616
638
  <div className="absolute -left-[20px] transform -translate-y-1/2 top-[16px]">
617
639
  {renderIcon("fireIcon", "16px")}
618
640
  </div>
619
- <div
620
- className="absolute bottom-[35px] left-1/2 -translate-x-1/2 "
621
- style={{
622
- animation: "pulse-zoom 2s ease-in-out infinite",
623
- whiteSpace: "nowrap",
624
- }}
625
- >
626
- <span
627
- className="rounded-[100px] px-[6px] px-[2px] w-fit text-[12px] bold-text leading-[20px] text-white"
641
+ {CommonService.showDpBestPrice(serviceItem) && (
642
+ <div
643
+ className="absolute bottom-[35px] left-1/2 -translate-x-1/2 "
628
644
  style={{
629
645
  animation: "pulse-zoom 2s ease-in-out infinite",
630
646
  whiteSpace: "nowrap",
631
- color: dpSeatColor,
632
- backgroundColor: tooltipColor,
633
647
  }}
634
648
  >
635
- Mejor precio
636
- </span>
637
- </div>
649
+ <span
650
+ className="rounded-[100px] px-[6px] px-[2px] w-fit text-[12px] bold-text leading-[20px] text-white"
651
+ style={{
652
+ animation: "pulse-zoom 2s ease-in-out infinite",
653
+ whiteSpace: "nowrap",
654
+ color: dpSeatColor,
655
+ backgroundColor: tooltipColor,
656
+ }}
657
+ >
658
+ Mejor precio
659
+ </span>
660
+ </div>
661
+ )}
638
662
  <div
639
663
  className="text-[22px] bold-text leading-[30px] text-center"
640
664
  style={{ color: isSoldOut ? "#c0c0c0" : dpSeatColor }}
@@ -649,7 +673,7 @@ function SeatSection({
649
673
  );
650
674
  }
651
675
 
652
- if (dpDiscountEntry) {
676
+ if (dpDiscountEntry && !isDpUiHidden) {
653
677
  const originalDpPrice = getLowestDpValue(serviceItem.original_dp_price);
654
678
  const allFares = (serviceItem.seat_types ?? [])
655
679
  .map((st: any) => st.fare)
@@ -32,7 +32,7 @@ const ServiceBadges: React.FC<ServiceBadgesProps> = ({
32
32
  getAnimationIcon,
33
33
  }) => {
34
34
  return (
35
- <div className="absolute -top-[13px] left-0 w-full flex items-center justify-end gap-[12px] pr-[22px] z-10">
35
+ <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[22px] z-10">
36
36
  {showTopLabel && (
37
37
  <div
38
38
  className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-10`}
@@ -112,8 +112,9 @@ function addSeatDegree(label: string | null) {
112
112
  }
113
113
 
114
114
  function hasInclinationSeatTypes(seatTypes: SeatType[]): boolean {
115
+ // Matches "160°" as well as a bare "160" label
115
116
  return (seatTypes ?? []).some((seat) =>
116
- String(seat?.label ?? "").includes("°"),
117
+ /\b(120|140|160|180)\b/.test(String(seat?.label ?? "")),
117
118
  );
118
119
  }
119
120
 
@@ -247,7 +248,9 @@ function SeatSectionMobile({
247
248
  }}
248
249
  />
249
250
  )}
250
- {commonService.truncateSeatLabel(seat.label)}
251
+ {commonService.truncateSeatLabel(
252
+ commonService.addSeatDegree(seat.label),
253
+ )}
251
254
  </span>
252
255
  <span
253
256
  className="min-[420]:text-[13px] text-[12px] bold-text"
@@ -357,27 +360,29 @@ function SeatSectionMobile({
357
360
  className="relative flex flex-col justify-center h-full"
358
361
  style={{ opacity: isSoldOut ? 0.5 : 1 }}
359
362
  >
360
- <div
361
- className="absolute right-[0px]"
362
- style={{
363
- animation: "pulse-zoom 2s ease-in-out infinite",
364
- top:
365
- (serviceItem?.available_seats < 10 &&
366
- serviceItem?.available_seats > 0) ||
367
- isSoldOut
368
- ? "-20px"
369
- : "-15px",
370
- }}
371
- >
372
- <span
373
- className="rounded-[100px] px-[8px] text-[11px] bold-text leading-[20px] text-[#fff]"
363
+ {commonService.showDpBestPrice(serviceItem) && (
364
+ <div
365
+ className="absolute right-[0px]"
374
366
  style={{
375
- backgroundColor: tooltipBgColor,
367
+ animation: "pulse-zoom 2s ease-in-out infinite",
368
+ top:
369
+ (serviceItem?.available_seats < 10 &&
370
+ serviceItem?.available_seats > 0) ||
371
+ isSoldOut
372
+ ? "-20px"
373
+ : "-15px",
376
374
  }}
377
375
  >
378
- Mejor precio
379
- </span>
380
- </div>
376
+ <span
377
+ className="rounded-[100px] px-[8px] text-[11px] bold-text leading-[20px] text-[#fff]"
378
+ style={{
379
+ backgroundColor: tooltipBgColor,
380
+ }}
381
+ >
382
+ Mejor precio
383
+ </span>
384
+ </div>
385
+ )}
381
386
  <div className="w-[100%] flex flex-row justify-between items-center">
382
387
  <span
383
388
  className="min-[420]:text-[13px] text-[12px]"
@@ -565,7 +570,11 @@ function SeatSectionMobile({
565
570
  )
566
571
  ?.sort((a, b) => a.fare - b.fare)[0]?.fare;
567
572
 
573
+ // show_dp_ui: false falls through to the regular fare layout
574
+ const isDpUiHidden = commonService.isDpUiHidden(serviceItem);
575
+
568
576
  const hasDpDiscount =
577
+ !isDpUiHidden &&
569
578
  serviceItem?.dp_discounted_seats &&
570
579
  serviceItem?.dp_discount_percents &&
571
580
  originalDpPrice != null &&
@@ -578,6 +587,7 @@ function SeatSectionMobile({
578
587
  style={{ width: "40%", flexShrink: 0 }}
579
588
  >
580
589
  {serviceItem?.is_dp_enabled &&
590
+ !isDpUiHidden &&
581
591
  !serviceItem?.dp_discounted_seats?.length &&
582
592
  !dpDiscountPercent ? (
583
593
  <div
@@ -349,6 +349,19 @@ const commonService = {
349
349
  return match ? icons?.[`seat${match[1]}`] || null : null;
350
350
  },
351
351
 
352
+ // The backend can switch off the DP look (gradient card, ribbon, DP fare
353
+ // block) per DP service with show_dp_ui: false. DP fare rules (no changes /
354
+ // cancellations) still apply. A missing flag keeps the current UI.
355
+ isDpUiHidden: (serviceItem?: {
356
+ is_dp_enabled?: boolean;
357
+ show_dp_ui?: boolean;
358
+ }): boolean =>
359
+ Boolean(serviceItem?.is_dp_enabled) && serviceItem?.show_dp_ui === false,
360
+
361
+ // "Mejor precio" pill on DP fares; a missing flag keeps the current UI.
362
+ showDpBestPrice: (serviceItem?: { show_best_price?: boolean }): boolean =>
363
+ serviceItem?.show_best_price !== false,
364
+
352
365
  calculateDiscountedPrice: (
353
366
  originalPrice: number | string,
354
367
  serviceItem?: {