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
@@ -3,11 +3,6 @@ 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";
11
6
 
12
7
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
13
8
 
@@ -33,8 +28,6 @@ function ServiceItemMobile({
33
28
  amenitiesData,
34
29
  setShowDropdown,
35
30
  showDropdown,
36
- isExpanded,
37
- setIsExpanded,
38
31
  setAmenetiesAtomValue,
39
32
  isCiva,
40
33
  currencySign,
@@ -44,41 +37,8 @@ function ServiceItemMobile({
44
37
  removeDuplicateSeats,
45
38
  isLinatal,
46
39
  }: MobileServiceItemProps): React.ReactElement {
47
- const isItemExpanded = serviceItem.id === isExpanded;
48
40
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
49
41
  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
- };
82
42
 
83
43
  const labelId =
84
44
  typeof serviceItem.boarding_stages === "string"
@@ -101,6 +61,194 @@ function ServiceItemMobile({
101
61
  }
102
62
  };
103
63
 
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
+
104
252
  const amenities = () => {
105
253
  const raw = serviceItem?.operator_details?.[3];
106
254
  const list = Array.isArray(raw)
@@ -113,8 +261,8 @@ function ServiceItemMobile({
113
261
  <img
114
262
  key={i}
115
263
  className="amenity"
116
- height={12}
117
- width={12}
264
+ height={14}
265
+ width={14}
118
266
  // src={getAmenitiesImage(amenitiesData?.[am]?.toLowerCase())}
119
267
  src={commonService.getAmenitiesImage(
120
268
  amenitiesData?.[am]?.toLowerCase(),
@@ -150,6 +298,32 @@ function ServiceItemMobile({
150
298
  isConexion = true;
151
299
  }
152
300
 
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
+
153
327
  return (
154
328
  <div
155
329
  className={`relative ${
@@ -164,39 +338,23 @@ function ServiceItemMobile({
164
338
  } `}
165
339
  style={{ backgroundColor: "#fff", zIndex: 1 }}
166
340
  >
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
- /> */}
186
341
  <div
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
- }}
342
+ className={"border border-[#E6E6E6] rounded-[20px]"}
343
+ style={{ backgroundColor: "#fff", zIndex: 1 }}
195
344
  >
196
- <div style={{ padding: "12px 12px 8px 12px" }}>
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
+ >
197
355
  {/* Header with operator info and favorite */}
198
- <div className="flex justify-between items-center mb-[10px]">
199
- <div className="flex items-center justify-between">
356
+ <div className="flex justify-between mb-[8px]">
357
+ <div className="flex items-center w-[50%] justify-between">
200
358
  <div className="w-[120px] overflow-y-hidden">
201
359
  <img
202
360
  src={serviceItem.operator_details[0]}
@@ -211,99 +369,296 @@ function ServiceItemMobile({
211
369
  {serviceItem?.operator_details[2]}
212
370
  </div>
213
371
  ) : showRating ? (
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>
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>
238
383
  </div>
239
384
  ) : null}
240
385
  </div>
241
386
  {showLastSeats ? (
242
- <div className="flex justify-end ">
387
+ <div className="flex justify-end -mt-[5px] -mb-[5px] items-center pt-[5px] pb-[5px] text-center ">
243
388
  {serviceItem?.available_seats < 10 &&
244
389
  serviceItem?.available_seats > 0 && (
245
- <div className="text-[10px] text-[#464647] text-center">
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
+ >
246
397
  ¡ Últimos Asientos!
247
- </div>
398
+ </span>
248
399
  )}
249
400
  </div>
250
401
  ) : null}
251
402
  </div>
252
403
 
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
- />
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 === true && (
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 === true && (
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>
307
662
  </div>
308
663
 
309
664
  {serviceItem?.offer_text && (
@@ -317,58 +672,21 @@ function ServiceItemMobile({
317
672
  opacity: isSoldOut ? 0.5 : 1,
318
673
  }}
319
674
  >
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> */}
345
675
  <LottiePlayer
346
676
  animationData={serviceItem.icons.promoAnim}
347
677
  width="18px"
348
678
  height="18px"
349
679
  />
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>
680
+ <span className="ml-[10px] text-[#fff] min-[380px]:text-[11px] text-[10px]">
681
+ {serviceItem?.offer_text}
682
+ </span>
365
683
  </div>
366
684
  )}
367
685
 
368
- <div className="absolute -top-[14px] left-0 w-full flex items-center justify-end gap-[12px] pr-[13px] z-10 ">
686
+ <div className="absolute -top-[14px] left-0 w-full flex items-center justify-end gap-[12px] pr-[20px] z-10 ">
369
687
  {showTopLabel && (
370
688
  <div
371
- className={`flex items-center gap-[2px] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
689
+ className={`flex items-center gap-[2px] py-[5px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
372
690
  style={{
373
691
  backgroundColor: isSoldOut ? "#ccc" : colors.ratingBottomColor,
374
692
  }}
@@ -389,6 +707,7 @@ function ServiceItemMobile({
389
707
  </div>
390
708
  </div>
391
709
  )}
710
+
392
711
  {isConexion && (
393
712
  <div
394
713
  className={`flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[11px] z-20 ${
@@ -403,7 +722,22 @@ function ServiceItemMobile({
403
722
  <div>Conexión</div>
404
723
  </div>
405
724
  )}
406
-
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
+ )}
407
741
  {serviceItem?.train_type_label === "Tren Express (Nuevo)" && (
408
742
  <div
409
743
  className={`flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20 `}
@@ -421,23 +755,6 @@ function ServiceItemMobile({
421
755
  )}
422
756
  </div>
423
757
  </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>
441
758
  </div>
442
759
  );
443
760
  }