kupos-ui-components-lib 9.10.8 → 9.10.10
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/components/ServiceItem/ServiceItemDesktop.js +7 -2
- package/dist/styles.css +3 -3
- package/dist/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.d.ts +1 -1
- package/dist/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.js +117 -33
- package/dist/ui/FeatureServiceUI/FeatureServiceUi.js +110 -35
- package/dist/utils/CommonService.d.ts +7 -0
- package/dist/utils/CommonService.js +32 -2
- package/package.json +1 -1
- package/src/components/ServiceItem/ServiceItemDesktop.tsx +7 -2
- package/src/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.tsx +151 -70
- package/src/ui/FeatureServiceUI/FeatureServiceUi.tsx +128 -48
- package/src/utils/CommonService.ts +44 -4
|
@@ -89,6 +89,7 @@ const ANIMATION_MAP = {
|
|
|
89
89
|
};
|
|
90
90
|
function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isPeruSites, showAvailableSeats, isSeatIcon, isLinatal, isPeru, t = (key) => key, siteType, isAllinBus, isExpand, setIsExpand, coachKey, viewersConfig, isNewUi, showLoginModal, isLoggedIn, showLoginOption, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, operatorLabel, }) {
|
|
91
91
|
var _a, _b, _c;
|
|
92
|
+
console.log("🚀 ~ ServiceItemPB ~ serviceItem:", serviceItem);
|
|
92
93
|
const getAnimationIcon = (icon) => {
|
|
93
94
|
var _a;
|
|
94
95
|
const animation = ANIMATION_MAP[icon];
|
|
@@ -312,8 +313,12 @@ function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, child
|
|
|
312
313
|
} },
|
|
313
314
|
React.createElement("div", { style: Object.assign({ overflow: "hidden", minHeight: 0, marginTop: hasDpEnabled || (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) ? "" : "-10px" }, (hasOfferText || hasDpEnabled
|
|
314
315
|
? {
|
|
315
|
-
borderLeft: isSoldOut
|
|
316
|
-
|
|
316
|
+
borderLeft: isSoldOut
|
|
317
|
+
? ""
|
|
318
|
+
: `3px solid ${colors.leftGradiantColor || "#ff8842"}`,
|
|
319
|
+
borderRight: isSoldOut
|
|
320
|
+
? ""
|
|
321
|
+
: `3px solid ${colors.rightGradiantColor || "#ff8842"}`,
|
|
317
322
|
borderRadius: "0 0 18px 18px",
|
|
318
323
|
boxSizing: "border-box",
|
|
319
324
|
}
|
package/dist/styles.css
CHANGED
|
@@ -572,9 +572,6 @@
|
|
|
572
572
|
.grid-cols-2 {
|
|
573
573
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
574
574
|
}
|
|
575
|
-
.grid-cols-3 {
|
|
576
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
577
|
-
}
|
|
578
575
|
.grid-cols-4 {
|
|
579
576
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
580
577
|
}
|
|
@@ -854,6 +851,9 @@
|
|
|
854
851
|
.bg-\[lightgray\] {
|
|
855
852
|
background-color: lightgray;
|
|
856
853
|
}
|
|
854
|
+
.bg-\[white\] {
|
|
855
|
+
background-color: white;
|
|
856
|
+
}
|
|
857
857
|
.bg-transparent {
|
|
858
858
|
background-color: transparent;
|
|
859
859
|
}
|
|
@@ -14,7 +14,7 @@ declare const FeatureServiceUiMobile: ({ serviceItem, showTopLabel, colors, isSo
|
|
|
14
14
|
onIncreaseTicketQuantity: any;
|
|
15
15
|
onDecreaseTicketQuantity: any;
|
|
16
16
|
onBookButtonPress: any;
|
|
17
|
-
selectedTimeSlot
|
|
17
|
+
selectedTimeSlot: any;
|
|
18
18
|
onTimeSlotChange: any;
|
|
19
19
|
isTimeDropdownOpen: any;
|
|
20
20
|
onTimeDropdownToggle: any;
|
|
@@ -2,12 +2,6 @@ import React from "react";
|
|
|
2
2
|
import LottiePlayer from "../../assets/LottiePlayer";
|
|
3
3
|
import commonService from "../../utils/CommonService";
|
|
4
4
|
import flameAnimation from "../../assets/images/anims/service_list/flame_anim.json";
|
|
5
|
-
const TIME_SLOTS = [
|
|
6
|
-
"Entre 07:00 AM y 10:00 AM",
|
|
7
|
-
"Entre 11:00 AM y 14:00 AM",
|
|
8
|
-
"Entre 15:00 PM y 18:00 PM",
|
|
9
|
-
"Entre 19:00 PM y 22:00 PM",
|
|
10
|
-
];
|
|
11
5
|
const HARDCODED_OPERATORS = [
|
|
12
6
|
{
|
|
13
7
|
logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
|
|
@@ -28,8 +22,8 @@ const HARDCODED_OPERATORS = [
|
|
|
28
22
|
seatsAvailable: "3 disponibles",
|
|
29
23
|
},
|
|
30
24
|
];
|
|
31
|
-
const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot
|
|
32
|
-
var _a, _b, _c, _d, _e, _f
|
|
25
|
+
const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData = undefined, }) => {
|
|
26
|
+
var _a, _b, _c, _d, _e, _f;
|
|
33
27
|
// Use wow_deal data if available, otherwise fall back to serviceItem operators or hardcoded
|
|
34
28
|
const operators = ((_a = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _a === void 0 ? void 0 : _a.length) > 0
|
|
35
29
|
? wowDealData.services.slice(0, 3).map((service) => ({
|
|
@@ -52,6 +46,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
52
46
|
const dealWindowFrom = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.deal_window_from) || "07:00";
|
|
53
47
|
const dealWindowTo = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.deal_window_to) || "10:00";
|
|
54
48
|
const travelDate = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.travel_date;
|
|
49
|
+
const serviceWindowHours = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.service_window_hours;
|
|
55
50
|
// Calculate countdown seconds from expires_at ISO timestamp
|
|
56
51
|
const getCountdownSeconds = () => {
|
|
57
52
|
if (!expiresAt)
|
|
@@ -61,23 +56,47 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
61
56
|
const seconds = Math.max(0, Math.floor((expires - now) / 1000));
|
|
62
57
|
return seconds;
|
|
63
58
|
};
|
|
64
|
-
// Generate time
|
|
65
|
-
const
|
|
66
|
-
|
|
59
|
+
// Generate dynamic time slots from deal window + service window hours
|
|
60
|
+
const allTimeSlots = serviceWindowHours
|
|
61
|
+
? commonService.generateTimeSlots(dealWindowFrom, dealWindowTo, serviceWindowHours)
|
|
62
|
+
: [];
|
|
63
|
+
// Filter slots to only those that have at least one service
|
|
64
|
+
const services = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) || [];
|
|
65
|
+
const availableTimeSlots = allTimeSlots.filter((slot) => services.some((s) => {
|
|
66
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
67
|
+
return depMin >= slot.start && depMin < slot.end;
|
|
68
|
+
}));
|
|
69
|
+
// Active slot: the one matching selectedTimeSlot label, or the first available
|
|
70
|
+
const activeSlot = availableTimeSlots.find((s) => s.label === selectedTimeSlot) ||
|
|
71
|
+
availableTimeSlots[0];
|
|
72
|
+
// Services that fall within the active slot, sorted by final price ascending
|
|
73
|
+
const servicesInActiveSlot = activeSlot
|
|
74
|
+
? services
|
|
75
|
+
.filter((s) => {
|
|
76
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
77
|
+
return depMin >= activeSlot.start && depMin < activeSlot.end;
|
|
78
|
+
})
|
|
79
|
+
.sort((a, b) => a.final_price - b.final_price)
|
|
80
|
+
: services;
|
|
81
|
+
// The selected price (final and original) from the cheapest service in the active slot
|
|
82
|
+
const selectedSlotService = servicesInActiveSlot === null || servicesInActiveSlot === void 0 ? void 0 : servicesInActiveSlot[0];
|
|
83
|
+
const displayFinalPrice = selectedSlotService
|
|
84
|
+
? selectedSlotService.final_price
|
|
85
|
+
: finalPrice;
|
|
86
|
+
const displayOriginalPrice = selectedSlotService
|
|
87
|
+
? selectedSlotService.original_price
|
|
88
|
+
: originalPrice;
|
|
89
|
+
const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
|
|
90
|
+
? Math.round(((selectedSlotService.original_price - selectedSlotService.final_price) /
|
|
91
|
+
selectedSlotService.original_price) *
|
|
92
|
+
100)
|
|
93
|
+
: savingsPercent;
|
|
94
|
+
// The label shown on the dropdown button
|
|
95
|
+
const departureRange = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
|
|
67
96
|
const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
|
|
68
97
|
isFeatureDropDownExpand === true;
|
|
69
98
|
const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
|
|
70
99
|
const canDecreaseTicketQuantity = ticketQuantity > 1;
|
|
71
|
-
const departures = (_d = (_c = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _c === void 0 ? void 0 : _c.map((s) => s.departure_time)) === null || _d === void 0 ? void 0 : _d.filter(Boolean);
|
|
72
|
-
let departureRange = `Entre ${dealWindowFrom} y ${dealWindowTo}`;
|
|
73
|
-
if (departures === null || departures === void 0 ? void 0 : departures.length) {
|
|
74
|
-
const sorted = [...departures].sort((a, b) => {
|
|
75
|
-
const [ah, am] = a.split(":").map(Number);
|
|
76
|
-
const [bh, bm] = b.split(":").map(Number);
|
|
77
|
-
return ah * 60 + am - (bh * 60 + bm);
|
|
78
|
-
});
|
|
79
|
-
departureRange = `Entre ${sorted[0]} y ${sorted[sorted.length - 1]}`;
|
|
80
|
-
}
|
|
81
100
|
const HOW_IT_WORKS_STEPS = [
|
|
82
101
|
{
|
|
83
102
|
icon: "flexible",
|
|
@@ -145,28 +164,91 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
145
164
|
} },
|
|
146
165
|
React.createElement("span", null,
|
|
147
166
|
"AHORRAS ",
|
|
148
|
-
|
|
167
|
+
displaySavingsPercent,
|
|
149
168
|
"%")))),
|
|
150
169
|
React.createElement("div", { id: `service-card-${serviceItem.id}`, className: "bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]" },
|
|
151
170
|
React.createElement("div", { className: "flex flex-col gap-[10px]" },
|
|
152
171
|
React.createElement("div", { className: " text-[white]" },
|
|
153
172
|
React.createElement("div", { className: "flex flex-col gap-[10px] relative" },
|
|
154
173
|
React.createElement("div", { className: "flex items-center gap-[6px]" },
|
|
155
|
-
React.createElement("img", { src: (
|
|
174
|
+
React.createElement("img", { src: (_c = serviceItem.icons) === null || _c === void 0 ? void 0 : _c.whiteOrigin, alt: "origin", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
|
|
156
175
|
React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
|
|
157
176
|
React.createElement("span", { className: "mx-[6px] text-[14px] bold-text" }, "\u2192"),
|
|
158
|
-
React.createElement("img", { src: (
|
|
177
|
+
React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteDestination, alt: "destination", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }),
|
|
159
178
|
React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
|
|
160
179
|
React.createElement("div", { className: "flex items-center gap-[6px]" },
|
|
161
|
-
React.createElement("div", { className: "
|
|
180
|
+
React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
|
|
181
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
182
|
+
onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
|
|
183
|
+
}
|
|
184
|
+
}, style: { outline: "none" } },
|
|
185
|
+
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]" },
|
|
186
|
+
React.createElement("span", null, departureRange),
|
|
187
|
+
React.createElement("img", { src: (_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _e === void 0 ? void 0 : _e.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
|
|
188
|
+
width: "12px",
|
|
189
|
+
height: "8px",
|
|
190
|
+
filter: "brightness(0) invert(1)",
|
|
191
|
+
} })),
|
|
192
|
+
isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
|
|
193
|
+
React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
|
|
194
|
+
zIndex: 20,
|
|
195
|
+
backgroundColor: "#fff",
|
|
196
|
+
borderRadius: "14px",
|
|
197
|
+
minWidth: "190px",
|
|
198
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
|
|
199
|
+
overflow: "hidden",
|
|
200
|
+
padding: "6px 0",
|
|
201
|
+
} }, availableTimeSlots.map((slot) => {
|
|
202
|
+
const isActive = slot.label === selectedTimeSlot ||
|
|
203
|
+
slot === activeSlot;
|
|
204
|
+
// Count services in this slot
|
|
205
|
+
const count = services.filter((s) => {
|
|
206
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
207
|
+
return depMin >= slot.start && depMin < slot.end;
|
|
208
|
+
}).length;
|
|
209
|
+
return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
|
|
210
|
+
onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
|
|
211
|
+
onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
|
|
212
|
+
// const slotServices = services.filter((s) => {
|
|
213
|
+
// const depMin = commonService.timeToMinutes(
|
|
214
|
+
// s.departure_time,
|
|
215
|
+
// );
|
|
216
|
+
// return (
|
|
217
|
+
// depMin >= slot.start && depMin < slot.end
|
|
218
|
+
// );
|
|
219
|
+
// });
|
|
220
|
+
// console.log(
|
|
221
|
+
// "Selected slot label:",
|
|
222
|
+
// slot.label,
|
|
223
|
+
// );
|
|
224
|
+
// console.log(
|
|
225
|
+
// "Services in selected slot:",
|
|
226
|
+
// slotServices,
|
|
227
|
+
// );
|
|
228
|
+
}, 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]"}` },
|
|
229
|
+
React.createElement("span", null, slot.label),
|
|
230
|
+
count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
|
|
231
|
+
? "bg-[white] text-[#FF5C60]"
|
|
232
|
+
: "bg-[#FF5C60] text-[white]"}` }, count))));
|
|
233
|
+
})))))))),
|
|
162
234
|
React.createElement("div", { className: "border-t border-[#363c48] my-[8px]" }),
|
|
163
235
|
React.createElement("div", null,
|
|
164
236
|
React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[10px]", style: { textAlign: "center" } },
|
|
165
|
-
|
|
166
|
-
|
|
237
|
+
servicesInActiveSlot.length > 0
|
|
238
|
+
? servicesInActiveSlot.length
|
|
239
|
+
: operatorsCompetingCount,
|
|
240
|
+
" ",
|
|
241
|
+
"operadores compitiendo ",
|
|
167
242
|
React.createElement("br", null),
|
|
168
243
|
"por tu compra"),
|
|
169
|
-
React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%" } },
|
|
244
|
+
React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%" } }, (servicesInActiveSlot.length > 0
|
|
245
|
+
? servicesInActiveSlot.slice(0, 3).map((s) => ({
|
|
246
|
+
logo: s.operator_logo_url,
|
|
247
|
+
name: s.operator_name,
|
|
248
|
+
time: s.departure_time,
|
|
249
|
+
seatsAvailable: `${s.available_seats} disponibles`,
|
|
250
|
+
}))
|
|
251
|
+
: 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: {
|
|
170
252
|
flex: 1,
|
|
171
253
|
minWidth: 0,
|
|
172
254
|
height: "100px",
|
|
@@ -176,7 +258,9 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
176
258
|
} },
|
|
177
259
|
React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
|
|
178
260
|
var _a;
|
|
179
|
-
e.currentTarget.src =
|
|
261
|
+
e.currentTarget.src =
|
|
262
|
+
((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
|
|
263
|
+
"/images/service-list/bus-icon.svg";
|
|
180
264
|
}, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
|
|
181
265
|
React.createElement("span", { className: "text-[12px] truncate max-w-full text-center " }, op.name),
|
|
182
266
|
React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
|
|
@@ -220,7 +304,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
220
304
|
} },
|
|
221
305
|
React.createElement("div", { className: "flex flex-col" },
|
|
222
306
|
React.createElement("span", { className: "text-[18px] font-normal leading-[20px] text-[#9f9f9f] relative", style: { position: "relative" } },
|
|
223
|
-
`$${(
|
|
307
|
+
`$${(displayOriginalPrice * ticketQuantity).toLocaleString()}`,
|
|
224
308
|
React.createElement("span", { style: {
|
|
225
309
|
position: "absolute",
|
|
226
310
|
left: "-2px",
|
|
@@ -231,12 +315,12 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
231
315
|
transform: "rotate(-10deg)",
|
|
232
316
|
transformOrigin: "center",
|
|
233
317
|
} })),
|
|
234
|
-
React.createElement("span", { className: "text-[white] bold-text text-[24px] leading-none mt-[4px]" }, `$${(
|
|
318
|
+
React.createElement("span", { className: "text-[white] bold-text text-[24px] leading-none mt-[4px]" }, `$${(displayFinalPrice * ticketQuantity).toLocaleString()}`)),
|
|
235
319
|
React.createElement("span", { className: "text-[#FF8F45] bold-text text-[22px] leading-tight", style: {
|
|
236
320
|
animation: "pulse-zoom 2s ease-in-out infinite",
|
|
237
321
|
whiteSpace: "nowrap",
|
|
238
322
|
} },
|
|
239
|
-
|
|
323
|
+
displaySavingsPercent,
|
|
240
324
|
"% OFF")),
|
|
241
325
|
React.createElement("button", { type: "button", onClick: onBookButtonPress, className: "flex items-center gap-[6px] px-[20px] py-[10px] rounded-[16px] text-[white] bold-text text-[13px] mt-[10px] justify-center border-none cursor-pointer", style: {
|
|
242
326
|
backgroundColor: "#FF5C60",
|
|
@@ -246,7 +330,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
|
|
|
246
330
|
React.createElement(LottiePlayer, { animationData: serviceItem.icons.thunderAnim, width: "16px", height: "16px" }),
|
|
247
331
|
React.createElement("span", { className: "whitespace-nowrap" }, "\u00A1Lo quiero!")),
|
|
248
332
|
React.createElement("div", { className: "flex justify-end mt-[10px]", onClick: onToggleExpand },
|
|
249
|
-
React.createElement("img", { src: (
|
|
333
|
+
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: `transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, style: {
|
|
250
334
|
width: "14px",
|
|
251
335
|
height: "8px",
|
|
252
336
|
filter: "brightness(0) invert(1)",
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import LottiePlayer from "../../assets/LottiePlayer";
|
|
3
3
|
import commonService from "../../utils/CommonService";
|
|
4
|
-
const TIME_SLOTS = [
|
|
5
|
-
"Entre 07:00 AM y 10:00 AM",
|
|
6
|
-
"Entre 11:00 AM y 14:00 AM",
|
|
7
|
-
"Entre 15:00 PM y 18:00 PM",
|
|
8
|
-
"Entre 19:00 PM y 22:00 PM",
|
|
9
|
-
];
|
|
10
4
|
const HARDCODED_OPERATORS = [
|
|
11
5
|
{
|
|
12
6
|
logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
|
|
@@ -28,7 +22,7 @@ const HARDCODED_OPERATORS = [
|
|
|
28
22
|
},
|
|
29
23
|
];
|
|
30
24
|
const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIcon, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData = undefined, }) => {
|
|
31
|
-
var _a, _b, _c, _d, _e, _f
|
|
25
|
+
var _a, _b, _c, _d, _e, _f;
|
|
32
26
|
// Use wow_deal services if available, otherwise fall back to serviceItem operators or hardcoded
|
|
33
27
|
const operators = ((_a = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _a === void 0 ? void 0 : _a.length) > 0
|
|
34
28
|
? wowDealData.services.slice(0, 3).map((service) => ({
|
|
@@ -50,6 +44,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
50
44
|
const isPostPaymentAssignment = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.is_post_payment_assignment;
|
|
51
45
|
const dealWindowFrom = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.deal_window_from) || "07:00";
|
|
52
46
|
const dealWindowTo = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.deal_window_to) || "10:00";
|
|
47
|
+
const serviceWindowHours = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.service_window_hours;
|
|
53
48
|
const travelDate = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.travel_date;
|
|
54
49
|
// Calculate countdown seconds from expires_at ISO timestamp
|
|
55
50
|
const getCountdownSeconds = () => {
|
|
@@ -60,24 +55,48 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
60
55
|
const seconds = Math.max(0, Math.floor((expires - now) / 1000));
|
|
61
56
|
return seconds;
|
|
62
57
|
};
|
|
63
|
-
// Generate time
|
|
64
|
-
const
|
|
65
|
-
|
|
58
|
+
// Generate dynamic time slots from deal window + service window hours
|
|
59
|
+
const allTimeSlots = serviceWindowHours
|
|
60
|
+
? commonService.generateTimeSlots(dealWindowFrom, dealWindowTo, serviceWindowHours)
|
|
61
|
+
: [];
|
|
62
|
+
// Filter slots to only those that have at least one service
|
|
63
|
+
const services = (wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) || [];
|
|
64
|
+
const availableTimeSlots = allTimeSlots.filter((slot) => services.some((s) => {
|
|
65
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
66
|
+
return depMin >= slot.start && depMin < slot.end;
|
|
67
|
+
}));
|
|
68
|
+
// Active slot: the one matching selectedTimeSlot label, or the first available
|
|
69
|
+
const activeSlot = availableTimeSlots.find((s) => s.label === selectedTimeSlot) ||
|
|
70
|
+
availableTimeSlots[0];
|
|
71
|
+
// Services that fall within the active slot, sorted by final price ascending
|
|
72
|
+
const servicesInActiveSlot = activeSlot
|
|
73
|
+
? services
|
|
74
|
+
.filter((s) => {
|
|
75
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
76
|
+
return depMin >= activeSlot.start && depMin < activeSlot.end;
|
|
77
|
+
})
|
|
78
|
+
.sort((a, b) => a.final_price - b.final_price)
|
|
79
|
+
: services;
|
|
80
|
+
// The selected price (final and original) from the cheapest service in the active slot
|
|
81
|
+
const selectedSlotService = servicesInActiveSlot === null || servicesInActiveSlot === void 0 ? void 0 : servicesInActiveSlot[0];
|
|
82
|
+
const displayFinalPrice = selectedSlotService
|
|
83
|
+
? selectedSlotService.final_price
|
|
84
|
+
: finalPrice;
|
|
85
|
+
const displayOriginalPrice = selectedSlotService
|
|
86
|
+
? selectedSlotService.original_price
|
|
87
|
+
: originalPrice;
|
|
88
|
+
const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
|
|
89
|
+
? Math.round(((selectedSlotService.original_price - selectedSlotService.final_price) /
|
|
90
|
+
selectedSlotService.original_price) *
|
|
91
|
+
100)
|
|
92
|
+
: savingsPercent;
|
|
93
|
+
// The label shown on the dropdown button
|
|
94
|
+
const departureRange = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
|
|
66
95
|
const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
|
|
67
96
|
isFeatureDropDownExpand === true;
|
|
68
97
|
const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
|
|
69
98
|
const canDecreaseTicketQuantity = ticketQuantity > 1;
|
|
70
99
|
const canIncreaseTicketQuantity = ticketQuantity < maxSeatsPerBooking;
|
|
71
|
-
const departures = (_d = (_c = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _c === void 0 ? void 0 : _c.map((s) => s.departure_time)) === null || _d === void 0 ? void 0 : _d.filter(Boolean);
|
|
72
|
-
let departureRange = `Entre ${dealWindowFrom} y ${dealWindowTo}`;
|
|
73
|
-
if (departures === null || departures === void 0 ? void 0 : departures.length) {
|
|
74
|
-
const sorted = [...departures].sort((a, b) => {
|
|
75
|
-
const [ah, am] = a.split(":").map(Number);
|
|
76
|
-
const [bh, bm] = b.split(":").map(Number);
|
|
77
|
-
return ah * 60 + am - (bh * 60 + bm);
|
|
78
|
-
});
|
|
79
|
-
departureRange = `Entre ${sorted[0]} y ${sorted[sorted.length - 1]}`;
|
|
80
|
-
}
|
|
81
100
|
const HOW_IT_WORKS_STEPS = [
|
|
82
101
|
{
|
|
83
102
|
icon: "flexible",
|
|
@@ -143,7 +162,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
143
162
|
} },
|
|
144
163
|
React.createElement("span", null,
|
|
145
164
|
"AHORRAS ",
|
|
146
|
-
|
|
165
|
+
displaySavingsPercent,
|
|
147
166
|
"%"))),
|
|
148
167
|
React.createElement("div", { className: "flex items-center" },
|
|
149
168
|
React.createElement("div", { className: "mb-[2px]" },
|
|
@@ -165,10 +184,10 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
165
184
|
React.createElement("div", { className: "flex flex-col justify-between gap-[20px] mb-[16px] pr-[22px]" },
|
|
166
185
|
React.createElement("div", { className: "flex flex-col gap-[8px]" },
|
|
167
186
|
React.createElement("div", { className: "flex items-center gap-[8px]" },
|
|
168
|
-
React.createElement("img", { src: (
|
|
187
|
+
React.createElement("img", { src: (_c = serviceItem.icons) === null || _c === void 0 ? void 0 : _c.whiteOrigin, alt: "origin", className: `w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
|
|
169
188
|
React.createElement("span", { className: "text-[13px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0])),
|
|
170
189
|
React.createElement("div", { className: "flex items-center gap-[8px]" },
|
|
171
|
-
React.createElement("img", { src: (
|
|
190
|
+
React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteDestination, alt: "destination", className: `w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }),
|
|
172
191
|
React.createElement("span", { className: "text-[13px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0]))),
|
|
173
192
|
React.createElement("div", { className: "flex flex-col gap-[10px]" },
|
|
174
193
|
React.createElement("div", { className: "text-[12px] bold-text" }, travelDate
|
|
@@ -178,7 +197,50 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
178
197
|
month: "long",
|
|
179
198
|
})
|
|
180
199
|
: "Viernes 23 de mayo"),
|
|
181
|
-
React.createElement("div", { className: "
|
|
200
|
+
React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
|
|
201
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
202
|
+
onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
|
|
203
|
+
}
|
|
204
|
+
}, style: { outline: "none" } },
|
|
205
|
+
React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex whitespace-nowrap cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[12px] text-[white]" },
|
|
206
|
+
React.createElement("span", null, departureRange),
|
|
207
|
+
React.createElement("img", { src: (_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _e === void 0 ? void 0 : _e.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
|
|
208
|
+
width: "12px",
|
|
209
|
+
height: "8px",
|
|
210
|
+
filter: "brightness(0) invert(1)",
|
|
211
|
+
} })),
|
|
212
|
+
isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
|
|
213
|
+
React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
|
|
214
|
+
zIndex: 20,
|
|
215
|
+
backgroundColor: "#fff",
|
|
216
|
+
borderRadius: "14px",
|
|
217
|
+
minWidth: "190px",
|
|
218
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
|
|
219
|
+
overflow: "hidden",
|
|
220
|
+
padding: "6px 0",
|
|
221
|
+
} }, availableTimeSlots.map((slot) => {
|
|
222
|
+
const isActive = slot.label === selectedTimeSlot ||
|
|
223
|
+
slot === activeSlot;
|
|
224
|
+
// Count services in this slot
|
|
225
|
+
const count = services.filter((s) => {
|
|
226
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
227
|
+
return depMin >= slot.start && depMin < slot.end;
|
|
228
|
+
}).length;
|
|
229
|
+
return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
|
|
230
|
+
onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
|
|
231
|
+
onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
|
|
232
|
+
const slotServices = services.filter((s) => {
|
|
233
|
+
const depMin = commonService.timeToMinutes(s.departure_time);
|
|
234
|
+
return (depMin >= slot.start && depMin < slot.end);
|
|
235
|
+
});
|
|
236
|
+
console.log("Selected slot label:", slot.label);
|
|
237
|
+
console.log("Services in selected slot:", slotServices);
|
|
238
|
+
}, 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]"}` },
|
|
239
|
+
React.createElement("span", null, slot.label),
|
|
240
|
+
count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
|
|
241
|
+
? "bg-white text-[#FF5C60]"
|
|
242
|
+
: "bg-[#FF5C60] text-white"}` }, count))));
|
|
243
|
+
})))))),
|
|
182
244
|
React.createElement("div", { className: "flex flex-col items-start gap-[10px] text-[12px] " },
|
|
183
245
|
React.createElement("div", { className: "flex items-justify gap-[8px]" },
|
|
184
246
|
renderIcon("sheildIcon", "16px"),
|
|
@@ -193,12 +255,25 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
193
255
|
React.createElement("div", { className: "min-w-0 px-[22px] flex flex-col items-center justify-between gap-[16px] py-[2px] border-r border-[#363c48] border-l border-[#363c48]" },
|
|
194
256
|
React.createElement("div", { className: "text-center" },
|
|
195
257
|
React.createElement("div", { className: "bold-text text-[13px]" },
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
258
|
+
servicesInActiveSlot.length > 0
|
|
259
|
+
? servicesInActiveSlot.length
|
|
260
|
+
: operatorsCompetingCount,
|
|
261
|
+
" ",
|
|
262
|
+
"operadores compitiendo por tu compra")),
|
|
263
|
+
React.createElement("div", { className: "grid w-full items-stretch gap-[14px]", style: {
|
|
264
|
+
gridTemplateColumns: `repeat(${Math.min(servicesInActiveSlot.length || operators.length, 3)}, 1fr)`,
|
|
265
|
+
} }, (servicesInActiveSlot.length > 0
|
|
266
|
+
? servicesInActiveSlot.slice(0, 3).map((s) => ({
|
|
267
|
+
logo: s.operator_logo_url,
|
|
268
|
+
name: s.operator_name,
|
|
269
|
+
time: s.departure_time,
|
|
270
|
+
seatsAvailable: `${s.available_seats} disponibles`,
|
|
271
|
+
}))
|
|
272
|
+
: 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: {
|
|
273
|
+
// border: "1px solid #363c48",
|
|
274
|
+
// backgroundColor: "#fff",
|
|
200
275
|
border: "1px solid #363c48",
|
|
201
|
-
backgroundColor: "#
|
|
276
|
+
backgroundColor: "#1a202e",
|
|
202
277
|
padding: "14px 10px",
|
|
203
278
|
} },
|
|
204
279
|
React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
|
|
@@ -207,10 +282,10 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
207
282
|
((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
|
|
208
283
|
"/images/service-list/bus-icon.svg";
|
|
209
284
|
}, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
|
|
210
|
-
React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[
|
|
285
|
+
React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[white]" }, op.name),
|
|
211
286
|
React.createElement("div", { className: "bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap" },
|
|
212
287
|
React.createElement("span", null, op === null || op === void 0 ? void 0 : op.time)),
|
|
213
|
-
React.createElement("span", { className: "text-[10px] mt-[6px] text-[
|
|
288
|
+
React.createElement("span", { className: "text-[10px] mt-[6px] text-[white]" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
|
|
214
289
|
React.createElement("div", { className: "flex w-full items-center justify-center gap-[6px] text-[12px] rounded-full", style: {
|
|
215
290
|
padding: "8px 14px",
|
|
216
291
|
marginBottom: "6px",
|
|
@@ -244,10 +319,10 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
244
319
|
animation: "pulse-zoom 2s ease-in-out infinite",
|
|
245
320
|
whiteSpace: "nowrap",
|
|
246
321
|
} },
|
|
247
|
-
|
|
322
|
+
displaySavingsPercent,
|
|
248
323
|
"% OFF"),
|
|
249
324
|
React.createElement("span", { className: "text-[13.33px] font-normal leading-[20px] text-[#9f9f9f] relative", style: { position: "relative" } },
|
|
250
|
-
`$${(
|
|
325
|
+
`$${(displayOriginalPrice * ticketQuantity).toLocaleString()}`,
|
|
251
326
|
React.createElement("span", { style: {
|
|
252
327
|
position: "absolute",
|
|
253
328
|
left: "-2px",
|
|
@@ -258,7 +333,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
258
333
|
transform: "rotate(-10deg)",
|
|
259
334
|
transformOrigin: "center",
|
|
260
335
|
} })),
|
|
261
|
-
React.createElement("span", { className: "text-white bold-text text-[28px] leading-none" }, `$${(
|
|
336
|
+
React.createElement("span", { className: "text-white bold-text text-[28px] leading-none" }, `$${(displayFinalPrice * ticketQuantity).toLocaleString()}`)),
|
|
262
337
|
React.createElement("div", { className: "mt-[4px] flex flex-col items-center gap-[8px]" },
|
|
263
338
|
React.createElement("span", { className: "text-[12px] text-white" }, "\u00BFCu\u00E1ntos pasajes quieres?"),
|
|
264
339
|
React.createElement("div", { className: "flex w-full items-center justify-between rounded-[16px] ", style: {
|
|
@@ -288,7 +363,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
288
363
|
animationData: getAnimationIcon("thunderAnimation"), width: "16px", height: "16px" }),
|
|
289
364
|
React.createElement("span", { className: "whitespace-nowrap" }, "\u00A1Lo quiero!"))),
|
|
290
365
|
React.createElement("div", { className: `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, onClick: onToggleExpand },
|
|
291
|
-
React.createElement("img", { src: (
|
|
366
|
+
React.createElement("img", { src: (_f = serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", style: {
|
|
292
367
|
width: "14px",
|
|
293
368
|
height: "8px",
|
|
294
369
|
filter: "brightness(0) invert(1)",
|
|
@@ -28,5 +28,12 @@ declare const commonService: {
|
|
|
28
28
|
}) => void;
|
|
29
29
|
startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
|
|
30
30
|
startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
|
|
31
|
+
timeToMinutes: (time: string) => number;
|
|
32
|
+
minutesToTime: (minutes: number) => string;
|
|
33
|
+
generateTimeSlots: (from: string, to: string, windowHours: number) => Array<{
|
|
34
|
+
label: string;
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
}>;
|
|
31
38
|
};
|
|
32
39
|
export default commonService;
|
|
@@ -352,7 +352,8 @@ const commonService = {
|
|
|
352
352
|
if (!node)
|
|
353
353
|
return;
|
|
354
354
|
const configKey = `${min}-${max}`;
|
|
355
|
-
if (node.dataset.comprandoId &&
|
|
355
|
+
if (node.dataset.comprandoId &&
|
|
356
|
+
node.dataset.comprandoConfig === configKey) {
|
|
356
357
|
return;
|
|
357
358
|
}
|
|
358
359
|
const prevId = node.dataset.comprandoId;
|
|
@@ -365,7 +366,7 @@ const commonService = {
|
|
|
365
366
|
const changePercent = 0.05; // 5% change
|
|
366
367
|
const change = Math.ceil(current * changePercent);
|
|
367
368
|
const direction = Math.random() > 0.5 ? 1 : -1;
|
|
368
|
-
let next = current +
|
|
369
|
+
let next = current + change * direction;
|
|
369
370
|
// Clamp within min and max
|
|
370
371
|
next = Math.min(max, Math.max(min, next));
|
|
371
372
|
node.textContent = String(next);
|
|
@@ -373,5 +374,34 @@ const commonService = {
|
|
|
373
374
|
node.dataset.comprandoId = String(id);
|
|
374
375
|
node.dataset.comprandoConfig = configKey;
|
|
375
376
|
},
|
|
377
|
+
timeToMinutes: (time) => {
|
|
378
|
+
const [h, m] = time.split(":").map(Number);
|
|
379
|
+
return h * 60 + (m || 0);
|
|
380
|
+
},
|
|
381
|
+
minutesToTime: (minutes) => {
|
|
382
|
+
const h = Math.floor(minutes / 60) % 24;
|
|
383
|
+
const m = minutes % 60;
|
|
384
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
385
|
+
},
|
|
386
|
+
generateTimeSlots: (from, to, windowHours) => {
|
|
387
|
+
if (!windowHours || windowHours <= 0)
|
|
388
|
+
return [];
|
|
389
|
+
const startMin = commonService.timeToMinutes(from);
|
|
390
|
+
const rawEnd = commonService.timeToMinutes(to);
|
|
391
|
+
const endMin = rawEnd === commonService.timeToMinutes("23:59") ? 1440 : rawEnd;
|
|
392
|
+
const windowMin = windowHours * 60;
|
|
393
|
+
const slots = [];
|
|
394
|
+
for (let cur = startMin; cur < endMin; cur += windowMin) {
|
|
395
|
+
const slotEnd = Math.min(cur + windowMin, endMin);
|
|
396
|
+
const startLabel = commonService.minutesToTime(cur);
|
|
397
|
+
const endLabel = commonService.minutesToTime(slotEnd === 1440 ? 1439 : slotEnd);
|
|
398
|
+
slots.push({
|
|
399
|
+
label: `Entre ${startLabel} y ${endLabel}`,
|
|
400
|
+
start: cur,
|
|
401
|
+
end: slotEnd,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return slots;
|
|
405
|
+
},
|
|
376
406
|
};
|
|
377
407
|
export default commonService;
|