@weball/ui-kit
Version:
UI Kit library with flexible fixture and tournament components for sports applications
1,258 lines (1,234 loc) • 84.3 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import React, { forwardRef, useRef, useState, useEffect, useMemo } from 'react';
import { Tooltip, Modal, InputNumber } from 'antd';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var Box = forwardRef(function (_a, ref) {
var children = _a.children,
_b = _a.className,
className = _b === void 0 ? '' : _b,
position = _a.position,
width = _a.width,
height = _a.height,
borderWidth = _a.borderWidth,
borderTopWidth = _a.borderTopWidth,
borderLeftWidth = _a.borderLeftWidth,
borderRightWidth = _a.borderRightWidth,
borderBottomWidth = _a.borderBottomWidth,
borderStyle = _a.borderStyle,
borderTopRightRadius = _a.borderTopRightRadius,
borderBottomRightRadius = _a.borderBottomRightRadius,
borderTopLeftRadius = _a.borderTopLeftRadius,
borderBottomLeftRadius = _a.borderBottomLeftRadius,
borderColor = _a.borderColor,
backgroundColor = _a.backgroundColor,
px = _a.px,
py = _a.py,
gap = _a.gap,
color = _a.color,
fontSize = _a.fontSize,
fontWeight = _a.fontWeight,
textAlign = _a.textAlign,
pointerEvents = _a.pointerEvents,
style = _a.style,
props = __rest(_a, ["children", "className", "position", "width", "height", "borderWidth", "borderTopWidth", "borderLeftWidth", "borderRightWidth", "borderBottomWidth", "borderStyle", "borderTopRightRadius", "borderBottomRightRadius", "borderTopLeftRadius", "borderBottomLeftRadius", "borderColor", "backgroundColor", "px", "py", "gap", "color", "fontSize", "fontWeight", "textAlign", "pointerEvents", "style"]);
var computedStyle = __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({
display: 'block',
boxSizing: 'border-box'
}, style), {
width: typeof width === 'number' ? "".concat(width, "px") : width,
height: typeof height === 'number' ? "".concat(height, "px") : height
}), borderWidth && {
borderWidth: "".concat(borderWidth, "px"),
borderStyle: borderStyle || 'solid',
borderColor: borderColor || 'currentColor'
}), borderTopWidth !== undefined && {
borderTopWidth: "".concat(borderTopWidth, "px"),
borderTopStyle: borderStyle || 'solid',
borderTopColor: borderColor || 'currentColor'
}), borderLeftWidth !== undefined && {
borderLeftWidth: "".concat(borderLeftWidth, "px"),
borderLeftStyle: borderLeftWidth === 0 ? 'none' : borderStyle || 'solid',
borderLeftColor: borderLeftWidth === 0 ? 'transparent' : borderColor || 'currentColor'
}), borderRightWidth !== undefined && {
borderRightWidth: "".concat(borderRightWidth, "px"),
borderRightStyle: borderRightWidth === 0 ? 'none' : borderStyle || 'solid',
borderRightColor: borderRightWidth === 0 ? 'transparent' : borderColor || 'currentColor'
}), borderBottomWidth !== undefined && {
borderBottomWidth: "".concat(borderBottomWidth, "px"),
borderBottomStyle: borderBottomWidth === 0 ? 'none' : borderStyle || 'solid',
borderBottomColor: borderBottomWidth === 0 ? 'transparent' : borderColor || 'currentColor'
}), {
// Border radius
borderTopRightRadius: borderTopRightRadius ? "".concat(borderTopRightRadius, "px") : undefined,
borderBottomRightRadius: borderBottomRightRadius ? "".concat(borderBottomRightRadius, "px") : undefined,
borderTopLeftRadius: borderTopLeftRadius ? "".concat(borderTopLeftRadius, "px") : undefined,
borderBottomLeftRadius: borderBottomLeftRadius ? "".concat(borderBottomLeftRadius, "px") : undefined,
// Background
backgroundColor: backgroundColor,
// Spacing - Chakra UI behavior
paddingLeft: px ? "".concat(px * 4, "px") : undefined,
paddingRight: px ? "".concat(px * 4, "px") : undefined,
paddingTop: py ? "".concat(py * 4, "px") : undefined,
paddingBottom: py ? "".concat(py * 4, "px") : undefined,
gap: gap ? "".concat(gap * 4, "px") : undefined,
// Layout
position: position,
// Typography
color: color,
fontSize: fontSize,
fontWeight: fontWeight,
textAlign: textAlign,
// Interaction
pointerEvents: pointerEvents
});
return jsx("div", __assign({
ref: ref,
className: className,
style: computedStyle
}, props, {
children: children
}));
});
Box.displayName = 'Box';
var Flex = forwardRef(function (_a, ref) {
var children = _a.children,
_b = _a.className,
className = _b === void 0 ? '' : _b,
direction = _a.direction,
alignItems = _a.alignItems,
justifyContent = _a.justifyContent,
gap = _a.gap,
width = _a.width,
height = _a.height,
position = _a.position,
px = _a.px,
py = _a.py,
mt = _a.mt,
borderRadius = _a.borderRadius,
flexDirection = _a.flexDirection,
color = _a.color,
fontWeight = _a.fontWeight,
cursor = _a.cursor,
zIndex = _a.zIndex,
backgroundColor = _a.backgroundColor,
style = _a.style,
props = __rest(_a, ["children", "className", "direction", "alignItems", "justifyContent", "gap", "width", "height", "position", "px", "py", "mt", "borderRadius", "flexDirection", "color", "fontWeight", "cursor", "zIndex", "backgroundColor", "style"]);
var baseClasses = 'flex';
var directionClasses = {
'row': 'flex-row',
'column': 'flex-col',
'row-reverse': 'flex-row-reverse',
'column-reverse': 'flex-col-reverse'
};
var alignClasses = {
'flex-start': 'items-start',
'flex-end': 'items-end',
'center': 'items-center',
'baseline': 'items-baseline',
'stretch': 'items-stretch'
};
var justifyClasses = {
'flex-start': 'justify-start',
'flex-end': 'justify-end',
'center': 'justify-center',
'space-between': 'justify-between',
'space-around': 'justify-around',
'space-evenly': 'justify-evenly'
};
var finalDirection = direction || flexDirection;
var directionClass = finalDirection ? directionClasses[finalDirection] : '';
var alignClass = alignItems ? alignClasses[alignItems] : '';
var justifyClass = justifyContent ? justifyClasses[justifyContent] : '';
var positionClass = position || '';
var computedStyle = __assign(__assign({}, style), {
width: typeof width === 'number' ? "".concat(width, "px") : width,
height: typeof height === 'number' ? "".concat(height, "px") : height,
gap: gap ? "".concat(gap * 4, "px") : undefined,
paddingLeft: px ? "".concat(px * 4, "px") : undefined,
paddingRight: px ? "".concat(px * 4, "px") : undefined,
paddingTop: py ? "".concat(py * 4, "px") : undefined,
paddingBottom: py ? "".concat(py * 4, "px") : undefined,
marginTop: mt ? "".concat(mt * 4, "px") : undefined,
borderRadius: borderRadius ? "".concat(borderRadius, "px") : undefined,
position: position,
color: color,
fontWeight: fontWeight,
cursor: cursor,
zIndex: zIndex,
backgroundColor: backgroundColor
});
return jsx("div", __assign({
ref: ref,
className: "".concat(baseClasses, " ").concat(directionClass, " ").concat(alignClass, " ").concat(justifyClass, " ").concat(positionClass, " ").concat(className),
style: computedStyle
}, props, {
children: children
}));
});
Flex.displayName = 'Flex';
var Text = forwardRef(function (_a, ref) {
var children = _a.children,
_b = _a.className,
className = _b === void 0 ? '' : _b,
fontSize = _a.fontSize,
fontWeight = _a.fontWeight,
color = _a.color,
textAlign = _a.textAlign,
px = _a.px,
py = _a.py,
height = _a.height,
position = _a.position,
pointerEvents = _a.pointerEvents,
_c = _a.as,
as = _c === void 0 ? 'p' : _c,
style = _a.style,
onClick = _a.onClick,
width = _a.width;
__rest(_a, ["children", "className", "fontSize", "fontWeight", "color", "textAlign", "px", "py", "height", "position", "pointerEvents", "as", "style", "onClick", "width"]);
var computedStyle = __assign(__assign({}, style), {
fontSize: fontSize,
fontWeight: fontWeight,
color: color,
textAlign: textAlign,
paddingLeft: px ? "".concat(px * 4, "px") : undefined,
paddingRight: px ? "".concat(px * 4, "px") : undefined,
paddingTop: py ? "".concat(py * 4, "px") : undefined,
paddingBottom: py ? "".concat(py * 4, "px") : undefined,
height: typeof height === 'number' ? "".concat(height * 4, "px") : height,
position: position,
pointerEvents: pointerEvents,
width: width
});
var commonProps = {
ref: ref,
className: className,
style: computedStyle,
onClick: onClick
};
switch (as) {
case 'span':
return jsx("span", __assign({}, commonProps, {
children: children
}));
case 'div':
return jsx("div", __assign({}, commonProps, {
children: children
}));
case 'h1':
return jsx("h1", __assign({}, commonProps, {
children: children
}));
case 'h2':
return jsx("h2", __assign({}, commonProps, {
children: children
}));
case 'h3':
return jsx("h3", __assign({}, commonProps, {
children: children
}));
case 'h4':
return jsx("h4", __assign({}, commonProps, {
children: children
}));
case 'h5':
return jsx("h5", __assign({}, commonProps, {
children: children
}));
case 'h6':
return jsx("h6", __assign({}, commonProps, {
children: children
}));
default:
return jsx("p", __assign({}, commonProps, {
children: children
}));
}
});
Text.displayName = 'Text';
var Image = function (_a) {
var src = _a.src,
_b = _a.alt,
alt = _b === void 0 ? '' : _b,
width = _a.width,
height = _a.height,
_c = _a.className,
className = _c === void 0 ? '' : _c,
style = _a.style,
onError = _a.onError,
onLoad = _a.onLoad,
props = __rest(_a, ["src", "alt", "width", "height", "className", "style", "onError", "onLoad"]);
var computedStyle = __assign(__assign({}, style), {
width: width,
height: height
});
return jsx("img", __assign({
src: src,
alt: alt,
className: className,
style: computedStyle,
onError: onError,
onLoad: onLoad
}, props));
};
var Collapse = function (_a) {
var children = _a.children,
isOpen = _a.in,
_b = _a.animateOpacity,
animateOpacity = _b === void 0 ? false : _b,
_c = _a.className,
className = _c === void 0 ? '' : _c,
style = _a.style;
var contentRef = useRef(null);
var _d = useState(isOpen ? 'auto' : 0),
height = _d[0],
setHeight = _d[1];
useEffect(function () {
if (contentRef.current) {
var contentHeight = contentRef.current.scrollHeight;
setHeight(isOpen ? contentHeight : 0);
}
}, [isOpen]);
var computedStyle = __assign(__assign({}, style), {
height: height === 'auto' ? 'auto' : "".concat(height, "px"),
overflow: 'hidden',
transition: "height 0.3s ease-in-out".concat(animateOpacity ? ', opacity 0.3s ease-in-out' : ''),
opacity: animateOpacity ? isOpen ? 1 : 0 : 1
});
return jsx("div", {
ref: contentRef,
className: className,
style: computedStyle,
children: children
});
};
var Divider = function (_a) {
var _b = _a.className,
className = _b === void 0 ? '' : _b,
style = _a.style,
_c = _a.orientation,
orientation = _c === void 0 ? 'horizontal' : _c,
_d = _a.color,
color = _d === void 0 ? '#e2e8f0' : _d;
var computedStyle = __assign(__assign(__assign({}, style), {
backgroundColor: color,
border: 'none'
}), orientation === 'horizontal' ? {
width: '100%',
height: '1px'
} : {
width: '1px',
height: '100%'
});
return jsx("hr", {
className: className,
style: computedStyle
});
};
var FIXTURE_BRACE_WIDTH = 10;
var FIXTURE_LINE_WIDTH = 10;
var FIXTURE_FINAL_LINE_WIDTH = 50;
var FIXTURE_NODE_HEIGHT = 80;
var FIXTURE_NODE_WIDTH = 170;
var FIXTURE_HEIGHT_BETWEEN_NODES = 10;
var FIXTURE_HEIGHT_BETWEEN_GROUPS = 35;
var FIXTURE_WIDTH_BETWEEN_WINGS = 70;
var FIXTURE_STAGE_SIZE = 14;
var FIXTURE_WINNER_STAGE_SIZE = 40;
var FIXTURE_WINNER_WIDTH = 170;
var FIXTURE_WINNER_HEIGHT = 60;
var FIXTURE_SCROLL_SIZE = 30;
var SRC_IMG = "https://img.freepik.com/vector-gratis/balon-futbol-aislado_1284-41812.jpg?size=338&ext=jpg&ga=GA1.1.1788614524.1719187200&semt=ais_user";
function getOrderedMatchesByParentChildrenCount(tree, stages, fromStage) {
var _a;
// 1) Recolectamos datos en un array auxiliar
var matches = [];
var parentIdCounter = 1;
function traverse(node, parentId) {
var _a;
// Si no hay hijos
if (!node.children || node.children.length === 0) {
node.matchesPlanning.forEach(function (m) {
var _a;
if ((_a = m.tournamentMatches) === null || _a === void 0 ? void 0 : _a.length) {
matches.push({
match: m,
parentChildrenCount: 0,
parentId: parentId
});
}
});
return 0;
}
var childrenCount = node.children.length;
// Recolectamos del nodo actual
(_a = node.matchesPlanning) === null || _a === void 0 ? void 0 : _a.forEach(function (m) {
matches.push({
match: m,
parentChildrenCount: childrenCount,
parentId: parentId
});
});
// Recorremos recursivamente
for (var _i = 0, _b = node.children; _i < _b.length; _i++) {
var child = _b[_i];
traverse(child, parentIdCounter);
parentIdCounter++;
}
return childrenCount;
}
// 2) Iniciar la recursividad
traverse(tree);
// 3) Orden descendente según hijos
matches.sort(function (a, b) {
return b.parentChildrenCount - a.parentChildrenCount;
});
// 4) Variables para la lógica de "stageNumberFromFinal" y "parentRank"
var currentRank = 1;
var previousParentId = (_a = matches[0]) === null || _a === void 0 ? void 0 : _a.parentId;
var lastStageNumberFromFinal;
var parentRankCounter = 1;
// 5) Creamos el array de WBFixtureNode (sin mutar match)
var wbFixtureNodes = matches.map(function (item, index) {
// Si cambió el parentId, subimos la “ronda”
if (item.parentId !== previousParentId) {
currentRank++;
previousParentId = item.parentId;
}
var stageNumber = stages - currentRank + 1;
// Si cambió la ronda, reseteamos parentRankCounter a 1
if (stageNumber !== lastStageNumberFromFinal) {
lastStageNumberFromFinal = stageNumber;
parentRankCounter = 1;
}
// Construimos un nuevo objeto con las props que SÍ tiene FixtureVisualizerMatch
// (por ejemplo, 'id', 'score', 'participants', etc.),
// y agregamos las 5 propiedades que definiste en WBFixtureNode.
var parentRank = parentRankCounter;
var wbNode = __assign(__assign({}, item.match), {
stageNumberFromFinal: stageNumber,
nodeNumber: index + 1,
parentRank: parentRank,
groupNumber: parentRank // O la lógica que tú quieras
});
parentRankCounter++;
return wbNode;
});
return wbFixtureNodes.filter(function (m) {
return m.stageNumberFromFinal >= (0);
});
}
function getShortestNameClubInscription(clubInscription, slice) {
var _a, _b, _c;
if (slice === void 0) {
slice = 11;
}
return ((clubInscription === null || clubInscription === void 0 ? void 0 : clubInscription.tableName) || (clubInscription === null || clubInscription === void 0 ? void 0 : clubInscription.name) || ((_a = clubInscription === null || clubInscription === void 0 ? void 0 : clubInscription.club) === null || _a === void 0 ? void 0 : _a.tableShortName) || ((_b = clubInscription === null || clubInscription === void 0 ? void 0 : clubInscription.club) === null || _b === void 0 ? void 0 : _b.tableName) || ((_c = clubInscription === null || clubInscription === void 0 ? void 0 : clubInscription.club) === null || _c === void 0 ? void 0 : _c.name) || "").slice(0, slice);
}
var WbFixtureNodeClub = function (props) {
var _a, _b, _c, _d, _e, _f, _g;
var onClickMatch = props.onClickMatch,
visualizerMatch = props.match,
local = props.local,
nodeSelected = props.nodeSelected,
_h = props.editMode,
editMode = _h === void 0 ? false : _h;
var match = visualizerMatch;
var _j = useMemo(function () {
var _a, _b, _c, _d, _e;
if (!match) return {};
var hasOnlyOneMAtch = ((_a = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _a === void 0 ? void 0 : _a.length) === 1;
if (hasOnlyOneMAtch) {
return {
clubScore: local ? (_b = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _b === void 0 ? void 0 : _b[0].scoreHome : (_c = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _c === void 0 ? void 0 : _c[0].scoreAway,
clubPenaltyScore: local ? (_d = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _d === void 0 ? void 0 : _d[0].scoreHomePenalty : (_e = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _e === void 0 ? void 0 : _e[0].scoreAwayPenalty
};
}
return {
clubScore: local ? match === null || match === void 0 ? void 0 : match.valueScoreHome : match.valueScoreAway,
clubPenaltyScore: local ? match === null || match === void 0 ? void 0 : match.valueScoreHomePenalty : match === null || match === void 0 ? void 0 : match.valueScoreAwayPenalty
};
}, [match, local]),
clubScore = _j.clubScore,
clubPenaltyScore = _j.clubPenaltyScore;
var hasLost = match.teamWon && (local ? ((_a = match.teamWon) === null || _a === void 0 ? void 0 : _a.id) !== ((_b = match.clubHome) === null || _b === void 0 ? void 0 : _b.id) : ((_c = match.teamWon) === null || _c === void 0 ? void 0 : _c.id) !== ((_d = match.clubAway) === null || _d === void 0 ? void 0 : _d.id));
var club = local ? match.clubHome : match.clubAway;
var vacancy = local ? match === null || match === void 0 ? void 0 : match.vacancyHome : match === null || match === void 0 ? void 0 : match.vacancyAway;
if (!match) return null;
return jsxs(Flex, {
direction: "row",
alignItems: "center",
justifyContent: "space-between",
style: {
opacity: nodeSelected ? 1 : hasLost ? 0.5 : 1,
backgroundColor: nodeSelected ? "gray" : "unset"
},
children: [jsxs(Flex, {
direction: "row",
alignItems: "center",
className: "flex-1 max-w-[80%] hover:bg-gray-300 transition-all duration-400",
gap: 2,
onClick: editMode ? function () {
return onClickMatch(match, local ? "local" : "visit");
} : undefined,
children: [((_e = club === null || club === void 0 ? void 0 : club.clubInscription) === null || _e === void 0 ? void 0 : _e.logo) ? jsx(Fragment, {
children: jsx(Image, {
src: ((_f = club === null || club === void 0 ? void 0 : club.clubInscription) === null || _f === void 0 ? void 0 : _f.logo) || SRC_IMG,
width: "20px",
height: "20px"
})
}) : jsx("div", {
className: "block h-4 aspect-square rounded-md",
style: {
backgroundColor: ((_g = club === null || club === void 0 ? void 0 : club.clubInscription) === null || _g === void 0 ? void 0 : _g.color) || (vacancy === null || vacancy === void 0 ? void 0 : vacancy.color)
}
}), jsx(Tooltip, {
title: getShortestNameClubInscription(club === null || club === void 0 ? void 0 : club.clubInscription) || (vacancy === null || vacancy === void 0 ? void 0 : vacancy.name),
mouseEnterDelay: 0.1,
mouseLeaveDelay: 0.1,
placement: "topLeft",
trigger: "hover",
overlayClassName: "custom-tooltip",
children: jsx(Text, {
className: "text-[10px] whitespace-nowrap overflow-hidden text-ellipsis ",
fontWeight: "bold",
children: getShortestNameClubInscription(club === null || club === void 0 ? void 0 : club.clubInscription) || (vacancy === null || vacancy === void 0 ? void 0 : vacancy.name) || ""
})
})]
}), jsx("div", {
className: "transition-all duration-400 min-w-[40px] flex flex-col items-center justify-center ".concat(editMode ? "hover:bg-gray-300 cursor-pointer" : "cursor-default"),
onClick: editMode ? function () {
return onClickMatch(match, local ? "resultLocal" : "resultVisit");
} : undefined,
children: jsxs("div", {
className: "flex items-center gap-1",
children: [jsx(Text, {
fontWeight: "bold",
height: 6,
children: clubScore
}), clubPenaltyScore !== undefined && clubPenaltyScore !== null && jsxs(Text, {
fontSize: "10px",
className: "text-gray-500",
children: ["(", clubPenaltyScore, ")"]
})]
})
})]
});
};
/**
* WbFixtureResult component - displays tournament match results with team information
*
* @example
* ```tsx
* // Basic usage with minimum required properties
* <WbFixtureResult
* tournamentMatch={{
* id: 1,
* scoreHome: 2,
* scoreAway: 1,
* category: { categoryInstance: { name: "Final" } },
* matchInfo: { vacancyHome: null, vacancyAway: null }
* }}
* fixtureMatch={{
* id: 1,
* clubHome: { clubInscription: { logo: "...", name: "Team A", color: "#fff" } },
* clubAway: { clubInscription: { logo: "...", name: "Team B", color: "#000" } }
* }}
* />
*
* // Usage with extended data
* <WbFixtureResult
* tournamentMatch={{
* id: 1,
* scoreHome: 2,
* scoreAway: 1,
* customField: "my custom data",
* category: { categoryInstance: { name: "Final", customCategoryData: "..." } },
* matchInfo: { vacancyHome: null, vacancyAway: null, customMatchInfo: "..." }
* }}
* fixtureMatch={{
* id: 1,
* clubHome: { clubInscription: { logo: "...", name: "Team A", color: "#fff", customClubData: "..." } },
* clubAway: { clubInscription: { logo: "...", name: "Team B", color: "#000" } },
* customFixtureData: "my custom fixture data"
* }}
* />
* ```
*/
var WbFixtureResult = function (props) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
var tournamentMatch = props.tournamentMatch,
fixtureMatch = props.fixtureMatch;
tournamentMatch.scoreHomePenalty !== undefined && tournamentMatch.scoreHomePenalty !== null || tournamentMatch.scoreAwayPenalty !== undefined && tournamentMatch.scoreAwayPenalty !== null;
if (!tournamentMatch) return null;
return jsxs(Flex, {
flexDirection: "column",
gap: 2,
px: 4,
py: 3,
borderRadius: 12,
width: FIXTURE_NODE_WIDTH + 10 + "px",
height: FIXTURE_NODE_HEIGHT + 20 + "px",
maxWidth: "100%",
children: [jsx(Text, {
className: "text-white text-xs p-0",
children: (_b = (_a = tournamentMatch.category) === null || _a === void 0 ? void 0 : _a.categoryInstance) === null || _b === void 0 ? void 0 : _b.name
}), jsxs(Flex, {
direction: "row",
alignItems: "center",
justifyContent: "space-between",
children: [jsxs(Flex, {
direction: "row",
alignItems: "center",
className: "flex-1 max-w-[80%] text-white transition-all duration-400",
gap: 2,
children: [((_d = (_c = fixtureMatch.clubHome) === null || _c === void 0 ? void 0 : _c.clubInscription) === null || _d === void 0 ? void 0 : _d.logo) ? jsx(Fragment, {
children: jsx(Image, {
src: ((_f = (_e = fixtureMatch.clubHome) === null || _e === void 0 ? void 0 : _e.clubInscription) === null || _f === void 0 ? void 0 : _f.logo) || SRC_IMG,
width: "20px",
height: "20px"
})
}) : jsx("div", {
className: "block h-4 aspect-square rounded-md",
style: {
backgroundColor: ((_h = (_g = fixtureMatch.clubHome) === null || _g === void 0 ? void 0 : _g.clubInscription) === null || _h === void 0 ? void 0 : _h.color) || ((_j = tournamentMatch.matchInfo.vacancyHome) === null || _j === void 0 ? void 0 : _j.color)
}
}), jsx(Tooltip, {
title: getShortestNameClubInscription((_k = fixtureMatch === null || fixtureMatch === void 0 ? void 0 : fixtureMatch.clubHome) === null || _k === void 0 ? void 0 : _k.clubInscription) || ((_l = tournamentMatch.matchInfo.vacancyHome) === null || _l === void 0 ? void 0 : _l.name) || "",
mouseEnterDelay: 0.1,
mouseLeaveDelay: 0.1,
placement: "topLeft",
trigger: "hover",
children: jsx(Text, {
className: "text-[10px] whitespace-nowrap overflow-hidden text-ellipsis ",
fontWeight: "bold",
children: getShortestNameClubInscription((_m = fixtureMatch === null || fixtureMatch === void 0 ? void 0 : fixtureMatch.clubHome) === null || _m === void 0 ? void 0 : _m.clubInscription) || ((_o = tournamentMatch.matchInfo.vacancyHome) === null || _o === void 0 ? void 0 : _o.name) || ""
})
})]
}), jsx("div", {
className: "text-white transition-all duration-400 min-w-[40px] flex flex-col items-center justify-center",
children: jsxs("div", {
className: "flex items-center gap-1",
children: [jsx(Text, {
fontWeight: "bold",
height: 6,
children: tournamentMatch.scoreHome || 0
}), tournamentMatch.scoreHomePenalty !== undefined && tournamentMatch.scoreHomePenalty !== null && jsxs(Text, {
fontSize: "10px",
className: "text-gray-300",
children: ["(", tournamentMatch.scoreHomePenalty, ")"]
})]
})
})]
}), jsxs(Flex, {
direction: "row",
alignItems: "center",
justifyContent: "space-between",
children: [jsxs(Flex, {
direction: "row",
alignItems: "center",
className: "flex-1 max-w-[80%] text-white transition-all duration-400",
gap: 2,
children: [((_q = (_p = fixtureMatch.clubAway) === null || _p === void 0 ? void 0 : _p.clubInscription) === null || _q === void 0 ? void 0 : _q.logo) ? jsx(Fragment, {
children: jsx(Image, {
src: ((_s = (_r = fixtureMatch.clubAway) === null || _r === void 0 ? void 0 : _r.clubInscription) === null || _s === void 0 ? void 0 : _s.logo) || SRC_IMG,
width: "20px",
height: "20px"
})
}) : jsx("div", {
className: "block h-4 aspect-square rounded-md",
style: {
backgroundColor: ((_u = (_t = fixtureMatch.clubAway) === null || _t === void 0 ? void 0 : _t.clubInscription) === null || _u === void 0 ? void 0 : _u.color) || ((_v = tournamentMatch.matchInfo.vacancyAway) === null || _v === void 0 ? void 0 : _v.color)
}
}), jsx(Tooltip, {
title: getShortestNameClubInscription((_w = fixtureMatch.clubAway) === null || _w === void 0 ? void 0 : _w.clubInscription) || ((_x = tournamentMatch.matchInfo.vacancyAway) === null || _x === void 0 ? void 0 : _x.name) || "",
mouseEnterDelay: 0.1,
mouseLeaveDelay: 0.1,
placement: "topLeft",
trigger: "hover",
children: jsx(Text, {
className: "text-[10px] whitespace-nowrap overflow-hidden text-ellipsis ",
fontWeight: "bold",
children: getShortestNameClubInscription((_y = fixtureMatch.clubAway) === null || _y === void 0 ? void 0 : _y.clubInscription) || ((_z = tournamentMatch.matchInfo.vacancyAway) === null || _z === void 0 ? void 0 : _z.name) || ""
})
})]
}), jsx("div", {
className: "text-white transition-all duration-400 min-w-[40px] flex flex-col items-center justify-center",
children: jsxs("div", {
className: "flex items-center gap-1",
children: [jsx(Text, {
fontWeight: "bold",
height: 6,
children: tournamentMatch.scoreAway || 0
}), tournamentMatch.scoreAwayPenalty !== undefined && tournamentMatch.scoreAwayPenalty !== null && jsxs(Text, {
fontSize: "10px",
className: "text-gray-300",
children: ["(", tournamentMatch.scoreAwayPenalty, ")"]
})]
})
})]
})]
});
};
var WbColors = {
light: {
backgroundGrey: "#e9e9e9",
inputBorder: "#00000040",
darkGrey: "#464646",
grey: "#707070"}};
/**
* Helper function to convert an array of fixtures into a FixtureVisualizer object
*
* @param fixtures - Array of fixture data
* @param options - Optional configuration
* @returns FixtureVisualizer object
*
* @example
* ```typescript
* const fixtures = [/* your fixture array *\/];
* const fixtureRoot = createFixtureRoot(fixtures);
*
* // Use in your component:
* // <WbFixture fixtureVisualizerRoot={fixtureRoot} />
* ```
*/
function createFixtureRoot(fixtures, options) {
var _a, _b;
if (options === void 0) {
options = {};
}
return {
id: (_a = options.id) !== null && _a !== void 0 ? _a : 1,
children: [],
matchesPlanning: (_b = options.matchesPlanning) !== null && _b !== void 0 ? _b : [],
fixtures: fixtures
};
}
// eslint-disable-next-line react/display-name
var WbFixtureNode = React.forwardRef(function (props, ref) {
var _a, _b, _c;
var match = props.match,
onClickMatch = props.onClickMatch,
nodeSelected = props.nodeSelected,
_d = props.editMode,
editMode = _d === void 0 ? false : _d;
var _e = useState(false),
showDetails = _e[0],
setShowDetails = _e[1];
var handleClickMatch = function (match, position) {
var _a, _b;
if (position === "resultLocal" || position === "resultVisit") {
// Only allow editing results if editMode is enabled
if (editMode && ((_a = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _a === void 0 ? void 0 : _a.length) && ((_b = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _b === void 0 ? void 0 : _b.length) >= 1) {
setEditingResult({
position: position,
visible: true
});
}
return;
}
onClickMatch(match, position);
setShowDetails(function (prev) {
return !prev;
});
};
var handleContainerClick = function () {
// Only trigger expandable when not in edit mode
if (!editMode) {
setShowDetails(function (prev) {
return !prev;
});
}
};
var _f = useState({
position: "resultLocal",
visible: false
}),
editingResult = _f[0],
setEditingResult = _f[1];
var _g = useState(null),
inputScore = _g[0],
setInputScore = _g[1];
return jsxs(Fragment, {
children: [jsxs(Flex, {
direction: "column",
cursor: "pointer",
ref: ref,
children: [jsxs(Flex, {
flexDirection: "column",
gap: 2,
px: 4,
py: 3,
borderRadius: 12,
width: FIXTURE_NODE_WIDTH + "px",
height: FIXTURE_NODE_HEIGHT + "px",
onClick: handleContainerClick,
style: {
backgroundColor: nodeSelected ? "gray" : WbColors.light.backgroundGrey
},
children: [jsx(WbFixtureNodeClub, {
nodeSelected: nodeSelected,
local: true,
onClickMatch: handleClickMatch,
match: match,
editMode: editMode
}), jsx(WbFixtureNodeClub, {
nodeSelected: nodeSelected,
local: false,
onClickMatch: handleClickMatch,
match: match,
editMode: editMode
})]
}), jsx(Collapse, {
in: showDetails,
animateOpacity: true,
style: {
zIndex: 999
},
children: ((_a = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _a === void 0 ? void 0 : _a.length) && ((_b = match === null || match === void 0 ? void 0 : match.tournamentMatches) === null || _b === void 0 ? void 0 : _b.length) > 1 && jsx(Flex, {
direction: "column",
gap: 1,
mt: 1,
py: 2,
borderRadius: 8,
zIndex: 999,
backgroundColor: WbColors.light.darkGrey,
width: FIXTURE_NODE_WIDTH + "px",
children: (_c = match.tournamentMatches) === null || _c === void 0 ? void 0 : _c.map(function (tm, index) {
return jsxs(React.Fragment, {
children: [jsx(WbFixtureResult, {
fixtureMatch: match,
tournamentMatch: tm
}), index !== match.tournamentMatches.length - 1 && jsx(Divider, {})]
}, tm.id);
})
})
})]
}), jsx(Modal, {
title: editingResult.position === "resultLocal" ? "Editar resultado Local" : "Editar resultado Visitante",
open: editingResult.visible,
onOk: function () {
setEditingResult(__assign(__assign({}, editingResult), {
visible: false
}));
if (inputScore !== null) {
var scoreHome = editingResult.position === "resultLocal" ? +inputScore : match.valueScoreHome ? +match.valueScoreHome : 0;
var scoreAway = editingResult.position === "resultVisit" ? +inputScore : match.valueScoreAway ? +match.valueScoreAway : 0;
props.onResultSaved(match.id, scoreHome, scoreAway);
}
setInputScore(null);
},
onCancel: function () {
setEditingResult(__assign(__assign({}, editingResult), {
visible: false
}));
setInputScore(null);
},
okText: "Guardar",
cancelText: "Cancelar",
children: jsx(InputNumber, {
autoFocus: true,
min: 0,
style: {
width: "100%"
},
value: inputScore !== null && inputScore !== void 0 ? inputScore : undefined,
onChange: function (value) {
return setInputScore(value !== null && value !== void 0 ? value : null);
},
placeholder: "Ingres\u00E1 el nuevo score"
})
})]
});
});
var WbStages = ["Final", "Semi Final", "Cuartos de Final", "Octavos de Final", "16avos de Final", "32avos de Final", "64avos de Final", "128avos de Final", "256avos de Final", "512avos de Final", "1024avos de Final"];
var WbAvatar = function (_a) {
var _b = _a.padding,
padding = _b === void 0 ? 12 : _b,
props = __rest(_a, ["padding"]);
var sizeStyle = typeof props.size === 'number' ? "".concat(props.size, "px") : props.size;
if (props.new) return jsx("div", {
className: "flex items-center justify-center rounded-full overflow-hidden bg-white",
style: {
height: sizeStyle,
width: sizeStyle,
padding: padding
},
children: jsx("img", {
src: props.src,
alt: "",
className: "object-contain"
})
});
return jsx("div", {
className: "flex items-center justify-center rounded-full overflow-hidden",
style: {
width: sizeStyle,
height: sizeStyle,
backgroundColor: "white",
borderColor: props.borderColor || "white",
borderWidth: "2px",
borderStyle: "solid"
},
children: jsx("img", {
src: props.src,
alt: "",
className: "w-full h-full object-cover rounded-full"
})
});
};
var KEY_PREFIX = {
BRACE: "brace-",
LINE: "line-",
STAGE: "stage-"
};
// ======================================================
// 2) Componente principal: WbFixture
// ======================================================
/**
* WbFixture component - displays tournament bracket/fixture visualization
*
* @template TFixtureData - Type for fixture visualizer data (can contain any additional properties)
* @template TCupWinnerData - Type for cup winner data (can contain any additional properties)
*
* @example
* ```tsx
* // Basic usage with minimum required properties
* <WbFixture
* fixtureVisualizerRoot={{
* id: 1,
* children: [],
* matchesPlanning: [
* {
* id: 1,
* clubHome: { clubInscription: { logo: "...", name: "Team A", color: "#fff" } },
* clubAway: { clubInscription: { logo: "...", name: "Team B", color: "#000" } }
* }
* ]
* }}
* cupWinner={{ id: 1, logo: "...", name: "Winner", color: "#gold" }}
* onClickNode={(match, position) => console.log(match, position)}
* />
*
* // Usage with extended custom data
* <WbFixture
* fixtureVisualizerRoot={{
* id: 1,
* children: [],
* matchesPlanning: [],
* customTournamentName: "World Cup 2024",
* metadata: { season: "2024", region: "Global" }
* }}
* cupWinner={{
* id: 1,
* logo: "...",
* name: "Winner",
* color: "#gold",
* stats: { wins: 15, goals: 45 },
* country: "Argentina"
* }}
* />
* ```
*/
var WbFixture = function (props) {
var _a, _b;
var fixtureVisualizerRoot = props.fixtureVisualizerRoot,
cupWinner = props.cupWinner,
cupLogo = props.cupLogo,
nodeSelected = props.nodeSelected,
onClickNode = props.onClickNode,
onResultSaved = props.onResultSaved,
_c = props.editMode,
editMode = _c === void 0 ? false : _c,
_d = props.responsive,
responsive = _d === void 0 ? true : _d;
__rest(props, ["fixtureVisualizerRoot", "cupWinner", "cupLogo", "nodeSelected", "onClickNode", "onResultSaved", "editMode", "responsive"]);
// Estado con la lista final de nodos a renderizar (partidos)
var _e = useState([]),
matches = _e[0],
setMatches = _e[1];
// Cantidad de líneas que se dibujan entre partidos
var _f = useState(0),
linesQuantity = _f[0],
setLinesQuantity = _f[1];
// Cantidad de “stages” (rondas)
var _g = useState(0),
rootStageNumberToShow = _g[0],
setRootStageNumberToShow = _g[1];
// Cantidad de textos de etapas que dibujaremos
var _h = useState(0),
stagesTextQuantity = _h[0],
setStagesTextQuantity = _h[1];
// Campeón detectado automáticamente cuando cupWinner no está presente
var _j = useState(null),
detectedWinner = _j[0],
setDetectedWinner = _j[1];
// Referencia al contenedor principal
var containerRef = useRef(null);
// Referencias para cada parte del fixture
var refStages = useRef([]);
var refmatches = useRef([]);
var refBraces = useRef([]);
var refLines = useRef([]);
var refFinalStage = useRef(null);
var refWinner = useRef(null);
// Estado para responsive scaling
var _k = useState(1),
scale = _k[0],
setScale = _k[1];
var _l = useState(false),
isResponsiveActive = _l[0],
setIsResponsiveActive = _l[1];
var _m = useState({
width: 0,
height: 0
}),
containerDimensions = _m[0],
setContainerDimensions = _m[1];
// Refs para debouncing y control
var scaleTimeoutRef = useRef(null);
var lastCalculatedScale = useRef(1);
var resizeObserverRef = useRef(null);
// ------------------------------------------------------
// Función para detectar al campeón automáticamente
// ------------------------------------------------------
var detectChampion = function (matches) {
var _a, _b, _c, _d;
if (!matches || matches.length === 0) return null;
var winner = null;
// Buscar la final (partido con stageNumberFromFinal === 1)
var finalMatchVisualizer = matches.find(function (match) {
return match.stageNumberFromFinal === 1;
});
if (!finalMatchVisualizer) return null;
var hasMoreThantOneMatch = finalMatchVisualizer.tournamentMatches && finalMatchVisualizer.tournamentMatches.length > 1;
if (hasMoreThantOneMatch) {
winner = finalMatchVisualizer.clubWon;
} else {
var finalMatch = (_a = finalMatchVisualizer.tournamentMatches) === null || _a === void 0 ? void 0 : _a[0];
// Aplicar la misma lógica de determinación del ganador
if (finalMatch.scoreHome > finalMatch.scoreAway) {
winner = finalMatchVisualizer.clubHome;
} else if (finalMatch.scoreHome < finalMatch.scoreAway) {
winner = finalMatchVisualizer.clubAway;
} else {
// Empate en tiempo regular, revisar penalties
if (!finalMatch.scoreHomePenalty && !finalMatch.scoreAwayPenalty) {
// Sin penalties, no hay ganador definido
return null;
} else {
if (finalMatch.scoreHomePenalty > finalMatch.scoreAwayPenalty) {
winner = finalMatchVisualizer.clubHome;
} else if (finalMatch.scoreHomePenalty < finalMatch.scoreAwayPenalty) {
winner = finalMatchVisualizer.clubAway;
} else {
// Empate en penalties también, no hay ganador definido
return null;
}
}
}
}
// Retornar el ganador en el formato esperado por cupWinner
if (winner === null || winner === void 0 ? void 0 : winner.clubInscription) {
return {
id: winner.id,
name: ((_c = (_b = winner.clubInscription) === null || _b === void 0 ? void 0 : _b.club) === null || _c === void 0 ? void 0 : _c.name) || winner.clubInscription.name,
logo: winner.clubInscription.logo || ((_d = winner.clubInscription.club) === null || _d === void 0 ? void 0 : _d.logo),
color: winner.clubInscription.color
};
}
return null;
};
// ------------------------------------------------------
// useEffect: detectar campeón cuando cambian los matches
// ------------------------------------------------------
useEffect(function () {
if (!cupWinner && matches.length > 0) {
var champion = detectChampion(matches);
setDetectedWinner(champion);
} else {
setDetectedWinner(null);
}
}, [matches, cupWinner]);
// ------------------------------------------------------
// useEffect: cuando cambia el fixtureVisualizerRoot
// ------------------------------------------------------
useEffect(function () {
if (!fixtureVisualizerRoot) return;
// Interpretamos 'stages' como la cantidad de hijos directos de la raíz
var stages = fixtureVisualizerRoot.children.length;
var maxChildren = 8;
setRootStageNumberToShow(stages);
setStagesTextQuantity((stages - 1) * 2);
// Obtenemos y ordenamos los partidos
var orderedMatches = getOrderedMatchesByParentChildrenCount(fixtureVisualizerRoot, stages);
// Calculamos ancho y alto del contenedor
var containerWidth = stages * FIXTURE_NODE_WIDTH + (stages - 1) * FIXTURE_BRACE_WIDTH + (stages - 2) * FIXTURE_LINE_WIDTH + FIXTURE_FINAL_LINE_WIDTH + (FIXTURE_WINNER_WIDTH - FIXTURE_NODE_WIDTH) / 2;
var containerHeight = maxChildren * FIXTURE_NODE_HEIGHT + (maxChildren - 1) * FIXTURE_HEIGHT_BETWEEN_NODES + (maxChildren / 2 - 1) * FIXTURE_HEIGHT_BETWEEN_GROUPS + (FIXTURE_STAGE_SIZE + FIXTURE_HEIGHT_BETWEEN_NODES) * 2 + FIXTURE_SCROLL_SIZE;
// Store calculated dimensions for responsive handling
setContainerDimensions({
width: containerWidth,
height: containerHeight
});
if (containerRef.current) {
// Solo establecer dimensiones fijas si no es responsive
if (!responsive) {
containerRef.current.style.width = containerWidth + "px";
containerRef.current.style.height = containerHeight + "px";
} else {
// En modo responsive, establecer mínimas dimensiones
containerRef.current.style.minWidth = containerWidth + "px";
containerRef.current.style.minHeight = containerHeight + "px";
}
}
// Cantidad de “llaves” (líneas)
var auxLinesQuantity = Math.pow(2, stages - 1) - 1;
setLinesQuantity(auxLinesQuantity);
// Mapeamos los FixtureVisualizerMatch -> FixtureNode
// (para que WbFixtureNode los reciba con la misma estructura esperada)
setMatches(orderedMatches);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fixtureVisualizerRoot]);
// ------------------------------------------------------
// useEffect: responsive scaling - SIMPLIFIED
// ------------------------------------------------------
useEffect(function () {
if (!responsive) {
setScale(1);
setIsResponsiveActive(false);
return;
}
var calculateAndApplyScale = function () {
var container = containerRef.current;
if (!container) return;
// Get parent container dimensions
var parentElement = container.parentElement;
if (!parentElement) return;
var parentWidth = parentElement.clientWidth;
// Calculate total fixture width based on stages
var totalFixtureWidth = rootStageNumberToShow * (FIXTURE_NODE_WIDTH + FIXTURE_BRACE_WIDTH) + FIXTURE_WINNER_WIDTH;
var newScale = 1;
var shouldBeResponsive = false;
if (parentWidth > 0 && totalFixtureWidth > parentWidth) {
newScale = Math.max(0.3, Math.min(1, (parentWidth - 40) / totalFixtureWidth)); // 40px margin
shouldBeResponsive = true;
}
// Only update if there's a significant change (prevents render loops)
var scaleDifference = Math.abs(newScale - lastCalculatedScale.current);
if (scaleDifference > 0.01) {
lastCalculatedScale.current = newScale;
// Clear any existing timeout
if (scaleTimeoutRef.current) {
clearTimeout(scaleTimeoutRef.current);
}
// Debounce the update
scaleTimeoutRef.current = setTimeout(function () {
setScale(newScale);
setIsResponsiveActive(shouldBeResponsive);
}, 50);
}
};
// Initial calculation
calculateAndApplyScale();
// Setup ResizeObserver with proper cleanup
if (containerRef.current) {
resizeObserverRef.current = new ResizeObserver(function () {
// Throttle resize events
if (scaleTimeoutRef.current) {
clearTimeout(scaleTimeoutRef.current);
}
scaleTimeoutRef.current = setTimeout(calculateAndApplyScale, 100);
});
// Observe the parent container, not the fixture itself
var parentElement = containerRef.current.parentElement;
if (parentElement) {
resizeObserverRef.current.observe(parentElement);
}
}
// Cleanup function
return function () {
if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect();
resizeObserverRef.current = null;
}
if (scaleTimeoutRef.current) {
clearTimeout(scaleTimeoutRef.current);
scaleTimeoutRef.current = null;
}
};
}, [responsive, rootStageNumberToShow]); // Removed scale from dependencies to prevent loops
// ------------------------------------------------------
// useEffect: posicionar y dibujar nodos/lines una vez
// que `matches` y las refs están listas
// ------------------------------------------------------
useEffect(function () {
var _a;
if (!matches || matches.length === 0) return;
if (!refmatches.current || refmatches.current.length !== matches.length) return;
var linesEndPositions = [];
var braceTop = 0;
var lineIndex = 0;
var lastNodeTop = 0;
var lastNodeLeft = 0;
var lastStageIndex = 0;
var lastStageNumber = matches[0].stageNumberFromFinal;
for (var i = 0; i < matches.length; i++) {
// ------------------------------
// ETAPA NUEVA: Texto al final de la fase anterior
// ------------------------------
if (matches[i].stageNumberFromFinal !== lastStageNumber) {
if (refStages.current[lastStageIndex]) {
var stageTop = lastNodeTop + FIXTURE_NODE_HEIGHT + FIXTURE_HEIGHT_BETWEEN_NODES;
refStages.current[lastStageIndex].innerText = WbStages[lastStageNumber - 1];
refStages.current[lastStageIndex].style.top = stageTop + "px";
refStages.current[lastStageIndex].style.left = lastNodeLeft + "px";
}
lastStageIndex++;
}
// ------------------------------
// POSICIONAMIENTO DE LA PRIMERA FASE