act piscina.js

This commit is contained in:
Fernando Escobar Robles 2025-05-07 18:53:03 -06:00
parent c9afa55243
commit df417b036a
1 changed files with 249 additions and 90 deletions
public/js

View File

@ -1,104 +1,263 @@
const canvas = document.getElementById("poolCanvas");
const ctx = canvas.getContext("2d");
document.addEventListener('DOMContentLoaded', async () => {
const rutinaId = new URLSearchParams(window.location.search).get('routineId');
if (!rutinaId) return alert('No se proporcionó ID de rutina.');
let currentAtleta = null;
let rutinaId = new URLSearchParams(window.location.search).get("routineId");
let atletas = [];
let colocados = [];
const select = document.getElementById('selectAtleta');
const tituloRutina = document.getElementById('tituloRutina');
const tipoRutina = document.getElementById('tipoRutina');
const modalidadRutina = document.getElementById('modalidadRutina');
function drawPool() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#cceeff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
const rutina = await fetch(`/routines/${rutinaId}`).then(res => res.json());
const modalidad = rutina.modalidad;
ctx.beginPath();
ctx.moveTo(canvas.width / 2, 0);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.strokeStyle = "#555";
ctx.setLineDash([6, 4]);
ctx.stroke();
ctx.setLineDash([]);
tituloRutina.textContent = rutina.title || rutina.nombreCompetencia;
tipoRutina.textContent = rutina.tipoCompetencia;
modalidadRutina.textContent = modalidad;
colocados.forEach(({ x, y, atleta }) => drawAthlete(x, y, atleta));
}
const atletas = await fetch('/users/athletes').then(res => res.json());
atletas.forEach(a => {
const opt = document.createElement('option');
opt.value = a._id;
opt.textContent = a.name;
select.appendChild(opt);
});
function drawAthlete(x, y, atleta) {
ctx.beginPath();
ctx.arc(x, y, 14, 0, Math.PI * 2);
ctx.fillStyle = "#ff4444";
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = "#fff";
ctx.stroke();
const stage = new Konva.Stage({
container: 'piscina',
width: 900,
height: 400
});
ctx.fillStyle = "#fff";
ctx.font = "bold 12px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(atleta.idPersonalizado, x, y);
}
const layer = new Konva.Layer();
stage.add(layer);
canvas.addEventListener("click", (e) => {
if (!currentAtleta) return;
let formacionActual = [];
let currentAtleta = null;
const maxAtletas = modalidad === 'solo' ? 1 : modalidad === 'duo' ? 2 : 8;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x < 0 || y < 0 || x > canvas.width || y > canvas.height) return;
if (colocados.find(a => a.atleta.idPersonalizado === currentAtleta.idPersonalizado)) {
alert("Este atleta ya fue colocado.");
return;
}
colocados.push({ x, y, atleta: currentAtleta });
updateLegend(currentAtleta);
drawPool();
currentAtleta = null;
});
function updateLegend(atleta) {
const li = document.createElement("li");
li.textContent = `${atleta.idPersonalizado}: ${atleta.atletaId?.name || 'Desconocido'} (${atleta.rol})`;
document.getElementById("legend").appendChild(li);
}
function enablePlacement() {
const index = document.getElementById("athleteSelect").value;
if (index >= 0) {
currentAtleta = atletas[index];
}
}
async function loadRoutine() {
try {
const res = await fetch(`/routines/${rutinaId}`);
const data = await res.json();
document.getElementById("routineTitle").textContent = data.nombreCompetencia;
document.getElementById("routineType").textContent = data.tipoCompetencia;
document.getElementById("routineMode").textContent = data.modalidad;
atletas = data.participantes;
const select = document.getElementById("athleteSelect");
if (!atletas || atletas.length === 0) {
select.innerHTML = `<option disabled>No hay atletas</option>`;
document.getElementById('btnAgregarAtleta').addEventListener('click', () => {
if (formacionActual.length >= maxAtletas) {
alert(`Modalidad "${modalidad}" permite un máximo de ${maxAtletas} atletas.`);
return;
}
atletas.forEach((a, i) => {
const opt = document.createElement("option");
opt.value = i;
opt.textContent = `${a.idPersonalizado} - ${a.atletaId?.name || 'Desconocido'} (${a.rol})`;
select.appendChild(opt);
const idAtleta = select.value;
const rol = document.getElementById('rolAtleta').value.trim().toLowerCase();
const idPersonalizado = document.getElementById('idPersonalizado').value.trim();
if (!idAtleta || !rol || !idPersonalizado) {
alert('Faltan datos del atleta');
return;
}
currentAtleta = {
atletaId: idAtleta,
rol,
idPersonalizado
};
alert('Haz clic en la piscina para colocarlo');
});
stage.on('click', (e) => {
if (!currentAtleta) return;
const pos = stage.getPointerPosition();
let fillColor = 'gray';
if (currentAtleta.rol === 'volador') fillColor = 'purple';
else if (currentAtleta.rol === 'pilar') fillColor = 'blue';
else fillColor = 'red';
const grupo = prompt("Grupo del atleta (A, B o vacío)", "");
let strokeColor = 'white';
if (grupo.toUpperCase() === 'A') strokeColor = 'green';
else if (grupo.toUpperCase() === 'B') strokeColor = 'yellow';
const circle = new Konva.Circle({
x: pos.x,
y: pos.y,
radius: 15,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 3,
draggable: true
});
drawPool();
} catch (err) {
console.error("Error cargando rutina:", err);
}
}
const text = new Konva.Text({
x: pos.x - 10,
y: pos.y - 7,
text: currentAtleta.idPersonalizado,
fontSize: 12,
fill: 'white'
});
window.addEventListener("DOMContentLoaded", loadRoutine);
layer.add(circle);
layer.add(text);
layer.draw();
const atletaObj = {
...currentAtleta,
x: pos.x,
y: pos.y,
grupo: grupo || '',
direccion: ''
};
formacionActual.push(atletaObj);
circle.on('click', (evt) => {
if (evt.evt.ctrlKey) {
const pointer = stage.getPointerPosition();
const dx = pointer.x - circle.x();
const dy = pointer.y - circle.y();
const angle = Math.atan2(dy, dx);
const length = 40;
const toX = circle.x() + length * Math.cos(angle);
const toY = circle.y() + length * Math.sin(angle);
const line = new Konva.Line({
points: [circle.x(), circle.y(), toX, toY],
stroke: 'black',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
layer.draw();
atletaObj.direccion = angle.toFixed(2);
}
});
currentAtleta = null;
document.getElementById('rolAtleta').value = '';
document.getElementById('idPersonalizado').value = '';
});
document.getElementById('btnGuardarFormacion').addEventListener('click', async () => {
const nombre = document.getElementById('nombreFormacion').value.trim();
const notas = document.getElementById('notasFormacion').value.trim();
if (!nombre || formacionActual.length === 0) {
alert('Agrega nombre de formación y al menos un atleta');
return;
}
const body = {
nombreColoquial: nombre,
notasTacticas: notas,
atletas: formacionActual
};
const res = await fetch(`/routines/${rutinaId}/formations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.ok) {
alert('✅ Formación guardada correctamente');
window.location.reload();
} else {
alert('❌ Error al guardar formación');
}
});
// 🔽 Botón exportar imagen
const exportBtn = document.createElement('button');
exportBtn.textContent = 'Exportar formación como imagen';
exportBtn.className = 'btn btn-outline-secondary w-100 mb-3';
exportBtn.onclick = () => {
const dataURL = stage.toDataURL({ pixelRatio: 3 });
const link = document.createElement('a');
link.download = `formacion_${Date.now()}.png`;
link.href = dataURL;
link.click();
};
document.querySelector('#piscinaContainer').appendChild(exportBtn);
// 🔁 HISTORIAL
const formaciones = await fetch(`/routines/${rutinaId}/formations`).then(r => r.json());
const historial = document.getElementById('formacionesPrevias');
if (Array.isArray(formaciones)) {
formaciones.forEach((f, i) => {
const div = document.createElement('div');
div.className = 'col-md-6 mb-2';
div.innerHTML = `
<div class="p-2 border rounded shadow-sm bg-light">
<strong>${i + 1}. ${f.nombreColoquial}</strong>
<br />
<small>${f.notasTacticas}</small>
</div>
`;
historial.appendChild(div);
});
}
// ▶ Ver animación
const btnVerAnimacion = document.createElement('button');
btnVerAnimacion.textContent = '▶ Ver animación';
btnVerAnimacion.className = 'btn btn-primary w-100 mt-3';
btnVerAnimacion.onclick = async () => {
if (!formaciones || formaciones.length === 0) return alert('No hay formaciones.');
for (let i = 0; i < formaciones.length; i++) {
const formacion = formaciones[i];
layer.destroyChildren();
formacion.atletas.forEach((a) => {
let color = 'gray';
if (a.rol === 'volador') color = 'purple';
else if (a.rol === 'pilar') color = 'blue';
else color = 'red';
let stroke = 'white';
if (a.grupo === 'A') stroke = 'green';
else if (a.grupo === 'B') stroke = 'yellow';
const circle = new Konva.Circle({
x: a.x,
y: a.y,
radius: 15,
fill: color,
stroke: stroke,
strokeWidth: 3
});
const text = new Konva.Text({
x: a.x - 10,
y: a.y - 7,
text: a.idPersonalizado,
fontSize: 12,
fill: 'white'
});
layer.add(circle);
layer.add(text);
if (a.direccion) {
const angle = parseFloat(a.direccion);
const len = 40;
const toX = a.x + len * Math.cos(angle);
const toY = a.y + len * Math.sin(angle);
const line = new Konva.Line({
points: [a.x, a.y, toX, toY],
stroke: 'black',
strokeWidth: 2,
lineCap: 'round'
});
layer.add(line);
}
});
layer.draw();
await new Promise(res => setTimeout(res, 2000));
}
};
document.querySelector('#historial').appendChild(btnVerAnimacion);
});