feat: improve scale configuration
This commit is contained in:
287
docs/CONFIGURATION_SCALES.md
Normal file
287
docs/CONFIGURATION_SCALES.md
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
# 📏 Configuration des Échelles - Notytex
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Notytex utilise un système de notation hybride qui distingue trois types de valeurs d'évaluation :
|
||||||
|
|
||||||
|
1. **Notes** : Valeurs numériques décimales (ex: 2.5/4, 18/20, 15.5/20)
|
||||||
|
2. **Scores** : Échelle fixe de 0 à 3 pour l'évaluation par compétences
|
||||||
|
3. **Valeurs spéciales** : Valeurs configurables comme "." (non évalué), "d" (dispensé), etc.
|
||||||
|
|
||||||
|
## Architecture Technique
|
||||||
|
|
||||||
|
### Modèle de Données
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Modèle CompetenceScaleValue
|
||||||
|
class CompetenceScaleValue(db.Model):
|
||||||
|
value: str # "0", "1", "2", "3", ".", "d", etc.
|
||||||
|
label: str # "Non acquis", "En cours d'acquisition", etc.
|
||||||
|
color: str # "#dc2626", "#059669", etc.
|
||||||
|
included_in_total: bool # True/False pour inclusion dans calculs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Centralisée
|
||||||
|
|
||||||
|
La configuration est gérée par `app_config.py` avec stockage en SQLite :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Configuration des dégradés de couleurs pour les notes
|
||||||
|
'grading.notes_gradient.min_color': '#dc2626' # Rouge pour note 0
|
||||||
|
'grading.notes_gradient.max_color': '#059669' # Vert pour note max
|
||||||
|
'grading.notes_gradient.enabled': True
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types de Notation
|
||||||
|
|
||||||
|
### 1. Notes (Numerical Grading)
|
||||||
|
|
||||||
|
**Caractéristiques :**
|
||||||
|
- Valeurs numériques décimales : 2.5, 18, 15.5, etc.
|
||||||
|
- Barème variable selon l'exercice : /4, /20, /10, etc.
|
||||||
|
- Calcul automatique des pourcentages et moyennes
|
||||||
|
- **Nouveau** : Système de dégradé de couleurs automatique
|
||||||
|
|
||||||
|
**Dégradé de Couleurs (2025) :**
|
||||||
|
- Configuration via interface `/config/scale`
|
||||||
|
- Couleur minimum pour note 0 (par défaut : rouge #dc2626)
|
||||||
|
- Couleur maximum pour note maximale (par défaut : vert #059669)
|
||||||
|
- Interpolation HSL pour des transitions naturelles (évite le gris)
|
||||||
|
- Calcul automatique des couleurs intermédiaires selon ratio note/maximum
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Exemple de calcul de couleur
|
||||||
|
function calculateNoteColor(note, maxPoints, minColor, maxColor) {
|
||||||
|
const factor = note / maxPoints;
|
||||||
|
return interpolateColorHSL(minColor, maxColor, factor);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interface Utilisateur :**
|
||||||
|
- Sélecteurs de couleurs min/max sur une ligne
|
||||||
|
- Barre de dégradé visuelle avec interpolation HSL
|
||||||
|
- Exemples de notes positionnés sur le dégradé (5/20, 10/20, 15/20)
|
||||||
|
- Sauvegarde intégrée au formulaire principal
|
||||||
|
|
||||||
|
### 2. Scores (Competence Grading)
|
||||||
|
|
||||||
|
**Caractéristiques :**
|
||||||
|
- Échelle fixe et non configurable : **0, 1, 2, 3**
|
||||||
|
- Signification par défaut :
|
||||||
|
- **0** : Non acquis (rouge #dc2626)
|
||||||
|
- **1** : En cours d'acquisition (orange #ea580c)
|
||||||
|
- **2** : Acquis (vert #059669)
|
||||||
|
- **3** : Expert (bleu #2563eb)
|
||||||
|
- Chaque niveau peut être personnalisé (libellé, couleur, inclusion)
|
||||||
|
|
||||||
|
**Configuration :**
|
||||||
|
- Labels modifiables via interface
|
||||||
|
- Couleurs personnalisables avec sélecteurs
|
||||||
|
- Option d'inclusion/exclusion du calcul global
|
||||||
|
- Valeurs 0-3 dans la section "Échelle numérique"
|
||||||
|
|
||||||
|
### 3. Valeurs Spéciales
|
||||||
|
|
||||||
|
**Valeurs Prédéfinies :**
|
||||||
|
- **"."** : Non évalué (par défaut gris #6b7280)
|
||||||
|
- Compte comme 0 dans les notes mais inclus dans le total possible
|
||||||
|
- Généralement incluse pour maintenir la cohérence des calculs
|
||||||
|
- **"d"** : Dispensé (configurable)
|
||||||
|
- Exclu des calculs par défaut
|
||||||
|
|
||||||
|
**Valeurs Personnalisées :**
|
||||||
|
- Ajout via interface : "NA", "ABS", "X", etc.
|
||||||
|
- Configuration complète : valeur, libellé, couleur, inclusion
|
||||||
|
- Suppression possible (sauf valeurs de base)
|
||||||
|
|
||||||
|
## Interface de Configuration
|
||||||
|
|
||||||
|
### Page `/config/scale` - "Échelle de réussite"
|
||||||
|
|
||||||
|
**Structure :**
|
||||||
|
1. **Dégradé de couleurs pour les notes**
|
||||||
|
- Sélecteur couleur minimum (gauche)
|
||||||
|
- Barre de dégradé visuelle (centre, pleine largeur)
|
||||||
|
- Sélecteur couleur maximum (droite)
|
||||||
|
- Exemples positionnés sur le dégradé
|
||||||
|
|
||||||
|
2. **Échelle numérique (0 à 3)**
|
||||||
|
- Configuration des 4 niveaux fixes
|
||||||
|
- Libellé, couleur et inclusion pour chaque niveau
|
||||||
|
- Interface en grille responsive
|
||||||
|
|
||||||
|
3. **Valeurs spéciales**
|
||||||
|
- Liste des valeurs non-numériques
|
||||||
|
- Ajout/modification/suppression
|
||||||
|
- Protection de la valeur "." (non supprimable)
|
||||||
|
|
||||||
|
**Sauvegarde Unifiée :**
|
||||||
|
- Bouton unique : "Enregistrer les paramètres"
|
||||||
|
- Sauvegarde simultanée : échelle + dégradé + valeurs spéciales
|
||||||
|
- Messages de succès différenciés selon les modifications
|
||||||
|
|
||||||
|
## Utilisation dans le Code
|
||||||
|
|
||||||
|
### Calcul des Couleurs de Notes
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Côté serveur : Transmission de la configuration
|
||||||
|
notes_gradient = {
|
||||||
|
'min_color': config_manager.get('grading.notes_gradient.min_color', '#dc2626'),
|
||||||
|
'max_color': config_manager.get('grading.notes_gradient.max_color', '#059669'),
|
||||||
|
'enabled': config_manager.get('grading.notes_gradient.enabled', False)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Côté client : Calcul dynamique
|
||||||
|
window.getNotesGradientColor = function(note, maxPoints) {
|
||||||
|
const minColor = '{{ notes_gradient.min_color }}';
|
||||||
|
const maxColor = '{{ notes_gradient.max_color }}';
|
||||||
|
const enabled = {{ notes_gradient.enabled|tojson }};
|
||||||
|
|
||||||
|
if (!enabled) return '#6b7280';
|
||||||
|
return calculateNoteColor(note, maxPoints, minColor, maxColor);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accès aux Valeurs d'Échelle
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Repository pattern pour l'accès aux données
|
||||||
|
competence_scale = config_manager.get_competence_scale_values()
|
||||||
|
|
||||||
|
# Structure retournée
|
||||||
|
{
|
||||||
|
'0': {'label': 'Non acquis', 'color': '#dc2626', 'included_in_total': True},
|
||||||
|
'1': {'label': 'En cours', 'color': '#ea580c', 'included_in_total': True},
|
||||||
|
'2': {'label': 'Acquis', 'color': '#059669', 'included_in_total': True},
|
||||||
|
'3': {'label': 'Expert', 'color': '#2563eb', 'included_in_total': True},
|
||||||
|
'.': {'label': 'Non évalué', 'color': '#6b7280', 'included_in_total': True}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interpolation des Couleurs
|
||||||
|
|
||||||
|
### Problématique RGB vs HSL
|
||||||
|
|
||||||
|
**RGB (Ancien système) :**
|
||||||
|
- Interpolation linéaire entre composantes R, G, B
|
||||||
|
- Problème : transitions via des couleurs "sales" (gris/marron)
|
||||||
|
- Exemple : Rouge → Vert passe par un gris terne
|
||||||
|
|
||||||
|
**HSL (Nouveau système) :**
|
||||||
|
- Interpolation dans l'espace teinte-saturation-luminosité
|
||||||
|
- Transitions naturelles via le cercle chromatique
|
||||||
|
- Gestion du "chemin le plus court" pour les teintes
|
||||||
|
- Résultat : dégradés vibrants et naturels
|
||||||
|
|
||||||
|
### Implementation JavaScript
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function interpolateColorHSL(color1, color2, factor) {
|
||||||
|
const rgb1 = hexToRgb(color1);
|
||||||
|
const rgb2 = hexToRgb(color2);
|
||||||
|
|
||||||
|
const hsl1 = rgbToHsl(rgb1.r, rgb1.g, rgb1.b);
|
||||||
|
const hsl2 = rgbToHsl(rgb2.r, rgb2.g, rgb2.b);
|
||||||
|
|
||||||
|
// Gérer transition teinte (chemin le plus court)
|
||||||
|
let deltaH = hsl2.h - hsl1.h;
|
||||||
|
if (deltaH > 180) hsl2.h -= 360;
|
||||||
|
else if (deltaH < -180) hsl2.h += 360;
|
||||||
|
|
||||||
|
// Interpolation HSL
|
||||||
|
const h = hsl1.h + (hsl2.h - hsl1.h) * factor;
|
||||||
|
const s = hsl1.s + (hsl2.s - hsl1.s) * factor;
|
||||||
|
const l = hsl1.l + (hsl2.l - hsl1.l) * factor;
|
||||||
|
|
||||||
|
const rgb = hslToRgb(h, s, l);
|
||||||
|
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routes et Endpoints
|
||||||
|
|
||||||
|
### Configuration des Échelles
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Routes principales
|
||||||
|
GET /config/scale # Interface de configuration
|
||||||
|
POST /config/scale/update # Sauvegarde unifiée (échelle + dégradé)
|
||||||
|
POST /config/scale/add # Ajouter valeur spéciale
|
||||||
|
POST /config/scale/delete/<val> # Supprimer valeur spéciale
|
||||||
|
POST /config/scale/reset # Réinitialisation par défaut
|
||||||
|
|
||||||
|
# Route dépréciée (fusionnée dans update_scale)
|
||||||
|
POST /config/scale/notes-gradient # Ancienne sauvegarde séparée
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation des Données
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Validation couleurs hexadécimales
|
||||||
|
if not re.match(r'^#[0-9a-fA-F]{6}$', color):
|
||||||
|
flash('Format de couleur invalide', 'error')
|
||||||
|
|
||||||
|
# Protection valeurs de base
|
||||||
|
base_values = ['0', '1', '2', '3', '.']
|
||||||
|
if value in base_values:
|
||||||
|
flash('Valeur de base non supprimable', 'error')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Evolution et Améliorations
|
||||||
|
|
||||||
|
### Phase Actuelle (2025)
|
||||||
|
|
||||||
|
✅ **Réalisé :**
|
||||||
|
- Système de dégradé HSL pour les notes
|
||||||
|
- Interface unifiée de configuration
|
||||||
|
- Sauvegarde intégrée échelle + dégradé
|
||||||
|
- Exemples visuels positionnés sur le dégradé
|
||||||
|
- Validation et protection des données
|
||||||
|
|
||||||
|
### Évolutions Possibles
|
||||||
|
|
||||||
|
🔮 **Futures améliorations :**
|
||||||
|
- Export/import de configurations d'échelles
|
||||||
|
- Présets de couleurs (thèmes prédéfinis)
|
||||||
|
- Aperçu temps réel sur vraies données
|
||||||
|
- Historique des modifications
|
||||||
|
- Configuration par classe/matière
|
||||||
|
- API REST pour configuration programmatique
|
||||||
|
|
||||||
|
## Cas d'Usage Typiques
|
||||||
|
|
||||||
|
### Enseignant Mathématiques
|
||||||
|
```
|
||||||
|
Échelle 0-3 : Non acquis → En cours → Acquis → Expert
|
||||||
|
Dégradé notes : Rouge foncé → Vert clair (sur /20)
|
||||||
|
Valeurs spéciales : "." pour absents, "d" pour dispensés sport
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enseignant Langues
|
||||||
|
```
|
||||||
|
Échelle 0-3 : A découvrir → En apprentissage → Maîtrisé → Excellence
|
||||||
|
Dégradé notes : Orange → Bleu (évaluations /10)
|
||||||
|
Valeurs spéciales : "NA" pour non applicable, "O" pour oral seulement
|
||||||
|
```
|
||||||
|
|
||||||
|
## Impact UX
|
||||||
|
|
||||||
|
### Bénéfices Utilisateur
|
||||||
|
- **Cohérence visuelle** : Couleurs automatiques selon performance
|
||||||
|
- **Simplicité** : Un seul bouton de sauvegarde
|
||||||
|
- **Feedback immédiat** : Prévisualisation temps réel
|
||||||
|
- **Flexibilité** : Adaptation aux pratiques pédagogiques
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- **Client** : Calculs JavaScript optimisés
|
||||||
|
- **Serveur** : Configuration mise en cache
|
||||||
|
- **Base** : Index sur valeurs d'échelle
|
||||||
|
- **Rendu** : Templates précompilés
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Documentation maintenue à jour - Version 2025**
|
||||||
|
*Dernière modification : Janvier 2025*
|
||||||
@@ -149,14 +149,17 @@ notytex/
|
|||||||
- ✅ **Progress Tracking** : Suivi de progression des corrections
|
- ✅ **Progress Tracking** : Suivi de progression des corrections
|
||||||
- ✅ **Statistics** : Analyses statistiques des résultats
|
- ✅ **Statistics** : Analyses statistiques des résultats
|
||||||
|
|
||||||
### **Configuration System (Existant)**
|
### **Configuration System (✅ Complet)**
|
||||||
|
|
||||||
**Responsabilité** : Gestion configuration dynamique application
|
**Responsabilité** : Gestion configuration dynamique application
|
||||||
|
|
||||||
- ✅ **Dynamic Settings** : Configuration runtime modifiable
|
- ✅ **Dynamic Settings** : Configuration runtime modifiable
|
||||||
- ✅ **Feature Flags** : Activation/désactivation fonctionnalités
|
- ✅ **Scales Management** : Échelles de notation configurables (0-3 + spéciales)
|
||||||
|
- ✅ **Color Gradients** : Système dégradé couleurs notes avec HSL
|
||||||
- ✅ **Business Rules** : Règles métier configurables
|
- ✅ **Business Rules** : Règles métier configurables
|
||||||
- ✅ **Multi-tenancy** : Support configuration par établissement
|
- ✅ **Unified Interface** : Interface de configuration unifiée
|
||||||
|
|
||||||
|
**Documentation** : [../CONFIGURATION_SCALES.md](../CONFIGURATION_SCALES.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -394,13 +397,13 @@ sqlite3 instance/school_management.db
|
|||||||
### **✅ Documenté (100%)**
|
### **✅ Documenté (100%)**
|
||||||
- Système CRUD Classes (complet avec exemples)
|
- Système CRUD Classes (complet avec exemples)
|
||||||
- Repository Pattern ClassGroup (architecture complète)
|
- Repository Pattern ClassGroup (architecture complète)
|
||||||
|
- **Système d'échelles et dégradés** (notes, scores, valeurs spéciales)
|
||||||
- Architecture générale et patterns
|
- Architecture générale et patterns
|
||||||
- Standards de sécurité et validation
|
- Standards de sécurité et validation
|
||||||
|
|
||||||
### **🔄 En cours (20-80%)**
|
### **🔄 En cours (20-80%)**
|
||||||
- Assessment Services (code existant, doc à faire)
|
- Assessment Services (code existant, doc à faire)
|
||||||
- Configuration System (code existant, doc à faire)
|
- Configuration System général (code existant, doc à faire)
|
||||||
- Grading System (code existant, doc à faire)
|
|
||||||
|
|
||||||
### **📋 À faire**
|
### **📋 À faire**
|
||||||
- Repository Pattern guide complet
|
- Repository Pattern guide complet
|
||||||
|
|||||||
@@ -179,7 +179,17 @@ def scale():
|
|||||||
"""Page de configuration de l'échelle de réussite."""
|
"""Page de configuration de l'échelle de réussite."""
|
||||||
try:
|
try:
|
||||||
competence_scale = config_manager.get_competence_scale_values()
|
competence_scale = config_manager.get_competence_scale_values()
|
||||||
return render_template('config/scale.html', competence_scale=competence_scale)
|
|
||||||
|
# Récupérer la configuration du dégradé des notes
|
||||||
|
notes_gradient = {
|
||||||
|
'min_color': config_manager.get('grading.notes_gradient.min_color', '#dc2626'),
|
||||||
|
'max_color': config_manager.get('grading.notes_gradient.max_color', '#059669'),
|
||||||
|
'enabled': config_manager.get('grading.notes_gradient.enabled', False)
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template('config/scale.html',
|
||||||
|
competence_scale=competence_scale,
|
||||||
|
notes_gradient=notes_gradient)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return handle_error(e, "Erreur lors du chargement de l'échelle")
|
return handle_error(e, "Erreur lors du chargement de l'échelle")
|
||||||
|
|
||||||
@@ -205,8 +215,19 @@ def update_scale():
|
|||||||
|
|
||||||
scale_data[value][field] = form_value
|
scale_data[value][field] = form_value
|
||||||
|
|
||||||
|
# Récupérer aussi les paramètres du dégradé de couleurs des notes
|
||||||
|
notes_gradient_min = request.form.get('notes_gradient_color_min')
|
||||||
|
notes_gradient_max = request.form.get('notes_gradient_color_max')
|
||||||
|
|
||||||
# Validation des données
|
# Validation des données
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
# Validation des couleurs du dégradé si présentes
|
||||||
|
if notes_gradient_min and notes_gradient_max:
|
||||||
|
if not re.match(r'^#[0-9a-fA-F]{6}$', notes_gradient_min) or not re.match(r'^#[0-9a-fA-F]{6}$', notes_gradient_max):
|
||||||
|
flash('Format de couleur invalide pour le dégradé', 'error')
|
||||||
|
return redirect(url_for('config.scale'))
|
||||||
|
|
||||||
for value, config in scale_data.items():
|
for value, config in scale_data.items():
|
||||||
# Vérifier que tous les champs requis sont présents
|
# Vérifier que tous les champs requis sont présents
|
||||||
if not all(field in config for field in ['label', 'color', 'included_in_total']):
|
if not all(field in config for field in ['label', 'color', 'included_in_total']):
|
||||||
@@ -234,8 +255,22 @@ def update_scale():
|
|||||||
):
|
):
|
||||||
success_count += 1
|
success_count += 1
|
||||||
|
|
||||||
if success_count == len(scale_data):
|
# Sauvegarder le dégradé des notes si présent
|
||||||
|
gradient_saved = True
|
||||||
|
if notes_gradient_min and notes_gradient_max:
|
||||||
|
config_manager.set('grading.notes_gradient.min_color', notes_gradient_min)
|
||||||
|
config_manager.set('grading.notes_gradient.max_color', notes_gradient_max)
|
||||||
|
config_manager.set('grading.notes_gradient.enabled', True)
|
||||||
|
gradient_saved = config_manager.save()
|
||||||
|
|
||||||
|
# Messages de succès
|
||||||
|
if success_count == len(scale_data) and gradient_saved:
|
||||||
|
if notes_gradient_min and notes_gradient_max:
|
||||||
|
flash('Paramètres mis à jour avec succès (échelle et dégradé)', 'success')
|
||||||
|
else:
|
||||||
flash('Échelle de réussite mise à jour avec succès', 'success')
|
flash('Échelle de réussite mise à jour avec succès', 'success')
|
||||||
|
elif success_count == len(scale_data) and not gradient_saved:
|
||||||
|
flash('Échelle mise à jour mais erreur sauvegarde dégradé', 'warning')
|
||||||
else:
|
else:
|
||||||
flash(f'Mise à jour partielle : {success_count}/{len(scale_data)} valeurs mises à jour', 'warning')
|
flash(f'Mise à jour partielle : {success_count}/{len(scale_data)} valeurs mises à jour', 'warning')
|
||||||
|
|
||||||
@@ -337,6 +372,36 @@ def reset_scale():
|
|||||||
|
|
||||||
return redirect(url_for('config.scale'))
|
return redirect(url_for('config.scale'))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/scale/notes-gradient', methods=['POST'])
|
||||||
|
def save_notes_gradient():
|
||||||
|
"""Sauvegarder la configuration du dégradé de couleurs pour les notes."""
|
||||||
|
try:
|
||||||
|
min_color = request.form.get('notes_gradient_min_color', '#dc2626')
|
||||||
|
max_color = request.form.get('notes_gradient_max_color', '#059669')
|
||||||
|
|
||||||
|
# Validation des couleurs hexadécimales
|
||||||
|
import re
|
||||||
|
if not re.match(r'^#[0-9a-fA-F]{6}$', min_color) or not re.match(r'^#[0-9a-fA-F]{6}$', max_color):
|
||||||
|
flash('Format de couleur invalide', 'error')
|
||||||
|
return redirect(url_for('config.scale'))
|
||||||
|
|
||||||
|
# Sauvegarder dans la configuration
|
||||||
|
config_manager.set('grading.notes_gradient.min_color', min_color)
|
||||||
|
config_manager.set('grading.notes_gradient.max_color', max_color)
|
||||||
|
config_manager.set('grading.notes_gradient.enabled', True)
|
||||||
|
|
||||||
|
if config_manager.save():
|
||||||
|
flash('Configuration du dégradé de couleurs des notes sauvegardée avec succès', 'success')
|
||||||
|
else:
|
||||||
|
flash('Erreur lors de la sauvegarde', 'error')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erreur sauvegarde dégradé notes: {e}")
|
||||||
|
flash('Erreur lors de la sauvegarde du dégradé', 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('config.scale'))
|
||||||
|
|
||||||
@bp.route('/general')
|
@bp.route('/general')
|
||||||
def general():
|
def general():
|
||||||
"""Page de configuration générale."""
|
"""Page de configuration générale."""
|
||||||
|
|||||||
@@ -69,41 +69,126 @@
|
|||||||
|
|
||||||
<form method="POST" action="{{ url_for('config.update_scale') }}" class="p-6">
|
<form method="POST" action="{{ url_for('config.update_scale') }}" class="p-6">
|
||||||
|
|
||||||
|
<!-- Configuration du dégradé de couleurs pour les notes -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h4 class="text-lg font-medium text-gray-900">🎨 Dégradé de couleurs pour les notes</h4>
|
||||||
|
<p class="text-sm text-gray-500 mt-1">Définissez les couleurs min et max, le dégradé sera calculé automatiquement pour toutes les valeurs intermédiaires</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Interface sur une ligne : Min - Dégradé - Max -->
|
||||||
|
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
||||||
|
<div class="flex items-center justify-between space-x-6">
|
||||||
|
|
||||||
|
<!-- Couleur minimum (0) -->
|
||||||
|
<div class="flex flex-col items-center space-y-3 min-w-0">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-sm font-medium text-gray-700 mb-1">Note 0</div>
|
||||||
|
<div class="text-xs text-gray-500">Échec</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center space-y-2">
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="notes_gradient_color_min"
|
||||||
|
name="notes_gradient_color_min"
|
||||||
|
value="{{ notes_gradient.min_color }}"
|
||||||
|
class="w-16 h-16 border-2 border-gray-300 rounded-lg cursor-pointer shadow-sm hover:border-gray-400"
|
||||||
|
onchange="updateNotesGradientPreview()"
|
||||||
|
title="Couleur pour note 0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="notes_gradient_color_min_text"
|
||||||
|
value="{{ notes_gradient.min_color }}"
|
||||||
|
class="w-20 px-2 py-1 text-xs text-center border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-purple-500"
|
||||||
|
pattern="#[0-9a-fA-F]{6}"
|
||||||
|
onchange="syncNotesGradientColor('min')"
|
||||||
|
title="Code couleur hexadécimal"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dégradé visuel -->
|
||||||
|
<div class="flex-1 flex flex-col space-y-3">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-sm font-medium text-gray-700 mb-1">Dégradé automatique</div>
|
||||||
|
<div class="text-xs text-gray-500">Exemples de notes</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Barre de dégradé -->
|
||||||
|
<div class="w-full">
|
||||||
|
<div id="gradient-bar" class="h-8 rounded-lg shadow-inner bg-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Exemples de notes positionnés sur le dégradé -->
|
||||||
|
<div class="relative w-full">
|
||||||
|
<!-- Note 5/20 à 25% -->
|
||||||
|
<div class="absolute flex flex-col items-center" style="left: 25%; transform: translateX(-50%);">
|
||||||
|
<div id="notes-preview-25" class="w-8 h-8 rounded border-2 border-white shadow-sm flex items-center justify-center text-white text-xs font-bold">5</div>
|
||||||
|
<span class="text-xs text-gray-600 mt-1">5/20</span>
|
||||||
|
</div>
|
||||||
|
<!-- Note 10/20 à 50% -->
|
||||||
|
<div class="absolute flex flex-col items-center" style="left: 50%; transform: translateX(-50%);">
|
||||||
|
<div id="notes-preview-50" class="w-8 h-8 rounded border-2 border-white shadow-sm flex items-center justify-center text-white text-xs font-bold">10</div>
|
||||||
|
<span class="text-xs text-gray-600 mt-1">10/20</span>
|
||||||
|
</div>
|
||||||
|
<!-- Note 15/20 à 75% -->
|
||||||
|
<div class="absolute flex flex-col items-center" style="left: 75%; transform: translateX(-50%);">
|
||||||
|
<div id="notes-preview-75" class="w-8 h-8 rounded border-2 border-white shadow-sm flex items-center justify-center text-white text-xs font-bold">15</div>
|
||||||
|
<span class="text-xs text-gray-600 mt-1">15/20</span>
|
||||||
|
</div>
|
||||||
|
<!-- Espacement pour les labels -->
|
||||||
|
<div class="h-12"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Couleur maximum (max) -->
|
||||||
|
<div class="flex flex-col items-center space-y-3 min-w-0">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-sm font-medium text-gray-700 mb-1">Note max</div>
|
||||||
|
<div class="text-xs text-gray-500">Réussite</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center space-y-2">
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="notes_gradient_color_max"
|
||||||
|
name="notes_gradient_color_max"
|
||||||
|
value="{{ notes_gradient.max_color }}"
|
||||||
|
class="w-16 h-16 border-2 border-gray-300 rounded-lg cursor-pointer shadow-sm hover:border-gray-400"
|
||||||
|
onchange="updateNotesGradientPreview()"
|
||||||
|
title="Couleur pour note maximale"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="notes_gradient_color_max_text"
|
||||||
|
value="{{ notes_gradient.max_color }}"
|
||||||
|
class="w-20 px-2 py-1 text-xs text-center border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-purple-500"
|
||||||
|
pattern="#[0-9a-fA-F]{6}"
|
||||||
|
onchange="syncNotesGradientColor('max')"
|
||||||
|
title="Code couleur hexadécimal"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Échelle numérique -->
|
<!-- Échelle numérique -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h4 class="text-lg font-medium text-gray-900">🔢 Échelle numérique</h4>
|
<h4 class="text-lg font-medium text-gray-900">🔢 Échelle numérique (0 à 3)</h4>
|
||||||
|
|
||||||
<!-- Contrôle d'étendue -->
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<label for="scale_max" class="text-sm font-medium text-gray-700">
|
|
||||||
Étendue :
|
|
||||||
</label>
|
|
||||||
<select id="scale_max" onchange="updateScaleRange()" class="px-3 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500">
|
|
||||||
{% set current_max = 3 %}
|
|
||||||
{% for value in competence_scale.keys() %}
|
|
||||||
{% if value in [0, 1, 2, 3, 4, 5] %}
|
|
||||||
{% if value > current_max %}
|
|
||||||
{% set current_max = value %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% for i in range(2, 6) %}
|
|
||||||
<option value="{{ i }}" {% if i == current_max %}selected{% endif %}>0 à {{ i }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
<p class="text-sm text-gray-600">
|
<p class="text-sm text-gray-600">
|
||||||
Configurez chaque niveau de l'échelle numérique. L'étendue actuelle va de 0 à {{ current_max }}.
|
Configurez chaque niveau de l'échelle numérique fixe de 0 à 3.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="numeric-scale" class="space-y-4">
|
<div id="numeric-scale" class="space-y-4">
|
||||||
{% for value, config in competence_scale.items() %}
|
{% for value, config in competence_scale.items() %}
|
||||||
{% if value in [0, 1, 2, 3, 4, 5] %}
|
{% if value in ['0', '1', '2', '3'] %}
|
||||||
<div class="border border-gray-200 rounded-lg p-4" data-numeric-value="{{ value }}">
|
<div class="border border-gray-200 rounded-lg p-4" data-numeric-value="{{ value }}">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
@@ -203,7 +288,7 @@
|
|||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{% for value, config in competence_scale.items() %}
|
{% for value, config in competence_scale.items() %}
|
||||||
{% if value not in [0, 1, 2, 3, 4, 5] %}
|
{% if value not in ['0', '1', '2', '3'] %}
|
||||||
<div class="border border-gray-200 rounded-lg p-4">
|
<div class="border border-gray-200 rounded-lg p-4">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
@@ -340,7 +425,7 @@
|
|||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
Enregistrer l'échelle
|
Enregistrer les paramètres
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -366,28 +451,256 @@ document.getElementById('scale_{{ value }}_color').addEventListener('input', fun
|
|||||||
});
|
});
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
// Mettre à jour l'étendue de l'échelle
|
// === GESTION DU DÉGRADÉ DE COULEURS POUR LES NOTES ===
|
||||||
function updateScaleRange() {
|
|
||||||
const newMax = parseInt(document.getElementById('scale_max').value);
|
|
||||||
const currentElements = document.querySelectorAll('#numeric-scale [data-numeric-value]');
|
|
||||||
|
|
||||||
// Masquer les éléments au-dessus du nouveau maximum
|
// Synchroniser les champs de couleur du dégradé des notes
|
||||||
currentElements.forEach(element => {
|
function syncNotesGradientColor(type) {
|
||||||
const value = parseInt(element.getAttribute('data-numeric-value'));
|
const colorPicker = document.getElementById(`notes_gradient_color_${type}`);
|
||||||
if (value > newMax) {
|
const colorText = document.getElementById(`notes_gradient_color_${type}_text`);
|
||||||
element.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
element.style.display = 'block';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cette fonction pourrait être étendue pour créer dynamiquement de nouveaux éléments
|
if (colorText.value.match(/^#[0-9a-fA-F]{6}$/)) {
|
||||||
if (confirm(`Changer l'étendue à 0-${newMax} ? Cela nécessite une sauvegarde pour créer les nouveaux niveaux.`)) {
|
colorPicker.value = colorText.value;
|
||||||
// Vous pourriez ajouter ici une requête AJAX pour créer les nouveaux niveaux
|
updateNotesGradientPreview();
|
||||||
alert('Fonctionnalité à implémenter : création dynamique des nouveaux niveaux');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synchroniser les color pickers avec leurs champs texte
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const minPicker = document.getElementById('notes_gradient_color_min');
|
||||||
|
const maxPicker = document.getElementById('notes_gradient_color_max');
|
||||||
|
|
||||||
|
if (minPicker) {
|
||||||
|
minPicker.addEventListener('input', function() {
|
||||||
|
document.getElementById('notes_gradient_color_min_text').value = this.value;
|
||||||
|
updateNotesGradientPreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxPicker) {
|
||||||
|
maxPicker.addEventListener('input', function() {
|
||||||
|
document.getElementById('notes_gradient_color_max_text').value = this.value;
|
||||||
|
updateNotesGradientPreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser la prévisualisation au chargement
|
||||||
|
updateNotesGradientPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convertir une couleur hex en RGB
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result ? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
} : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir RGB en hex
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir RGB vers HSL
|
||||||
|
function rgbToHsl(r, g, b) {
|
||||||
|
r /= 255;
|
||||||
|
g /= 255;
|
||||||
|
b /= 255;
|
||||||
|
|
||||||
|
const max = Math.max(r, g, b);
|
||||||
|
const min = Math.min(r, g, b);
|
||||||
|
let h, s, l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
h = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
const d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: h = (b - r) / d + 2; break;
|
||||||
|
case b: h = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { h: h * 360, s: s * 100, l: l * 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir HSL vers RGB
|
||||||
|
function hslToRgb(h, s, l) {
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
|
||||||
|
const hue2rgb = (p, q, t) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1/2) return q;
|
||||||
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
let r, g, b;
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1/3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1/3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: Math.round(r * 255),
|
||||||
|
g: Math.round(g * 255),
|
||||||
|
b: Math.round(b * 255)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpoler entre deux couleurs en HSL pour un rendu plus naturel
|
||||||
|
function interpolateColorHSL(color1, color2, factor) {
|
||||||
|
const rgb1 = hexToRgb(color1);
|
||||||
|
const rgb2 = hexToRgb(color2);
|
||||||
|
|
||||||
|
if (!rgb1 || !rgb2) return color1;
|
||||||
|
|
||||||
|
const hsl1 = rgbToHsl(rgb1.r, rgb1.g, rgb1.b);
|
||||||
|
const hsl2 = rgbToHsl(rgb2.r, rgb2.g, rgb2.b);
|
||||||
|
|
||||||
|
// Interpolation en HSL
|
||||||
|
let h1 = hsl1.h;
|
||||||
|
let h2 = hsl2.h;
|
||||||
|
|
||||||
|
// Gérer la transition des teintes (éviter le long chemin autour du cercle)
|
||||||
|
let deltaH = h2 - h1;
|
||||||
|
if (deltaH > 180) {
|
||||||
|
h2 -= 360;
|
||||||
|
} else if (deltaH < -180) {
|
||||||
|
h2 += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
const h = h1 + (h2 - h1) * factor;
|
||||||
|
const s = hsl1.s + (hsl2.s - hsl1.s) * factor;
|
||||||
|
const l = hsl1.l + (hsl2.l - hsl1.l) * factor;
|
||||||
|
|
||||||
|
// Normaliser la teinte
|
||||||
|
const normalizedH = ((h % 360) + 360) % 360;
|
||||||
|
|
||||||
|
const rgb = hslToRgb(normalizedH, s, l);
|
||||||
|
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction de compatibilité (utilise maintenant HSL)
|
||||||
|
function interpolateColor(color1, color2, factor) {
|
||||||
|
return interpolateColorHSL(color1, color2, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculer la couleur d'une note selon sa valeur relative
|
||||||
|
function calculateNoteColor(note, maxPoints, minColor, maxColor) {
|
||||||
|
if (!minColor || !maxColor || maxPoints <= 0) return '#6b7280'; // Gris par défaut
|
||||||
|
|
||||||
|
const factor = Math.max(0, Math.min(1, note / maxPoints)); // Borner entre 0 et 1
|
||||||
|
return interpolateColor(minColor, maxColor, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour la prévisualisation du dégradé des notes
|
||||||
|
function updateNotesGradientPreview() {
|
||||||
|
const minColor = document.getElementById('notes_gradient_color_min')?.value;
|
||||||
|
const maxColor = document.getElementById('notes_gradient_color_max')?.value;
|
||||||
|
|
||||||
|
if (!minColor || !maxColor) return;
|
||||||
|
|
||||||
|
// Créer une barre de dégradé avec plusieurs stops HSL pour éviter le gris CSS
|
||||||
|
const gradientBar = document.getElementById('gradient-bar');
|
||||||
|
if (gradientBar) {
|
||||||
|
// Créer 10 stops pour un dégradé fluide en HSL
|
||||||
|
const stops = [];
|
||||||
|
for (let i = 0; i <= 10; i++) {
|
||||||
|
const factor = i / 10;
|
||||||
|
const color = interpolateColorHSL(minColor, maxColor, factor);
|
||||||
|
stops.push(`${color} ${factor * 100}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
gradientBar.style.background = `linear-gradient(to right, ${stops.join(', ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exemples sur 20 points (25%, 50%, 75% seulement car 0% et 100% sont déjà visibles)
|
||||||
|
const examples = [
|
||||||
|
{ percent: 0.25, note: 5, id: 'notes-preview-25' },
|
||||||
|
{ percent: 0.5, note: 10, id: 'notes-preview-50' },
|
||||||
|
{ percent: 0.75, note: 15, id: 'notes-preview-75' }
|
||||||
|
];
|
||||||
|
|
||||||
|
examples.forEach(example => {
|
||||||
|
const preview = document.getElementById(example.id);
|
||||||
|
if (preview) {
|
||||||
|
const color = interpolateColorHSL(minColor, maxColor, example.percent);
|
||||||
|
preview.style.backgroundColor = color;
|
||||||
|
|
||||||
|
// Ajuster la couleur du texte pour la lisibilité
|
||||||
|
const rgb = hexToRgb(color);
|
||||||
|
if (rgb) {
|
||||||
|
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||||||
|
preview.style.color = brightness > 128 ? '#000000' : '#ffffff';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder la configuration du dégradé des notes
|
||||||
|
function saveNotesGradientConfig() {
|
||||||
|
const minColor = document.getElementById('notes_gradient_color_min').value;
|
||||||
|
const maxColor = document.getElementById('notes_gradient_color_max').value;
|
||||||
|
|
||||||
|
if (!minColor || !maxColor) {
|
||||||
|
alert('Veuillez sélectionner les couleurs minimum et maximum');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Sauvegarder cette configuration de dégradé pour les notes ?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer un formulaire pour envoyer les données
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.method = 'POST';
|
||||||
|
form.action = '{{ url_for("config.save_notes_gradient") }}';
|
||||||
|
|
||||||
|
const minInput = document.createElement('input');
|
||||||
|
minInput.type = 'hidden';
|
||||||
|
minInput.name = 'notes_gradient_min_color';
|
||||||
|
minInput.value = minColor;
|
||||||
|
|
||||||
|
const maxInput = document.createElement('input');
|
||||||
|
maxInput.type = 'hidden';
|
||||||
|
maxInput.name = 'notes_gradient_max_color';
|
||||||
|
maxInput.value = maxColor;
|
||||||
|
|
||||||
|
form.appendChild(minInput);
|
||||||
|
form.appendChild(maxInput);
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction globale pour calculer la couleur d'une note (utilisable ailleurs)
|
||||||
|
window.getNotesGradientColor = function(note, maxPoints) {
|
||||||
|
// Récupérer les couleurs configurées
|
||||||
|
const minColor = '{{ notes_gradient.min_color }}';
|
||||||
|
const maxColor = '{{ notes_gradient.max_color }}';
|
||||||
|
const enabled = {{ notes_gradient.enabled|tojson }};
|
||||||
|
|
||||||
|
// Si le dégradé n'est pas activé, retourner une couleur neutre
|
||||||
|
if (!enabled) return '#6b7280';
|
||||||
|
|
||||||
|
return calculateNoteColor(note, maxPoints, minColor, maxColor);
|
||||||
|
};
|
||||||
|
|
||||||
// Réinitialiser aux valeurs par défaut
|
// Réinitialiser aux valeurs par défaut
|
||||||
function resetToDefaults() {
|
function resetToDefaults() {
|
||||||
if (confirm('Êtes-vous sûr de vouloir restaurer l\'échelle par défaut ? Toutes vos modifications seront perdues.')) {
|
if (confirm('Êtes-vous sûr de vouloir restaurer l\'échelle par défaut ? Toutes vos modifications seront perdues.')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user