kupos-ui-components-lib 9.1.2 → 9.1.3

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 (46) hide show
  1. package/README copy.md +67 -223
  2. package/dist/assets/images/anims/service_list/directo.json +1 -1
  3. package/dist/components/ServiceItem/PeruServiceItemDesktop.js +1 -1
  4. package/dist/components/ServiceItem/RatingHover.js +33 -32
  5. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  6. package/dist/components/ServiceItem/ServiceItemDesktop.js +267 -147
  7. package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
  8. package/dist/components/ServiceItem/ServiceItemMobile.js +278 -87
  9. package/dist/components/ServiceItem/mobileTypes.d.ts +0 -5
  10. package/dist/components/ServiceItem/types.d.ts +0 -7
  11. package/dist/styles.css +32 -131
  12. package/dist/ui/AmenitiesBlock.js +30 -23
  13. package/dist/ui/DurationBlock.js +4 -4
  14. package/dist/ui/FlexibleBlock.js +6 -5
  15. package/dist/ui/PetBlock.js +3 -1
  16. package/dist/ui/RatingBlock.d.ts +1 -9
  17. package/dist/ui/RatingBlock.js +3 -7
  18. package/dist/utils/CommonService.d.ts +1 -1
  19. package/dist/utils/CommonService.js +0 -2
  20. package/package.json +1 -2
  21. package/src/assets/images/anims/service_list/directo.json +1 -1
  22. package/src/components/ServiceItem/PeruServiceItemDesktop.tsx +0 -1
  23. package/src/components/ServiceItem/RatingHover.tsx +45 -44
  24. package/src/components/ServiceItem/ServiceItemDesktop.tsx +537 -313
  25. package/src/components/ServiceItem/ServiceItemMobile.tsx +530 -213
  26. package/src/components/ServiceItem/mobileTypes.ts +0 -5
  27. package/src/components/ServiceItem/types.ts +0 -7
  28. package/src/ui/AmenitiesBlock.tsx +29 -50
  29. package/src/ui/DurationBlock.tsx +4 -4
  30. package/src/ui/FlexibleBlock.tsx +5 -6
  31. package/src/ui/PetBlock.tsx +2 -2
  32. package/src/ui/RatingBlock.tsx +6 -18
  33. package/src/utils/CommonService.ts +0 -2
  34. package/src/assets/images/anims/service_list/bomb.json +0 -1
  35. package/src/ui/BottomAmenities/BottomAmenities.tsx +0 -110
  36. package/src/ui/DateTimeSection/DateTimeSection.tsx +0 -207
  37. package/src/ui/DirectoBlock.tsx +0 -31
  38. package/src/ui/ExpendedDropDown/ExpandedDropdown.tsx +0 -103
  39. package/src/ui/KuposButton/KuposButton.tsx +0 -48
  40. package/src/ui/SeatSection/SeatSection.tsx +0 -207
  41. package/src/ui/TopAmenities/TopAmenities.tsx +0 -127
  42. package/src/ui/mobileweb/BottomAmenitiesMobile.tsx +0 -169
  43. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +0 -192
  44. package/src/ui/mobileweb/ExpandedDropdownMobile.tsx +0 -56
  45. package/src/ui/mobileweb/SeatSectionMobile.tsx +0 -256
  46. package/src/ui/mobileweb/TopAmenitieMobile.tsx +0 -126
@@ -1,11 +1,12 @@
1
- import React from "react";
1
+ import React, { useState, useEffect } from "react";
2
2
  import { ServiceItemProps } from "./types";
3
+ import DateService from "../../utils/DateService";
3
4
  import CommonService from "../../utils/CommonService";
5
+ import RatingHover from "./RatingHover";
4
6
  import ModalEventManager from "../../utils/ModalEventManager";
5
7
  import InternationalServicePopupBody from "../InternationalServicePopupBody";
6
8
  import LottiePlayer from "../../assets/LottiePlayer";
7
9
  import PeruServiceItemDesktop from "./PeruServiceItemDesktop";
8
- import ExpandedDropdown from "../../ui/ExpendedDropDown/ExpandedDropdown";
9
10
  import promoAnimation from "../../assets/images/anims/service_list/promocion.json";
10
11
  import flexibleAnimation from "../../assets/images/anims/service_list/flexible.json";
11
12
  import locationAnimation from "../../assets/images/anims/service_list/location.json";
@@ -17,60 +18,32 @@ import pullmanFlexibleAnimation from "../../assets/images/anims/service_list/pul
17
18
  import pullmanPetFriendlyAnimation from "../../assets/images/anims/service_list/pullmanPetFriendly.json";
18
19
  import pullmanLocationAnimation from "../../assets/images/anims/service_list/pullmanLocation.json";
19
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";
20
23
 
21
24
  import opsitesFlexibleAnimation from "../../assets/images/anims/service_list/opsitesFlexible.json";
22
25
  import opsitesPetFriendlyAnimation from "../../assets/images/anims/service_list/opsitesPetFriendly.json";
23
26
  import opsitesLocationAnimation from "../../assets/images/anims/service_list/opsitesLocation.json";
24
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";
25
30
 
26
- 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";
27
37
 
28
38
  import RatingBlock from "../../ui/RatingBlock";
29
39
  import DurationBlock from "../../ui/DurationBlock";
30
- import DirectoBlock from "../../ui/DirectoBlock";
31
40
  import PetBlock from "../../ui/PetBlock";
32
41
  import FlexibleBlock from "../../ui/FlexibleBlock";
33
42
  import AmenitiesBlock from "../../ui/AmenitiesBlock";
34
- import KuposButton from "../../ui/KuposButton/KuposButton";
35
- import TopAmenities from "../../ui/TopAmenities/TopAmenities";
36
- import BottomAmenities from "../../ui/BottomAmenities/BottomAmenities";
37
- import SeatSection from "../../ui/SeatSection/SeatSection";
38
- import DateTimeSection from "../../ui/DateTimeSection/DateTimeSection";
43
+ import StageTooltip from "../../ui/StagesTooltip";
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
47
  function ServiceItemPB({
75
48
  serviceItem,
76
49
  onBookButtonPress,
@@ -98,12 +71,48 @@ function ServiceItemPB({
98
71
  t = (key: string) => key,
99
72
  siteType,
100
73
  isAllinBus,
101
- isExpand,
102
- setIsExpand,
103
- coachKey,
104
74
  }: 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
+
105
114
  const getAnimationIcon = (icon: string) => {
106
- const animation = ANIMATION_MAP[icon];
115
+ const animation = animationMap[icon];
107
116
  if (!animation) return null;
108
117
  return animation[siteType] ?? animation.kupos;
109
118
  };
@@ -142,7 +151,9 @@ function ServiceItemPB({
142
151
  style={{
143
152
  filter: color === "white" ? "brightness(0) invert(1)" : "",
144
153
  }}
145
- className="object-contain w-[16px] h-[16px]"
154
+ className={`object-contain ${
155
+ moreAnemities ? "w-[16px] h-[16px]" : "w-[16px] h-[16px]"
156
+ }`}
146
157
  />
147
158
  );
148
159
  };
@@ -160,11 +171,6 @@ function ServiceItemPB({
160
171
 
161
172
  let isSoldOut = serviceItem.available_seats <= 0;
162
173
 
163
- const showPromo = Math.random() > 0.5;
164
-
165
- const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
166
- const grayscaleClass = isSoldOut ? "grayscale" : "";
167
-
168
174
  const renderIcon = (iconKey: string, size: string = "14px") => {
169
175
  const iconValue = serviceItem.icons?.[iconKey];
170
176
  if (iconValue) {
@@ -173,7 +179,7 @@ function ServiceItemPB({
173
179
  <img
174
180
  src={iconValue}
175
181
  alt={iconKey}
176
- className={`${`w-[${size}] h-[${size}]`} `}
182
+ className={`${`w-[${size}] h-[${size}]`} mr-[5px]`}
177
183
  />
178
184
  );
179
185
  }
@@ -181,8 +187,136 @@ function ServiceItemPB({
181
187
  return null;
182
188
  };
183
189
 
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
+
184
319
  const checkMidnight = () => {
185
- setIsExpand?.(null);
186
320
  if (
187
321
  cityOrigin?.label &&
188
322
  cityDestination?.label &&
@@ -261,63 +395,57 @@ function ServiceItemPB({
261
395
  ),
262
396
  });
263
397
  return;
398
+ } else {
399
+ onBookButtonPress();
264
400
  }
265
-
266
- onBookButtonPress();
267
401
  };
268
402
 
269
- const countdownSeconds = 7830;
270
-
271
- const startCountdown = (node: HTMLSpanElement | null) => {
272
- if (!node) return;
273
-
274
- const prevId = node.dataset.countdownId;
275
- if (prevId) clearInterval(Number(prevId));
276
-
277
- let remaining = countdownSeconds;
278
-
279
- const formatTime = (totalSecs: number) => {
280
- const h = Math.floor(totalSecs / 3600);
281
- const m = Math.floor((totalSecs % 3600) / 60);
282
- const s = totalSecs % 60;
283
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
284
- };
285
-
286
- node.textContent = formatTime(remaining);
287
-
288
- const id = setInterval(() => {
289
- remaining -= 1;
290
- if (remaining <= 0) {
291
- remaining = 0;
292
- clearInterval(id);
293
- }
294
- node.textContent = formatTime(remaining);
295
- }, 1000);
296
-
297
- node.dataset.countdownId = String(id);
298
- };
403
+ const depTime = serviceItem.dep_time || "";
404
+
405
+ // Extract hours and minutes and check for AM/PM
406
+ const hasAM = depTime.includes("AM");
407
+ 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
+ }
299
428
 
300
429
  const items = [
301
430
  {
302
- key: "amenities",
303
- width: "20%",
304
- condition: true,
431
+ key: "rating",
432
+ width: "30%",
305
433
  render: (
306
- <AmenitiesBlock
434
+ <RatingBlock
435
+ showRating={showRating}
307
436
  serviceItem={serviceItem}
308
- metaData={metaData}
309
437
  isSoldOut={isSoldOut}
310
438
  colors={colors}
439
+ t={t}
440
+ translation={translation}
311
441
  isPeru={isPeru}
312
- getAnimationIcon={getAnimationIcon}
313
- getAmenityName={CommonService.getAmenityName}
314
- SvgAmenities={SvgAmenities}
315
442
  />
316
443
  ),
317
444
  },
445
+
318
446
  {
319
447
  key: "duration",
320
- width: "12%",
448
+ width: "20%",
321
449
  condition: serviceItem.duration,
322
450
  render: (
323
451
  <DurationBlock
@@ -330,20 +458,6 @@ function ServiceItemPB({
330
458
  ),
331
459
  },
332
460
 
333
- {
334
- key: "directo",
335
- width: "12%",
336
- condition: serviceItem?.is_direct_trip === true,
337
- render: (
338
- <DirectoBlock
339
- translation={translation}
340
- getAnimationIcon={getAnimationIcon}
341
- colors={colors}
342
- isSoldOut={isSoldOut}
343
- />
344
- ),
345
- },
346
-
347
461
  {
348
462
  key: "pet",
349
463
  width: "20%",
@@ -374,10 +488,28 @@ function ServiceItemPB({
374
488
  />
375
489
  ),
376
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
+ },
377
508
  ];
378
509
 
510
+ const amenitiesItem = items.find((i) => i.key === "amenities");
379
511
  const otherItems = items.filter(
380
- (i) => i.key !== "pet" && i.key !== "flexible" && !!i.condition,
512
+ (i) => i.key !== "amenities" && i.condition !== false,
381
513
  );
382
514
 
383
515
  return (
@@ -412,10 +544,6 @@ function ServiceItemPB({
412
544
  />
413
545
  ) : (
414
546
  <div
415
- // className={`relative ${
416
- // serviceItem.offer_text ? "mb-[55px]" : "mb-[10px]"
417
- // } mt-[14px]`}
418
-
419
547
  className={`relative ${
420
548
  serviceItem.offer_text ? "mb-[55px]" : "mb-[10px]"
421
549
  } ${
@@ -426,105 +554,182 @@ function ServiceItemPB({
426
554
  : "mt-[20px]"
427
555
  } `}
428
556
  >
429
- {/* <TopAmenities
430
- showPromo={showPromo}
431
- showTopLabel={showTopLabel}
432
- isSoldOut={isSoldOut}
433
- priceColor={colors.priceColor}
434
- buttonColor={colors.kuposButtonColor}
435
- boardingIcon={renderIcon("whiteBoardingIcon", "14px")}
436
- getAnimationIcon={getAnimationIcon}
437
- countdownSeconds={countdownSeconds}
438
- onCountdownEnd={() => {
439
- const cardEl = document.getElementById(
440
- `service-card-${serviceItem.id}`,
441
- );
442
- if (!cardEl) return;
443
- cardEl.style.border = "1px solid #ccc";
444
- if (!showTopLabel) {
445
- cardEl.style.borderRadius = "10px";
446
- }
447
- }}
448
- offerText={serviceItem?.offer_text}
449
- /> */}
450
557
  <div
451
- id={`service-card-${serviceItem.id}`}
452
- className="bg-white mx-auto relative rounded-[10px] border border-[#ccc]"
453
- // style={{
454
- // border:
455
- // countdownSeconds > 0 && serviceItem?.offer_text
456
- // ? `1px solid ${colors.priceColor}`
457
- // : "1px solid #ccc",
458
- // borderRadius: showTopLabel
459
- // ? "10px 0 10px 10px"
460
- // : countdownSeconds > 0 && serviceItem?.offer_text
461
- // ? "10px 0 10px 10px"
462
- // : "10px",
463
- // }}
558
+ className={
559
+ "bg-white rounded-[20px] shadow-service mx-auto relative"
560
+ }
464
561
  >
465
- <div
466
- // className="p-[15px] pt-[20px]"
467
- className=" pt-[20px]"
468
- style={{
469
- padding: coachKey
470
- ? "15px 15px 20px 15px"
471
- : "20px 15px 11px 15px",
472
- }}
473
- >
562
+ <div className="p-[15px] pt-[20px]">
474
563
  <div
475
- // className="grid text-[#464647] w-full [grid-template-columns:18%_28%_2.5%_28%_15.5%] gap-x-[2%] items-center"
476
- className="grid text-[#464647] w-full [grid-template-columns:22%_28%_2.5%_24%_15.5%] gap-x-[2%] items-center"
477
- // 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" : "" }}
478
566
  >
479
567
  {/* OPERATOR LOGO */}
480
- <div
481
- style={{
482
- display: "flex",
483
- flexDirection: "column",
484
- gap: "5px",
485
- }}
486
- >
487
- <div
488
- // className="flex items-center justify-center m-[auto]"
489
- className=""
490
- >
568
+ <div className="flex items-center justify-center m-[auto]">
569
+ <div className=" ">
491
570
  <img
492
571
  src={serviceItem.operator_details[0]}
493
572
  alt="service logo"
494
- className={`h-auto w-[150px] object-contain ${
573
+ className={` h-auto object-contain ${
495
574
  isSoldOut ? "grayscale" : ""
496
575
  }`}
497
576
  />
498
- {isCiva ? (
499
- <div className="text-[13.33px] black-text ml-2">
500
- {serviceItem.operator_details[2]}
501
- </div>
502
- ) : null}
503
577
  </div>
504
- <RatingBlock
505
- showRating={showRating}
506
- serviceItem={serviceItem}
507
- isSoldOut={isSoldOut}
508
- colors={colors}
509
- t={t}
510
- translation={translation}
511
- isPeru={isPeru}
512
- />
578
+ {isCiva ? (
579
+ <div className="text-[13.33px] black-text ml-2">
580
+ {serviceItem.operator_details[2]}
581
+ </div>
582
+ ) : null}
513
583
  </div>
514
584
 
515
585
  {/* DATE AND TIME - Grid Layout */}
516
- <DateTimeSection
517
- serviceItem={serviceItem}
518
- isSoldOut={isSoldOut}
519
- isCiva={isCiva}
520
- isLinatal={isLinatal}
521
- removeArrivalTime={removeArrivalTime}
522
- orignLabel={orignLabel}
523
- destinationLabel={destinationLabel}
524
- busStage={busStage}
525
- metaData={metaData}
526
- colors={colors}
527
- />
586
+ <div
587
+ className={`min-h-[2.5rem] grid grid-cols-[26px_auto_26%_1fr] gap-x-4 items-center text-[13.33px] ${
588
+ isSoldOut ? "text-[#c0c0c0]" : ""
589
+ }`}
590
+ style={{
591
+ gridTemplateRows: "1fr",
592
+ }}
593
+ >
594
+ {/* ICONS COLUMN */}
595
+ <div className="flex flex-col gap-[6px]">
596
+ {/* Origin Icon */}
597
+ {orignLabel ? (
598
+ <div className="w-[60px] h-[20px] flex items-center">
599
+ {orignLabel}
600
+ </div>
601
+ ) : (
602
+ <div className="h-[20px] flex items-center">
603
+ <img
604
+ src={serviceItem.icons?.origin}
605
+ alt="origin"
606
+ className={`w-[18px] h-auto mr-[8px] ${
607
+ isSoldOut ? "grayscale" : ""
608
+ }`}
609
+ />
610
+ </div>
611
+ )}
612
+
613
+ {/* Destination Icon */}
614
+ {!isCiva &&
615
+ (destinationLabel ? (
616
+ <div className="w-[60px] h-[20px] flex items-center">
617
+ {destinationLabel}
618
+ </div>
619
+ ) : (
620
+ <div className="h-[20px] flex items-center">
621
+ <img
622
+ src={serviceItem.icons?.destination}
623
+ className={`w-[18px] h-auto mr-[8px] ${
624
+ isSoldOut ? "grayscale" : ""
625
+ }`}
626
+ style={{ opacity: isSoldOut ? 0.5 : 1 }}
627
+ />
628
+ </div>
629
+ ))}
630
+ </div>
631
+
632
+ {/* DATES COLUMN */}
633
+ <div className="flex flex-col gap-[6px]">
634
+ {/* Departure Date */}
635
+ <StageTooltip
636
+ stageData={serviceItem.boarding_stages}
637
+ direction={1}
638
+ terminals={busStage}
639
+ serviceItem={serviceItem}
640
+ metaData={metaData}
641
+ colors={colors}
642
+ >
643
+ <span className="cursor-pointer bold-text capitalize">
644
+ {DateService.getServiceItemDate(
645
+ serviceItem.travel_date,
646
+ )}
647
+ </span>
648
+ </StageTooltip>
649
+
650
+ {/* Arrival Date */}
651
+ {!isCiva && (
652
+ <StageTooltip
653
+ stageData={serviceItem.boarding_stages}
654
+ direction={1}
655
+ terminals={busStage}
656
+ serviceItem={serviceItem}
657
+ metaData={metaData}
658
+ colors={colors}
659
+ >
660
+ <span className="cursor-pointer bold-text capitalize">
661
+ {DateService.getServiceItemDate(
662
+ serviceItem.arrival_date,
663
+ )}
664
+ </span>
665
+ </StageTooltip>
666
+ )}
667
+ </div>
668
+
669
+ {/* DOTS COLUMN */}
670
+ <div className="flex flex-col gap-[6px] items-center">
671
+ {/* Departure Dot */}
672
+ <div className="h-[20px] flex items-center justify-center">
673
+ <div>•</div>
674
+ </div>
675
+
676
+ {/* Arrival Dot */}
677
+ {!isCiva && (
678
+ <div className="h-[20px] flex items-center justify-center">
679
+ {removeArrivalTime ? null : serviceItem.arr_time ? (
680
+ <div>•</div>
681
+ ) : null}
682
+ </div>
683
+ )}
684
+ </div>
685
+
686
+ {/* TIMES COLUMN */}
687
+ <div className="flex flex-col gap-[6px]">
688
+ {/* Departure Time */}
689
+ <StageTooltip
690
+ stageData={serviceItem.dropoff_stages}
691
+ direction={2}
692
+ terminals={busStage}
693
+ serviceItem={serviceItem}
694
+ metaData={metaData}
695
+ colors={colors}
696
+ >
697
+ <div className="font-[900] bold-text">
698
+ {isLinatal ? (
699
+ <>
700
+ {cleanedDepTime}{" "}
701
+ <span>{hasPM ? "PM" : hasAM ? "AM" : ""}</span>
702
+ {!serviceItem?.dep_time.includes("AM") &&
703
+ !serviceItem?.dep_time.includes("PM") &&
704
+ DateService.ampmOnly(serviceItem.dep_time)}
705
+ </>
706
+ ) : (
707
+ DateService.formatTime(serviceItem.dep_time)
708
+ )}
709
+ </div>
710
+ </StageTooltip>
711
+
712
+ {/* Arrival Time */}
713
+ {!isCiva && (
714
+ <StageTooltip
715
+ stageData={serviceItem.dropoff_stages}
716
+ direction={2}
717
+ terminals={busStage}
718
+ serviceItem={serviceItem}
719
+ metaData={metaData}
720
+ colors={colors}
721
+ >
722
+ <div className="font-[900] bold-text">
723
+ {removeArrivalTime
724
+ ? null
725
+ : serviceItem.arr_time
726
+ ? DateService.formatTime(serviceItem.arr_time)
727
+ : null}
728
+ </div>
729
+ </StageTooltip>
730
+ )}
731
+ </div>
732
+ </div>
528
733
  <div
529
734
  style={{
530
735
  width: "1px",
@@ -534,84 +739,139 @@ function ServiceItemPB({
534
739
  ></div>
535
740
  {/* SEATS */}
536
741
  <div className="content-center">
537
- <SeatSection
538
- seatTypes={serviceItem.seat_types}
539
- availableSeats={serviceItem.available_seats}
540
- isSoldOut={isSoldOut}
541
- priceColor={colors.priceColor}
542
- currencySign={currencySign}
543
- removeDuplicateSeats={removeDuplicateSeats}
544
- isPeru={isPeru}
545
- />
546
- </div>
547
-
548
- {/* BUTTON */}
549
-
550
- <div>
551
- {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
+ >
552
752
  <div
553
- className="flex justify-end mr-[11px] "
753
+ className="flex flex-col justify-between"
754
+ style={{ gap: "10px" }}
755
+ >
756
+ {getSeatNames()}
757
+ </div>
758
+ <div
759
+ className="flex flex-col justify-between absolute inset-y-0 right-0 left-1/2 h-full"
554
760
  style={{
555
- marginBottom:
556
- serviceItem?.available_seats < 10 &&
557
- serviceItem?.available_seats > 0
558
- ? "6px"
559
- : "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",
560
772
  }}
561
773
  >
562
- {serviceItem?.available_seats < 10 &&
563
- serviceItem?.available_seats > 0 && (
564
- <div className="text-[12.5px] text-[#464647] mt-1 text-center">
565
- ¡Últimos Asientos!
566
- </div>
567
- )}
774
+ {getSeatPrice()}
568
775
  </div>
569
- ) : null}
570
- <KuposButton
571
- isSoldOut={isSoldOut}
572
- isLoading={serviceDetailsLoading}
573
- buttonColor={colors.kuposButtonColor}
574
- buyLabel={translation?.buyButton}
575
- soldOutLabel={translation?.soldOutButton}
576
- soldOutIcon={renderIcon("soldOutIcon", "14px")}
577
- onClick={checkMidnight}
578
- />
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>
579
812
  </div>
580
813
  </div>
581
- <BottomAmenities
582
- otherItems={otherItems}
583
- serviceItem={serviceItem}
584
- grayscaleClass={grayscaleClass}
585
- isSoldOut={isSoldOut}
586
- isItemExpanded={isItemExpanded}
587
- colors={colors}
588
- translation={translation}
589
- getAnimationIcon={getAnimationIcon}
590
- downArrowIcon={renderIcon("downArrow", "10px")}
591
- onToggleExpand={() =>
592
- setIsExpand &&
593
- setIsExpand(isItemExpanded ? null : serviceItem.id)
594
- }
595
- />
596
- </div>
597
- </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}
598
824
 
599
- {/* 🔹 EXPANDABLE DROPDOWN (below the card) */}
600
- <div
601
- style={{
602
- display: "grid",
603
- gridTemplateRows: isItemExpanded ? "1fr" : "0fr",
604
- opacity: isItemExpanded ? 1 : 0,
605
- transition:
606
- "grid-template-rows 0.3s ease-in-out, opacity 0.25s ease-in-out",
607
- position: "relative",
608
- zIndex: -1,
609
- }}
610
- >
611
- <div
612
- style={{ overflow: "hidden", minHeight: 0, marginTop: "-10px" }}
613
- >
614
- <ExpandedDropdown serviceItem={serviceItem} />
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
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}
866
+ </div>
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}
873
+ </div>
874
+ </div>
615
875
  </div>
616
876
  </div>
617
877
 
@@ -619,63 +879,27 @@ function ServiceItemPB({
619
879
  {/* Bottom discount banner */}
620
880
  {serviceItem?.offer_text && (
621
881
  <div
622
- 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]`}
623
883
  style={{
624
- backgroundColor: colors?.bottomStripColor,
884
+ backgroundColor: isSoldOut
885
+ ? colors?.bottomStripColor
886
+ : colors?.bottomStripColor,
625
887
  opacity: isSoldOut ? 0.5 : 1,
626
888
  }}
627
889
  >
628
- {/* <div className="flex justify-between items-center w-full">
629
- <div className="flex items-center">
630
- <LottiePlayer
631
- animationData={getAnimationIcon("bombAnimation")}
632
- width="18px"
633
- height="18px"
634
- />
635
- <span className="bold-text ml-[8px]">
636
- {" "}
637
- {serviceItem?.offer_text || ""}
638
- </span>
639
- </div>
640
- <div>
641
- Termina en&nbsp;
642
- <span
643
- className="bold-text text-end"
644
- ref={startCountdown}
645
- style={{
646
- fontVariantNumeric: "tabular-nums",
647
- display: "inline-block",
648
- // minWidth: "70px",
649
- }}
650
- />
651
- </div>
652
- </div> */}
653
890
  <LottiePlayer
654
- animationData={getAnimationIcon("bombAnimation")}
891
+ animationData={getAnimationIcon("promoAnim")}
655
892
  width="18px"
656
893
  height="18px"
657
894
  />
658
- <div className="flex items-center mt-[2px]">
659
- <span className="bold-text ml-[6px]">
660
- {serviceItem?.offer_text || ""}&nbsp;
661
- </span>{" "}
662
- | Termina en&nbsp;
663
- <span
664
- className="bold-text text-end"
665
- ref={startCountdown}
666
- style={{
667
- fontVariantNumeric: "tabular-nums",
668
- display: "inline-block",
669
- // minWidth: "70px",
670
- }}
671
- />
672
- </div>
895
+ <span className="ml-[10px]">{serviceItem?.offer_text}</span>
673
896
  </div>
674
897
  )}
898
+
675
899
  <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[15px] z-10 ">
676
900
  {showTopLabel && (
677
901
  <div
678
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-10`}
902
+ className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
679
903
  style={{
680
904
  backgroundColor: isSoldOut
681
905
  ? "#ddd"
@@ -715,7 +939,7 @@ function ServiceItemPB({
715
939
  <div>{"Conexión"}</div>
716
940
  </div>
717
941
  )}
718
- {/* {serviceItem?.is_direct_trip && (
942
+ {serviceItem?.is_direct_trip && (
719
943
  <div
720
944
  className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20 `}
721
945
  style={{
@@ -730,7 +954,7 @@ function ServiceItemPB({
730
954
  />
731
955
  <div>{translation?.directService}</div>
732
956
  </div>
733
- )} */}
957
+ )}
734
958
  {serviceItem?.train_type_label === "Tren Express (Nuevo)" && (
735
959
  <div
736
960
  className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20 `}