kupos-ui-components-lib 9.0.4 → 9.0.6

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