feat: add commentary in concil prep

This commit is contained in:
2025-08-15 07:59:23 +02:00
parent c3ef5287b3
commit f438082c4c
4 changed files with 656 additions and 5 deletions

View File

@@ -33,6 +33,26 @@ class CouncilPreparationService:
class StudentEvaluationService:
def get_students_summaries(class_id, trimester) -> List[StudentTrimesterSummary]
def calculate_student_trimester_average(student_id, trimester) -> float
# Nouvelles méthodes 2025
def get_student_special_values_summary(student_id, trimester) -> Dict[str, Any]
def get_student_competence_domain_breakdown(student_id, trimester) -> Dict[str, List[Dict]]
```
#### GradeRepository (Nouvelles méthodes 2025)
```python
# Repository étendu pour valeurs spéciales et commentaires
class GradeRepository:
# Méthodes existantes...
# Nouvelles méthodes pour valeurs spéciales
def get_special_values_counts_by_student_trimester(student_id, trimester) -> Dict[str, int]
def get_special_values_counts_by_student_assessment(student_id, assessment_id) -> Dict[str, int]
def get_special_values_details_by_student_trimester(student_id, trimester) -> Dict[str, List[Dict]]
def get_special_values_details_by_student_assessment(student_id, assessment_id) -> Dict[str, List[Dict]]
# Nouvelle méthode pour commentaires organisés
def get_all_comments_by_student_trimester(student_id, trimester) -> Dict[str, Any]
```
#### AppreciationService
@@ -55,6 +75,56 @@ class StudentTrimesterSummary:
grades_by_assessment: Dict[int, Dict] # {assessment_id: {score, max, title}}
appreciation: Optional[CouncilAppreciation]
performance_status: str # 'excellent', 'good', 'average', 'struggling'
competence_domain_breakdown: Optional[Dict] = None # Données compétences/domaines
special_values_summary: Optional[Dict] = None # Nouvelle: résumé valeurs spéciales et commentaires
```
#### Structure special_values_summary (Nouveau 2025)
```python
special_values_summary = {
# Résumé global des valeurs spéciales pour le trimestre
"global": {
".": {"count": 2, "label": "Pas de réponse", "color": "#ef4444", "details": [...]},
"d": {"count": 1, "label": "Dispensé", "color": "#6b7280", "details": [...]},
"a": {"count": 1, "label": "Absent", "color": "#f59e0b", "details": [...]}
},
# Détail par évaluation
"by_assessment": {
1: {
"title": "Contrôle Chapitre 1",
"date": "2025-08-10",
"special_values": {
".": {"count": 1, "label": "Pas de réponse", "details": [...]}
}
}
},
# Commentaires organisés par évaluations (Nouveau)
"comments_by_assessments": {
"assessments": [
{
"id": 1,
"title": "Contrôle Chapitre 1 - Nombres entiers",
"date": "2025-08-10",
"comments": [
{
"element_label": "Additions",
"element_description": "Calculs simples",
"value": ".",
"comment": "Élève absent lors de cette question"
}
]
}
],
"total_comments": 3,
"has_comments": true
},
# Métadonnées
"total_special_values": 4,
"has_special_values": true
}
```
#### CouncilPreparationData
@@ -113,6 +183,14 @@ class CouncilPreparationData:
<div class="appreciation-status">[Rédigée|À rédiger]</div>
</div>
<!-- Résumé compact des valeurs spéciales -->
<div class="special-values-summary">
<span class="text-gray-500">Valeurs spéciales:</span>
<span class="badge-absent">. 2</span> <!-- Absent/Pas de réponse -->
<span class="badge-excused">d 1</span> <!-- Dispensé -->
<span class="badge-away">a 1</span> <!-- Absent justifié -->
</div>
<!-- Résultats par évaluation -->
<div class="assessment-results">
<div class="assessment-item">
@@ -131,6 +209,100 @@ class CouncilPreparationData:
</div>
```
### Vue détaillée expandable
Chaque carte élève peut être étendue pour révéler des informations complémentaires essentielles à la préparation du conseil de classe.
#### Valeurs spéciales par évaluation
```html
<!-- Section des valeurs spéciales détaillées -->
<div class="special-values-detail">
<h6>Valeurs spéciales</h6>
<div class="assessment-special-values">
<div class="assessment-row">
<span>Contrôle Chapitre 1</span>
<div class="special-badges">
<span class="badge" title="Pas de réponse (2)">. 2</span>
<span class="badge" title="Dispensé (1)">d 1</span>
</div>
</div>
</div>
</div>
```
#### Commentaires enseignant organisés 📝
**Nouvelle fonctionnalité 2025** : Affichage compact et structuré de tous les commentaires saisis par l'enseignant.
```html
<!-- Section des commentaires par évaluation -->
<div class="comments-section">
<h6>Commentaires (3)</h6>
<!-- Regroupement par évaluation -->
<div class="assessment-comments">
<div class="assessment-group">
<div class="assessment-header">
<span class="assessment-title">Contrôle Chapitre 1 - Nombres entiers</span>
<span class="comment-count">3 commentaire(s)</span>
</div>
<!-- Commentaires compacts sur 2 lignes -->
<div class="comments-list">
<div class="comment-item">
<!-- Ligne 1: Label • Description -->
<div class="element-info">Additions • Calculs simples</div>
<!-- Ligne 2: [Valeur] Commentaire -->
<div class="comment-content">
<span class="value-badge">.</span>
<span class="comment-text">Élève absent lors de cette question</span>
</div>
</div>
<div class="comment-item">
<div class="element-info">Multiplications • Tables et calculs</div>
<div class="comment-content">
<span class="value-badge">d</span>
<span class="comment-text">Dispensé par décision médicale</span>
</div>
</div>
</div>
</div>
</div>
</div>
```
#### Structure des données commentaires
```python
# Nouvelle structure pour les commentaires
comments_by_assessments = {
"assessments": [
{
"id": 1,
"title": "Contrôle Chapitre 1 - Nombres entiers",
"date": "2025-08-10",
"comments": [
{
"element_label": "Additions",
"element_description": "Calculs simples",
"value": ".", # Valeur optionnelle (peut être None)
"comment": "Élève absent lors de cette question",
"exercise_title": "Exercice 1"
}
]
}
],
"total_comments": 3,
"has_comments": true
}
```
#### Avantages de l'affichage compact
- **Gain d'espace** : 2 lignes par commentaire maximum
- **Contexte complet** : Label, description, valeur et commentaire visibles
- **Organisation logique** : Regroupement par évaluation chronologique
- **Lecture rapide** : Information hiérarchisée et codes couleur cohérents
- **Positionnement optimal** : Après résultats et valeurs spéciales, avant compétences
## 🎛️ Modes de Visualisation
### Mode Liste (par défaut)
@@ -299,6 +471,28 @@ this.state = {
## 🎯 Fonctionnalités Avancées
### Analyse des valeurs spéciales et commentaires 📊
**Nouveauté 2025** : Système complet d'analyse des valeurs spéciales (absences, dispenses) et des commentaires enseignant pour faciliter la préparation des conseils de classe.
#### Valeurs spéciales configurables
- **Configuration dynamique** : Valeurs et couleurs modifiables via interface d'administration
- **Valeurs par défaut** : `.` (Pas de réponse), `d` (Dispensé), `a` (Absent)
- **Comptage automatique** : Calcul global et par évaluation en temps réel
- **Tooltips informatifs** : Détail des éléments concernés au survol
#### Commentaires structurés
- **Regroupement intelligent** : Organisation automatique par évaluation
- **Affichage compact** : 2 lignes par commentaire (contexte + contenu)
- **Métadonnées complètes** : Label, description, valeur associée
- **Tri chronologique** : Évaluations les plus récentes en premier
#### Bénéfices pédagogiques
- **Vue d'ensemble rapide** : Identification immédiate des difficultés
- **Contexte enrichi** : Commentaires précédents accessibles lors des discussions
- **Suivi longitudinal** : Évolution des problématiques par trimestre
- **Préparation optimisée** : Toutes les informations centralisées
### Raccourcis clavier globaux
```javascript
// Raccourcis disponibles
@@ -409,13 +603,20 @@ console.log('⬅️ Navigation vers élève précédent');
- **Structuration** : Commencer par les cas prioritaires
- **Révision** : Mode Liste final pour cohérence globale
## 🔄 Évolutions Futures
## 🔄 Évolutions Récentes et Futures
### Version 2.1
### Version 2.0.1 (Août 2025) ✅
- [x] **Valeurs spéciales** : Comptage et affichage automatiques (`.`, `d`, `a`)
- [x] **Commentaires structurés** : Organisation par évaluation avec affichage compact
- [x] **Repository étendu** : Nouvelles méthodes optimisées pour l'analyse
- [x] **Interface enrichie** : Vue détaillée expandable avec toutes les données contextuelles
### Version 2.1 (Roadmap)
- [ ] **Collaboration** : Plusieurs enseignants simultanément
- [ ] **Templates** : Appréciations pré-rédigées personnalisables
- [ ] **IA Assistant** : Suggestions d'amélioration automatiques
- [ ] **Analytics** : Tendances longitudinales élèves
- [ ] **Export enrichi** : PDF avec valeurs spéciales et commentaires
### Version 2.2
- [ ] **Mobile App** : Application native iOS/Android
@@ -434,6 +635,9 @@ La **Préparation du Conseil de Classe** de Notytex révolutionne le workflow tr
-**Analyse statistique** automatique des performances
-**Navigation optimisée** avec raccourcis clavier
-**Architecture robuste** avec gestion d'erreurs complète
- 🆕 **Valeurs spéciales** : Comptage automatique des absences et dispenses
- 🆕 **Commentaires structurés** : Historique organisé par évaluation
- 🆕 **Vue contextuelle** : Toutes les données pédagogiques centralisées
Cette fonctionnalité transforme une tâche chronophage en un processus fluide et efficace, permettant aux enseignants de se concentrer sur l'essentiel : l'analyse pédagogique et la rédaction d'appréciations personnalisées.

View File

@@ -1,7 +1,9 @@
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import joinedload
from sqlalchemy import func
from models import Grade, GradingElement, Exercise, Assessment, Student
from .base_repository import BaseRepository
from app_config import config_manager
class GradeRepository(BaseRepository[Grade]):
@@ -127,4 +129,261 @@ class GradeRepository(BaseRepository[Grade]):
Exercise.assessment_id == assessment_id
).options(
joinedload(Grade.grading_element).joinedload(GradingElement.exercise)
).all()
).all()
def get_special_values_counts_by_student_trimester(self, student_id: int, trimester: int) -> Dict[str, int]:
"""
Compte les valeurs spéciales pour un élève dans un trimestre donné.
Args:
student_id: ID de l'élève
trimester: Numéro du trimestre (1, 2, ou 3)
Returns:
Dictionnaire avec les comptes par valeur spéciale (ex: {'.': 3, 'd': 1, 'a': 0})
"""
# Récupération dynamique des valeurs spéciales configurées
special_values = config_manager.get_special_values()
# Requête pour compter les valeurs spéciales avec jointures optimisées
grade_values = Grade.query.join(
GradingElement
).join(
Exercise
).join(
Assessment
).filter(
Grade.student_id == student_id,
Assessment.trimester == trimester,
Grade.value.in_(list(special_values.keys()))
).with_entities(
Grade.value,
func.count(Grade.value).label('count')
).group_by(
Grade.value
).all()
# Initialiser le dictionnaire avec toutes les valeurs spéciales à 0
result = {value: 0 for value in special_values.keys()}
# Mettre à jour avec les comptes réels
for value, count in grade_values:
if value in result:
result[value] = count
return result
def get_special_values_counts_by_student_assessment(self, student_id: int, assessment_id: int) -> Dict[str, int]:
"""
Compte les valeurs spéciales pour un élève dans une évaluation donnée.
Args:
student_id: ID de l'élève
assessment_id: ID de l'évaluation
Returns:
Dictionnaire avec les comptes par valeur spéciale (ex: {'.': 2, 'd': 0, 'a': 1})
"""
# Récupération dynamique des valeurs spéciales configurées
special_values = config_manager.get_special_values()
# Requête pour compter les valeurs spéciales avec jointures optimisées
grade_values = Grade.query.join(
GradingElement
).join(
Exercise
).filter(
Grade.student_id == student_id,
Exercise.assessment_id == assessment_id,
Grade.value.in_(list(special_values.keys()))
).with_entities(
Grade.value,
func.count(Grade.value).label('count')
).group_by(
Grade.value
).all()
# Initialiser le dictionnaire avec toutes les valeurs spéciales à 0
result = {value: 0 for value in special_values.keys()}
# Mettre à jour avec les comptes réels
for value, count in grade_values:
if value in result:
result[value] = count
return result
def get_special_values_details_by_student_trimester(self, student_id: int, trimester: int) -> Dict[str, List[Dict[str, Any]]]:
"""
Récupère les détails des valeurs spéciales pour un élève dans un trimestre donné.
Args:
student_id: ID de l'élève
trimester: Numéro du trimestre (1, 2, ou 3)
Returns:
Dictionnaire avec les détails par valeur spéciale incluant les commentaires
Format: {'.': [{'element_name': str, 'comment': str, 'assessment_title': str}, ...]}
"""
# Récupération dynamique des valeurs spéciales configurées
special_values = config_manager.get_special_values()
# Requête pour récupérer les détails des valeurs spéciales
grade_details = Grade.query.join(
GradingElement
).join(
Exercise
).join(
Assessment
).filter(
Grade.student_id == student_id,
Assessment.trimester == trimester,
Grade.value.in_(list(special_values.keys()))
).with_entities(
Grade.value,
Grade.comment,
GradingElement.label.label('element_name'),
Assessment.title.label('assessment_title'),
Assessment.id.label('assessment_id')
).all()
# Organiser par valeur spéciale
result_details = {}
# Initialiser avec toutes les valeurs spéciales
for special_value in special_values.keys():
result_details[special_value] = []
# Ajouter les détails trouvés
for detail in grade_details:
result_details[detail.value].append({
'element_name': detail.element_name,
'comment': detail.comment,
'assessment_title': detail.assessment_title,
'assessment_id': detail.assessment_id
})
return result_details
def get_special_values_details_by_student_assessment(self, student_id: int, assessment_id: int) -> Dict[str, List[Dict[str, Any]]]:
"""
Récupère les détails des valeurs spéciales pour un élève dans une évaluation donnée.
Args:
student_id: ID de l'élève
assessment_id: ID de l'évaluation
Returns:
Dictionnaire avec les détails par valeur spéciale incluant les commentaires
Format: {'.': [{'element_name': str, 'comment': str}, ...]}
"""
# Récupération dynamique des valeurs spéciales configurées
special_values = config_manager.get_special_values()
# Requête pour récupérer les détails des valeurs spéciales
grade_details = Grade.query.join(
GradingElement
).join(
Exercise
).filter(
Grade.student_id == student_id,
Exercise.assessment_id == assessment_id,
Grade.value.in_(list(special_values.keys()))
).with_entities(
Grade.value,
Grade.comment,
GradingElement.label.label('element_name')
).all()
# Organiser par valeur spéciale
result_details = {}
# Initialiser avec toutes les valeurs spéciales
for special_value in special_values.keys():
result_details[special_value] = []
# Ajouter les détails trouvés
for detail in grade_details:
result_details[detail.value].append({
'element_name': detail.element_name,
'comment': detail.comment
})
return result_details
def get_all_comments_by_student_trimester(self, student_id: int, trimester: int) -> Dict[str, Any]:
"""
Récupère tous les commentaires regroupés par évaluations pour un élève dans un trimestre.
Args:
student_id: ID de l'élève
trimester: Numéro du trimestre (1, 2, ou 3)
Returns:
Dict avec commentaires organisés par évaluation avec métadonnées complètes
"""
# Requête pour récupérer tous les commentaires non vides avec toutes les informations
grade_comments = Grade.query.join(
GradingElement
).join(
Exercise
).join(
Assessment
).filter(
Grade.student_id == student_id,
Assessment.trimester == trimester,
Grade.comment.isnot(None),
Grade.comment != ''
).with_entities(
Grade.value,
Grade.comment,
GradingElement.label.label('element_label'),
GradingElement.description.label('element_description'),
Assessment.title.label('assessment_title'),
Assessment.id.label('assessment_id'),
Assessment.date.label('assessment_date'),
Exercise.title.label('exercise_title'),
Exercise.order.label('exercise_order')
).order_by(
Assessment.date.desc(),
Exercise.order,
GradingElement.id
).all()
# Organiser par évaluation
comments_by_assessment = {}
for comment_data in grade_comments:
assessment_id = comment_data.assessment_id
# Initialiser l'évaluation si pas encore présente
if assessment_id not in comments_by_assessment:
comments_by_assessment[assessment_id] = {
'id': assessment_id,
'title': comment_data.assessment_title,
'date': comment_data.assessment_date,
'comments': []
}
# Ajouter le commentaire
comments_by_assessment[assessment_id]['comments'].append({
'value': comment_data.value,
'comment': comment_data.comment,
'element_label': comment_data.element_label,
'element_description': comment_data.element_description,
'exercise_title': comment_data.exercise_title,
'exercise_order': comment_data.exercise_order
})
# Convertir en liste triée par date (plus récent en premier)
assessments_with_comments = list(comments_by_assessment.values())
assessments_with_comments.sort(key=lambda x: x['date'] if x['date'] else '', reverse=True)
# Calculer le total de commentaires
total_comments = sum(len(assessment['comments']) for assessment in assessments_with_comments)
return {
'assessments': assessments_with_comments,
'total_comments': total_comments,
'has_comments': total_comments > 0
}

View File

@@ -3,7 +3,7 @@ Services pour la préparation du conseil de classe.
Comprend CouncilPreparationService, StudentEvaluationService, AppreciationService.
"""
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime
from repositories.appreciation_repository import AppreciationRepository
from repositories.grade_repository import GradeRepository
@@ -22,6 +22,7 @@ class StudentTrimesterSummary:
appreciation: Optional[CouncilAppreciation]
performance_status: str # 'excellent', 'good', 'average', 'struggling'
competence_domain_breakdown: Optional[Dict] = None # Données des compétences et domaines
special_values_summary: Optional[Dict] = None # Résumé des valeurs spéciales
@property
def has_appreciation(self) -> bool:
@@ -101,6 +102,13 @@ class StudentEvaluationService:
# Calculer les données de compétences et domaines
competence_domain_breakdown = self.get_student_competence_domain_breakdown(student_id, trimester)
# Calculer le résumé des valeurs spéciales
special_values_summary = self.get_student_special_values_summary(student_id, trimester)
# Ajouter tous les commentaires organisés par évaluations
all_comments_data = self.grade_repo.get_all_comments_by_student_trimester(student_id, trimester)
special_values_summary['comments_by_assessments'] = all_comments_data
return StudentTrimesterSummary(
student=student,
overall_average=overall_average,
@@ -108,7 +116,8 @@ class StudentEvaluationService:
grades_by_assessment=grades_by_assessment,
appreciation=appreciation,
performance_status=performance_status,
competence_domain_breakdown=competence_domain_breakdown
competence_domain_breakdown=competence_domain_breakdown,
special_values_summary=special_values_summary
)
def get_students_summaries(self, class_group_id: int, trimester: int) -> List[StudentTrimesterSummary]:
@@ -349,6 +358,80 @@ class StudentEvaluationService:
'domains': domains
}
def get_student_special_values_summary(self, student_id: int, trimester: int) -> Dict[str, Any]:
"""
Calcule le résumé des valeurs spéciales pour un élève sur un trimestre.
Returns:
Dict avec 'global' (total par trimestre) et 'by_assessment' (détail par évaluation)
"""
from app_config import config_manager
# Récupérer les valeurs spéciales configurées
special_values_config = config_manager.get_special_values()
# 1. Comptes globaux par trimestre
global_counts = self.grade_repo.get_special_values_counts_by_student_trimester(student_id, trimester)
global_details = self.grade_repo.get_special_values_details_by_student_trimester(student_id, trimester)
# 2. Comptes par évaluation
assessments = self.assessment_repo.find_by_class_trimester_with_details(
Student.query.get(student_id).class_group_id, trimester
)
by_assessment = {}
for assessment in assessments:
assessment_counts = self.grade_repo.get_special_values_counts_by_student_assessment(
student_id, assessment.id
)
assessment_details = self.grade_repo.get_special_values_details_by_student_assessment(
student_id, assessment.id
)
# Ajouter seulement si l'élève a des valeurs spéciales dans cette évaluation
if any(count > 0 for count in assessment_counts.values()):
by_assessment[assessment.id] = {
'title': assessment.title,
'date': assessment.date,
'counts': assessment_counts,
'details': assessment_details
}
# 3. Enrichir avec les métadonnées de configuration
enriched_global = {}
enriched_by_assessment = {}
for special_value, config in special_values_config.items():
enriched_global[special_value] = {
'count': global_counts.get(special_value, 0),
'label': config['label'],
'color': config['color'],
'counts_in_total': config['counts'],
'details': global_details.get(special_value, [])
}
for assessment_id, assessment_data in by_assessment.items():
enriched_by_assessment[assessment_id] = {
'title': assessment_data['title'],
'date': assessment_data['date'],
'special_values': {}
}
for special_value, config in special_values_config.items():
enriched_by_assessment[assessment_id]['special_values'][special_value] = {
'count': assessment_data['counts'].get(special_value, 0),
'label': config['label'],
'color': config['color'],
'details': assessment_data['details'].get(special_value, [])
}
return {
'global': enriched_global,
'by_assessment': enriched_by_assessment,
'total_special_values': sum(global_counts.values()),
'has_special_values': sum(global_counts.values()) > 0
}
def _calculate_assessment_score_for_student(self, assessment: Assessment, student_id: int) -> Optional[float]:
"""Calcule le score d'un élève pour une évaluation."""
grades = self.grade_repo.get_student_grades_by_assessment(student_id, assessment.id)

View File

@@ -272,6 +272,30 @@
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</div>
{# Résumé compact des valeurs spéciales #}
{% if summary.special_values_summary and summary.special_values_summary.has_special_values %}
<div class="mt-2 flex items-center space-x-1 text-xs">
<span class="text-gray-500">Valeurs spéciales:</span>
{% for special_value, data in summary.special_values_summary.global.items() %}
{% if data.count > 0 %}
{% set tooltip_content = [] %}
{% for detail in data.details %}
{% if detail.comment %}
{% set _ = tooltip_content.append(detail.element_name + ': ' + detail.comment) %}
{% else %}
{% set _ = tooltip_content.append(detail.element_name) %}
{% endif %}
{% endfor %}
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium cursor-help"
style="background-color: {{ data.color }}20; color: {{ data.color }};"
title="{{ data.label }} ({{ data.count }}){% if tooltip_content|length > 0 %}&#10;{{ tooltip_content|join('&#10;') }}{% endif %}">
{{ special_value }} {{ data.count }}
</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
@@ -392,6 +416,87 @@
</div>
{% endfor %}
</div>
{# Section Valeurs spéciales par évaluation #}
{% if summary.special_values_summary and summary.special_values_summary.by_assessment %}
<div class="mt-4">
<h6 class="text-xs font-medium text-gray-600 mb-2 flex items-center">
<svg class="w-3 h-3 text-gray-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
</svg>
Valeurs spéciales
</h6>
<div class="space-y-1">
{% for assessment_id, assessment_data in summary.special_values_summary.by_assessment.items() %}
<div class="flex items-center justify-between p-1.5 bg-gray-50 rounded border border-gray-100">
<span class="text-xs text-gray-700 truncate">{{ assessment_data.title }}</span>
<div class="flex items-center space-x-1">
{% for special_value, data in assessment_data.special_values.items() %}
{% if data.count > 0 %}
{% set detail_tooltip = [] %}
{% for detail in data.details %}
{% if detail.comment %}
{% set _ = detail_tooltip.append(detail.element_name + ': ' + detail.comment) %}
{% else %}
{% set _ = detail_tooltip.append(detail.element_name) %}
{% endif %}
{% endfor %}
<span class="inline-flex items-center px-1 py-0.5 rounded text-xs font-medium cursor-help"
style="background-color: {{ data.color }}15; color: {{ data.color }}; border: 1px solid {{ data.color }}30;"
title="{{ data.label }} ({{ data.count }}){% if detail_tooltip|length > 0 %}&#10;{{ detail_tooltip|join('&#10;') }}{% endif %}">
{{ special_value }} {{ data.count }}
</span>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{# Section Commentaires par évaluation #}
{% if summary.special_values_summary and summary.special_values_summary.comments_by_assessments and summary.special_values_summary.comments_by_assessments.has_comments %}
<div class="mt-4">
<h6 class="text-xs font-medium text-gray-600 mb-2 flex items-center">
<svg class="w-3 h-3 text-gray-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
Commentaires ({{ summary.special_values_summary.comments_by_assessments.total_comments }})
</h6>
<div class="space-y-2">
{% for assessment in summary.special_values_summary.comments_by_assessments.assessments %}
<div class="bg-amber-50 border border-amber-200 rounded p-2">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-amber-800">{{ assessment.title }}</span>
<span class="text-xs text-amber-600">{{ assessment.comments|length }} commentaire(s)</span>
</div>
<div class="space-y-1">
{% for comment in assessment.comments %}
<div class="bg-white border border-amber-100 rounded p-1.5">
<div class="flex-1 min-w-0">
{# Ligne 1: Label et description #}
<div class="text-xs font-medium text-gray-800">
{{ comment.element_label }}{% if comment.element_description %} • {{ comment.element_description }}{% endif %}
</div>
{# Ligne 2: Valeur et commentaire #}
<div class="text-xs text-amber-700 mt-0.5 flex items-center">
{% if comment.value %}
<span class="inline-flex items-center px-1 py-0.5 rounded text-xs font-medium mr-2 bg-orange-100 text-orange-800">
{{ comment.value }}
</span>
{% endif %}
<span class="flex-1">{{ comment.comment }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endif %}