Spaces:
Sleeping
Sleeping
Refactorizacion de nappgin con perfil ultra flash
Browse files- tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py +3 -3
- tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py +3 -5
- tecnicas/static/js/test-napping-plane.js +5 -11
- tecnicas/static/js/test-napping-ultra-flash.js +80 -64
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py
CHANGED
|
@@ -72,9 +72,9 @@ class InitSessionNappingController(InitSessionController):
|
|
| 72 |
|
| 73 |
def setStatusSession(self):
|
| 74 |
technique_mode = TecnicaModalidad.objects.get(
|
| 75 |
-
tecnica=self.session.tecnica
|
| 76 |
|
| 77 |
-
if technique_mode
|
| 78 |
self.context["status"] = "La sesión usa Napping"
|
| 79 |
else:
|
| 80 |
-
self.context["status"] = f"La sesión usa Napping con modalidad {technique_mode
|
|
|
|
| 72 |
|
| 73 |
def setStatusSession(self):
|
| 74 |
technique_mode = TecnicaModalidad.objects.get(
|
| 75 |
+
tecnica=self.session.tecnica).modalidad.nombre
|
| 76 |
|
| 77 |
+
if technique_mode == "sin modalidad":
|
| 78 |
self.context["status"] = "La sesión usa Napping"
|
| 79 |
else:
|
| 80 |
+
self.context["status"] = f"La sesión usa Napping con modalidad {technique_mode}"
|
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py
CHANGED
|
@@ -28,7 +28,7 @@ class TestNappingController(GenetalTestController):
|
|
| 28 |
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 29 |
|
| 30 |
name_mode_activate = TecnicaModalidad.objects.get(
|
| 31 |
-
tecnica=technique
|
| 32 |
|
| 33 |
if name_mode_activate == "sin modalidad":
|
| 34 |
self.context["mode"] = "sin modalidad"
|
|
@@ -60,9 +60,7 @@ class TestNappingController(GenetalTestController):
|
|
| 60 |
|
| 61 |
def nappingPufTest(self, request: HttpRequest):
|
| 62 |
maked_previus_napping = TecnicaModalidad.objects.get(
|
| 63 |
-
tecnica=self.session.tecnica
|
| 64 |
-
modalidad=Modalidad.objects.get(nombre="sin modalidad")
|
| 65 |
-
)
|
| 66 |
|
| 67 |
self.context["maked_napping"] = True if maked_previus_napping else False
|
| 68 |
self.context["mode"] = "perfil ultra flash"
|
|
@@ -91,7 +89,7 @@ class TestNappingController(GenetalTestController):
|
|
| 91 |
code=F("calificacion__id_producto__codigoProducto"),
|
| 92 |
px=F("x"),
|
| 93 |
py=F("y"),
|
| 94 |
-
id_product=F("
|
| 95 |
)
|
| 96 |
|
| 97 |
self.context["data_points"] = list(data_points)
|
|
|
|
| 28 |
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 29 |
|
| 30 |
name_mode_activate = TecnicaModalidad.objects.get(
|
| 31 |
+
tecnica=technique).modalidad.nombre
|
| 32 |
|
| 33 |
if name_mode_activate == "sin modalidad":
|
| 34 |
self.context["mode"] = "sin modalidad"
|
|
|
|
| 60 |
|
| 61 |
def nappingPufTest(self, request: HttpRequest):
|
| 62 |
maked_previus_napping = TecnicaModalidad.objects.get(
|
| 63 |
+
tecnica=self.session.tecnica)
|
|
|
|
|
|
|
| 64 |
|
| 65 |
self.context["maked_napping"] = True if maked_previus_napping else False
|
| 66 |
self.context["mode"] = "perfil ultra flash"
|
|
|
|
| 89 |
code=F("calificacion__id_producto__codigoProducto"),
|
| 90 |
px=F("x"),
|
| 91 |
py=F("y"),
|
| 92 |
+
id_product=F("calificacion__id_producto__id")
|
| 93 |
)
|
| 94 |
|
| 95 |
self.context["data_points"] = list(data_points)
|
tecnicas/static/js/test-napping-plane.js
CHANGED
|
@@ -13,13 +13,16 @@ let selectedProductId = null;
|
|
| 13 |
window.placedPoints = {};
|
| 14 |
|
| 15 |
const modeElement = document.querySelector('[data-mode]');
|
| 16 |
-
window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash')
|
| 17 |
|
|
|
|
|
|
|
| 18 |
if (window.isUltraFlash) {
|
| 19 |
document.getElementById("question-save").classList.add("hidden");
|
| 20 |
window.isPlacementActive = true;
|
| 21 |
} else {
|
| 22 |
-
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
// 1. Handle Product Selection
|
|
@@ -90,15 +93,6 @@ window.renderPoint = function (code, xPx, yPx, xVal, yVal) {
|
|
| 90 |
point.style.left = `${xPx}px`;
|
| 91 |
point.style.top = `${yPx}px`;
|
| 92 |
|
| 93 |
-
const label = document.createElement('div');
|
| 94 |
-
label.className = 'absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block';
|
| 95 |
-
label.innerHTML = `
|
| 96 |
-
<strong>${code}</strong><br>
|
| 97 |
-
X: ${xVal.toFixed(1)}<br>
|
| 98 |
-
Y: ${yVal.toFixed(1)}
|
| 99 |
-
`;
|
| 100 |
-
|
| 101 |
-
point.appendChild(label);
|
| 102 |
planeContainer.appendChild(point);
|
| 103 |
|
| 104 |
const textLabel = document.createElement('span');
|
|
|
|
| 13 |
window.placedPoints = {};
|
| 14 |
|
| 15 |
const modeElement = document.querySelector('[data-mode]');
|
| 16 |
+
window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash');
|
| 17 |
|
| 18 |
+
// For normal mode (sin modalidad), placement is always active
|
| 19 |
+
// For ultra flash mode, it starts active but will be controlled by ultra-flash.js
|
| 20 |
if (window.isUltraFlash) {
|
| 21 |
document.getElementById("question-save").classList.add("hidden");
|
| 22 |
window.isPlacementActive = true;
|
| 23 |
} else {
|
| 24 |
+
// Normal mode: placement is always active
|
| 25 |
+
window.isPlacementActive = true;
|
| 26 |
}
|
| 27 |
|
| 28 |
// 1. Handle Product Selection
|
|
|
|
| 93 |
point.style.left = `${xPx}px`;
|
| 94 |
point.style.top = `${yPx}px`;
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
planeContainer.appendChild(point);
|
| 97 |
|
| 98 |
const textLabel = document.createElement('span');
|
tecnicas/static/js/test-napping-ultra-flash.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
// Store words: { "CODE": ["word1", "word2"] }
|
| 2 |
const productWords = {};
|
|
|
|
|
|
|
| 3 |
if (window.isUltraFlash) {
|
| 4 |
initUltraFlash(productWords);
|
| 5 |
}
|
|
@@ -20,25 +22,31 @@ function initUltraFlash(productWords) {
|
|
| 20 |
const points = document.querySelectorAll('.data-point');
|
| 21 |
let hasExistingWords = false;
|
| 22 |
|
| 23 |
-
// Check if there are existing words
|
| 24 |
points.forEach(point => {
|
| 25 |
const code = point.dataset.code;
|
| 26 |
const wordsAttr = point.dataset.words;
|
| 27 |
|
| 28 |
-
if (wordsAttr) {
|
| 29 |
-
productWords[code] = wordsAttr.split(',').filter(w => w);
|
| 30 |
-
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
});
|
| 33 |
|
|
|
|
| 34 |
if (hasExistingWords) {
|
| 35 |
startDescriptionPhase();
|
|
|
|
| 36 |
points.forEach(point => {
|
| 37 |
const code = point.dataset.code;
|
| 38 |
-
|
| 39 |
-
|
|
|
|
| 40 |
});
|
| 41 |
} else {
|
|
|
|
| 42 |
continueBtn.classList.remove('hidden');
|
| 43 |
}
|
| 44 |
|
|
@@ -57,15 +65,24 @@ function initUltraFlash(productWords) {
|
|
| 57 |
|
| 58 |
function startDescriptionPhase() {
|
| 59 |
isDescriptionPhase = true;
|
| 60 |
-
window.isPlacementActive = false;
|
|
|
|
|
|
|
| 61 |
continueBtn.classList.add('hidden');
|
| 62 |
-
|
| 63 |
|
|
|
|
| 64 |
spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false);
|
| 65 |
|
| 66 |
-
|
| 67 |
-
document.getElementById('napping-plane')
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
// Handle Point Click for Description
|
|
@@ -120,10 +137,7 @@ function initUltraFlash(productWords) {
|
|
| 120 |
productWords[currentProductCode] = [];
|
| 121 |
}
|
| 122 |
|
| 123 |
-
|
| 124 |
-
spanNotifaction("Máximo 5 palabras por producto.");
|
| 125 |
-
return;
|
| 126 |
-
}
|
| 127 |
|
| 128 |
if (productWords[currentProductCode].includes(word)) {
|
| 129 |
spanNotifaction("Palabra duplicada");
|
|
@@ -155,56 +169,61 @@ function initUltraFlash(productWords) {
|
|
| 155 |
const point = document.getElementById(`point-${code}`);
|
| 156 |
if (!point) return;
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
| 160 |
if (tooltip) {
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
const yVal = parseFloat(point.dataset.py).toFixed(1);
|
| 164 |
-
const words = productWords[code] || [];
|
| 165 |
-
|
| 166 |
-
let wordsHtml = '';
|
| 167 |
-
if (words.length > 0) {
|
| 168 |
-
wordsHtml = `<div class="mt-1 pt-1 border-t border-gray-600 text-yellow-300 italic">${words.join(', ')}</div>`;
|
| 169 |
-
}
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
tooltip.innerHTML = `
|
| 172 |
-
<strong>${code}</strong>
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
}
|
| 179 |
|
| 180 |
-
// Override saveData to include words
|
| 181 |
-
//
|
| 182 |
-
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
-
|
| 187 |
-
// If in description phase, validate words
|
| 188 |
if (isDescriptionPhase) {
|
| 189 |
-
const
|
| 190 |
-
for (const code of codes) {
|
| 191 |
const words = productWords[code] || [];
|
| 192 |
-
if (words.length <
|
| 193 |
-
spanNotifaction(`El producto ${code} debe tener al menos
|
| 194 |
return false;
|
| 195 |
}
|
| 196 |
}
|
| 197 |
}
|
| 198 |
|
| 199 |
-
// Prepare data
|
| 200 |
-
const codeProducts = Object.keys(window.placedPoints);
|
| 201 |
const data = [];
|
| 202 |
-
|
| 203 |
-
if (document.querySelectorAll('.item-product').length != codeProducts.length) {
|
| 204 |
-
spanNotifaction("Por favor, coloca todos los puntos")
|
| 205 |
-
return false;
|
| 206 |
-
}
|
| 207 |
-
|
| 208 |
codeProducts.forEach((code) => {
|
| 209 |
const point = window.placedPoints[code];
|
| 210 |
const words = productWords[code] || [];
|
|
@@ -214,17 +233,14 @@ function initUltraFlash(productWords) {
|
|
| 214 |
x: point.x,
|
| 215 |
y: point.y,
|
| 216 |
idProduct: point.id,
|
| 217 |
-
words: words //
|
| 218 |
};
|
| 219 |
|
| 220 |
data.push(objData);
|
| 221 |
-
})
|
| 222 |
-
|
| 223 |
-
// We can reuse the rest of the logic, but we need to send the data.
|
| 224 |
-
// The original function constructs data inside it. We can't easily inject data into it unless we rewrite it.
|
| 225 |
-
// So I will rewrite the fetch part here.
|
| 226 |
|
| 227 |
-
|
|
|
|
| 228 |
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
| 229 |
|
| 230 |
try {
|
|
@@ -235,25 +251,25 @@ function initUltraFlash(productWords) {
|
|
| 235 |
"X-CSRFToken": csrfToken,
|
| 236 |
},
|
| 237 |
body: JSON.stringify(data),
|
| 238 |
-
})
|
| 239 |
|
| 240 |
if (!response.ok) {
|
| 241 |
-
spanNotifaction("Error en la respuesta del servidor")
|
| 242 |
return false;
|
| 243 |
}
|
| 244 |
|
| 245 |
-
const result = await response.json()
|
| 246 |
|
| 247 |
if (result.error) {
|
| 248 |
-
spanNotifaction(result.error)
|
| 249 |
-
return false
|
| 250 |
} else {
|
| 251 |
-
spanNotifaction(result.message, false)
|
| 252 |
-
return true
|
| 253 |
}
|
| 254 |
} catch (error) {
|
| 255 |
-
spanNotifaction("Error en proceso de guardar los datos")
|
| 256 |
-
return false
|
| 257 |
}
|
| 258 |
}
|
| 259 |
}
|
|
|
|
| 1 |
// Store words: { "CODE": ["word1", "word2"] }
|
| 2 |
const productWords = {};
|
| 3 |
+
|
| 4 |
+
// Only initialize ultra flash if the mode is active
|
| 5 |
if (window.isUltraFlash) {
|
| 6 |
initUltraFlash(productWords);
|
| 7 |
}
|
|
|
|
| 22 |
const points = document.querySelectorAll('.data-point');
|
| 23 |
let hasExistingWords = false;
|
| 24 |
|
| 25 |
+
// Check if there are existing words from backend
|
| 26 |
points.forEach(point => {
|
| 27 |
const code = point.dataset.code;
|
| 28 |
const wordsAttr = point.dataset.words;
|
| 29 |
|
| 30 |
+
if (wordsAttr && wordsAttr.trim() !== '') {
|
| 31 |
+
productWords[code] = wordsAttr.split(',').filter(w => w.trim() !== '');
|
| 32 |
+
if (productWords[code].length > 0) {
|
| 33 |
+
hasExistingWords = true;
|
| 34 |
+
}
|
| 35 |
}
|
| 36 |
});
|
| 37 |
|
| 38 |
+
// If words already exist, skip phase 1 and go directly to description phase
|
| 39 |
if (hasExistingWords) {
|
| 40 |
startDescriptionPhase();
|
| 41 |
+
// Update all point labels to show existing words
|
| 42 |
points.forEach(point => {
|
| 43 |
const code = point.dataset.code;
|
| 44 |
+
if (productWords[code] && productWords[code].length > 0) {
|
| 45 |
+
updatePointLabel(code);
|
| 46 |
+
}
|
| 47 |
});
|
| 48 |
} else {
|
| 49 |
+
// No existing words, show continue button for phase 1
|
| 50 |
continueBtn.classList.remove('hidden');
|
| 51 |
}
|
| 52 |
|
|
|
|
| 65 |
|
| 66 |
function startDescriptionPhase() {
|
| 67 |
isDescriptionPhase = true;
|
| 68 |
+
window.isPlacementActive = false; // Freeze point placement
|
| 69 |
+
|
| 70 |
+
// Hide continue button and show finish options
|
| 71 |
continueBtn.classList.add('hidden');
|
| 72 |
+
questionSaveBtn.classList.remove("hidden");
|
| 73 |
|
| 74 |
+
// Update UI to indicate description phase
|
| 75 |
spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false);
|
| 76 |
|
| 77 |
+
// Change cursor style to indicate different mode
|
| 78 |
+
const plane = document.getElementById('napping-plane');
|
| 79 |
+
plane.classList.remove('cursor-crosshair');
|
| 80 |
+
plane.classList.add('cursor-default');
|
| 81 |
+
|
| 82 |
+
// Remove any product selection highlights
|
| 83 |
+
document.querySelectorAll('.item-product').forEach(p => {
|
| 84 |
+
p.classList.remove('ring-4', 'ring-primary');
|
| 85 |
+
});
|
| 86 |
}
|
| 87 |
|
| 88 |
// Handle Point Click for Description
|
|
|
|
| 137 |
productWords[currentProductCode] = [];
|
| 138 |
}
|
| 139 |
|
| 140 |
+
// No maximum limit on words
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
if (productWords[currentProductCode].includes(word)) {
|
| 143 |
spanNotifaction("Palabra duplicada");
|
|
|
|
| 169 |
const point = document.getElementById(`point-${code}`);
|
| 170 |
if (!point) return;
|
| 171 |
|
| 172 |
+
const words = productWords[code] || [];
|
| 173 |
+
|
| 174 |
+
// Remove existing tooltip if present
|
| 175 |
+
let tooltip = point.querySelector('.group-hover\\:block');
|
| 176 |
if (tooltip) {
|
| 177 |
+
tooltip.remove();
|
| 178 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
// Only create tooltip in description phase and if there are words
|
| 181 |
+
if (isDescriptionPhase && words.length > 0) {
|
| 182 |
+
tooltip = document.createElement('div');
|
| 183 |
+
tooltip.className = 'absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 hidden group-hover:block';
|
| 184 |
+
|
| 185 |
+
// Display words in a 3-column grid (no coordinates)
|
| 186 |
+
const wordBadges = words.map(w => `<span class="inline-block px-2 py-1 bg-yellow-600 text-white rounded text-xs">${w}</span>`).join('');
|
| 187 |
tooltip.innerHTML = `
|
| 188 |
+
<strong>${code}</strong>
|
| 189 |
+
<div class="mt-2 pt-2 border-t border-gray-600" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; max-width: 300px;">
|
| 190 |
+
${wordBadges}
|
| 191 |
+
</div>
|
| 192 |
`;
|
| 193 |
+
|
| 194 |
+
// Add max-width to tooltip
|
| 195 |
+
tooltip.style.maxWidth = '320px';
|
| 196 |
+
tooltip.style.whiteSpace = 'normal';
|
| 197 |
+
|
| 198 |
+
point.appendChild(tooltip);
|
| 199 |
}
|
| 200 |
}
|
| 201 |
|
| 202 |
+
// Override saveData to include words for ultra flash mode
|
| 203 |
+
// This replaces the basic saveData from test-napping-plane.js
|
| 204 |
+
window.saveData = async function () {
|
| 205 |
+
const codeProducts = Object.keys(window.placedPoints);
|
| 206 |
+
const totalProducts = document.querySelectorAll('.item-product').length;
|
| 207 |
|
| 208 |
+
// Validate all products are placed
|
| 209 |
+
if (totalProducts != codeProducts.length) {
|
| 210 |
+
spanNotifaction("Por favor, coloca todos los puntos");
|
| 211 |
+
return false;
|
| 212 |
+
}
|
| 213 |
|
| 214 |
+
// If in description phase, validate words (minimum 1 per product)
|
|
|
|
| 215 |
if (isDescriptionPhase) {
|
| 216 |
+
for (const code of codeProducts) {
|
|
|
|
| 217 |
const words = productWords[code] || [];
|
| 218 |
+
if (words.length < 1) {
|
| 219 |
+
spanNotifaction(`El producto ${code} debe tener al menos 1 palabra.`);
|
| 220 |
return false;
|
| 221 |
}
|
| 222 |
}
|
| 223 |
}
|
| 224 |
|
| 225 |
+
// Prepare data with coordinates and words
|
|
|
|
| 226 |
const data = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
codeProducts.forEach((code) => {
|
| 228 |
const point = window.placedPoints[code];
|
| 229 |
const words = productWords[code] || [];
|
|
|
|
| 233 |
x: point.x,
|
| 234 |
y: point.y,
|
| 235 |
idProduct: point.id,
|
| 236 |
+
words: words // Include words for ultra flash mode
|
| 237 |
};
|
| 238 |
|
| 239 |
data.push(objData);
|
| 240 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
// Send data to server
|
| 243 |
+
const URL = "/cata/testers/api/rating-napping/no-mode";
|
| 244 |
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
| 245 |
|
| 246 |
try {
|
|
|
|
| 251 |
"X-CSRFToken": csrfToken,
|
| 252 |
},
|
| 253 |
body: JSON.stringify(data),
|
| 254 |
+
});
|
| 255 |
|
| 256 |
if (!response.ok) {
|
| 257 |
+
spanNotifaction("Error en la respuesta del servidor");
|
| 258 |
return false;
|
| 259 |
}
|
| 260 |
|
| 261 |
+
const result = await response.json();
|
| 262 |
|
| 263 |
if (result.error) {
|
| 264 |
+
spanNotifaction(result.error);
|
| 265 |
+
return false;
|
| 266 |
} else {
|
| 267 |
+
spanNotifaction(result.message, false);
|
| 268 |
+
return true;
|
| 269 |
}
|
| 270 |
} catch (error) {
|
| 271 |
+
spanNotifaction("Error en proceso de guardar los datos");
|
| 272 |
+
return false;
|
| 273 |
}
|
| 274 |
}
|
| 275 |
}
|