kupos-ui-components-lib 9.0.6 → 9.0.7

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 (38) hide show
  1. package/README copy.md +67 -223
  2. package/dist/assets/images/anims/service_list/directo.json +1 -1
  3. package/dist/assets/images/anims/service_list/priority_stage.json +1 -1
  4. package/dist/components/ServiceItem/RatingHover.js +31 -31
  5. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  6. package/dist/components/ServiceItem/ServiceItemDesktop.js +263 -125
  7. package/dist/components/ServiceItem/ServiceItemMobile.js +291 -44
  8. package/dist/components/ServiceItem/mobileTypes.d.ts +0 -3
  9. package/dist/components/ServiceItem/types.d.ts +0 -7
  10. package/dist/styles.css +17 -66
  11. package/dist/ui/AmenitiesBlock.js +36 -25
  12. package/dist/ui/DurationBlock.js +2 -2
  13. package/dist/ui/FlexibleBlock.js +4 -2
  14. package/dist/ui/PetBlock.js +3 -1
  15. package/dist/ui/RatingBlock.js +2 -6
  16. package/package.json +1 -1
  17. package/src/assets/images/anims/service_list/directo.json +1 -1
  18. package/src/assets/images/anims/service_list/priority_stage.json +1 -1
  19. package/src/components/ServiceItem/RatingHover.tsx +51 -51
  20. package/src/components/ServiceItem/ServiceItemDesktop.tsx +466 -222
  21. package/src/components/ServiceItem/ServiceItemMobile.tsx +501 -120
  22. package/src/components/ServiceItem/mobileTypes.ts +0 -3
  23. package/src/components/ServiceItem/types.ts +0 -7
  24. package/src/ui/AmenitiesBlock.tsx +15 -34
  25. package/src/ui/DurationBlock.tsx +2 -2
  26. package/src/ui/FlexibleBlock.tsx +3 -3
  27. package/src/ui/PetBlock.tsx +2 -2
  28. package/src/ui/RatingBlock.tsx +4 -16
  29. package/src/assets/images/anims/service_list/bomb.json +0 -1
  30. package/src/ui/BottomAmenities/BottomAmenities.tsx +0 -109
  31. package/src/ui/ExpendedDropDown/ExpandedDropdown.tsx +0 -85
  32. package/src/ui/KuposButton/KuposButton.tsx +0 -48
  33. package/src/ui/SeatSection/SeatSection.tsx +0 -187
  34. package/src/ui/TopAmenities/TopAmenities.tsx +0 -82
  35. package/src/ui/mobileweb/BottomAmenitiesMobile.tsx +0 -168
  36. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +0 -192
  37. package/src/ui/mobileweb/SeatSectionMobile.tsx +0 -256
  38. package/src/ui/mobileweb/TopAmenitieMobile.tsx +0 -82
@@ -1,12 +1,12 @@
1
- import React from "react";
1
+ import React, { useState, useEffect } from "react";
2
2
  import { ServiceItemProps } from "./types";
3
3
  import DateService from "../../utils/DateService";
4
4
  import CommonService from "../../utils/CommonService";
5
+ import RatingHover from "./RatingHover";
5
6
  import ModalEventManager from "../../utils/ModalEventManager";
6
7
  import InternationalServicePopupBody from "../InternationalServicePopupBody";
7
8
  import LottiePlayer from "../../assets/LottiePlayer";
8
9
  import PeruServiceItemDesktop from "./PeruServiceItemDesktop";
9
- import ExpandedDropdown from "../../ui/ExpendedDropDown/ExpandedDropdown";
10
10
  import promoAnimation from "../../assets/images/anims/service_list/promocion.json";
11
11
  import flexibleAnimation from "../../assets/images/anims/service_list/flexible.json";
12
12
  import locationAnimation from "../../assets/images/anims/service_list/location.json";
@@ -18,13 +18,22 @@ import pullmanFlexibleAnimation from "../../assets/images/anims/service_list/pul
18
18
  import pullmanPetFriendlyAnimation from "../../assets/images/anims/service_list/pullmanPetFriendly.json";
19
19
  import pullmanLocationAnimation from "../../assets/images/anims/service_list/pullmanLocation.json";
20
20
  import pullmanPriorityStageAnimation from "../../assets/images/anims/service_list/pullmanPriorityStage.json";
21
+ import pullmanPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
22
+ import pullmanDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
21
23
 
22
24
  import opsitesFlexibleAnimation from "../../assets/images/anims/service_list/opsitesFlexible.json";
23
25
  import opsitesPetFriendlyAnimation from "../../assets/images/anims/service_list/opsitesPetFriendly.json";
24
26
  import opsitesLocationAnimation from "../../assets/images/anims/service_list/opsitesLocation.json";
25
27
  import opsitesPriorityStageAnimation from "../../assets/images/anims/service_list/opsitesPriorityStage.json";
28
+ import opsitesPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
29
+ import opsitesDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
26
30
 
27
- import bombAnimation from "../../assets/images/anims/service_list/bomb.json";
31
+ import linatalFlexibleAnimation from "../../assets/images/anims/service_list/flexible.json";
32
+ import linatalPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
33
+ import linatalDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
34
+ import linatalPriorityStageAnimation from "../../assets/images/anims/service_list/priority_stage.json";
35
+ import linatalPetFriendlyAnimation from "../../assets/images/anims/service_list/pet_friendly.json";
36
+ import linatalLocationAnimation from "../../assets/images/anims/service_list/location.json";
28
37
 
29
38
  import RatingBlock from "../../ui/RatingBlock";
30
39
  import DurationBlock from "../../ui/DurationBlock";
@@ -32,61 +41,9 @@ import PetBlock from "../../ui/PetBlock";
32
41
  import FlexibleBlock from "../../ui/FlexibleBlock";
33
42
  import AmenitiesBlock from "../../ui/AmenitiesBlock";
34
43
  import StageTooltip from "../../ui/StagesTooltip";
35
- import KuposButton from "../../ui/KuposButton/KuposButton";
36
- import TopAmenities from "../../ui/TopAmenities/TopAmenities";
37
- import BottomAmenities from "../../ui/BottomAmenities/BottomAmenities";
38
- import SeatSection from "../../ui/SeatSection/SeatSection";
39
44
 
40
45
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
41
46
 
42
- const ANIMATION_MAP: Record<string, Record<string, any>> = {
43
- promoAnim: {
44
- kupos: promoAnimation,
45
- },
46
- locationAnim: {
47
- kupos: locationAnimation,
48
- pullman: pullmanLocationAnimation,
49
- opsites: opsitesLocationAnimation,
50
- },
51
- directoAnim: {
52
- kupos: directoAnimation,
53
- },
54
- petFriendlyAnim: {
55
- kupos: petFriendlyAnimation,
56
- pullman: pullmanPetFriendlyAnimation,
57
- opsites: opsitesPetFriendlyAnimation,
58
- },
59
- priorityStageAnim: {
60
- kupos: priorityStageAnimation,
61
- pullman: pullmanPriorityStageAnimation,
62
- opsites: opsitesPriorityStageAnimation,
63
- },
64
- flexibleIcon: {
65
- kupos: flexibleAnimation,
66
- pullman: pullmanFlexibleAnimation,
67
- opsites: opsitesFlexibleAnimation,
68
- },
69
- bombAnimation: {
70
- kupos: bombAnimation,
71
- },
72
- };
73
-
74
- const convertTo24Hour = (depTime: string): string => {
75
- const hasAM = depTime.includes("AM");
76
- const hasPM = depTime.includes("PM");
77
- const [timePart] = depTime.split(/AM|PM/).map((part) => part.trim());
78
- const [hour, minute] = timePart.split(":").map(Number);
79
- const pad = (n: number) => (n < 10 ? "0" + n : String(n));
80
-
81
- if (hasAM) {
82
- return `${pad(hour === 12 ? 0 : hour)}:${pad(minute)}`;
83
- }
84
- if (hasPM) {
85
- return `${hour === 12 ? hour : hour + 12}:${pad(minute)}`;
86
- }
87
- return timePart;
88
- };
89
-
90
47
  function ServiceItemPB({
91
48
  serviceItem,
92
49
  onBookButtonPress,
@@ -114,12 +71,49 @@ function ServiceItemPB({
114
71
  t = (key: string) => key,
115
72
  siteType,
116
73
  isAllinBus,
117
- isExpand,
118
- setIsExpand,
119
- coachKey,
120
74
  }: ServiceItemProps & { currencySign?: string }): React.ReactElement {
75
+ console.log("🚀 ~ ServiceItemPB ~ serviceItem:", serviceItem);
76
+ const animationMap: Record<string, Record<string, any>> = {
77
+ promoAnim: {
78
+ kupos: promoAnimation,
79
+ pullman: pullmanPromoAnimation,
80
+ opsites: opsitesPromoAnimation,
81
+ linatal: linatalPromoAnimation,
82
+ },
83
+ locationAnim: {
84
+ kupos: locationAnimation,
85
+ pullman: pullmanLocationAnimation,
86
+ opsites: opsitesLocationAnimation,
87
+ linatal: linatalLocationAnimation,
88
+ },
89
+ directoAnim: {
90
+ kupos: directoAnimation,
91
+ pullman: pullmanDirectoAnimation,
92
+ opsites: opsitesDirectoAnimation,
93
+ linatal: linatalDirectoAnimation,
94
+ },
95
+ petFriendlyAnim: {
96
+ kupos: petFriendlyAnimation,
97
+ pullman: pullmanPetFriendlyAnimation,
98
+ opsites: opsitesPetFriendlyAnimation,
99
+ linatal: linatalPetFriendlyAnimation,
100
+ },
101
+ priorityStageAnim: {
102
+ kupos: priorityStageAnimation,
103
+ pullman: pullmanPriorityStageAnimation,
104
+ opsites: opsitesPriorityStageAnimation,
105
+ linatal: linatalPriorityStageAnimation,
106
+ },
107
+ flexibleIcon: {
108
+ kupos: flexibleAnimation,
109
+ pullman: pullmanFlexibleAnimation,
110
+ opsites: opsitesFlexibleAnimation,
111
+ linatal: linatalFlexibleAnimation,
112
+ },
113
+ };
114
+
121
115
  const getAnimationIcon = (icon: string) => {
122
- const animation = ANIMATION_MAP[icon];
116
+ const animation = animationMap[icon];
123
117
  if (!animation) return null;
124
118
  return animation[siteType] ?? animation.kupos;
125
119
  };
@@ -158,7 +152,9 @@ function ServiceItemPB({
158
152
  style={{
159
153
  filter: color === "white" ? "brightness(0) invert(1)" : "",
160
154
  }}
161
- className="object-contain w-[16px] h-[16px]"
155
+ className={`object-contain ${
156
+ moreAnemities ? "w-[16px] h-[16px]" : "w-[16px] h-[16px]"
157
+ }`}
162
158
  />
163
159
  );
164
160
  };
@@ -176,11 +172,6 @@ function ServiceItemPB({
176
172
 
177
173
  let isSoldOut = serviceItem.available_seats <= 0;
178
174
 
179
- const showPromo = Math.random() > 0.5;
180
-
181
- const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
182
- const grayscaleClass = isSoldOut ? "grayscale" : "";
183
-
184
175
  const renderIcon = (iconKey: string, size: string = "14px") => {
185
176
  const iconValue = serviceItem.icons?.[iconKey];
186
177
  if (iconValue) {
@@ -189,7 +180,7 @@ function ServiceItemPB({
189
180
  <img
190
181
  src={iconValue}
191
182
  alt={iconKey}
192
- className={`${`w-[${size}] h-[${size}]`} `}
183
+ className={`${`w-[${size}] h-[${size}]`} mr-[5px]`}
193
184
  />
194
185
  );
195
186
  }
@@ -197,8 +188,136 @@ function ServiceItemPB({
197
188
  return null;
198
189
  };
199
190
 
191
+ const getAllSeatTypes = () => {
192
+ if (!serviceItem?.seat_types?.length) {
193
+ return [{ label: "Salon cama", price: 0 }];
194
+ }
195
+
196
+ let seatTypesWithPrices = serviceItem.seat_types
197
+ .filter((item) => getFilteredSeats(item))
198
+ .map((val) => ({
199
+ label: val?.label,
200
+ price: val?.fare,
201
+ }));
202
+
203
+ seatTypesWithPrices.sort((a, b) => a.price - b.price);
204
+
205
+ return seatTypesWithPrices;
206
+ };
207
+
208
+ const getSortedSeatTypes = () => {
209
+ if (!serviceItem?.seat_types?.length) {
210
+ return [{ label: "Salon cama", price: 0 }];
211
+ }
212
+
213
+ let seatTypesWithPrices = getAllSeatTypes();
214
+ const premiumIndex = seatTypesWithPrices.findIndex(
215
+ (item) => item.label === "Premium",
216
+ );
217
+
218
+ if (premiumIndex >= 3) {
219
+ seatTypesWithPrices[2] = seatTypesWithPrices[premiumIndex];
220
+ }
221
+
222
+ seatTypesWithPrices = seatTypesWithPrices.slice(0, 2);
223
+
224
+ return seatTypesWithPrices;
225
+ };
226
+
227
+ const getUniqueSeats = () => {
228
+ const allSeatTypes = getAllSeatTypes();
229
+ const seatMap = new Map();
230
+
231
+ allSeatTypes.forEach((seat) => {
232
+ if (SEAT_EXCEPTIONS.includes(seat.label)) return;
233
+
234
+ // Only check if the label already exists in the map, don't compare prices
235
+ if (!seatMap.has(seat.label)) {
236
+ seatMap.set(seat.label, seat);
237
+ }
238
+ });
239
+
240
+ return Array.from(seatMap.values());
241
+ };
242
+
243
+ const getNumberOfSeats = () => {
244
+ return serviceItem.seat_types.filter(
245
+ (val) => !SEAT_EXCEPTIONS.includes(val.label),
246
+ ).length;
247
+ };
248
+
249
+ const getSeatNames = () => {
250
+ const uniqueSeats = getUniqueSeats();
251
+ const sortedSeatTypes = getSortedSeatTypes();
252
+
253
+ if (removeDuplicateSeats) {
254
+ return uniqueSeats.map((val, key: number) =>
255
+ SEAT_EXCEPTIONS.includes(val.label) ? null : (
256
+ <span
257
+ key={key}
258
+ className={`flex items-center justify-between text-[13.33px] ${
259
+ isSoldOut ? "text-[#c0c0c0]" : ""
260
+ }`}
261
+ >
262
+ {typeof val.label === "string" || typeof val.label === "number"
263
+ ? isPeru
264
+ ? CommonService.truncateSeatLabel(val.label)
265
+ : val.label
266
+ : null}
267
+ </span>
268
+ ),
269
+ );
270
+ }
271
+ return sortedSeatTypes.map((val, key: number) =>
272
+ SEAT_EXCEPTIONS.includes(val.label) ? null : (
273
+ <span
274
+ key={key}
275
+ className={`flex items-center justify-between text-[13.33px] ${
276
+ isSoldOut ? "text-[#c0c0c0]" : ""
277
+ }`}
278
+ >
279
+ {typeof val.label === "string" || typeof val.label === "number"
280
+ ? val.label
281
+ : null}
282
+ </span>
283
+ ),
284
+ );
285
+ };
286
+
287
+ const getSeatPrice = () => {
288
+ const sortedSeatTypes = getSortedSeatTypes();
289
+ const uniqueSeats = getUniqueSeats();
290
+ if (removeDuplicateSeats) {
291
+ return uniqueSeats.map((val, key) => (
292
+ <span key={key}>
293
+ {serviceItem?.available_seats <= 0
294
+ ? CommonService.currency(0, currencySign)
295
+ : CommonService.currency(val.price, currencySign)}
296
+ </span>
297
+ ));
298
+ }
299
+ return sortedSeatTypes.map((val, key: number) => {
300
+ return SEAT_EXCEPTIONS.includes(val.label) ? null : (
301
+ <span key={key} className="flex items-center text-[13.33px] bold-text">
302
+ {typeof val.price === "string"
303
+ ? serviceItem?.available_seats <= 0
304
+ ? CommonService.currency(0, currencySign)
305
+ : CommonService.currency(val.price, currencySign)
306
+ : typeof val.price === "number"
307
+ ? serviceItem?.available_seats <= 0
308
+ ? CommonService.currency(0, currencySign)
309
+ : CommonService.currency(val.price, currencySign)
310
+ : null}
311
+ </span>
312
+ );
313
+ });
314
+ };
315
+
316
+ const getFilteredSeats = (item) => {
317
+ return item;
318
+ };
319
+
200
320
  const checkMidnight = () => {
201
- setIsExpand(null);
202
321
  if (
203
322
  cityOrigin?.label &&
204
323
  cityDestination?.label &&
@@ -277,37 +396,56 @@ function ServiceItemPB({
277
396
  ),
278
397
  });
279
398
  return;
399
+ } else {
400
+ onBookButtonPress();
280
401
  }
281
-
282
- onBookButtonPress();
283
402
  };
284
403
 
285
404
  const depTime = serviceItem.dep_time || "";
405
+
406
+ // Extract hours and minutes and check for AM/PM
286
407
  const hasAM = depTime.includes("AM");
287
408
  const hasPM = depTime.includes("PM");
288
- const cleanedDepTime = convertTo24Hour(depTime);
409
+ const [timePart] = depTime.split(/AM|PM/).map((part) => part.trim());
410
+ const [hour, minute] = timePart.split(":").map(Number);
411
+
412
+ // Convert to 24-hour format
413
+ let cleanedDepTime;
414
+ if (hasAM) {
415
+ cleanedDepTime =
416
+ hour === 12
417
+ ? `00:${minute < 10 ? "0" + minute : minute}`
418
+ : `${hour < 10 ? "0" + hour : hour}:${
419
+ minute < 10 ? "0" + minute : minute
420
+ }`;
421
+ } else if (hasPM) {
422
+ cleanedDepTime =
423
+ hour === 12
424
+ ? `${hour}:${minute < 10 ? "0" + minute : minute}`
425
+ : `${hour + 12}:${minute < 10 ? "0" + minute : minute}`;
426
+ } else {
427
+ cleanedDepTime = timePart;
428
+ }
289
429
 
290
430
  const items = [
291
431
  {
292
- key: "amenities",
293
- width: "20%",
294
- condition: true,
432
+ key: "rating",
433
+ width: "30%",
295
434
  render: (
296
- <AmenitiesBlock
435
+ <RatingBlock
436
+ showRating={showRating}
297
437
  serviceItem={serviceItem}
298
- metaData={metaData}
299
438
  isSoldOut={isSoldOut}
300
439
  colors={colors}
301
- isPeru={isPeru}
302
- getAnimationIcon={getAnimationIcon}
303
- getAmenityName={CommonService.getAmenityName}
304
- SvgAmenities={SvgAmenities}
440
+ t={t}
441
+ translation={translation}
305
442
  />
306
443
  ),
307
444
  },
445
+
308
446
  {
309
447
  key: "duration",
310
- width: "12%",
448
+ width: "20%",
311
449
  condition: serviceItem.duration,
312
450
  render: (
313
451
  <DurationBlock
@@ -320,22 +458,6 @@ function ServiceItemPB({
320
458
  ),
321
459
  },
322
460
 
323
- {
324
- key: "directo",
325
- width: "12%",
326
- condition: serviceItem?.is_direct_trip,
327
- render: (
328
- <div className="flex items-center gap-[10px] text-[12.5px] z-10 mt-[6px]">
329
- <LottiePlayer
330
- animationData={getAnimationIcon("directoAnim")}
331
- width="14px"
332
- height="14px"
333
- />
334
- <div>{translation?.directService}</div>
335
- </div>
336
- ),
337
- },
338
-
339
461
  {
340
462
  key: "pet",
341
463
  width: "20%",
@@ -366,10 +488,28 @@ function ServiceItemPB({
366
488
  />
367
489
  ),
368
490
  },
491
+
492
+ {
493
+ key: "amenities",
494
+ width: "20%",
495
+ render: (
496
+ <AmenitiesBlock
497
+ serviceItem={serviceItem}
498
+ metaData={metaData}
499
+ isSoldOut={isSoldOut}
500
+ colors={colors}
501
+ isPeru={isPeru}
502
+ getAnimationIcon={getAnimationIcon}
503
+ getAmenityName={CommonService.getAmenityName}
504
+ SvgAmenities={SvgAmenities}
505
+ />
506
+ ),
507
+ },
369
508
  ];
370
509
 
510
+ const amenitiesItem = items.find((i) => i.key === "amenities");
371
511
  const otherItems = items.filter(
372
- (i) => i.key !== "pet" && i.key !== "flexible" && !!i.condition,
512
+ (i) => i.key !== "amenities" && i.condition !== false,
373
513
  );
374
514
 
375
515
  return (
@@ -406,77 +546,40 @@ function ServiceItemPB({
406
546
  <div
407
547
  className={`relative ${
408
548
  serviceItem.offer_text ? "mb-[55px]" : "mb-[10px]"
409
- } mt-[14px]`}
549
+ } ${
550
+ serviceItem?.is_direct_trip ||
551
+ serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
552
+ showTopLabel
553
+ ? "mt-[24px]"
554
+ : "mt-[20px]"
555
+ } `}
410
556
  >
411
- <TopAmenities
412
- showPromo={showPromo}
413
- showTopLabel={showTopLabel}
414
- isSoldOut={isSoldOut}
415
- priceColor={colors.priceColor}
416
- buttonColor={colors.kuposButtonColor}
417
- boardingIcon={renderIcon("whiteBoardingIcon", "14px")}
418
- getAnimationIcon={getAnimationIcon}
419
- />
420
557
  <div
421
- className="bg-white mx-auto relative"
422
- style={{
423
- border: `1px solid ${colors.priceColor}`,
424
- // showPromo
425
- // ? `1px solid ${colors.priceColor}`
426
- // : "1px solid #ccc",
427
- borderRadius:
428
- // showPromo || showTopLabel ?
429
- "10px 0 10px 10px",
430
- // : "10px",
431
- }}
558
+ className={
559
+ "bg-white rounded-[20px] shadow-service mx-auto relative"
560
+ }
432
561
  >
433
- <div
434
- // className="p-[15px] pt-[20px]"
435
- className=" pt-[20px]"
436
- style={{
437
- padding: coachKey
438
- ? "15px 15px 20px 15px"
439
- : "20px 15px 11px 15px",
440
- }}
441
- >
562
+ <div className="p-[15px] pt-[20px]">
442
563
  <div
443
- // className="grid text-[#464647] w-full [grid-template-columns:18%_28%_2.5%_28%_15.5%] gap-x-[2%] items-center"
444
- className="grid text-[#464647] w-full [grid-template-columns:22%_28%_2.5%_24%_15.5%] gap-x-[2%] items-center"
445
- // style={{ marginTop: showTopLabel ? "8px" : "" }}
564
+ className="grid text-[#464647] w-full [grid-template-columns:14%_30%_2.5%_30%_15.5%] gap-x-[2%] items-center"
565
+ style={{ marginTop: showTopLabel ? "8px" : "" }}
446
566
  >
447
567
  {/* OPERATOR LOGO */}
448
- <div
449
- style={{
450
- display: "flex",
451
- flexDirection: "column",
452
- gap: "5px",
453
- }}
454
- >
455
- <div
456
- // className="flex items-center justify-center m-[auto]"
457
- className=""
458
- >
568
+ <div className="flex items-center justify-center m-[auto]">
569
+ <div className=" ">
459
570
  <img
460
571
  src={serviceItem.operator_details[0]}
461
572
  alt="service logo"
462
- className={`h-auto w-[150px] object-contain ${
573
+ className={` h-auto object-contain ${
463
574
  isSoldOut ? "grayscale" : ""
464
575
  }`}
465
576
  />
466
- {isCiva ? (
467
- <div className="text-[13.33px] black-text ml-2">
468
- {serviceItem.operator_details[2]}
469
- </div>
470
- ) : null}
471
577
  </div>
472
- <RatingBlock
473
- showRating={showRating}
474
- serviceItem={serviceItem}
475
- isSoldOut={isSoldOut}
476
- colors={colors}
477
- t={t}
478
- translation={translation}
479
- />
578
+ {isCiva ? (
579
+ <div className="text-[13.33px] black-text ml-2">
580
+ {serviceItem.operator_details[2]}
581
+ </div>
582
+ ) : null}
480
583
  </div>
481
584
 
482
585
  {/* DATE AND TIME - Grid Layout */}
@@ -500,7 +603,7 @@ function ServiceItemPB({
500
603
  <img
501
604
  src={serviceItem.icons?.origin}
502
605
  alt="origin"
503
- className={`w-[16px] h-auto mr-[8px] ${
606
+ className={`w-[18px] h-auto mr-[8px] ${
504
607
  isSoldOut ? "grayscale" : ""
505
608
  }`}
506
609
  />
@@ -517,7 +620,7 @@ function ServiceItemPB({
517
620
  <div className="h-[20px] flex items-center">
518
621
  <img
519
622
  src={serviceItem.icons?.destination}
520
- className={`w-[16px] h-auto mr-[8px] ${
623
+ className={`w-[18px] h-auto mr-[8px] ${
521
624
  isSoldOut ? "grayscale" : ""
522
625
  }`}
523
626
  style={{ opacity: isSoldOut ? 0.5 : 1 }}
@@ -636,87 +739,151 @@ function ServiceItemPB({
636
739
  ></div>
637
740
  {/* SEATS */}
638
741
  <div className="content-center">
639
- <SeatSection
640
- seatTypes={serviceItem.seat_types}
641
- availableSeats={serviceItem.available_seats}
642
- isSoldOut={isSoldOut}
643
- priceColor={colors.priceColor}
644
- currencySign={currencySign}
645
- removeDuplicateSeats={removeDuplicateSeats}
646
- isPeru={isPeru}
647
- />
648
- </div>
649
-
650
- {/* BUTTON */}
651
-
652
- <div>
653
- {showLastSeats ? (
742
+ <div
743
+ className={`relative flex gap-[10px] text-[13.33px] justify-between min-h-[2.5rem] ${
744
+ getNumberOfSeats() < 3 ? "" : ""
745
+ }`}
746
+ style={
747
+ getNumberOfSeats() < 2 || removeDuplicateSeats
748
+ ? { alignItems: "center" }
749
+ : {}
750
+ }
751
+ >
752
+ <div
753
+ className="flex flex-col justify-between"
754
+ style={{ gap: "10px" }}
755
+ >
756
+ {getSeatNames()}
757
+ </div>
654
758
  <div
655
- className="flex justify-end mr-[11px] "
759
+ className="flex flex-col justify-between absolute inset-y-0 right-0 left-1/2 h-full"
656
760
  style={{
657
- marginBottom:
658
- serviceItem?.available_seats < 10 &&
659
- serviceItem?.available_seats > 0
660
- ? "6px"
661
- : "0",
761
+ color: isSoldOut ? "#c0c0c0" : colors.priceColor,
762
+ top: 0,
763
+ bottom: 0,
764
+ left: "clamp(60%, 65% + (100vw - 1300px) * 0.1, 65%)",
765
+ // left: "68%",
766
+ right: 0,
767
+ justifyContent:
768
+ getNumberOfSeats() < 2 || removeDuplicateSeats
769
+ ? "center"
770
+ : "",
771
+ gap: "10px",
662
772
  }}
663
773
  >
664
- {serviceItem?.available_seats < 10 &&
665
- serviceItem?.available_seats > 0 && (
666
- <div className="text-[12.5px] text-[#464647] mt-1 text-center">
667
- ¡ Últimos Asientos!
668
- </div>
774
+ {getSeatPrice()}
775
+ </div>
776
+ </div>
777
+ </div>
778
+
779
+ {/* BUTTON */}
780
+ <div>
781
+ <button
782
+ onClick={() => (!isSoldOut ? checkMidnight() : null)}
783
+ disabled={serviceDetailsLoading}
784
+ className={`w-full ${
785
+ serviceDetailsLoading || isSoldOut
786
+ ? "py-[12px]"
787
+ : "py-[12px]"
788
+ } text-[13.33px] font-bold text-white rounded-[10px] border-none px-[20px] flex items-center justify-center`}
789
+ style={{
790
+ backgroundColor:
791
+ serviceDetailsLoading || isSoldOut
792
+ ? "lightgray"
793
+ : colors.kuposButtonColor,
794
+ cursor:
795
+ serviceDetailsLoading || isSoldOut
796
+ ? "not-allowed"
797
+ : "pointer",
798
+ }}
799
+ >
800
+ <span className="min-w-[75px] flex justify-center items-center bold-text uppercase">
801
+ {isSoldOut ? renderIcon("soldOutIcon", "14px") : null}
802
+
803
+ {serviceDetailsLoading ? (
804
+ <span className="loader-circle"></span>
805
+ ) : !isSoldOut ? (
806
+ translation?.buyButton
807
+ ) : (
808
+ translation?.soldOutButton
809
+ )}
810
+ </span>
811
+ </button>
812
+ </div>
813
+ </div>
814
+ {showLastSeats ? (
815
+ <div className="flex justify-end mr-[11px]">
816
+ {serviceItem?.available_seats < 10 &&
817
+ serviceItem?.available_seats > 0 && (
818
+ <div className="text-[12px] text-[red] mt-1 text-center">
819
+ ¡ Últimos Asientos!
820
+ </div>
821
+ )}
822
+ </div>
823
+ ) : null}
824
+
825
+ {/* <div className="flex items-center mt-[15px] border-t border-[#eee] pt-[10px]">
826
+ <div
827
+ className="grid items-center gap-[12px] w-full"
828
+ style={{
829
+ gridTemplateColumns: "30% 20% 20% 20% auto",
830
+ }}
831
+ >
832
+ {items
833
+ .sort((a, b) =>
834
+ a.key === "amenities"
835
+ ? 1
836
+ : b.key === "amenities"
837
+ ? -1
838
+ : 0,
839
+ )
840
+ .map((item) => (
841
+ <div key={item.key} className="flex items-center">
842
+ {item.condition === false ? (
843
+ <div style={{ minHeight: "20px", width: "100%" }} />
844
+ ) : (
845
+ item.render
669
846
  )}
847
+ </div>
848
+ ))}
849
+ </div>
850
+ </div> */}
851
+
852
+ <div className="flex items-center mt-[15px] border-t border-[#eee] pt-[10px]">
853
+ {/* 🔹 LEFT SIDE (GRID ITEMS) */}
854
+ <div
855
+ className="grid items-center gap-[2%] flex-1"
856
+ style={{
857
+ gridTemplateColumns: "40% 18% 23% 23%",
858
+ // otherItems
859
+ // .map((i) => i.width)
860
+ // .join(" "),
861
+ }}
862
+ >
863
+ {otherItems.map((item) => (
864
+ <div key={item.key} className="flex items-center ">
865
+ {item.render}
670
866
  </div>
671
- ) : null}
672
- <KuposButton
673
- isSoldOut={isSoldOut}
674
- isLoading={serviceDetailsLoading}
675
- buttonColor={colors.kuposButtonColor}
676
- buyLabel={translation?.buyButton}
677
- soldOutLabel={translation?.soldOutButton}
678
- soldOutIcon={renderIcon("soldOutIcon", "14px")}
679
- onClick={checkMidnight}
680
- />
867
+ ))}
868
+ </div>
869
+
870
+ {/* 🔹 RIGHT SIDE (ALWAYS END) */}
871
+ <div className="flex items-center ml-[12px] shrink-0 w-[130px] justify-end">
872
+ {amenitiesItem?.render}
681
873
  </div>
682
874
  </div>
683
- <BottomAmenities
684
- otherItems={otherItems}
685
- serviceItem={serviceItem}
686
- grayscaleClass={grayscaleClass}
687
- isSoldOut={isSoldOut}
688
- isItemExpanded={isItemExpanded}
689
- colors={colors}
690
- translation={translation}
691
- getAnimationIcon={getAnimationIcon}
692
- downArrowIcon={renderIcon("downArrow", "10px")}
693
- onToggleExpand={() =>
694
- setIsExpand &&
695
- setIsExpand(isItemExpanded ? null : serviceItem.id)
696
- }
697
- />
698
875
  </div>
699
876
  </div>
700
877
 
701
- {/* 🔹 EXPANDABLE DROPDOWN (below the card) */}
702
- {isItemExpanded && (
703
- <ExpandedDropdown
704
- serviceItem={serviceItem}
705
- showPromo={showPromo}
706
- colors={colors}
707
- grayscaleClass={grayscaleClass}
708
- translation={translation}
709
- getAnimationIcon={getAnimationIcon}
710
- />
711
- )}
712
-
713
878
  {children}
714
879
  {/* Bottom discount banner */}
715
880
  {serviceItem?.offer_text && (
716
881
  <div
717
- className="text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] -z-10 rounded-b-[14px] text-[14px]"
882
+ className={` text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] -z-10 rounded-b-[14px] text-[14px]`}
718
883
  style={{
719
- backgroundColor: colors?.bottomStripColor,
884
+ backgroundColor: isSoldOut
885
+ ? colors?.bottomStripColor
886
+ : colors?.bottomStripColor,
720
887
  opacity: isSoldOut ? 0.5 : 1,
721
888
  }}
722
889
  >
@@ -728,6 +895,83 @@ function ServiceItemPB({
728
895
  <span className="ml-[10px]">{serviceItem?.offer_text}</span>
729
896
  </div>
730
897
  )}
898
+
899
+ <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[15px] z-10 ">
900
+ {showTopLabel && (
901
+ <div
902
+ className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
903
+ style={{
904
+ backgroundColor: isSoldOut
905
+ ? "#ddd"
906
+ : colors.ratingBottomColor,
907
+ }}
908
+ >
909
+ <div className={isSoldOut ? "grayscale" : ""}>
910
+ <LottiePlayer
911
+ // animationData={serviceItem.icons.priorityStageAnim}
912
+ animationData={getAnimationIcon("priorityStageAnim")}
913
+ width="14px"
914
+ height="14px"
915
+ />
916
+ </div>
917
+ <div
918
+ className={
919
+ isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
920
+ }
921
+ >
922
+ {showTopLabel}
923
+ </div>
924
+ </div>
925
+ )}
926
+ {serviceItem?.is_transpordo && (
927
+ <div
928
+ className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20`}
929
+ style={{
930
+ backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
931
+ }}
932
+ >
933
+ <LottiePlayer
934
+ animationData={serviceItem.icons.connectingServiceIcon}
935
+ // animationData={getAnimationIcon(connectingServiceIcon)}
936
+ width="14px"
937
+ height="14px"
938
+ />
939
+ <div>{"Conexión"}</div>
940
+ </div>
941
+ )}
942
+ {serviceItem?.is_direct_trip && (
943
+ <div
944
+ className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20 `}
945
+ style={{
946
+ backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
947
+ }}
948
+ >
949
+ <LottiePlayer
950
+ // animationData={serviceItem.icons.directoAnim}
951
+ animationData={getAnimationIcon("directoAnim")}
952
+ width="14px"
953
+ height="14px"
954
+ />
955
+ <div>{translation?.directService}</div>
956
+ </div>
957
+ )}
958
+ {serviceItem?.train_type_label === "Tren Express (Nuevo)" && (
959
+ <div
960
+ className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20 `}
961
+ style={{
962
+ backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
963
+ }}
964
+ >
965
+ <LottiePlayer
966
+ // animationData={serviceItem.icons.directoAnim}
967
+ animationData={getAnimationIcon("directoAnim")}
968
+ width="14px"
969
+ height="14px"
970
+ />
971
+ <div>{"Tren Express"}</div>
972
+ </div>
973
+ )}
974
+ </div>
731
975
  </div>
732
976
  )}
733
977
  </>