kupos-ui-components-lib 10.4.5 → 10.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.css CHANGED
@@ -189,9 +189,6 @@
189
189
  .m-\[auto\] {
190
190
  margin: auto;
191
191
  }
192
- .mx-\[6px\] {
193
- margin-inline: 6px;
194
- }
195
192
  .mx-auto {
196
193
  margin-inline: auto;
197
194
  }
@@ -375,12 +372,12 @@
375
372
  .h-\[12px\] {
376
373
  height: 12px;
377
374
  }
378
- .h-\[13px\] {
379
- height: 13px;
380
- }
381
375
  .h-\[14px\] {
382
376
  height: 14px;
383
377
  }
378
+ .h-\[15px\] {
379
+ height: 15px;
380
+ }
384
381
  .h-\[16px\] {
385
382
  height: 16px;
386
383
  }
@@ -432,12 +429,12 @@
432
429
  .w-\[12px\] {
433
430
  width: 12px;
434
431
  }
435
- .w-\[13px\] {
436
- width: 13px;
437
- }
438
432
  .w-\[14px\] {
439
433
  width: 14px;
440
434
  }
435
+ .w-\[15px\] {
436
+ width: 15px;
437
+ }
441
438
  .w-\[16px\] {
442
439
  width: 16px;
443
440
  }
@@ -92,8 +92,26 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
92
92
  selectedSlotService.original_price) *
93
93
  100)
94
94
  : savingsPercent;
95
+ // Convert "HH:MM" (24h) → "H:MM AM/PM"
96
+ const formatTimeAmPm = (time) => {
97
+ const match = time.match(/(\d{1,2}):(\d{2})/);
98
+ if (!match)
99
+ return time;
100
+ let h = parseInt(match[1], 10);
101
+ const m = match[2];
102
+ const period = h >= 12 ? "PM" : "AM";
103
+ h = h % 12 || 12;
104
+ return `${h}:${m} ${period}`;
105
+ };
106
+ // Convert all HH:MM occurrences in a label string to AM/PM
107
+ const formatLabelAmPm = (label) => label.replace(/(\d{1,2}):(\d{2})/g, (_, hh, mm) => {
108
+ let h = parseInt(hh, 10);
109
+ const period = h >= 12 ? "PM" : "AM";
110
+ h = h % 12 || 12;
111
+ return `${h}:${mm} ${period}`;
112
+ });
95
113
  // The label shown on the dropdown button
96
- const departureRange = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
114
+ const departureRange = formatLabelAmPm((activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`);
97
115
  const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
98
116
  isFeatureDropDownExpand === true;
99
117
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
@@ -159,7 +177,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
159
177
  React.createElement("div", { className: "bold-text font-[9px]", style: {
160
178
  backgroundColor: "#FF5C60",
161
179
  padding: "4px 8px",
162
- borderRadius: "10px",
180
+ borderRadius: "6px",
163
181
  color: "#fff",
164
182
  animation: "pulse-zoom 2s ease-in-out infinite",
165
183
  whiteSpace: "nowrap",
@@ -172,68 +190,77 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
172
190
  React.createElement("div", { id: `service-card-${serviceItem.id}`, className: "bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]" },
173
191
  React.createElement("div", { className: "flex flex-col gap-[10px]" },
174
192
  React.createElement("div", { className: " text-[white]" },
175
- React.createElement("div", { className: "flex flex-col gap-[10px] relative" },
176
- React.createElement("div", { className: "flex items-center gap-[6px]" },
177
- React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteOrigin, alt: "origin", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
178
- React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
179
- React.createElement("span", { className: "mx-[6px] text-[14px] bold-text" }, "\u2192"),
180
- React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.whiteDestination, alt: "destination", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }),
181
- React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
182
- React.createElement("div", { className: "flex items-center gap-[6px]" },
183
- React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
184
- if (!e.currentTarget.contains(e.relatedTarget)) {
185
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
186
- }
187
- }, style: { outline: "none" } },
188
- React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]" },
189
- React.createElement("span", null, departureRange),
190
- React.createElement("img", { src: (_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
191
- width: "12px",
192
- height: "8px",
193
- filter: "brightness(0) invert(1)",
194
- } })),
195
- isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
196
- React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
197
- zIndex: 20,
198
- backgroundColor: "#fff",
199
- borderRadius: "14px",
200
- minWidth: "190px",
201
- boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
202
- overflow: "hidden",
203
- padding: "6px 0",
204
- } }, availableTimeSlots.map((slot) => {
205
- const isActive = slot.label === selectedTimeSlot ||
206
- slot === activeSlot;
207
- // Count services in this slot
208
- const count = services.filter((s) => {
209
- const depMin = commonService.timeToMinutes(s.departure_time);
210
- return depMin >= slot.start && depMin < slot.end;
211
- }).length;
212
- return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
213
- onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
214
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
215
- // const slotServices = services.filter((s) => {
216
- // const depMin = commonService.timeToMinutes(
217
- // s.departure_time,
218
- // );
219
- // return (
220
- // depMin >= slot.start && depMin < slot.end
221
- // );
222
- // });
223
- // console.log(
224
- // "Selected slot label:",
225
- // slot.label,
226
- // );
227
- // console.log(
228
- // "Services in selected slot:",
229
- // slotServices,
230
- // );
231
- }, className: `flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}` },
232
- React.createElement("span", null, slot.label),
233
- count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
234
- ? "bg-[white] text-[#FF5C60]"
235
- : "bg-[#FF5C60] text-[white]"}` }, count))));
236
- })))))))),
193
+ React.createElement("div", { className: "flex flex-col gap-[6px] relative" },
194
+ React.createElement("div", { className: "flex items-start gap-[10px]" },
195
+ React.createElement("div", { className: "flex flex-col items-center shrink-0", style: { paddingTop: "2px" } },
196
+ React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteOrigin, alt: "origin", className: `w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
197
+ React.createElement("div", { style: {
198
+ width: "1.5px",
199
+ flex: 1,
200
+ minHeight: "8px",
201
+ backgroundColor: "rgba(255,255,255,0.35)",
202
+ margin: "2px 0",
203
+ } }),
204
+ React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.whiteDestination, alt: "destination", className: `w-[15px] h-[15px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } })),
205
+ React.createElement("div", { className: "flex flex-col gap-[6px]" },
206
+ React.createElement("div", { className: "flex items-center gap-[6px]" },
207
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
208
+ React.createElement("span", { className: "text-[14px] bold-text" }, "\u2192"),
209
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
210
+ React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
211
+ if (!e.currentTarget.contains(e.relatedTarget)) {
212
+ onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
213
+ }
214
+ }, style: { outline: "none", marginTop: "3px" } },
215
+ React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]" },
216
+ React.createElement("span", null, departureRange),
217
+ React.createElement("img", { src: (_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
218
+ width: "12px",
219
+ height: "8px",
220
+ filter: "brightness(0) invert(1)",
221
+ } })),
222
+ isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
223
+ React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
224
+ zIndex: 20,
225
+ backgroundColor: "#fff",
226
+ borderRadius: "14px",
227
+ minWidth: "190px",
228
+ boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
229
+ overflow: "hidden",
230
+ padding: "6px 0",
231
+ } }, availableTimeSlots.map((slot) => {
232
+ const isActive = slot.label === selectedTimeSlot ||
233
+ slot === activeSlot;
234
+ // Count services in this slot
235
+ const count = services.filter((s) => {
236
+ const depMin = commonService.timeToMinutes(s.departure_time);
237
+ return (depMin >= slot.start && depMin < slot.end);
238
+ }).length;
239
+ return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
240
+ onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
241
+ onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
242
+ // const slotServices = services.filter((s) => {
243
+ // const depMin = commonService.timeToMinutes(
244
+ // s.departure_time,
245
+ // );
246
+ // return (
247
+ // depMin >= slot.start && depMin < slot.end
248
+ // );
249
+ // });
250
+ // console.log(
251
+ // "Selected slot label:",
252
+ // slot.label,
253
+ // );
254
+ // console.log(
255
+ // "Services in selected slot:",
256
+ // slotServices,
257
+ // );
258
+ }, className: `flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}` },
259
+ React.createElement("span", null, slot.label),
260
+ count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
261
+ ? "bg-[white] text-[#FF5C60]"
262
+ : "bg-[#FF5C60] text-[white]"}` }, count))));
263
+ }))))))))),
237
264
  React.createElement("div", { className: "border-t border-[#363c48] my-[8px]" }),
238
265
  React.createElement("div", null,
239
266
  React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[10px]", style: { textAlign: "center" } },
@@ -244,29 +271,38 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
244
271
  "operadores compitiendo ",
245
272
  React.createElement("br", null),
246
273
  "por tu compra"),
247
- React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%" } }, (servicesInActiveSlot.length > 0
248
- ? servicesInActiveSlot.slice(0, 3).map((s) => ({
249
- logo: s.operator_logo_url,
250
- name: s.operator_name,
251
- time: s.departure_time,
252
- seatsAvailable: `${s.available_seats} disponibles`,
253
- }))
254
- : operators).map((op, idx) => (React.createElement("div", { key: idx, className: "flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]", style: {
255
- flex: 1,
256
- minWidth: 0,
257
- height: "100px",
258
- border: "1px solid #363c48",
259
- backgroundColor: "#1a202e",
260
- padding: "14px 6px",
261
- } },
262
- React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
263
- var _a;
264
- e.currentTarget.src =
265
- ((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
266
- "/images/service-list/bus-icon.svg";
267
- }, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
268
- React.createElement("span", { className: "text-[12px] truncate max-w-full text-center " }, op.name),
269
- React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
274
+ React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%", justifyContent: "center" } }, (() => {
275
+ const displayOps = servicesInActiveSlot.length > 0
276
+ ? servicesInActiveSlot.slice(0, 3).map((s) => ({
277
+ logo: s.operator_logo_url,
278
+ name: s.operator_name,
279
+ time: s.departure_time,
280
+ seatsAvailable: `${s.available_seats} disponibles`,
281
+ }))
282
+ : operators;
283
+ const isSingle = displayOps.length === 1;
284
+ return displayOps.map((op, idx) => (React.createElement("div", { key: idx, className: "flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]", style: {
285
+ flex: isSingle ? "none" : 1,
286
+ width: isSingle ? "32%" : undefined,
287
+ margin: isSingle ? "0 auto" : undefined,
288
+ minWidth: 0,
289
+ height: "100px",
290
+ border: "1px solid #363c48",
291
+ backgroundColor: "#1a202e",
292
+ padding: "14px 6px",
293
+ } },
294
+ React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
295
+ var _a;
296
+ e.currentTarget.src =
297
+ ((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
298
+ "/images/service-list/bus-icon.svg";
299
+ }, className: `object-contain ${isSoldOut ? "grayscale" : ""}`, style: {
300
+ height: "24px",
301
+ maxWidth: "100%",
302
+ } }),
303
+ React.createElement("span", { className: "text-[12px] truncate max-w-full text-center" }, op.name),
304
+ React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))));
305
+ })()),
270
306
  React.createElement("div", { className: "flex w-full items-center justify-center gap-[6px] text-[12px] mt-[12px]", style: {
271
307
  padding: "4px 14px",
272
308
  } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "10.4.5",
3
+ "version": "10.4.6",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -131,9 +131,30 @@ const FeatureServiceUiMobile = ({
131
131
  )
132
132
  : savingsPercent;
133
133
 
134
+ // Convert "HH:MM" (24h) → "H:MM AM/PM"
135
+ const formatTimeAmPm = (time: string): string => {
136
+ const match = time.match(/(\d{1,2}):(\d{2})/);
137
+ if (!match) return time;
138
+ let h = parseInt(match[1], 10);
139
+ const m = match[2];
140
+ const period = h >= 12 ? "PM" : "AM";
141
+ h = h % 12 || 12;
142
+ return `${h}:${m} ${period}`;
143
+ };
144
+
145
+ // Convert all HH:MM occurrences in a label string to AM/PM
146
+ const formatLabelAmPm = (label: string): string =>
147
+ label.replace(/(\d{1,2}):(\d{2})/g, (_, hh, mm) => {
148
+ let h = parseInt(hh, 10);
149
+ const period = h >= 12 ? "PM" : "AM";
150
+ h = h % 12 || 12;
151
+ return `${h}:${mm} ${period}`;
152
+ });
153
+
134
154
  // The label shown on the dropdown button
135
- const departureRange =
136
- activeSlot?.label || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
155
+ const departureRange = formatLabelAmPm(
156
+ activeSlot?.label || `Entre ${dealWindowFrom} y ${dealWindowTo}`,
157
+ );
137
158
 
138
159
  const isItemExpanded =
139
160
  serviceItem.id === isFeatureDropDownExpand ||
@@ -213,7 +234,7 @@ const FeatureServiceUiMobile = ({
213
234
  style={{
214
235
  backgroundColor: "#FF5C60",
215
236
  padding: "4px 8px",
216
- borderRadius: "10px",
237
+ borderRadius: "6px",
217
238
  color: "#fff",
218
239
  animation: "pulse-zoom 2s ease-in-out infinite",
219
240
  whiteSpace: "nowrap",
@@ -230,141 +251,160 @@ const FeatureServiceUiMobile = ({
230
251
  >
231
252
  <div className="flex flex-col gap-[10px]">
232
253
  <div className=" text-[white]">
233
- <div className="flex flex-col gap-[10px] relative">
234
- <div className="flex items-center gap-[6px]">
235
- <img
236
- src={serviceItem.icons?.whiteOrigin}
237
- alt="origin"
238
- className={`w-[13px] h-[13px] shrink-0 ${
239
- isSoldOut ? "grayscale" : ""
240
- }`}
241
- />
242
-
243
- <span className="text-[14px] bold-text">
244
- {cityOrigin?.label.split(",")[0]}
245
- </span>
246
-
247
- <span className="mx-[6px] text-[14px] bold-text">→</span>
248
-
249
- <img
250
- src={serviceItem.icons?.whiteDestination}
251
- alt="destination"
252
- className={`w-[13px] h-[13px] shrink-0 ${
253
- isSoldOut ? "grayscale" : ""
254
- }`}
255
- style={{ opacity: isSoldOut ? 0.5 : 1 }}
256
- />
257
-
258
- <span className="text-[14px] bold-text">
259
- {cityDestination?.label.split(",")[0]}
260
- </span>
261
- </div>
262
-
263
- <div className="flex items-center gap-[6px]">
254
+ <div className="flex flex-col gap-[6px] relative">
255
+ {/* Origin / Destination row with connecting-line icons */}
256
+ <div className="flex items-start gap-[10px]">
257
+ {/* Icon column with connecting line */}
264
258
  <div
265
- className="kupos-time-dd relative"
266
- tabIndex={0}
267
- onBlur={(e) => {
268
- if (!e.currentTarget.contains(e.relatedTarget as Node)) {
269
- onTimeDropdownToggle?.(null);
270
- }
271
- }}
272
- style={{ outline: "none" }}
259
+ className="flex flex-col items-center shrink-0"
260
+ style={{ paddingTop: "2px" }}
273
261
  >
274
- <button
275
- type="button"
276
- onClick={() =>
277
- onTimeDropdownToggle?.(
278
- isThisTimeDropdownOpen ? null : serviceItem.id,
279
- )
280
- }
281
- className="flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]"
262
+ <img
263
+ src={serviceItem.icons?.whiteOrigin}
264
+ alt="origin"
265
+ className={`w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}`}
266
+ />
267
+ <div
268
+ style={{
269
+ width: "1.5px",
270
+ flex: 1,
271
+ minHeight: "8px",
272
+ backgroundColor: "rgba(255,255,255,0.35)",
273
+ margin: "2px 0",
274
+ }}
275
+ />
276
+ <img
277
+ src={serviceItem.icons?.whiteDestination}
278
+ alt="destination"
279
+ className={`w-[15px] h-[15px] shrink-0 ${isSoldOut ? "grayscale" : ""}`}
280
+ style={{ opacity: isSoldOut ? 0.5 : 1 }}
281
+ />
282
+ </div>
283
+ {/* Text column */}
284
+ <div className="flex flex-col gap-[6px]">
285
+ <div className="flex items-center gap-[6px]">
286
+ <span className="text-[14px] bold-text">
287
+ {cityOrigin?.label.split(",")[0]}
288
+ </span>
289
+ <span className="text-[14px] bold-text">→</span>
290
+ <span className="text-[14px] bold-text">
291
+ {cityDestination?.label.split(",")[0]}
292
+ </span>
293
+ </div>
294
+
295
+ <div
296
+ className="kupos-time-dd relative"
297
+ tabIndex={0}
298
+ onBlur={(e) => {
299
+ if (
300
+ !e.currentTarget.contains(e.relatedTarget as Node)
301
+ ) {
302
+ onTimeDropdownToggle?.(null);
303
+ }
304
+ }}
305
+ style={{ outline: "none", marginTop: "3px" }}
282
306
  >
283
- <span>{departureRange}</span>
284
- <img
285
- src={serviceItem?.icons?.downArrow}
286
- alt="down arrow"
287
- className={`kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`}
288
- style={{
289
- width: "12px",
290
- height: "8px",
291
- filter: "brightness(0) invert(1)",
292
- }}
293
- />
294
- </button>
295
- {isThisTimeDropdownOpen && (
296
- <>
297
- <div
298
- className="absolute left-0 top-[calc(100%+10px)]"
307
+ <button
308
+ type="button"
309
+ onClick={() =>
310
+ onTimeDropdownToggle?.(
311
+ isThisTimeDropdownOpen ? null : serviceItem.id,
312
+ )
313
+ }
314
+ className="flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]"
315
+ >
316
+ <span>{departureRange}</span>
317
+ <img
318
+ src={serviceItem?.icons?.downArrow}
319
+ alt="down arrow"
320
+ className={`kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`}
299
321
  style={{
300
- zIndex: 20,
301
- backgroundColor: "#fff",
302
- borderRadius: "14px",
303
- minWidth: "190px",
304
- boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
305
- overflow: "hidden",
306
- padding: "6px 0",
322
+ width: "12px",
323
+ height: "8px",
324
+ filter: "brightness(0) invert(1)",
307
325
  }}
308
- >
309
- {availableTimeSlots.map((slot) => {
310
- const isActive =
311
- slot.label === selectedTimeSlot ||
312
- slot === activeSlot;
313
- // Count services in this slot
314
- const count = services.filter((s) => {
315
- const depMin = commonService.timeToMinutes(
316
- s.departure_time,
326
+ />
327
+ </button>
328
+ {isThisTimeDropdownOpen && (
329
+ <>
330
+ <div
331
+ className="absolute left-0 top-[calc(100%+10px)]"
332
+ style={{
333
+ zIndex: 20,
334
+ backgroundColor: "#fff",
335
+ borderRadius: "14px",
336
+ minWidth: "190px",
337
+ boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
338
+ overflow: "hidden",
339
+ padding: "6px 0",
340
+ }}
341
+ >
342
+ {availableTimeSlots.map((slot) => {
343
+ const isActive =
344
+ slot.label === selectedTimeSlot ||
345
+ slot === activeSlot;
346
+ // Count services in this slot
347
+ const count = services.filter((s) => {
348
+ const depMin = commonService.timeToMinutes(
349
+ s.departure_time,
350
+ );
351
+ return (
352
+ depMin >= slot.start && depMin < slot.end
353
+ );
354
+ }).length;
355
+ return (
356
+ <button
357
+ key={slot.label}
358
+ type="button"
359
+ onClick={() => {
360
+ onTimeSlotChange?.(slot.label);
361
+ onTimeDropdownToggle?.(null);
362
+ // const slotServices = services.filter((s) => {
363
+ // const depMin = commonService.timeToMinutes(
364
+ // s.departure_time,
365
+ // );
366
+ // return (
367
+ // depMin >= slot.start && depMin < slot.end
368
+ // );
369
+ // });
370
+ // console.log(
371
+ // "Selected slot label:",
372
+ // slot.label,
373
+ // );
374
+ // console.log(
375
+ // "Services in selected slot:",
376
+ // slotServices,
377
+ // );
378
+ }}
379
+ className={`flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}`}
380
+ >
381
+ <span>{slot.label}</span>
382
+ {count > 0 && (
383
+ <span
384
+ className={`text-[11px] rounded-full px-[6px] py-[2px] font-bold ${
385
+ isActive
386
+ ? "bg-[white] text-[#FF5C60]"
387
+ : "bg-[#FF5C60] text-[white]"
388
+ }`}
389
+ >
390
+ {count}
391
+ </span>
392
+ )}
393
+ </button>
317
394
  );
318
- return depMin >= slot.start && depMin < slot.end;
319
- }).length;
320
- return (
321
- <button
322
- key={slot.label}
323
- type="button"
324
- onClick={() => {
325
- onTimeSlotChange?.(slot.label);
326
- onTimeDropdownToggle?.(null);
327
- // const slotServices = services.filter((s) => {
328
- // const depMin = commonService.timeToMinutes(
329
- // s.departure_time,
330
- // );
331
- // return (
332
- // depMin >= slot.start && depMin < slot.end
333
- // );
334
- // });
335
- // console.log(
336
- // "Selected slot label:",
337
- // slot.label,
338
- // );
339
- // console.log(
340
- // "Services in selected slot:",
341
- // slotServices,
342
- // );
343
- }}
344
- className={`flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}`}
345
- >
346
- <span>{slot.label}</span>
347
- {count > 0 && (
348
- <span
349
- className={`text-[11px] rounded-full px-[6px] py-[2px] font-bold ${
350
- isActive
351
- ? "bg-[white] text-[#FF5C60]"
352
- : "bg-[#FF5C60] text-[white]"
353
- }`}
354
- >
355
- {count}
356
- </span>
357
- )}
358
- </button>
359
- );
360
- })}
361
- </div>
362
- </>
363
- )}
395
+ })}
396
+ </div>
397
+ </>
398
+ )}
399
+ </div>
364
400
  </div>
401
+ {/* end text column */}
365
402
  </div>
403
+ {/* end flex items-start row */}
366
404
  </div>
405
+ {/* end flex-col gap-6 relative */}
367
406
  </div>
407
+ {/* end text-[white] */}
368
408
 
369
409
  <div className="border-t border-[#363c48] my-[8px]" />
370
410
 
@@ -379,54 +419,62 @@ const FeatureServiceUiMobile = ({
379
419
  operadores compitiendo <br />
380
420
  por tu compra
381
421
  </span>
422
+ {/* Operator boxes: single full-width box when 1 operator, else flex row */}
382
423
  <div
383
424
  className="flex gap-[8px] text-[white]"
384
- style={{ width: "100%" }}
425
+ style={{ width: "100%", justifyContent: "center" }}
385
426
  >
386
- {(servicesInActiveSlot.length > 0
387
- ? servicesInActiveSlot.slice(0, 3).map((s: any) => ({
388
- logo: s.operator_logo_url,
389
- name: s.operator_name,
390
- time: s.departure_time,
391
- seatsAvailable: `${s.available_seats} disponibles`,
392
- }))
393
- : operators
394
- ).map((op, idx) => (
395
- <div
396
- key={idx}
397
- className="flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]"
398
- style={{
399
- flex: 1,
400
- minWidth: 0,
401
- height: "100px",
402
- border: "1px solid #363c48",
403
- backgroundColor: "#1a202e",
404
- padding: "14px 6px",
405
- }}
406
- >
407
- <img
408
- src={op.logo}
409
- alt={op.name}
410
- onError={(e) => {
411
- e.currentTarget.src =
412
- serviceItem?.operator_details?.[0] ||
413
- "/images/service-list/bus-icon.svg";
427
+ {(() => {
428
+ const displayOps =
429
+ servicesInActiveSlot.length > 0
430
+ ? servicesInActiveSlot.slice(0, 3).map((s: any) => ({
431
+ logo: s.operator_logo_url,
432
+ name: s.operator_name,
433
+ time: s.departure_time,
434
+ seatsAvailable: `${s.available_seats} disponibles`,
435
+ }))
436
+ : operators;
437
+ const isSingle = displayOps.length === 1;
438
+ return displayOps.map((op, idx) => (
439
+ <div
440
+ key={idx}
441
+ className="flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]"
442
+ style={{
443
+ flex: isSingle ? "none" : 1,
444
+ width: isSingle ? "32%" : undefined,
445
+ margin: isSingle ? "0 auto" : undefined,
446
+ minWidth: 0,
447
+ height: "100px",
448
+ border: "1px solid #363c48",
449
+ backgroundColor: "#1a202e",
450
+ padding: "14px 6px",
414
451
  }}
415
- className={`h-[24px] max-w-full object-contain ${
416
- isSoldOut ? "grayscale" : ""
417
- }`}
418
- />
419
- <span className="text-[12px] truncate max-w-full text-center ">
420
- {op.name}
421
- </span>
422
- {/* <div className="bg-[#FF8F45] text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
423
- <span>{op?.time}</span>
424
- </div> */}
425
- <span className="text-[11px] whitespace-nowrap">
426
- {op?.seatsAvailable}
427
- </span>
428
- </div>
429
- ))}
452
+ >
453
+ <img
454
+ src={op.logo}
455
+ alt={op.name}
456
+ onError={(e) => {
457
+ e.currentTarget.src =
458
+ serviceItem?.operator_details?.[0] ||
459
+ "/images/service-list/bus-icon.svg";
460
+ }}
461
+ className={`object-contain ${
462
+ isSoldOut ? "grayscale" : ""
463
+ }`}
464
+ style={{
465
+ height: "24px",
466
+ maxWidth: "100%",
467
+ }}
468
+ />
469
+ <span className="text-[12px] truncate max-w-full text-center">
470
+ {op.name}
471
+ </span>
472
+ <span className="text-[11px] whitespace-nowrap">
473
+ {op?.seatsAvailable}
474
+ </span>
475
+ </div>
476
+ ));
477
+ })()}
430
478
  </div>
431
479
 
432
480
  <div