Spaces:
Sleeping
Sleeping
Se guarda el progreso de napping con modalidad sort en descripcion
Browse files- tecnicas/controllers/api_controller/rating_napping_controller.py +9 -1
- tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py +34 -1
- tecnicas/static/js/test-napping-sort.js +105 -4
- tecnicas/templates/tecnicas/components/dialog-nap-sort.html +12 -18
- tecnicas/templates/tecnicas/forms_tester/test_napping_sort.html +14 -1
tecnicas/controllers/api_controller/rating_napping_controller.py
CHANGED
|
@@ -18,7 +18,6 @@ class RatingNappingController:
|
|
| 18 |
|
| 19 |
# Branch based on modality
|
| 20 |
if name_mod == 'sorting':
|
| 21 |
-
print(data)
|
| 22 |
return RatingNappingController.processSortingMode(
|
| 23 |
data, participation
|
| 24 |
)
|
|
@@ -50,6 +49,8 @@ class RatingNappingController:
|
|
| 50 |
RatingNappingController.processGroupsForSorting(
|
| 51 |
products, groups, participation, existing_ratings_map, products_map
|
| 52 |
)
|
|
|
|
|
|
|
| 53 |
|
| 54 |
return JsonResponse({"message": "Datos guardados exitosamente"})
|
| 55 |
|
|
@@ -163,6 +164,13 @@ class RatingNappingController:
|
|
| 163 |
RatingNappingController.processWordsForGroups(
|
| 164 |
groups, all_groups_for_words
|
| 165 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
@staticmethod
|
| 168 |
def processWordsForGroups(groups_data, groups_list):
|
|
|
|
| 18 |
|
| 19 |
# Branch based on modality
|
| 20 |
if name_mod == 'sorting':
|
|
|
|
| 21 |
return RatingNappingController.processSortingMode(
|
| 22 |
data, participation
|
| 23 |
)
|
|
|
|
| 49 |
RatingNappingController.processGroupsForSorting(
|
| 50 |
products, groups, participation, existing_ratings_map, products_map
|
| 51 |
)
|
| 52 |
+
else:
|
| 53 |
+
RatingNappingController.deleteAllGroups(participation)
|
| 54 |
|
| 55 |
return JsonResponse({"message": "Datos guardados exitosamente"})
|
| 56 |
|
|
|
|
| 164 |
RatingNappingController.processWordsForGroups(
|
| 165 |
groups, all_groups_for_words
|
| 166 |
)
|
| 167 |
+
|
| 168 |
+
@staticmethod
|
| 169 |
+
def deleteAllGroups(participation):
|
| 170 |
+
GrupoProducto.objects.filter(
|
| 171 |
+
tecnica=participation.tecnica,
|
| 172 |
+
catador=participation.catador
|
| 173 |
+
).delete()
|
| 174 |
|
| 175 |
@staticmethod
|
| 176 |
def processWordsForGroups(groups_data, groups_list):
|
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py
CHANGED
|
@@ -2,7 +2,7 @@ from django.http import HttpRequest
|
|
| 2 |
from django.shortcuts import redirect, render
|
| 3 |
from django.urls import reverse
|
| 4 |
from django.db.models import F
|
| 5 |
-
from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra
|
| 6 |
from tecnicas.forms import ListWordsForm
|
| 7 |
from tecnicas.utils import noValidTechnique
|
| 8 |
from tecnicas.controllers import ParticipacionController
|
|
@@ -90,7 +90,9 @@ class TestNappingController(GenetalTestController):
|
|
| 90 |
products_in_technique = Producto.objects.filter(id_tecnica=technique)
|
| 91 |
self.context["products"] = products_in_technique
|
| 92 |
|
|
|
|
| 93 |
self.setCoordinates()
|
|
|
|
| 94 |
|
| 95 |
return render(request, self.sort_direction, self.context)
|
| 96 |
|
|
@@ -114,6 +116,37 @@ class TestNappingController(GenetalTestController):
|
|
| 114 |
|
| 115 |
self.context["data_points"] = list(data_points)
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def setWords(self):
|
| 118 |
technique = self.session.tecnica
|
| 119 |
|
|
|
|
| 2 |
from django.shortcuts import redirect, render
|
| 3 |
from django.urls import reverse
|
| 4 |
from django.db.models import F
|
| 5 |
+
from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra, GrupoProducto
|
| 6 |
from tecnicas.forms import ListWordsForm
|
| 7 |
from tecnicas.utils import noValidTechnique
|
| 8 |
from tecnicas.controllers import ParticipacionController
|
|
|
|
| 90 |
products_in_technique = Producto.objects.filter(id_tecnica=technique)
|
| 91 |
self.context["products"] = products_in_technique
|
| 92 |
|
| 93 |
+
self.context["form"] = ListWordsForm()
|
| 94 |
self.setCoordinates()
|
| 95 |
+
self.setGroups()
|
| 96 |
|
| 97 |
return render(request, self.sort_direction, self.context)
|
| 98 |
|
|
|
|
| 116 |
|
| 117 |
self.context["data_points"] = list(data_points)
|
| 118 |
|
| 119 |
+
def setGroups(self):
|
| 120 |
+
technique = self.session.tecnica
|
| 121 |
+
|
| 122 |
+
# Get all product groups for this tester
|
| 123 |
+
grupos_producto = GrupoProducto.objects.filter(
|
| 124 |
+
tecnica=technique,
|
| 125 |
+
catador=self.participation.catador
|
| 126 |
+
).prefetch_related('productos', 'palabras')
|
| 127 |
+
|
| 128 |
+
groups = []
|
| 129 |
+
for group in grupos_producto:
|
| 130 |
+
# Get products in this group
|
| 131 |
+
products_list = []
|
| 132 |
+
for product in group.productos.all():
|
| 133 |
+
products_list.append({
|
| 134 |
+
'id': product.id,
|
| 135 |
+
'codigoProducto': product.codigoProducto
|
| 136 |
+
})
|
| 137 |
+
|
| 138 |
+
# Get words for this group
|
| 139 |
+
words_list = list(group.palabras.values_list(
|
| 140 |
+
'nombre_palabra', flat=True))
|
| 141 |
+
|
| 142 |
+
groups.append({
|
| 143 |
+
'id': group.id,
|
| 144 |
+
'products': products_list,
|
| 145 |
+
'words': words_list
|
| 146 |
+
})
|
| 147 |
+
|
| 148 |
+
self.context["groups"] = groups
|
| 149 |
+
|
| 150 |
def setWords(self):
|
| 151 |
technique = self.session.tecnica
|
| 152 |
|
tecnicas/static/js/test-napping-sort.js
CHANGED
|
@@ -32,6 +32,40 @@ if (isSortMode) {
|
|
| 32 |
initSortMode();
|
| 33 |
}
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
function initSortMode() {
|
| 36 |
const continueGroupingBtn = document.getElementById('continue-grouping');
|
| 37 |
const continueDescriptionBtn = document.getElementById('continue-description');
|
|
@@ -39,17 +73,50 @@ function initSortMode() {
|
|
| 39 |
const dissolveGroupBtn = document.getElementById('dissolve-group-btn');
|
| 40 |
const groupControls = document.getElementById('group-controls');
|
| 41 |
const questionSaveBtn = document.getElementById('question-save');
|
|
|
|
| 42 |
const groupWordDialog = document.getElementById('group-word-dialog');
|
| 43 |
const groupWordForm = document.getElementById('group-word-form');
|
| 44 |
-
const groupWordInput = document.
|
|
|
|
| 45 |
const groupWordList = document.getElementById('group-word-list');
|
| 46 |
const dialogGroupId = document.getElementById('dialog-group-id');
|
| 47 |
|
| 48 |
// Hide question save button initially
|
| 49 |
questionSaveBtn.classList.add('hidden');
|
| 50 |
|
| 51 |
-
//
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
// Phase 1: Product Placement
|
| 55 |
// Show continue to grouping button when all products are placed
|
|
@@ -289,7 +356,7 @@ function initSortMode() {
|
|
| 289 |
spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false);
|
| 290 |
|
| 291 |
// Auto-save groups when transitioning to Phase 3
|
| 292 |
-
|
| 293 |
}
|
| 294 |
|
| 295 |
// Group Word Dialog Functions
|
|
@@ -317,6 +384,7 @@ function initSortMode() {
|
|
| 317 |
});
|
| 318 |
|
| 319 |
groupWordList.appendChild(badge);
|
|
|
|
| 320 |
});
|
| 321 |
|
| 322 |
// Update group display with word count
|
|
@@ -381,6 +449,39 @@ function initSortMode() {
|
|
| 381 |
tooltip.style.maxWidth = '320px';
|
| 382 |
tooltip.style.whiteSpace = 'normal';
|
| 383 |
tooltip.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
}
|
| 385 |
}
|
| 386 |
|
|
|
|
| 32 |
initSortMode();
|
| 33 |
}
|
| 34 |
|
| 35 |
+
function loadExistingGroups() {
|
| 36 |
+
const dataGroupContainer = document.querySelector('.data-group-products');
|
| 37 |
+
if (!dataGroupContainer) return { hasGroups: false, hasWords: false };
|
| 38 |
+
|
| 39 |
+
const groupElements = dataGroupContainer.querySelectorAll('.item-group');
|
| 40 |
+
let hasGroups = false;
|
| 41 |
+
let hasWords = false;
|
| 42 |
+
|
| 43 |
+
groupElements.forEach((groupEl, index) => {
|
| 44 |
+
const groupId = `group-${groupCounter++}`;
|
| 45 |
+
const products = [];
|
| 46 |
+
const words = groupEl.dataset.words ? groupEl.dataset.words.split(',').map(w => w.trim()).filter(w => w) : [];
|
| 47 |
+
|
| 48 |
+
// Get products in this group
|
| 49 |
+
groupEl.querySelectorAll('.item-group-product').forEach(productEl => {
|
| 50 |
+
const code = productEl.dataset.code;
|
| 51 |
+
products.push(code);
|
| 52 |
+
productToGroup[code] = groupId;
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
if (products.length > 0) {
|
| 56 |
+
hasGroups = true;
|
| 57 |
+
productGroups[groupId] = products;
|
| 58 |
+
groupWords[groupId] = words;
|
| 59 |
+
|
| 60 |
+
if (words.length > 0) {
|
| 61 |
+
hasWords = true;
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
return { hasGroups, hasWords };
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
function initSortMode() {
|
| 70 |
const continueGroupingBtn = document.getElementById('continue-grouping');
|
| 71 |
const continueDescriptionBtn = document.getElementById('continue-description');
|
|
|
|
| 73 |
const dissolveGroupBtn = document.getElementById('dissolve-group-btn');
|
| 74 |
const groupControls = document.getElementById('group-controls');
|
| 75 |
const questionSaveBtn = document.getElementById('question-save');
|
| 76 |
+
|
| 77 |
const groupWordDialog = document.getElementById('group-word-dialog');
|
| 78 |
const groupWordForm = document.getElementById('group-word-form');
|
| 79 |
+
const groupWordInput = document.getElementsByName('nombre_palabra')[0];
|
| 80 |
+
groupWordInput.value = "";
|
| 81 |
const groupWordList = document.getElementById('group-word-list');
|
| 82 |
const dialogGroupId = document.getElementById('dialog-group-id');
|
| 83 |
|
| 84 |
// Hide question save button initially
|
| 85 |
questionSaveBtn.classList.add('hidden');
|
| 86 |
|
| 87 |
+
// Load existing groups from backend and determine initial phase
|
| 88 |
+
const { hasGroups, hasWords } = loadExistingGroups();
|
| 89 |
+
|
| 90 |
+
setTimeout(() => {
|
| 91 |
+
// Determine initial phase based on existing data
|
| 92 |
+
if (hasGroups && hasWords) {
|
| 93 |
+
// Skip to Phase 3 (Description) if groups have words
|
| 94 |
+
currentPhase = 3;
|
| 95 |
+
window.isPlacementActive = false;
|
| 96 |
+
groupControls.classList.remove('hidden');
|
| 97 |
+
continueDescriptionBtn.classList.remove('hidden');
|
| 98 |
+
|
| 99 |
+
renderExistingGroups();
|
| 100 |
+
startDescriptionPhase();
|
| 101 |
+
} else if (hasGroups) {
|
| 102 |
+
// Skip to Phase 2 (Grouping) if groups exist but no words
|
| 103 |
+
currentPhase = 2;
|
| 104 |
+
renderExistingGroups();
|
| 105 |
+
window.isPlacementActive = false;
|
| 106 |
+
groupControls.classList.remove('hidden');
|
| 107 |
+
continueDescriptionBtn.classList.remove('hidden');
|
| 108 |
+
|
| 109 |
+
const plane = document.getElementById('napping-plane');
|
| 110 |
+
plane.classList.remove('cursor-crosshair');
|
| 111 |
+
plane.classList.add('cursor-default');
|
| 112 |
+
|
| 113 |
+
enablePointSelection();
|
| 114 |
+
spanNotifaction("Continúa agrupando productos o pasa a la descripción.", false);
|
| 115 |
+
} else {
|
| 116 |
+
// Start in Phase 1 (Placement)
|
| 117 |
+
currentPhase = 1;
|
| 118 |
+
}
|
| 119 |
+
}, 200);
|
| 120 |
|
| 121 |
// Phase 1: Product Placement
|
| 122 |
// Show continue to grouping button when all products are placed
|
|
|
|
| 356 |
spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false);
|
| 357 |
|
| 358 |
// Auto-save groups when transitioning to Phase 3
|
| 359 |
+
sortModeSaveData(false);
|
| 360 |
}
|
| 361 |
|
| 362 |
// Group Word Dialog Functions
|
|
|
|
| 384 |
});
|
| 385 |
|
| 386 |
groupWordList.appendChild(badge);
|
| 387 |
+
|
| 388 |
});
|
| 389 |
|
| 390 |
// Update group display with word count
|
|
|
|
| 449 |
tooltip.style.maxWidth = '320px';
|
| 450 |
tooltip.style.whiteSpace = 'normal';
|
| 451 |
tooltip.classList.remove('hidden');
|
| 452 |
+
} else {
|
| 453 |
+
let tooltip = displayElement.querySelector('.group-tooltip');
|
| 454 |
+
if (tooltip) {
|
| 455 |
+
tooltip.remove();
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
function renderExistingGroups() {
|
| 461 |
+
const colors = ['bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600'];
|
| 462 |
+
let colorIndex = 0;
|
| 463 |
+
|
| 464 |
+
for (const [groupId, products] of Object.entries(productGroups)) {
|
| 465 |
+
const color = colors[colorIndex % colors.length];
|
| 466 |
+
colorIndex++;
|
| 467 |
+
|
| 468 |
+
// Update point colors
|
| 469 |
+
products.forEach(code => {
|
| 470 |
+
const point = document.getElementById(`point-${code}`);
|
| 471 |
+
|
| 472 |
+
if (point) {
|
| 473 |
+
point.classList.remove('bg-red-600');
|
| 474 |
+
point.classList.add(color);
|
| 475 |
+
}
|
| 476 |
+
});
|
| 477 |
+
|
| 478 |
+
// Add group to display
|
| 479 |
+
addGroupToDisplay(groupId, products, color);
|
| 480 |
+
|
| 481 |
+
// Update display with words if they exist
|
| 482 |
+
if (groupWords[groupId] && groupWords[groupId].length > 0) {
|
| 483 |
+
updateGroupDisplayWithWords(groupId);
|
| 484 |
+
}
|
| 485 |
}
|
| 486 |
}
|
| 487 |
|
tecnicas/templates/tecnicas/components/dialog-nap-sort.html
CHANGED
|
@@ -1,29 +1,20 @@
|
|
| 1 |
<dialog id="group-word-dialog" class="modal">
|
| 2 |
-
<div class="modal-box max-w-2xl">
|
| 3 |
<h3 class="font-bold text-lg">Describir Grupo: <span id="dialog-group-id"></span></h3>
|
| 4 |
<p class="py-2 text-sm text-gray-600">Agrega palabras para describir este grupo de productos</p>
|
| 5 |
|
| 6 |
-
<form id="group-word-form" class="space-y-4">
|
| 7 |
-
<
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
</div>
|
| 14 |
-
|
| 15 |
-
<div class="flex justify-end">
|
| 16 |
-
<button type="submit" class="cts-btn-general cts-btn-primary btn-push">
|
| 17 |
-
Agregar Palabra
|
| 18 |
-
</button>
|
| 19 |
-
</div>
|
| 20 |
</form>
|
| 21 |
|
| 22 |
<div class="mt-4">
|
| 23 |
<h4 class="font-semibold mb-2">Palabras agregadas:</h4>
|
| 24 |
-
<div id="group-word-list" class="flex flex-wrap gap-2">
|
| 25 |
-
<!-- Words will be added here dynamically -->
|
| 26 |
-
</div>
|
| 27 |
</div>
|
| 28 |
|
| 29 |
<div class="modal-action">
|
|
@@ -32,4 +23,7 @@
|
|
| 32 |
</form>
|
| 33 |
</div>
|
| 34 |
</div>
|
|
|
|
|
|
|
|
|
|
| 35 |
</dialog>
|
|
|
|
| 1 |
<dialog id="group-word-dialog" class="modal">
|
| 2 |
+
<div class="modal-box max-w-2xl bg-surface-ligt">
|
| 3 |
<h3 class="font-bold text-lg">Describir Grupo: <span id="dialog-group-id"></span></h3>
|
| 4 |
<p class="py-2 text-sm text-gray-600">Agrega palabras para describir este grupo de productos</p>
|
| 5 |
|
| 6 |
+
<form id="group-word-form" class="space-y-4 flex justify-center items-center gap-4">
|
| 7 |
+
<label for="{{ form.nombre_palabra.id_for_label }}" class="text-left flex-1">
|
| 8 |
+
{{ form.nombre_palabra }}
|
| 9 |
+
</label>
|
| 10 |
+
<button type="submit" class="cts-btn-general-compress cts-btn-primary btn-push py-1 px-4">
|
| 11 |
+
Agregar
|
| 12 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</form>
|
| 14 |
|
| 15 |
<div class="mt-4">
|
| 16 |
<h4 class="font-semibold mb-2">Palabras agregadas:</h4>
|
| 17 |
+
<div id="group-word-list" class="flex flex-wrap gap-2"></div>
|
|
|
|
|
|
|
| 18 |
</div>
|
| 19 |
|
| 20 |
<div class="modal-action">
|
|
|
|
| 23 |
</form>
|
| 24 |
</div>
|
| 25 |
</div>
|
| 26 |
+
<form method="dialog" class="modal-backdrop">
|
| 27 |
+
<button>close</button>
|
| 28 |
+
</form>
|
| 29 |
</dialog>
|
tecnicas/templates/tecnicas/forms_tester/test_napping_sort.html
CHANGED
|
@@ -87,6 +87,19 @@
|
|
| 87 |
</button>
|
| 88 |
</div>
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
<!-- Display existing groups -->
|
| 91 |
<div id="groups-display" class="flex gap-3 flex-wrap justify-center"></div>
|
| 92 |
</section>
|
|
@@ -171,7 +184,7 @@
|
|
| 171 |
</section>
|
| 172 |
</article>
|
| 173 |
|
| 174 |
-
{% include "../components/dialog-nap-sort.html" %}
|
| 175 |
|
| 176 |
{% include "../components/toast-container.html" %}
|
| 177 |
</article>
|
|
|
|
| 87 |
</button>
|
| 88 |
</div>
|
| 89 |
|
| 90 |
+
{% if groups %}
|
| 91 |
+
<div class="data-group-products hidden">
|
| 92 |
+
{% for group in groups %}
|
| 93 |
+
<ul class="item-group" {% if group.words %} data-words="{{ group.words|join:',' }}" {% endif %}>
|
| 94 |
+
{% for product in group.products %}
|
| 95 |
+
<li class="item-group-product" data-id-product="{{ product.id }}"
|
| 96 |
+
data-code="{{ product.codigoProducto }}"></li>
|
| 97 |
+
{% endfor %}
|
| 98 |
+
</ul>
|
| 99 |
+
{% endfor %}
|
| 100 |
+
</div>
|
| 101 |
+
{% endif %}
|
| 102 |
+
|
| 103 |
<!-- Display existing groups -->
|
| 104 |
<div id="groups-display" class="flex gap-3 flex-wrap justify-center"></div>
|
| 105 |
</section>
|
|
|
|
| 184 |
</section>
|
| 185 |
</article>
|
| 186 |
|
| 187 |
+
{% include "../components/dialog-nap-sort.html" with form=form %}
|
| 188 |
|
| 189 |
{% include "../components/toast-container.html" %}
|
| 190 |
</article>
|