Spaces:
Sleeping
Sleeping
| const planeContainer = document.getElementById('napping-plane'); | |
| const productsContainer = document.getElementById('items'); | |
| const products = document.querySelectorAll('.item-product'); | |
| // Configuration for the physical dimensions of the tablecloth (in cm) | |
| const PHYSICAL_WIDTH = 60; | |
| const PHYSICAL_HEIGHT = 40; | |
| let selectedProductCode = null; | |
| let selectedProductId = null; | |
| // Object to store coordinates: { "CODE": { x: 10.5, y: 20.1, id: 123 } } | |
| window.placedPoints = {}; | |
| const modeElement = document.querySelector('[data-mode]'); | |
| window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash'); | |
| // For ultra flash mode, it starts active but will be controlled by ultra-flash.js | |
| if (window.isUltraFlash) { | |
| document.getElementById("question-save").classList.add("hidden"); | |
| window.isPlacementActive = true; | |
| } else { | |
| // Normal mode: placement is always active | |
| window.isPlacementActive = true; | |
| } | |
| // 1. Handle Product Selection | |
| products.forEach(product => { | |
| product.addEventListener('click', () => { | |
| if (!window.isPlacementActive) return; | |
| // Remove selection from others | |
| products.forEach(p => p.classList.remove('ring-4', 'ring-primary')); | |
| // Select current | |
| product.classList.add('ring-4', 'ring-primary'); | |
| selectedProductCode = product.dataset.code; | |
| selectedProductId = product.dataset.idProduct; | |
| }); | |
| }); | |
| // 2. Handle Plane Click (Placing Points) | |
| planeContainer.addEventListener('click', (e) => { | |
| if (!window.isPlacementActive) return; | |
| if (!selectedProductCode) { | |
| spanNotifaction("Por favor, selecciona un producto primero") | |
| return; | |
| } | |
| const rect = planeContainer.getBoundingClientRect(); | |
| // Calculate click position relative to the container (in pixels) | |
| const xPixel = e.clientX - rect.left; | |
| const yPixel = e.clientY - rect.top; | |
| // Calculate scaled coordinates (0 to PHYSICAL_DIMENSIONS) | |
| // X axis: 0 on left, 60 on right | |
| const xCoord = (xPixel / rect.width) * PHYSICAL_WIDTH; | |
| // Y axis: 0 on bottom, 40 on top (Invert Y because DOM Y is 0 at top) | |
| const yCoord = PHYSICAL_HEIGHT - ((yPixel / rect.height) * PHYSICAL_HEIGHT); | |
| // Update Data Object | |
| window.placedPoints[selectedProductCode] = { | |
| x: parseFloat(xCoord.toFixed(2)), | |
| y: parseFloat(yCoord.toFixed(2)), | |
| id: selectedProductId | |
| }; | |
| // Render Point | |
| window.renderPoint(selectedProductCode, xPixel, yPixel, xCoord, yCoord); | |
| }); | |
| // Make renderPoint global | |
| window.renderPoint = function (code, xPx, yPx, xVal, yVal) { | |
| // Remove existing point for this product if it exists | |
| const existingPoint = document.getElementById(`point-${code}`); | |
| if (existingPoint) { | |
| existingPoint.remove(); | |
| } | |
| const point = document.createElement('div'); | |
| point.id = `point-${code}`; | |
| point.className = 'data-point absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group'; | |
| point.dataset.code = code; | |
| point.dataset.px = xVal; | |
| point.dataset.py = yVal; | |
| point.dataset.idProduct = window.placedPoints[code]?.id; | |
| point.style.left = `${xPx}px`; | |
| point.style.top = `${yPx}px`; | |
| planeContainer.appendChild(point); | |
| const textLabel = document.createElement('span'); | |
| textLabel.className = 'absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none'; | |
| textLabel.innerText = code; | |
| point.appendChild(textLabel); | |
| } | |
| function checkPoints() { | |
| const points = document.querySelectorAll('.data-point'); | |
| points.forEach(point => { | |
| const code = point.dataset.code; | |
| const xVal = parseFloat(point.dataset.px); | |
| const yVal = parseFloat(point.dataset.py); | |
| const rect = planeContainer.getBoundingClientRect(); | |
| const px = (xVal / PHYSICAL_WIDTH) * rect.width; | |
| const py = ((PHYSICAL_HEIGHT - yVal) / PHYSICAL_HEIGHT) * rect.height; | |
| window.placedPoints[code] = { | |
| x: xVal.toFixed(2), | |
| y: yVal.toFixed(2), | |
| id: point.dataset.idProduct | |
| }; | |
| window.renderPoint(code, px, py, xVal, yVal); | |
| }); | |
| } | |
| setTimeout(checkPoints, 100); | |
| /* | |
| //// | |
| ////// | |
| //////// Question to finish session | |
| ////// | |
| //// | |
| */ | |
| function showOptionsSave() { | |
| document.getElementById("question-save").classList.add("hidden"); | |
| document.getElementById("finish-session").classList.remove("hidden"); | |
| document.getElementById("cancel-save").classList.remove("hidden"); | |
| } | |
| function showQuestionSave() { | |
| document.getElementById("question-save").classList.remove("hidden"); | |
| document.getElementById("finish-session").classList.add("hidden"); | |
| document.getElementById("cancel-save").classList.add("hidden"); | |
| } | |
| document | |
| .getElementById("question-save") | |
| .addEventListener("click", showOptionsSave); | |
| document | |
| .getElementById("cancel-save") | |
| .addEventListener("click", showQuestionSave); | |
| /* | |
| //// | |
| ////// | |
| //////// Save data and finish session | |
| ////// | |
| //// | |
| */ | |
| // Callback for additional validation before saving | |
| window.beforeSaveData = null; | |
| // Function to get extra data for each product | |
| window.getExtraDataForSave = null; | |
| window.saveData = async function (isFinishSession = false) { | |
| const codeProducts = Object.keys(window.placedPoints); | |
| const data = []; | |
| // Only validate all products placed if finishing session | |
| if (isFinishSession && products.length != codeProducts.length) { | |
| spanNotifaction("Por favor, coloca todos los puntos antes de finalizar la sesión") | |
| return false; | |
| } | |
| // Call beforeSaveData callback if it exists (for ultra flash validation) | |
| if (window.beforeSaveData && typeof window.beforeSaveData === 'function') { | |
| const validationResult = window.beforeSaveData(isFinishSession); | |
| if (validationResult === false) { | |
| return false; | |
| } | |
| } | |
| codeProducts.forEach((code) => { | |
| const point = window.placedPoints[code]; | |
| const objData = { | |
| code: code, | |
| x: point.x, | |
| y: point.y, | |
| idProduct: point.id | |
| }; | |
| // Get extra data if callback exists (for ultra flash words) | |
| if (window.getExtraDataForSave && typeof window.getExtraDataForSave === 'function') { | |
| const extraData = window.getExtraDataForSave(code); | |
| Object.assign(objData, extraData); | |
| } | |
| data.push(objData); | |
| }) | |
| const URL = "/cata/testers/api/rating-napping" | |
| const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; | |
| try { | |
| const response = await fetch(URL, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "X-CSRFToken": csrfToken, | |
| }, | |
| body: JSON.stringify(data), | |
| }) | |
| if (!response.ok) { | |
| spanNotifaction("Error en la respuesta del servidor") | |
| return false; | |
| } | |
| const result = await response.json() | |
| if (result.error) { | |
| spanNotifaction(result.error) | |
| return false | |
| } else { | |
| spanNotifaction(result.message, false) | |
| return true | |
| } | |
| } catch (error) { | |
| spanNotifaction("Error en proceso de guardar los datos") | |
| return false | |
| } | |
| } | |
| // Function to finish session with validation | |
| window.finishSession = async function () { | |
| const success = await window.saveData(true); | |
| if (success) { | |
| // Submit the form to finish session | |
| const formFinish = document.getElementById("form-finish-session") | |
| formFinish.action = "" | |
| formFinish.submit(); | |
| } | |
| } | |
| document | |
| .getElementById("save-progress") | |
| .addEventListener("click", () => window.saveData(false)); |