kupos-ui-components-lib 9.1.1 → 9.1.2

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 (47) hide show
  1. package/README copy.md +223 -67
  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 +32 -33
  5. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  6. package/dist/components/ServiceItem/ServiceItemDesktop.js +147 -267
  7. package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
  8. package/dist/components/ServiceItem/ServiceItemMobile.js +87 -278
  9. package/dist/components/ServiceItem/mobileTypes.d.ts +5 -0
  10. package/dist/components/ServiceItem/types.d.ts +7 -0
  11. package/dist/styles.css +131 -32
  12. package/dist/ui/AmenitiesBlock.js +23 -30
  13. package/dist/ui/BottomAmenities/BottomAmenities.js +0 -1
  14. package/dist/ui/DurationBlock.js +4 -4
  15. package/dist/ui/FlexibleBlock.js +5 -6
  16. package/dist/ui/PetBlock.js +1 -3
  17. package/dist/ui/RatingBlock.d.ts +9 -1
  18. package/dist/ui/RatingBlock.js +7 -3
  19. package/dist/utils/CommonService.d.ts +1 -1
  20. package/dist/utils/CommonService.js +2 -0
  21. package/package.json +2 -1
  22. package/src/assets/images/anims/service_list/bomb.json +1 -0
  23. package/src/assets/images/anims/service_list/directo.json +1 -1
  24. package/src/components/ServiceItem/PeruServiceItemDesktop.tsx +1 -0
  25. package/src/components/ServiceItem/RatingHover.tsx +44 -45
  26. package/src/components/ServiceItem/ServiceItemDesktop.tsx +313 -537
  27. package/src/components/ServiceItem/ServiceItemMobile.tsx +213 -530
  28. package/src/components/ServiceItem/mobileTypes.ts +5 -0
  29. package/src/components/ServiceItem/types.ts +7 -0
  30. package/src/ui/AmenitiesBlock.tsx +50 -29
  31. package/src/ui/BottomAmenities/BottomAmenities.tsx +110 -0
  32. package/src/ui/DateTimeSection/DateTimeSection.tsx +207 -0
  33. package/src/ui/DirectoBlock.tsx +31 -0
  34. package/src/ui/DurationBlock.tsx +4 -4
  35. package/src/ui/ExpendedDropDown/ExpandedDropdown.tsx +103 -0
  36. package/src/ui/FlexibleBlock.tsx +6 -5
  37. package/src/ui/KuposButton/KuposButton.tsx +48 -0
  38. package/src/ui/PetBlock.tsx +2 -2
  39. package/src/ui/RatingBlock.tsx +18 -6
  40. package/src/ui/SeatSection/SeatSection.tsx +207 -0
  41. package/src/ui/TopAmenities/TopAmenities.tsx +127 -0
  42. package/src/ui/mobileweb/BottomAmenitiesMobile.tsx +169 -0
  43. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +192 -0
  44. package/src/ui/mobileweb/ExpandedDropdownMobile.tsx +56 -0
  45. package/src/ui/mobileweb/SeatSectionMobile.tsx +256 -0
  46. package/src/ui/mobileweb/TopAmenitieMobile.tsx +126 -0
  47. package/src/utils/CommonService.ts +2 -0
@@ -3,6 +3,11 @@ import LottiePlayer from "../../assets/LottiePlayer";
3
3
  import DateService from "../../utils/DateService";
4
4
  import { MobileServiceItemProps } from "./mobileTypes";
5
5
  import commonService from "../../utils/CommonService";
6
+ import TopAmenitieMobile from "../../ui/mobileweb/TopAmenitieMobile";
7
+ import BottomAmenitiesMobile from "../../ui/mobileweb/BottomAmenitiesMobile";
8
+ import SeatSectionMobile from "../../ui/mobileweb/SeatSectionMobile";
9
+ import DateTimeSectionMobile from "../../ui/mobileweb/DateTimeSectionMobile";
10
+ import ExpandedDropdownMobile from "../../ui/mobileweb/ExpandedDropdownMobile";
6
11
 
7
12
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
8
13
 
@@ -28,6 +33,8 @@ function ServiceItemMobile({
28
33
  amenitiesData,
29
34
  setShowDropdown,
30
35
  showDropdown,
36
+ isExpanded,
37
+ setIsExpanded,
31
38
  setAmenetiesAtomValue,
32
39
  isCiva,
33
40
  currencySign,
@@ -37,8 +44,41 @@ function ServiceItemMobile({
37
44
  removeDuplicateSeats,
38
45
  isLinatal,
39
46
  }: MobileServiceItemProps): React.ReactElement {
47
+ const isItemExpanded = serviceItem.id === isExpanded;
40
48
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
41
49
  let isSoldOut = serviceItem.available_seats <= 0;
50
+ const showPromo = Math.random() > 0.5;
51
+
52
+ const countdownSeconds = 7830;
53
+
54
+ const startCountdown = (node: HTMLSpanElement | null) => {
55
+ if (!node) return;
56
+
57
+ const prevId = node.dataset.countdownId;
58
+ if (prevId) clearInterval(Number(prevId));
59
+
60
+ let remaining = countdownSeconds;
61
+
62
+ const formatTime = (totalSecs: number) => {
63
+ const h = Math.floor(totalSecs / 3600);
64
+ const m = Math.floor((totalSecs % 3600) / 60);
65
+ const s = totalSecs % 60;
66
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
67
+ };
68
+
69
+ node.textContent = formatTime(remaining);
70
+
71
+ const id = setInterval(() => {
72
+ remaining -= 1;
73
+ if (remaining <= 0) {
74
+ remaining = 0;
75
+ clearInterval(id);
76
+ }
77
+ node.textContent = formatTime(remaining);
78
+ }, 1000);
79
+
80
+ node.dataset.countdownId = String(id);
81
+ };
42
82
 
43
83
  const labelId =
44
84
  typeof serviceItem.boarding_stages === "string"
@@ -61,194 +101,6 @@ function ServiceItemMobile({
61
101
  }
62
102
  };
63
103
 
64
- const seatTypes = () => {
65
- let seatTypes = serviceItem.seat_types
66
- ?.filter((item) => getFilteredSeats(item.label))
67
- ?.sort((a, b) => a.fare - b.fare) // Add this line to sort by fare
68
- ?.slice(0, 2)
69
- ?.map((type, i) =>
70
- exceptions.includes(type.label) ? null : (
71
- <div
72
- className={
73
- serviceItem.seat_types?.length > 2
74
- ? "w-[100%] flex flex-row justify-between "
75
- : "w-[100%] flex flex-row justify-between items-center"
76
- }
77
- key={i}
78
- >
79
- <span
80
- className="min-[420]:text-[13px] text-[12px] "
81
- style={{
82
- // marginLeft: "10px",
83
- color: isSoldOut ? "#bbb" : "#464647",
84
- }}
85
- >
86
- {type.label}
87
- </span>
88
- <span
89
- className={"min-[420]:text-[13px] text-[12px] bold-text"}
90
- style={{ color: isSoldOut ? "#bbb" : colors.seatPriceColor }}
91
- >
92
- {commonService.currency(type.fare, currencySign)}
93
- </span>
94
- </div>
95
- ),
96
- );
97
- return seatTypes;
98
- };
99
-
100
- const seatTypesWithRemoveDuplicateSeats = () => {
101
- const seatMap = new Map();
102
-
103
- serviceItem.seat_types
104
- ?.filter((item) => getFilteredSeats(item.label))
105
- ?.forEach((item) => {
106
- if (exceptions.includes(item.key as any)) return;
107
- const existing = seatMap.get(item.label);
108
- if (
109
- !existing ||
110
- parseFloat(item.fare as any) < parseFloat(existing.fare)
111
- ) {
112
- seatMap.set(item.label, item);
113
- }
114
- });
115
-
116
- const uniqueSeats = Array.from(seatMap.values())
117
- .sort((a, b) => a.fare - b.fare)
118
- .slice(0, 3);
119
- return uniqueSeats.map((type, i) => {
120
- return exceptions.includes(type.label) ? null : (
121
- <div
122
- className={
123
- serviceItem.seat_types?.length > 2
124
- ? "w-[100%] flex flex-row justify-between "
125
- : "w-[100%] flex flex-row justify-between items-center"
126
- }
127
- key={i}
128
- >
129
- <span
130
- className="min-[420]:text-[13px] text-[11px] "
131
- style={{
132
- // marginLeft: "10px",
133
- color: isSoldOut ? "#bbb" : "#464647",
134
- }}
135
- >
136
- {commonService.truncateSeatLabel(type.label)}
137
- </span>
138
- <span
139
- className={"min-[420]:text-[13px] text-[11px] bold-text"}
140
- style={{ color: isSoldOut ? "#bbb" : colors.seatPriceColor }}
141
- >
142
- {/* {serviceItem?.operator_service_name === "Pullman San Andrés" &&
143
- serviceItem?.available_seats <= 0
144
- ? null
145
- : commonService.currency(type.fare, currencySign)} */}
146
-
147
- {serviceItem?.available_seats <= 0 && !isPeru
148
- ? commonService.currency(0, currencySign)
149
- : commonService.currency(type.fare, currencySign)}
150
- </span>
151
- </div>
152
- );
153
- });
154
- };
155
-
156
- const getFilteredSeats = (item) => {
157
- return item;
158
- };
159
-
160
- const getAmenitiesImage = (name: string): string => {
161
- switch (name) {
162
- case "air_condtion.png": {
163
- return serviceItem?.icons?.airConditionIcon;
164
- }
165
- case "baggage.png": {
166
- return serviceItem?.icons?.baggageIcon;
167
- }
168
- case "charging_plug.png": {
169
- return serviceItem?.icons?.chargingIcon;
170
- }
171
- case "coffee.png": {
172
- return serviceItem?.icons?.coffeeIcon;
173
- }
174
- case "food_new_icon.png": {
175
- return serviceItem?.icons?.foodIcon;
176
- }
177
- case "gaming.png": {
178
- return serviceItem?.icons?.gamingIcon;
179
- }
180
- case "handicap.png": {
181
- return serviceItem?.icons?.handicapIcon;
182
- }
183
- case "mobile_ticket.png": {
184
- return serviceItem?.icons?.mobileTicketIcon;
185
- }
186
- case "movie.png": {
187
- return serviceItem?.icons?.movieIcon;
188
- }
189
- case "restrooms.png": {
190
- return serviceItem?.icons?.restroomsIcon;
191
- }
192
- case "snacks_new.png": {
193
- return serviceItem?.icons?.snackIcon;
194
- }
195
- case "wifi.png": {
196
- return serviceItem?.icons?.wifiIcon;
197
- }
198
- case "cortina_divisoria.png": {
199
- return serviceItem?.icons?.cortinaIcon;
200
- }
201
- case "frazada.png": {
202
- return serviceItem?.icons?.frazaIcon;
203
- }
204
- case "blanket.png": {
205
- return serviceItem?.icons?.blankketIcon;
206
- }
207
- case "cctv.png": {
208
- return serviceItem?.icons?.cctvIcon;
209
- }
210
- case "cup_holder.png": {
211
- return serviceItem?.icons?.cupHolderIcon;
212
- }
213
- case "emergency_contact.png": {
214
- return serviceItem?.icons?.emergencyContactIcon;
215
- }
216
- case "emergency_exit.png": {
217
- return serviceItem?.icons?.emergencyExitIcon;
218
- }
219
- case "fire_extinguisher.png": {
220
- return serviceItem?.icons?.fireExtinguisherIcon;
221
- }
222
- case "reading_light.png": {
223
- return serviceItem?.icons?.readingLIghtIcon;
224
- }
225
- case "seat_belt.png": {
226
- return serviceItem?.icons?.sercurityBeltIcon;
227
- }
228
- case "service_on_board.png": {
229
- return serviceItem?.icons?.serviceOnBoardIcon;
230
- }
231
- case "personal_tv.png": {
232
- return serviceItem?.icons?.personalTVIcon;
233
- }
234
- case "pet_admission.png": {
235
- return serviceItem?.icons?.petAdmissionIcon;
236
- }
237
- case "music.png": {
238
- return serviceItem?.icons?.musicIcon;
239
- }
240
- case "headset.png": {
241
- return serviceItem?.icons?.headPhoneIcon;
242
- }
243
- case "allows_cancellation.png": {
244
- return serviceItem?.icons?.allowCancellationIcon;
245
- }
246
- default: {
247
- return "";
248
- }
249
- }
250
- };
251
-
252
104
  const amenities = () => {
253
105
  const raw = serviceItem?.operator_details?.[3];
254
106
  const list = Array.isArray(raw)
@@ -261,8 +113,8 @@ function ServiceItemMobile({
261
113
  <img
262
114
  key={i}
263
115
  className="amenity"
264
- height={14}
265
- width={14}
116
+ height={12}
117
+ width={12}
266
118
  // src={getAmenitiesImage(amenitiesData?.[am]?.toLowerCase())}
267
119
  src={commonService.getAmenitiesImage(
268
120
  amenitiesData?.[am]?.toLowerCase(),
@@ -298,32 +150,6 @@ function ServiceItemMobile({
298
150
  isConexion = true;
299
151
  }
300
152
 
301
- const depTime = serviceItem.dep_time || "";
302
-
303
- // Extract hours and minutes and check for AM/PM
304
- const hasAM = depTime.includes("AM");
305
- const hasPM = depTime.includes("PM");
306
- const [timePart] = depTime.split(/AM|PM/).map((part) => part.trim());
307
- const [hour, minute] = timePart.split(":").map(Number);
308
-
309
- // Convert to 24-hour format
310
- let cleanedDepTime;
311
- if (hasAM) {
312
- cleanedDepTime =
313
- hour === 12
314
- ? `00:${minute < 10 ? "0" + minute : minute}`
315
- : `${hour < 10 ? "0" + hour : hour}:${
316
- minute < 10 ? "0" + minute : minute
317
- }`;
318
- } else if (hasPM) {
319
- cleanedDepTime =
320
- hour === 12
321
- ? `${hour}:${minute < 10 ? "0" + minute : minute}`
322
- : `${hour + 12}:${minute < 10 ? "0" + minute : minute}`;
323
- } else {
324
- cleanedDepTime = timePart;
325
- }
326
-
327
153
  return (
328
154
  <div
329
155
  className={`relative ${
@@ -338,23 +164,39 @@ function ServiceItemMobile({
338
164
  } `}
339
165
  style={{ backgroundColor: "#fff", zIndex: 1 }}
340
166
  >
167
+ {/* <TopAmenitieMobile
168
+ showTopLabel={showTopLabel}
169
+ isSoldOut={isSoldOut}
170
+ seatPriceColor={colors.seatPriceColor}
171
+ bombAnim={serviceItem.icons.bombAnim}
172
+ priorityStageAnim={serviceItem.icons.priorityStageAnim}
173
+ countdownSeconds={countdownSeconds}
174
+ onCountdownEnd={() => {
175
+ const cardEl = document.getElementById(
176
+ `service-card-${serviceItem.id}`,
177
+ );
178
+ if (!cardEl) return;
179
+ cardEl.style.border = "1px solid #ccc";
180
+ if (!showTopLabel) {
181
+ cardEl.style.borderRadius = "10px";
182
+ }
183
+ }}
184
+ offerText={serviceItem?.offer_text}
185
+ /> */}
341
186
  <div
342
- className={"border border-[#E6E6E6] rounded-[20px]"}
343
- style={{ backgroundColor: "#fff", zIndex: 1 }}
187
+ className={" rounded-[20px]"}
188
+ style={{
189
+ backgroundColor: "#fff",
190
+ zIndex: 1,
191
+ // borderRadius: showTopLabel ? "10px 0 10px 10px" : "10px",
192
+ borderRadius: "12px",
193
+ border: "1px solid #ccc",
194
+ }}
344
195
  >
345
- <div
346
- className={`p-[12px] ${
347
- serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
348
- showTopLabel ||
349
- serviceItem?.is_direct_trip ||
350
- serviceItem?.is_transpordo
351
- ? "mt-[10px]"
352
- : ""
353
- }`}
354
- >
196
+ <div style={{ padding: "12px 12px 8px 12px" }}>
355
197
  {/* Header with operator info and favorite */}
356
- <div className="flex justify-between mb-[8px]">
357
- <div className="flex items-center w-[50%] justify-between">
198
+ <div className="flex justify-between items-center mb-[10px]">
199
+ <div className="flex items-center justify-between">
358
200
  <div className="w-[120px] overflow-y-hidden">
359
201
  <img
360
202
  src={serviceItem.operator_details[0]}
@@ -369,296 +211,99 @@ function ServiceItemMobile({
369
211
  {serviceItem?.operator_details[2]}
370
212
  </div>
371
213
  ) : showRating ? (
372
- <div className="flex min-[420]:text-[13px] text-[12px] bold-text items-center">
373
- <img
374
- src={serviceItem.icons.rating}
375
- alt="origin"
376
- className={`w-[12px] h-[12px] mr-[4px] object-contain ${
377
- isSoldOut ? "grayscale" : ""
378
- }`}
379
- />
380
- <span style={{ lineHeight: "normal" }}>
381
- {getServiceStars(serviceItem)}
382
- </span>
214
+ <div className="flex min-[420]:text-[13px] text-[12px] items-center">
215
+ <div className="flex items-center">
216
+ <div className="flex items-center">
217
+ <img
218
+ src={serviceItem.icons.rating}
219
+ alt="origin"
220
+ className={`w-[12px] h-[12px] mr-[4px] object-contain ${
221
+ isSoldOut ? "grayscale" : ""
222
+ }`}
223
+ />
224
+ <span style={{ lineHeight: "normal" }}>
225
+ {getServiceStars(serviceItem)}
226
+ </span>
227
+ </div>
228
+
229
+ <div
230
+ className="flex items-center cursor-pointer "
231
+ style={{ color: isSoldOut ? "#bbb" : "text-[#464647]" }}
232
+ >
233
+ <span className="ml-[3px] min-[420]:text-[13px] text-[12px] text-ellipsis">
234
+ {serviceItem.operator_details[2]}
235
+ </span>
236
+ </div>
237
+ </div>
383
238
  </div>
384
239
  ) : null}
385
240
  </div>
386
241
  {showLastSeats ? (
387
- <div className="flex justify-end -mt-[5px] -mb-[5px] items-center pt-[5px] pb-[5px] text-center ">
242
+ <div className="flex justify-end ">
388
243
  {serviceItem?.available_seats < 10 &&
389
244
  serviceItem?.available_seats > 0 && (
390
- <span
391
- className="text-[12px] text-[red] mt-1 flex justify-end pt-[5px] pb-[5px] pl-[15px] pr-[15px] rounded-[8px] "
392
- style={{
393
- backgroundColor: colors.lastSeatBg,
394
- color: colors.lastSeatText,
395
- }}
396
- >
245
+ <div className="text-[10px] text-[#464647] text-center">
397
246
  ¡ Últimos Asientos!
398
- </span>
247
+ </div>
399
248
  )}
400
249
  </div>
401
250
  ) : null}
402
251
  </div>
403
252
 
404
- <div
405
- className="flex justify-between gap-[5px] w-full"
406
- onClick={onBookButtonPress}
407
- >
408
- {/* DATE AND TIME */}
409
- <div
410
- className="min-h-[2.5rem] flex flex-col justify-between gap-[4px] w-[50%] "
411
- style={{ justifyContent: isCiva && "center" }}
412
- >
413
- <div
414
- className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
415
- isSoldOut ? "text-[#c0c0c0]" : ""
416
- }`}
417
- >
418
- <div className="flex items-center" style={{ flex: 1 }}>
419
- <div>
420
- {" "}
421
- {orignLabel ? (
422
- <div className="w-[60px]">{orignLabel}</div>
423
- ) : (
424
- <div className="w-[14px] h-auto mr-[5px]">
425
- <img
426
- src={serviceItem.icons?.origin}
427
- alt="origin"
428
- className={`w-[14px] h-auto mr-[5px] ${
429
- isSoldOut ? "grayscale" : ""
430
- }`}
431
- />
432
- </div>
433
- )}
434
- </div>
435
- <div
436
- className="flex items-center relative capitalize justify-between "
437
- style={{ flex: 1 }}
438
- >
439
- <span className="cursor-pointer black-text">
440
- {DateService.getServiceItemDate(serviceItem.travel_date)}
441
- </span>
442
- <div className="absolute left-[50%]">•</div>
443
- <div className="font-[900] relative black-text">
444
- {isLinatal ? (
445
- <div>
446
- <span>
447
- {" "}
448
- {cleanedDepTime}{" "}
449
- <span>{hasPM ? "PM" : hasAM ? "AM" : ""}</span>
450
- </span>
451
- <span>
452
- {serviceItem?.dep_time.includes("AM") ||
453
- serviceItem?.dep_time.includes("PM")
454
- ? null
455
- : DateService.ampmOnly(serviceItem.dep_time)}
456
- </span>
457
- </div>
458
- ) : (
459
- DateService.formatTime(serviceItem.dep_time)
460
- )}
461
- </div>
462
- </div>
463
- </div>
464
- </div>
465
- {isCiva ? null : (
466
- <div
467
- className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
468
- isSoldOut ? "text-[#c0c0c0]" : ""
469
- }`}
470
- >
471
- <div className="flex items-center" style={{ flex: 1 }}>
472
- <div>
473
- {" "}
474
- {destinationLabel ? (
475
- <div className="w-[60px]">{destinationLabel}</div>
476
- ) : (
477
- <div className="w-[14px] h-auto mr-[5px]">
478
- <img
479
- src={serviceItem.icons?.destination}
480
- alt="destination"
481
- className={`w-[14px] h-auto mr-[5px] ${
482
- isSoldOut ? "grayscale" : ""
483
- }`}
484
- />
485
- </div>
486
- )}
487
- </div>
488
- <div
489
- className="flex items-center relative capitalize justify-between "
490
- style={{ flex: 1 }}
491
- >
492
- <span className="cursor-pointer black-text">
493
- {DateService.getServiceItemDate(
494
- serviceItem.arrival_date,
495
- )}
496
- </span>
497
- <div className="absolute left-[50%]">•</div>
498
- <div className="font-[900] relative black-text">
499
- {DateService.formatTime(serviceItem.arr_time)}
500
- </div>
501
- </div>
502
- </div>
503
- </div>
504
- )}
505
- </div>
506
- <div
507
- style={{
508
- width: "1px",
509
- height: "2.5rem",
510
- backgroundColor: "#ccc",
511
- margin: "auto",
512
- }}
513
- ></div>
514
- {/* SEATS */}
515
- <div
516
- className="content-center"
517
- style={{
518
- width: isPeru ? "40%" : "40%",
519
- }}
520
- >
521
- <div
522
- className="flex flex-col justify-between h-[2.5rem] "
523
- style={{
524
- gap: isSoldOut ? "0px" : "5px",
525
- justifyContent:
526
- serviceItem.seat_types?.length > 2
527
- ? "space-between"
528
- : "center",
529
- }}
530
- >
531
- {removeDuplicateSeats
532
- ? seatTypesWithRemoveDuplicateSeats()
533
- : seatTypes()}
534
-
535
- {isSoldOut ? (
536
- <div className="flex justify-end">
537
- <span
538
- className={
539
- "min-[420]:text-[13px] text-[12px] text-[#ccc]"
540
- }
541
- >
542
- Agotado
543
- </span>
544
- </div>
545
- ) : null}
546
- </div>
547
- </div>
548
- </div>
549
- <div className="bg-[#E6E6E6] -ml-[12px] -mr-[12px] mt-[10px] mb-[10px] h-[1px]"></div>
550
- <div
551
- className={`${"flex justify-between items-center items-center "}`}
552
- >
553
- {/* Rating */}
554
- <div>
555
- <div className="flex items-center ">
556
- <div
557
- className="flex items-center cursor-pointer "
558
- style={{ color: isSoldOut ? "#bbb" : "text-[#464647]" }}
559
- >
560
- <span className="ml-[3px] min-[420]:text-[13px] text-[12px] bold-text">
561
- {serviceItem.operator_details[2]}
562
- </span>
563
- </div>
564
- </div>
565
- </div>
566
-
567
- <div
568
- className="flex relative "
569
- style={{ color: isSoldOut ? "#bbb" : "text-[#464647]" }}
570
- >
571
- <div
572
- className={`w-[12px] h-auto mr-[2px] ${
573
- isSoldOut ? "grayscale" : ""
574
- }`}
575
- >
576
- {renderIcon("hours", "14px")}
577
- </div>
578
- &nbsp;
579
- <div
580
- className={`cursor-default group min-[420]:text-[13px] text-[12px] ${
581
- isSoldOut ? "text-[#c0c0c0]" : ""
582
- }`}
583
- style={{ lineHeight: "normal" }}
584
- >
585
- {serviceItem.duration}hrs
586
- </div>
587
- </div>
588
-
589
- <div style={{ opacity: isSoldOut ? 0.5 : 1 }}>{amenities()}</div>
590
-
591
- {(serviceItem.is_change_ticket || isPetSeat) && (
592
- <div
593
- onClick={() => {
594
- setShowDropdown(!showDropdown);
595
- setAmenetiesAtomValue({
596
- service: serviceItem,
597
- showTopLabel: showTopLabel,
598
- });
599
- }}
600
- className="flex items-center"
601
- >
602
- {serviceItem.pet_seat_info &&
603
- Object.keys(serviceItem.pet_seat_info).length > 0 ? (
604
- <div className="flex items-center">
605
- <div className={`relative group cursor-default `}>
606
- <div className="flex items-center">
607
- <div
608
- className={`mr-[5px] ${isSoldOut ? "grayscale" : ""}`}
609
- >
610
- <LottiePlayer
611
- animationData={serviceItem.icons.petFriendlyAnim}
612
- width="20px"
613
- height="20px"
614
- />
615
- </div>
616
- </div>
617
- </div>
618
- </div>
619
- ) : null}
620
-
621
- {/* Flexible ticket */}
622
- {serviceItem.is_change_ticket && (
623
- <div className="flex items-center">
624
- <div className="relative group cursor-default">
625
- <div className="flex items-center">
626
- <div
627
- className={`mr-[5px] ${isSoldOut ? "grayscale" : ""}`}
628
- >
629
- <LottiePlayer
630
- animationData={serviceItem.icons.flexibleAnim}
631
- width="20px"
632
- height="20px"
633
- />
634
- </div>
635
- </div>
636
- </div>
637
- </div>
638
- )}
639
-
640
- {serviceItem?.is_tracking_enabled && (
641
- <div className="flex items-center mr-[10px]">
642
- <div
643
- className={`h-auto mr-[4px] min-[420]:text-[13px] text-[11px] text-[#464647] ${
644
- isSoldOut ? "grayscale" : ""
645
- }`}
646
- >
647
- <LottiePlayer
648
- animationData={serviceItem.icons.locationAnim}
649
- width="20px"
650
- height="20px"
651
- />
652
- </div>
653
- </div>
654
- )}
655
-
656
- {(serviceItem.is_change_ticket || isPetSeat) && (
657
- <img src={serviceItem.icons.plus} alt="icon" width={11} />
658
- )}
659
- </div>
660
- )}
661
- </div>
253
+ <DateTimeSectionMobile
254
+ onBookButtonPress={onBookButtonPress}
255
+ isCiva={isCiva}
256
+ isSoldOut={isSoldOut}
257
+ isLinatal={isLinatal}
258
+ isPeru={isPeru}
259
+ orignLabel={orignLabel}
260
+ destinationLabel={destinationLabel}
261
+ originIcon={serviceItem.icons?.origin}
262
+ destinationIcon={serviceItem.icons?.destination}
263
+ travelDate={serviceItem.travel_date}
264
+ arrivalDate={serviceItem.arrival_date}
265
+ depTime={serviceItem.dep_time}
266
+ arrTime={serviceItem.arr_time}
267
+ seatTypes={serviceItem.seat_types}
268
+ seatPriceColor={colors.seatPriceColor}
269
+ currencySign={currencySign}
270
+ availableSeats={serviceItem.available_seats}
271
+ removeDuplicateSeats={removeDuplicateSeats}
272
+ />
273
+ {/* <div className="bg-[#E6E6E6] -ml-[12px] -mr-[12px] mt-[10px] mb-[10px] h-[1px]"></div> */}
274
+ <div className="bg-[#E6E6E6] mt-[10px] mb-[10px] h-[1px]"></div>
275
+
276
+ <BottomAmenitiesMobile
277
+ isSoldOut={isSoldOut}
278
+ amenitiesNodes={amenities()}
279
+ hoursIcon={renderIcon("hours", "14px")}
280
+ duration={serviceItem.duration?.toString()}
281
+ isDirectTrip={serviceItem?.is_direct_trip}
282
+ directoColor={colors.directoColor}
283
+ directoAnim={serviceItem.icons.directoAnim}
284
+ isChangeTicket={serviceItem.is_change_ticket}
285
+ isPetSeat={isPetSeat}
286
+ petSeatInfo={serviceItem.pet_seat_info}
287
+ petFriendlyAnim={serviceItem.icons.petFriendlyAnim}
288
+ flexibleAnim={serviceItem.icons.flexibleAnim}
289
+ isTrackingEnabled={serviceItem?.is_tracking_enabled}
290
+ locationAnim={serviceItem.icons.locationAnim}
291
+ downArrowIcon={serviceItem.icons.downArrow}
292
+ showDropdown={isItemExpanded}
293
+ setShowDropdown={() =>
294
+ setIsExpanded(isItemExpanded ? null : serviceItem.id)
295
+ }
296
+ // onDropdownToggle={() => {
297
+ // setShowDropdown(!showDropdown);
298
+ // setAmenetiesAtomValue({
299
+ // service: serviceItem,
300
+ // showTopLabel: showTopLabel,
301
+ // });
302
+ // }}
303
+ onDropdownToggle={() => {
304
+ setIsExpanded(isItemExpanded ? null : serviceItem.id);
305
+ }}
306
+ />
662
307
  </div>
663
308
 
664
309
  {serviceItem?.offer_text && (
@@ -672,21 +317,58 @@ function ServiceItemMobile({
672
317
  opacity: isSoldOut ? 0.5 : 1,
673
318
  }}
674
319
  >
320
+ {/* <div className="flex justify-between items-center w-full">
321
+ <div className="flex items-center">
322
+ <LottiePlayer
323
+ animationData={serviceItem.icons.bombAnim}
324
+ width="18px"
325
+ height="18px"
326
+ />
327
+ <span className="bold-text ml-[8px]">
328
+ {" "}
329
+ {serviceItem?.offer_text || ""}
330
+ </span>
331
+ </div>
332
+ <div>
333
+ Termina en&nbsp;
334
+ <span
335
+ className="bold-text text-end"
336
+ ref={startCountdown}
337
+ style={{
338
+ fontVariantNumeric: "tabular-nums",
339
+ display: "inline-block",
340
+ // minWidth: "70px",
341
+ }}
342
+ />
343
+ </div>
344
+ </div> */}
675
345
  <LottiePlayer
676
346
  animationData={serviceItem.icons.promoAnim}
677
347
  width="18px"
678
348
  height="18px"
679
349
  />
680
- <span className="ml-[10px] text-[#fff] min-[380px]:text-[11px] text-[10px]">
681
- {serviceItem?.offer_text}
682
- </span>
350
+ <div className=" flex items-center mt-[2px]">
351
+ <span className="ml-[6px] text-[#fff] min-[380px]:text-[11px] text-[6px]">
352
+ {serviceItem?.offer_text || ""}&nbsp;
353
+ </span>{" "}
354
+ | Termina en&nbsp;
355
+ <span
356
+ className="bold-text"
357
+ ref={startCountdown}
358
+ style={{
359
+ fontVariantNumeric: "tabular-nums",
360
+ display: "inline-block",
361
+ // minWidth: "70px",
362
+ }}
363
+ />
364
+ </div>
683
365
  </div>
684
366
  )}
685
367
 
686
- <div className="absolute -top-[14px] left-0 w-full flex items-center justify-end gap-[12px] pr-[20px] z-10 ">
368
+ <div className="absolute -top-[14px] left-0 w-full flex items-center justify-end gap-[12px] pr-[13px] z-10 ">
687
369
  {showTopLabel && (
688
370
  <div
689
- className={`flex items-center gap-[2px] py-[5px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
371
+ className={`flex items-center gap-[2px] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
690
372
  style={{
691
373
  backgroundColor: isSoldOut ? "#ccc" : colors.ratingBottomColor,
692
374
  }}
@@ -707,7 +389,6 @@ function ServiceItemMobile({
707
389
  </div>
708
390
  </div>
709
391
  )}
710
-
711
392
  {isConexion && (
712
393
  <div
713
394
  className={`flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[11px] z-20 ${
@@ -722,22 +403,7 @@ function ServiceItemMobile({
722
403
  <div>Conexión</div>
723
404
  </div>
724
405
  )}
725
- {serviceItem?.is_direct_trip && (
726
- <div
727
- className={`flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
728
- style={{
729
- backgroundColor: isSoldOut ? "#ddd" : colors.tooltipBgColor,
730
- color: isSoldOut ? "#bbb" : colors.directoColor,
731
- }}
732
- >
733
- <LottiePlayer
734
- animationData={serviceItem.icons.directoAnim}
735
- width="16px"
736
- height="16px"
737
- />
738
- <div className="ml-[5px]">Directo</div>
739
- </div>
740
- )}
406
+
741
407
  {serviceItem?.train_type_label === "Tren Express (Nuevo)" && (
742
408
  <div
743
409
  className={`flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
@@ -755,6 +421,23 @@ function ServiceItemMobile({
755
421
  )}
756
422
  </div>
757
423
  </div>
424
+
425
+ {/* 🔹 EXPANDABLE DROPDOWN (below the card) */}
426
+ <div
427
+ style={{
428
+ display: "grid",
429
+ gridTemplateRows: isItemExpanded ? "1fr" : "0fr",
430
+ opacity: isItemExpanded ? 1 : 0,
431
+ transition:
432
+ "grid-template-rows 0.3s ease-in-out, opacity 0.25s ease-in-out",
433
+ position: "relative",
434
+ zIndex: -1,
435
+ }}
436
+ >
437
+ <div style={{ overflow: "hidden", minHeight: 0, marginTop: "-10px" }}>
438
+ <ExpandedDropdownMobile serviceItem={serviceItem} />
439
+ </div>
440
+ </div>
758
441
  </div>
759
442
  );
760
443
  }