act piscina.js
This commit is contained in:
parent
c9afa55243
commit
df417b036a
public/js
|
@ -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);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue