kupos-ui-components-lib 9.10.10 → 9.11.1
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 +7 -0
- package/dist/ui/FeatureServiceUI/FeatureServiceUi.js +5 -4
- package/dist/ui/SeatSection/SeatSection.js +29 -3
- package/dist/ui/mobileweb/SeatSectionMobile.js +17 -1
- package/dist/utils/CommonService.d.ts +1 -0
- package/dist/utils/CommonService.js +33 -0
- package/package.json +1 -1
- package/src/ui/FeatureServiceUI/FeatureServiceUi.tsx +12 -10
- package/src/ui/SeatSection/SeatSection.tsx +63 -4
- package/src/ui/mobileweb/SeatSectionMobile.tsx +45 -1
- package/src/utils/CommonService.ts +39 -0
package/dist/styles.css
CHANGED
|
@@ -86,7 +86,8 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
86
86
|
? selectedSlotService.original_price
|
|
87
87
|
: originalPrice;
|
|
88
88
|
const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
|
|
89
|
-
? Math.round(((selectedSlotService.original_price -
|
|
89
|
+
? Math.round(((selectedSlotService.original_price -
|
|
90
|
+
selectedSlotService.final_price) /
|
|
90
91
|
selectedSlotService.original_price) *
|
|
91
92
|
100)
|
|
92
93
|
: savingsPercent;
|
|
@@ -173,7 +174,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
173
174
|
animationData: getAnimationIcon("flameAnimation"), width: "18px", height: "18px" })),
|
|
174
175
|
React.createElement("span", { className: "bold-text" }, "Remate"),
|
|
175
176
|
"\u00A0t\u00E9rmina en \u00A0",
|
|
176
|
-
React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.
|
|
177
|
+
React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.startDealCountdown(node, getCountdownSeconds()), style: {
|
|
177
178
|
fontVariantNumeric: "tabular-nums",
|
|
178
179
|
display: "inline-block",
|
|
179
180
|
color: "#FF5C60",
|
|
@@ -210,7 +211,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
210
211
|
filter: "brightness(0) invert(1)",
|
|
211
212
|
} })),
|
|
212
213
|
isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
|
|
213
|
-
React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
|
|
214
|
+
React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)] hover:z-[200]", style: {
|
|
214
215
|
zIndex: 20,
|
|
215
216
|
backgroundColor: "#fff",
|
|
216
217
|
borderRadius: "14px",
|
|
@@ -282,7 +283,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
|
|
|
282
283
|
((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
|
|
283
284
|
"/images/service-list/bus-icon.svg";
|
|
284
285
|
}, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
|
|
285
|
-
React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[white]" }, op.name),
|
|
286
|
+
React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[white] whitespace-nowrap " }, op.name),
|
|
286
287
|
React.createElement("div", { className: "bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap" },
|
|
287
288
|
React.createElement("span", null, op === null || op === void 0 ? void 0 : op.time)),
|
|
288
289
|
React.createElement("span", { className: "text-[10px] mt-[6px] text-[white]" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
|
|
@@ -58,6 +58,7 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
|
|
|
58
58
|
var _a;
|
|
59
59
|
const uniqueSeats = getUniqueSeats(seatTypes);
|
|
60
60
|
const sortedSeatTypes = getSortedSeatTypes(seatTypes);
|
|
61
|
+
console.log("🚀 ~ SeatSection ~ sortedSeatTypes:", sortedSeatTypes);
|
|
61
62
|
const numberOfSeats = getNumberOfSeats(seatTypes);
|
|
62
63
|
const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
|
|
63
64
|
const formatPrice = (price) => availableSeats <= 0
|
|
@@ -73,6 +74,17 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
|
|
|
73
74
|
};
|
|
74
75
|
const renderSeatPrices = () => {
|
|
75
76
|
if (isPeru) {
|
|
77
|
+
const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
|
|
78
|
+
// Multiple unique seat types → show a price row for each (MovilBus only)
|
|
79
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
80
|
+
return uniqueSeats
|
|
81
|
+
.filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
|
|
82
|
+
.map((val, key) => {
|
|
83
|
+
const { discountedPrice } = CommonService.calculateDiscountedPrice(val.price, serviceItem);
|
|
84
|
+
return (React.createElement("span", { key: key, className: "flex items-center text-[13.33px] bold-text" }, formatPrice(discountedPrice)));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Single seat type → original behaviour (one lowest-fare price)
|
|
76
88
|
const allSeats = getAllSeatTypes(seatTypes);
|
|
77
89
|
const lowestFare = allSeats.length > 0 ? allSeats[0].price : 0;
|
|
78
90
|
const { discountedPrice } = CommonService.calculateDiscountedPrice(lowestFare, serviceItem);
|
|
@@ -116,13 +128,27 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
|
|
|
116
128
|
})();
|
|
117
129
|
const renderLabels = () => {
|
|
118
130
|
if (isPeru) {
|
|
131
|
+
const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
|
|
132
|
+
// Multiple unique seat types → show a label row for each (MovilBus only)
|
|
133
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
134
|
+
return uniqueSeats
|
|
135
|
+
.filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
|
|
136
|
+
.map((val, key) => (React.createElement("span", { key: key, className: `flex items-center justify-between text-[13.33px] ${isSoldOut ? "text-[#c0c0c0]" : ""}` }, CommonService.truncateSeatLabel(val.label))));
|
|
137
|
+
}
|
|
138
|
+
// Single seat type → original behaviour
|
|
139
|
+
const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
|
|
140
|
+
const filteredSeats = seats.filter((s) => !SEAT_EXCEPTIONS.includes(s.label));
|
|
141
|
+
const seatLabel = filteredSeats.length > 0
|
|
142
|
+
? removeDuplicateSeats
|
|
143
|
+
? CommonService.truncateSeatLabel(filteredSeats[0].label)
|
|
144
|
+
: filteredSeats[0].label
|
|
145
|
+
: null;
|
|
146
|
+
const operatorServiceName = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
|
|
119
147
|
return (React.createElement(React.Fragment, null,
|
|
120
148
|
hasDiscount && (React.createElement("span", { className: "text-[13.33px]", style: {
|
|
121
149
|
color: "#999",
|
|
122
|
-
// position: "relative",
|
|
123
|
-
// bottom: numberOfSeats ? "10px" : "",
|
|
124
150
|
} }, "Antes")),
|
|
125
|
-
React.createElement("span", { className: "text-[13.33px]" }, "Desde")));
|
|
151
|
+
React.createElement("span", { className: "text-[13.33px] flex flex-col" }, operatorServiceName ? (React.createElement("span", { className: "text-[13.33px]" }, seatLabel)) : (React.createElement("span", { className: "text-[13.33px]" }, "Desde")))));
|
|
126
152
|
}
|
|
127
153
|
return renderSeatNames();
|
|
128
154
|
};
|
|
@@ -64,13 +64,29 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
|
|
|
64
64
|
if (lowestFare === null)
|
|
65
65
|
return null;
|
|
66
66
|
const priceColor = isSoldOut ? "#bbb" : seatPriceColor;
|
|
67
|
+
const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
|
|
68
|
+
// Fetch ALL unique seats (no slice limit) for the multi-row MovilBus case
|
|
69
|
+
const uniqueSeats = getUniqueSeats(seatTypesData !== null && seatTypesData !== void 0 ? seatTypesData : [], Infinity);
|
|
70
|
+
// MovilBus + multiple unique seat types → render a row per seat
|
|
71
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
72
|
+
return (React.createElement(React.Fragment, null, uniqueSeats.map((seat, key) => {
|
|
73
|
+
const { discountedPrice } = commonService.calculateDiscountedPrice(Number(seat.fare), serviceItem);
|
|
74
|
+
return (React.createElement("div", { key: key, className: "w-[100%] flex flex-row justify-between items-center" },
|
|
75
|
+
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, commonService.truncateSeatLabel(seat.label)),
|
|
76
|
+
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: priceColor } }, commonService.currency(discountedPrice, currencySign))));
|
|
77
|
+
})));
|
|
78
|
+
}
|
|
79
|
+
// Single seat type (or non-MovilBus) → original behaviour
|
|
67
80
|
const { originalPrice, discountedPrice } = commonService.calculateDiscountedPrice(lowestFare, serviceItem);
|
|
81
|
+
const seatLabel = uniqueSeats.length > 0
|
|
82
|
+
? commonService.truncateSeatLabel(uniqueSeats[0].label)
|
|
83
|
+
: null;
|
|
68
84
|
return (React.createElement(React.Fragment, null,
|
|
69
85
|
originalPrice !== discountedPrice && (React.createElement("div", { className: "w-[100%] flex flex-row justify-between items-center" },
|
|
70
86
|
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px]", style: { color: "#bbb" } }, "Antes"),
|
|
71
87
|
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] line-through", style: { color: "#bbb" } }, commonService.currency(originalPrice, currencySign)))),
|
|
72
88
|
React.createElement("div", { className: "w-[100%] flex flex-row justify-between items-center" },
|
|
73
|
-
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, "Desde"),
|
|
89
|
+
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, isMovilBus ? seatLabel || "Desde" : "Desde"),
|
|
74
90
|
React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: priceColor } }, commonService.currency(discountedPrice, currencySign)))));
|
|
75
91
|
};
|
|
76
92
|
const renderDpSeats = () => {
|
|
@@ -27,6 +27,7 @@ declare const commonService: {
|
|
|
27
27
|
interval?: number;
|
|
28
28
|
}) => void;
|
|
29
29
|
startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
|
|
30
|
+
startDealCountdown: (node: HTMLSpanElement | null, countdownSeconds: number) => void;
|
|
30
31
|
startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
|
|
31
32
|
timeToMinutes: (time: string) => number;
|
|
32
33
|
minutesToTime: (minutes: number) => string;
|
|
@@ -348,6 +348,39 @@ const commonService = {
|
|
|
348
348
|
}, 1000);
|
|
349
349
|
node.dataset.countdownId = String(id);
|
|
350
350
|
},
|
|
351
|
+
startDealCountdown: (node, countdownSeconds) => {
|
|
352
|
+
if (!node)
|
|
353
|
+
return;
|
|
354
|
+
if (node.dataset.dealTimerStarted)
|
|
355
|
+
return;
|
|
356
|
+
node.dataset.dealTimerStarted = "true";
|
|
357
|
+
const prevId = node.dataset.dealCountdownId;
|
|
358
|
+
if (prevId)
|
|
359
|
+
clearInterval(Number(prevId));
|
|
360
|
+
let remaining = Math.max(0, Math.floor(countdownSeconds));
|
|
361
|
+
const formatTime = (totalSeconds) => {
|
|
362
|
+
if (totalSeconds <= 0)
|
|
363
|
+
return "Expirado";
|
|
364
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
365
|
+
const m = Math.floor((totalSeconds % 3600) / 60);
|
|
366
|
+
const s = totalSeconds % 60;
|
|
367
|
+
if (h > 0) {
|
|
368
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
369
|
+
}
|
|
370
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
371
|
+
};
|
|
372
|
+
node.textContent = formatTime(remaining);
|
|
373
|
+
if (remaining <= 0)
|
|
374
|
+
return;
|
|
375
|
+
const id = setInterval(() => {
|
|
376
|
+
remaining -= 1;
|
|
377
|
+
node.textContent = formatTime(remaining);
|
|
378
|
+
if (remaining <= 0) {
|
|
379
|
+
clearInterval(Number(node.dataset.dealCountdownId));
|
|
380
|
+
}
|
|
381
|
+
}, 1000);
|
|
382
|
+
node.dataset.dealCountdownId = String(id);
|
|
383
|
+
},
|
|
351
384
|
startComprandoCount: (node, min = 4, max = 16) => {
|
|
352
385
|
if (!node)
|
|
353
386
|
return;
|
package/package.json
CHANGED
|
@@ -120,13 +120,15 @@ const FeatureServiceUi = ({
|
|
|
120
120
|
const displayOriginalPrice = selectedSlotService
|
|
121
121
|
? selectedSlotService.original_price
|
|
122
122
|
: originalPrice;
|
|
123
|
-
const displaySavingsPercent =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
selectedSlotService.original_price
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
const displaySavingsPercent =
|
|
124
|
+
selectedSlotService && selectedSlotService.original_price
|
|
125
|
+
? Math.round(
|
|
126
|
+
((selectedSlotService.original_price -
|
|
127
|
+
selectedSlotService.final_price) /
|
|
128
|
+
selectedSlotService.original_price) *
|
|
129
|
+
100,
|
|
130
|
+
)
|
|
131
|
+
: savingsPercent;
|
|
130
132
|
|
|
131
133
|
// The label shown on the dropdown button
|
|
132
134
|
const departureRange =
|
|
@@ -231,7 +233,7 @@ const FeatureServiceUi = ({
|
|
|
231
233
|
<span
|
|
232
234
|
className="bold-text text-end"
|
|
233
235
|
ref={(node) =>
|
|
234
|
-
commonService.
|
|
236
|
+
commonService.startDealCountdown(node, getCountdownSeconds())
|
|
235
237
|
}
|
|
236
238
|
style={{
|
|
237
239
|
fontVariantNumeric: "tabular-nums",
|
|
@@ -326,7 +328,7 @@ const FeatureServiceUi = ({
|
|
|
326
328
|
{isThisTimeDropdownOpen && (
|
|
327
329
|
<>
|
|
328
330
|
<div
|
|
329
|
-
className="absolute left-0 top-[calc(100%+10px)]"
|
|
331
|
+
className="absolute left-0 top-[calc(100%+10px)] hover:z-[200]"
|
|
330
332
|
style={{
|
|
331
333
|
zIndex: 20,
|
|
332
334
|
backgroundColor: "#fff",
|
|
@@ -472,7 +474,7 @@ const FeatureServiceUi = ({
|
|
|
472
474
|
isSoldOut ? "grayscale" : ""
|
|
473
475
|
}`}
|
|
474
476
|
/>
|
|
475
|
-
<span className="text-[11px] truncate max-w-full text-center text-[white]">
|
|
477
|
+
<span className="text-[11px] truncate max-w-full text-center text-[white] whitespace-nowrap ">
|
|
476
478
|
{op.name}
|
|
477
479
|
</span>
|
|
478
480
|
<div className="bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
|
|
@@ -107,6 +107,7 @@ function SeatSection({
|
|
|
107
107
|
}: SeatSectionProps): React.ReactElement {
|
|
108
108
|
const uniqueSeats = getUniqueSeats(seatTypes);
|
|
109
109
|
const sortedSeatTypes = getSortedSeatTypes(seatTypes);
|
|
110
|
+
console.log("🚀 ~ SeatSection ~ sortedSeatTypes:", sortedSeatTypes);
|
|
110
111
|
const numberOfSeats = getNumberOfSeats(seatTypes);
|
|
111
112
|
const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
|
|
112
113
|
|
|
@@ -138,6 +139,26 @@ function SeatSection({
|
|
|
138
139
|
|
|
139
140
|
const renderSeatPrices = () => {
|
|
140
141
|
if (isPeru) {
|
|
142
|
+
const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
|
|
143
|
+
|
|
144
|
+
// Multiple unique seat types → show a price row for each (MovilBus only)
|
|
145
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
146
|
+
return uniqueSeats
|
|
147
|
+
.filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
|
|
148
|
+
.map((val, key) => {
|
|
149
|
+
const { discountedPrice } = CommonService.calculateDiscountedPrice(
|
|
150
|
+
val.price,
|
|
151
|
+
serviceItem,
|
|
152
|
+
);
|
|
153
|
+
return (
|
|
154
|
+
<span key={key} className="flex items-center text-[13.33px] bold-text">
|
|
155
|
+
{formatPrice(discountedPrice)}
|
|
156
|
+
</span>
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Single seat type → original behaviour (one lowest-fare price)
|
|
141
162
|
const allSeats = getAllSeatTypes(seatTypes);
|
|
142
163
|
const lowestFare = allSeats.length > 0 ? allSeats[0].price : 0;
|
|
143
164
|
const { discountedPrice } = CommonService.calculateDiscountedPrice(
|
|
@@ -217,6 +238,39 @@ function SeatSection({
|
|
|
217
238
|
|
|
218
239
|
const renderLabels = () => {
|
|
219
240
|
if (isPeru) {
|
|
241
|
+
const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
|
|
242
|
+
|
|
243
|
+
// Multiple unique seat types → show a label row for each (MovilBus only)
|
|
244
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
245
|
+
return uniqueSeats
|
|
246
|
+
.filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
|
|
247
|
+
.map((val, key) => (
|
|
248
|
+
<span
|
|
249
|
+
key={key}
|
|
250
|
+
className={`flex items-center justify-between text-[13.33px] ${
|
|
251
|
+
isSoldOut ? "text-[#c0c0c0]" : ""
|
|
252
|
+
}`}
|
|
253
|
+
>
|
|
254
|
+
{CommonService.truncateSeatLabel(val.label)}
|
|
255
|
+
</span>
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Single seat type → original behaviour
|
|
260
|
+
const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
|
|
261
|
+
const filteredSeats = seats.filter(
|
|
262
|
+
(s) => !SEAT_EXCEPTIONS.includes(s.label),
|
|
263
|
+
);
|
|
264
|
+
const seatLabel =
|
|
265
|
+
filteredSeats.length > 0
|
|
266
|
+
? removeDuplicateSeats
|
|
267
|
+
? CommonService.truncateSeatLabel(filteredSeats[0].label)
|
|
268
|
+
: filteredSeats[0].label
|
|
269
|
+
: null;
|
|
270
|
+
|
|
271
|
+
const operatorServiceName =
|
|
272
|
+
serviceItem?.operator_service_name === "MovilBus";
|
|
273
|
+
|
|
220
274
|
return (
|
|
221
275
|
<>
|
|
222
276
|
{hasDiscount && (
|
|
@@ -224,14 +278,19 @@ function SeatSection({
|
|
|
224
278
|
className="text-[13.33px]"
|
|
225
279
|
style={{
|
|
226
280
|
color: "#999",
|
|
227
|
-
// position: "relative",
|
|
228
|
-
// bottom: numberOfSeats ? "10px" : "",
|
|
229
281
|
}}
|
|
230
282
|
>
|
|
231
283
|
Antes
|
|
232
284
|
</span>
|
|
233
285
|
)}
|
|
234
|
-
|
|
286
|
+
|
|
287
|
+
<span className="text-[13.33px] flex flex-col">
|
|
288
|
+
{operatorServiceName ? (
|
|
289
|
+
<span className="text-[13.33px]">{seatLabel}</span>
|
|
290
|
+
) : (
|
|
291
|
+
<span className="text-[13.33px]">Desde</span>
|
|
292
|
+
)}
|
|
293
|
+
</span>
|
|
235
294
|
</>
|
|
236
295
|
);
|
|
237
296
|
}
|
|
@@ -318,7 +377,7 @@ function SeatSection({
|
|
|
318
377
|
<div className="col-start-2 row-start-3 flex h-[30px] items-end justify-center relative">
|
|
319
378
|
<span
|
|
320
379
|
className="flex items-center gap-[6px] text-[22px] bold-text leading-[30px]"
|
|
321
|
-
style={{ color: isSoldOut ? "#c0c0c0" : dpSeatColor||"#ff5964" }}
|
|
380
|
+
style={{ color: isSoldOut ? "#c0c0c0" : dpSeatColor || "#ff5964" }}
|
|
322
381
|
>
|
|
323
382
|
<div
|
|
324
383
|
className="absolute"
|
|
@@ -149,9 +149,53 @@ function SeatSectionMobile({
|
|
|
149
149
|
if (lowestFare === null) return null;
|
|
150
150
|
|
|
151
151
|
const priceColor = isSoldOut ? "#bbb" : seatPriceColor;
|
|
152
|
+
const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
|
|
153
|
+
|
|
154
|
+
// Fetch ALL unique seats (no slice limit) for the multi-row MovilBus case
|
|
155
|
+
const uniqueSeats = getUniqueSeats(seatTypesData ?? [], Infinity as number);
|
|
156
|
+
|
|
157
|
+
// MovilBus + multiple unique seat types → render a row per seat
|
|
158
|
+
if (isMovilBus && uniqueSeats.length > 1) {
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
{uniqueSeats.map((seat, key) => {
|
|
162
|
+
const { discountedPrice } = commonService.calculateDiscountedPrice(
|
|
163
|
+
Number(seat.fare),
|
|
164
|
+
serviceItem,
|
|
165
|
+
);
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
key={key}
|
|
169
|
+
className="w-[100%] flex flex-row justify-between items-center"
|
|
170
|
+
>
|
|
171
|
+
<span
|
|
172
|
+
className="min-[420]:text-[13px] text-[12px] bold-text"
|
|
173
|
+
style={{ color: isSoldOut ? "#bbb" : "#464647" }}
|
|
174
|
+
>
|
|
175
|
+
{commonService.truncateSeatLabel(seat.label)}
|
|
176
|
+
</span>
|
|
177
|
+
<span
|
|
178
|
+
className="min-[420]:text-[13px] text-[12px] bold-text"
|
|
179
|
+
style={{ color: priceColor }}
|
|
180
|
+
>
|
|
181
|
+
{commonService.currency(discountedPrice, currencySign)}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
186
|
+
</>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Single seat type (or non-MovilBus) → original behaviour
|
|
152
191
|
const { originalPrice, discountedPrice } =
|
|
153
192
|
commonService.calculateDiscountedPrice(lowestFare, serviceItem);
|
|
154
193
|
|
|
194
|
+
const seatLabel =
|
|
195
|
+
uniqueSeats.length > 0
|
|
196
|
+
? commonService.truncateSeatLabel(uniqueSeats[0].label)
|
|
197
|
+
: null;
|
|
198
|
+
|
|
155
199
|
return (
|
|
156
200
|
<>
|
|
157
201
|
{originalPrice !== discountedPrice && (
|
|
@@ -175,7 +219,7 @@ function SeatSectionMobile({
|
|
|
175
219
|
className="min-[420]:text-[13px] text-[12px] bold-text"
|
|
176
220
|
style={{ color: isSoldOut ? "#bbb" : "#464647" }}
|
|
177
221
|
>
|
|
178
|
-
Desde
|
|
222
|
+
{isMovilBus ? seatLabel || "Desde" : "Desde"}
|
|
179
223
|
</span>
|
|
180
224
|
<span
|
|
181
225
|
className="min-[420]:text-[13px] text-[12px] bold-text"
|
|
@@ -399,6 +399,45 @@ const commonService = {
|
|
|
399
399
|
node.dataset.countdownId = String(id);
|
|
400
400
|
},
|
|
401
401
|
|
|
402
|
+
startDealCountdown: (
|
|
403
|
+
node: HTMLSpanElement | null,
|
|
404
|
+
countdownSeconds: number,
|
|
405
|
+
) => {
|
|
406
|
+
if (!node) return;
|
|
407
|
+
if (node.dataset.dealTimerStarted) return;
|
|
408
|
+
node.dataset.dealTimerStarted = "true";
|
|
409
|
+
|
|
410
|
+
const prevId = node.dataset.dealCountdownId;
|
|
411
|
+
if (prevId) clearInterval(Number(prevId));
|
|
412
|
+
|
|
413
|
+
let remaining = Math.max(0, Math.floor(countdownSeconds));
|
|
414
|
+
|
|
415
|
+
const formatTime = (totalSeconds: number) => {
|
|
416
|
+
if (totalSeconds <= 0) return "Expirado";
|
|
417
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
418
|
+
const m = Math.floor((totalSeconds % 3600) / 60);
|
|
419
|
+
const s = totalSeconds % 60;
|
|
420
|
+
if (h > 0) {
|
|
421
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
422
|
+
}
|
|
423
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
node.textContent = formatTime(remaining);
|
|
427
|
+
|
|
428
|
+
if (remaining <= 0) return;
|
|
429
|
+
|
|
430
|
+
const id = setInterval(() => {
|
|
431
|
+
remaining -= 1;
|
|
432
|
+
node.textContent = formatTime(remaining);
|
|
433
|
+
if (remaining <= 0) {
|
|
434
|
+
clearInterval(Number(node.dataset.dealCountdownId));
|
|
435
|
+
}
|
|
436
|
+
}, 1000);
|
|
437
|
+
|
|
438
|
+
node.dataset.dealCountdownId = String(id);
|
|
439
|
+
},
|
|
440
|
+
|
|
402
441
|
startComprandoCount: (
|
|
403
442
|
node: HTMLSpanElement | null,
|
|
404
443
|
min: number = 4,
|