feat: add domain
This commit is contained in:
@@ -280,6 +280,12 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-gray-900 truncate">{{ element.label }}</div>
|
||||
<div class="flex items-center space-x-1 mt-1">
|
||||
{% if element.domain %}
|
||||
<span class="text-xs px-2 py-0.5 rounded-full"
|
||||
style="background-color: {{ element.domain.color }}20; color: {{ element.domain.color }};">
|
||||
{{ element.domain.name }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if element.skill %}
|
||||
<span class="text-xs text-purple-700 bg-purple-100 px-2 py-0.5 rounded-full">
|
||||
{{ element.skill }}
|
||||
|
||||
@@ -250,6 +250,16 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Domaine</label>
|
||||
<div class="relative">
|
||||
<input type="text" class="element-domain-input block w-full border border-gray-300 rounded-md px-2 py-1 text-sm focus:ring-purple-500 focus:border-purple-500" placeholder="Saisissez un domaine..." autocomplete="off">
|
||||
<input type="hidden" class="element-domain-id">
|
||||
<div class="element-domain-suggestions absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-48 overflow-y-auto hidden">
|
||||
<!-- Suggestions dynamiques -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Points max</label>
|
||||
<input type="number" step="0.1" min="0" class="element-max-points block w-full border border-gray-300 rounded-md px-2 py-1 text-sm focus:ring-purple-500 focus:border-purple-500" required>
|
||||
@@ -271,9 +281,427 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Modal pour création de domaine -->
|
||||
<div id="domain-creation-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 hidden">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Créer un nouveau domaine</h3>
|
||||
<button type="button" class="close-modal text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Nom du domaine</label>
|
||||
<input type="text" id="new-domain-name" class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-purple-500 focus:border-purple-500" placeholder="Ex: Calcul mental">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Couleur associée</label>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #3b82f6" data-color="#3b82f6"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #10b981" data-color="#10b981"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #f59e0b" data-color="#f59e0b"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #8b5cf6" data-color="#8b5cf6"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #ef4444" data-color="#ef4444"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #06b6d4" data-color="#06b6d4"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #84cc16" data-color="#84cc16"></button>
|
||||
<button type="button" class="color-option w-8 h-8 rounded-full border-2 border-gray-300 hover:border-gray-400" style="background-color: #f97316" data-color="#f97316"></button>
|
||||
</div>
|
||||
<input type="color" id="custom-color" class="w-full h-10 border border-gray-300 rounded cursor-pointer" value="#3b82f6">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Description (optionnel)</label>
|
||||
<textarea id="new-domain-description" class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-purple-500 focus:border-purple-500" rows="2" placeholder="Description du domaine..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<button type="button" class="close-modal px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md">Annuler</button>
|
||||
<button type="button" id="confirm-domain-creation" class="px-4 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 rounded-md">Créer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let exerciseCounter = 0;
|
||||
|
||||
// Variables globales pour les domaines
|
||||
let availableDomains = [];
|
||||
let currentDomainInput = null;
|
||||
let selectedColor = '#3b82f6';
|
||||
|
||||
// Charger les domaines disponibles
|
||||
async function loadDomains() {
|
||||
try {
|
||||
const response = await fetch('/api/domains/');
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
availableDomains = data.domains;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des domaines:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration de l'autocomplétion pour les domaines
|
||||
function setupDomainAutocomplete(container) {
|
||||
const inputElement = container.querySelector('.element-domain-input');
|
||||
const hiddenElement = container.querySelector('.element-domain-id');
|
||||
const suggestionsElement = container.querySelector('.element-domain-suggestions');
|
||||
|
||||
if (!inputElement || !hiddenElement || !suggestionsElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let debounceTimer = null;
|
||||
|
||||
inputElement.addEventListener('input', function() {
|
||||
const query = this.value.trim();
|
||||
clearTimeout(debounceTimer);
|
||||
|
||||
if (query.length === 0) {
|
||||
hideSuggestions(suggestionsElement);
|
||||
hiddenElement.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
debounceTimer = setTimeout(() => {
|
||||
searchAndShowSuggestions(query, inputElement, hiddenElement, suggestionsElement);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
inputElement.addEventListener('blur', function(e) {
|
||||
// Délai plus long pour permettre le clic sur les suggestions
|
||||
setTimeout(() => {
|
||||
if (!suggestionsElement.contains(document.activeElement) &&
|
||||
!suggestionsElement.matches(':hover')) {
|
||||
hideSuggestions(suggestionsElement);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
inputElement.addEventListener('focus', function() {
|
||||
if (this.value.trim()) {
|
||||
searchAndShowSuggestions(this.value.trim(), inputElement, hiddenElement, suggestionsElement);
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion des touches clavier avec navigation
|
||||
let selectedIndex = -1;
|
||||
|
||||
inputElement.addEventListener('keydown', function(e) {
|
||||
const suggestions = suggestionsElement.querySelectorAll('.suggestion-item');
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
hideSuggestions(suggestionsElement);
|
||||
selectedIndex = -1;
|
||||
this.blur();
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (suggestions.length > 0) {
|
||||
selectedIndex = Math.min(selectedIndex + 1, suggestions.length - 1);
|
||||
updateSelection(suggestions, selectedIndex);
|
||||
}
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (suggestions.length > 0) {
|
||||
selectedIndex = Math.max(selectedIndex - 1, -1);
|
||||
updateSelection(suggestions, selectedIndex);
|
||||
}
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (selectedIndex >= 0 && suggestions[selectedIndex]) {
|
||||
suggestions[selectedIndex].dispatchEvent(new Event('mousedown'));
|
||||
} else if (suggestions.length > 0) {
|
||||
suggestions[0].dispatchEvent(new Event('mousedown'));
|
||||
}
|
||||
} else if (e.key === 'Tab') {
|
||||
if (suggestions.length > 0 && selectedIndex === -1) {
|
||||
e.preventDefault();
|
||||
suggestions[0].dispatchEvent(new Event('mousedown'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Reset de la sélection quand on tape
|
||||
inputElement.addEventListener('input', function() {
|
||||
selectedIndex = -1;
|
||||
});
|
||||
|
||||
function updateSelection(suggestions, index) {
|
||||
suggestions.forEach((suggestion, i) => {
|
||||
suggestion.classList.remove('bg-purple-100', 'bg-blue-100', 'ring-2', 'ring-purple-500');
|
||||
if (i === index) {
|
||||
suggestion.classList.add('bg-blue-100', 'ring-2', 'ring-blue-500');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function searchAndShowSuggestions(query, inputElement, hiddenElement, suggestionsElement) {
|
||||
try {
|
||||
const response = await fetch(`/api/domains/search?q=${encodeURIComponent(query)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
console.error('Erreur lors de la recherche de domaines:', data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
suggestionsElement.innerHTML = '';
|
||||
const domains = data.domains || [];
|
||||
|
||||
if (domains.length > 0) {
|
||||
domains.forEach(domain => {
|
||||
const suggestionDiv = createSuggestionItem(domain, query, inputElement, hiddenElement, suggestionsElement);
|
||||
suggestionsElement.appendChild(suggestionDiv);
|
||||
});
|
||||
}
|
||||
|
||||
// Vérifier s'il y a une correspondance exacte
|
||||
const exactMatch = domains.find(domain =>
|
||||
domain.name.toLowerCase() === query.toLowerCase()
|
||||
);
|
||||
|
||||
// Option "Créer nouveau domaine" si pas de correspondance exacte
|
||||
if (!exactMatch) {
|
||||
const createDiv = document.createElement('div');
|
||||
createDiv.className = 'suggestion-item px-3 py-2 text-sm cursor-pointer hover:bg-purple-100 border-t border-gray-200';
|
||||
createDiv.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 text-purple-600 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span class="text-purple-600 font-medium">Créer "${query}"</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
createDiv.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
currentDomainInput = inputElement;
|
||||
showDomainCreationModal(query);
|
||||
hideSuggestions(suggestionsElement);
|
||||
});
|
||||
|
||||
suggestionsElement.appendChild(createDiv);
|
||||
}
|
||||
|
||||
// Affichage conditionnel des suggestions
|
||||
if (domains.length > 0 || !exactMatch) {
|
||||
suggestionsElement.classList.remove('hidden');
|
||||
} else {
|
||||
suggestionsElement.classList.add('hidden');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche de domaines:', error);
|
||||
suggestionsElement.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function createSuggestionItem(domain, query, inputElement, hiddenElement, suggestionsElement) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'suggestion-item px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 flex items-center';
|
||||
|
||||
const colorIndicator = `<div class="w-3 h-3 rounded-full mr-2" style="background-color: ${domain.color}"></div>`;
|
||||
const highlightedName = highlightMatch(domain.name, query);
|
||||
|
||||
div.innerHTML = `${colorIndicator}<span>${highlightedName}</span>`;
|
||||
|
||||
div.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
inputElement.value = domain.name;
|
||||
hiddenElement.value = domain.id;
|
||||
hideSuggestions(suggestionsElement);
|
||||
inputElement.focus();
|
||||
});
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function highlightMatch(text, query) {
|
||||
const lowerText = text.toLowerCase();
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
if (lowerText.includes(lowerQuery)) {
|
||||
const index = lowerText.indexOf(lowerQuery);
|
||||
return text.substring(0, index) +
|
||||
'<strong>' + text.substring(index, index + query.length) + '</strong>' +
|
||||
text.substring(index + query.length);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function hideSuggestions(suggestionsElement) {
|
||||
suggestionsElement.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Gestion du modal de création de domaine
|
||||
function showDomainCreationModal(suggestedName = '') {
|
||||
const modal = document.getElementById('domain-creation-modal');
|
||||
const nameInput = document.getElementById('new-domain-name');
|
||||
const customColorInput = document.getElementById('custom-color');
|
||||
|
||||
if (nameInput) {
|
||||
nameInput.value = suggestedName;
|
||||
}
|
||||
if (customColorInput) {
|
||||
customColorInput.value = selectedColor;
|
||||
}
|
||||
updateColorSelection(selectedColor);
|
||||
|
||||
setupModalEvents();
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
if (nameInput) {
|
||||
nameInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function setupModalEvents() {
|
||||
// Bouton créer
|
||||
const confirmBtn = document.getElementById('confirm-domain-creation');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.replaceWith(confirmBtn.cloneNode(true));
|
||||
const newConfirmBtn = document.getElementById('confirm-domain-creation');
|
||||
newConfirmBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
createNewDomain();
|
||||
});
|
||||
}
|
||||
|
||||
// Boutons fermer
|
||||
document.querySelectorAll('.close-modal').forEach(btn => {
|
||||
btn.replaceWith(btn.cloneNode(true));
|
||||
});
|
||||
|
||||
document.querySelectorAll('.close-modal').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
hideDomainCreationModal();
|
||||
});
|
||||
});
|
||||
|
||||
// Couleurs prédéfinies
|
||||
document.querySelectorAll('.color-option').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
updateColorSelection(this.dataset.color);
|
||||
});
|
||||
});
|
||||
|
||||
// Sélecteur de couleur personnalisé
|
||||
const customColorInput = document.getElementById('custom-color');
|
||||
if (customColorInput) {
|
||||
customColorInput.addEventListener('input', function() {
|
||||
updateColorSelection(this.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hideDomainCreationModal() {
|
||||
const modal = document.getElementById('domain-creation-modal');
|
||||
modal.classList.add('hidden');
|
||||
currentDomainInput = null;
|
||||
}
|
||||
|
||||
function updateColorSelection(color) {
|
||||
selectedColor = color;
|
||||
const customColorInput = document.getElementById('custom-color');
|
||||
if (customColorInput) {
|
||||
customColorInput.value = color;
|
||||
}
|
||||
|
||||
// Mettre à jour la sélection visuelle
|
||||
document.querySelectorAll('.color-option').forEach(btn => {
|
||||
btn.classList.remove('ring-2', 'ring-purple-500');
|
||||
if (btn.dataset.color === color) {
|
||||
btn.classList.add('ring-2', 'ring-purple-500');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Créer un nouveau domaine via API
|
||||
async function createNewDomain() {
|
||||
const nameInput = document.getElementById('new-domain-name');
|
||||
const descriptionInput = document.getElementById('new-domain-description');
|
||||
|
||||
if (!nameInput) {
|
||||
alert('Erreur: champ nom non trouvé');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = nameInput.value.trim();
|
||||
const description = descriptionInput ? descriptionInput.value.trim() : '';
|
||||
|
||||
if (!name) {
|
||||
alert('Le nom du domaine est obligatoire');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/domains/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
color: selectedColor,
|
||||
description: description
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Ajouter le nouveau domaine à la liste
|
||||
availableDomains.push(data.domain);
|
||||
|
||||
// Mettre à jour l'input actuel s'il existe
|
||||
if (currentDomainInput) {
|
||||
currentDomainInput.value = data.domain.name;
|
||||
const hiddenInput = currentDomainInput.parentElement.querySelector('.element-domain-id');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = data.domain.id;
|
||||
}
|
||||
}
|
||||
|
||||
hideDomainCreationModal();
|
||||
showNotification('Domaine créé avec succès !', 'success');
|
||||
} else {
|
||||
showNotification(data.error || 'Erreur lors de la création du domaine', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
showNotification('Erreur de connexion', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showNotification(message, type) {
|
||||
// Notification améliorée avec toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `fixed top-4 right-4 px-4 py-2 rounded-md text-white font-medium z-50 ${
|
||||
type === 'success' ? 'bg-green-500' : 'bg-red-500'
|
||||
}`;
|
||||
toast.textContent = message;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const addExerciseBtn = document.getElementById('add-exercise');
|
||||
const exercisesContainer = document.getElementById('exercises-container');
|
||||
@@ -351,6 +779,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
elementsContainer.appendChild(elementDiv);
|
||||
noElementsMsg.style.display = 'none';
|
||||
|
||||
// Configurer l'autocomplétion des domaines pour ce nouvel élément
|
||||
setupDomainAutocomplete(elementsContainer.lastElementChild);
|
||||
|
||||
// Focus automatique sur le champ label du nouvel élément pour faciliter la navigation clavier
|
||||
setTimeout(() => {
|
||||
const labelInput = elementsContainer.lastElementChild.querySelector('.element-label');
|
||||
@@ -407,16 +838,29 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const maxPoints = parseFloat(elementItem.querySelector('.element-max-points').value);
|
||||
const gradingType = elementItem.querySelector('.element-grading-type').value;
|
||||
const description = elementItem.querySelector('.element-description').value;
|
||||
const domainHiddenInput = elementItem.querySelector('.element-domain-id');
|
||||
const domainTextInput = elementItem.querySelector('.element-domain-input');
|
||||
|
||||
if (!label.trim() || !maxPoints || !gradingType) return;
|
||||
|
||||
gradingElements.push({
|
||||
const elementData = {
|
||||
label: label.trim(),
|
||||
skill: skill.trim(),
|
||||
max_points: maxPoints,
|
||||
grading_type: gradingType,
|
||||
description: description.trim()
|
||||
});
|
||||
};
|
||||
|
||||
// Gestion du domaine
|
||||
if (domainHiddenInput && domainHiddenInput.value) {
|
||||
// Domaine existant sélectionné via autocomplétion
|
||||
elementData.domain_id = parseInt(domainHiddenInput.value);
|
||||
} else if (domainTextInput && domainTextInput.value.trim() && !domainHiddenInput.value) {
|
||||
// Nouveau domaine à créer (texte saisi mais pas d'ID)
|
||||
elementData.domain_name = domainTextInput.value.trim();
|
||||
}
|
||||
|
||||
gradingElements.push(elementData);
|
||||
});
|
||||
|
||||
exercises.push({
|
||||
@@ -530,6 +974,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
elementDiv.querySelector('.element-grading-type').value = element.grading_type;
|
||||
elementDiv.querySelector('.element-description').value = element.description || '';
|
||||
|
||||
// Remplir le domaine s'il existe
|
||||
if (element.domain_id) {
|
||||
const domainHiddenInput = elementDiv.querySelector('.element-domain-id');
|
||||
const domainTextInput = elementDiv.querySelector('.element-domain-input');
|
||||
if (domainHiddenInput && domainTextInput) {
|
||||
domainHiddenInput.value = element.domain_id;
|
||||
// Trouver le nom du domaine correspondant
|
||||
const domain = availableDomains.find(d => d.id === element.domain_id);
|
||||
if (domain) {
|
||||
domainTextInput.value = domain.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter l'event listener pour supprimer
|
||||
const removeElementBtn = elementDiv.querySelector('.remove-grading-element');
|
||||
removeElementBtn.addEventListener('click', function() {
|
||||
@@ -537,6 +995,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
elementsContainer.appendChild(elementDiv);
|
||||
|
||||
// Configurer l'autocomplétion pour cet élément
|
||||
setupDomainAutocomplete(elementsContainer.lastElementChild);
|
||||
});
|
||||
|
||||
if (exercise.grading_elements.length > 0) {
|
||||
@@ -548,6 +1009,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
updateExercisesVisibility();
|
||||
}
|
||||
|
||||
// Initialisation des domaines
|
||||
loadDomains();
|
||||
|
||||
// Fermer le modal avec Escape
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
hideDomainCreationModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Configurer l'autocomplétion pour les éléments existants au chargement
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.grading-element-item').forEach(setupDomainAutocomplete);
|
||||
}, 500);
|
||||
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -184,6 +184,15 @@
|
||||
<th scope="col" class="grading-header px-2 py-1.5 text-center text-xs font-medium text-gray-500 uppercase tracking-wider min-w-28 {{ exercise_color }} bg-gradient-to-b from-gray-50 to-gray-100">
|
||||
<div class="element-label text-xs font-semibold text-gray-900 truncate">{{ element.label }}</div>
|
||||
|
||||
{% if element.domain %}
|
||||
<div class="mt-1">
|
||||
<span class="text-xs px-1.5 py-0.5 rounded font-medium"
|
||||
style="background-color: {{ element.domain.color }}20; color: {{ element.domain.color }};">
|
||||
{{ element.domain.name }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if element.description %}
|
||||
<div class="text-xs text-gray-600 mt-1 leading-tight">{{ element.description }}</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user