Files
notytex/templates/components/class/class_stats_card.html

297 lines
17 KiB
HTML

{#
Macro : class_stats_card(type, data, expanded=False, trimester_colors={})
TYPES : 'quantity', 'domains', 'competences', 'results'
DATA STRUCTURE attendue :
- quantity: {"total": 8, "completed": 6, "in_progress": 2, "not_started": 0}
- domains: [{"name": "Calcul", "color": "#3B82F6", "total_points": 45.0, "elements_count": 12}]
- competences: [{"name": "Calculer", "color": "#8B5CF6", "total_points": 25.0, "elements_count": 6}]
- results: {"mean": 14.2, "median": 15.0, "distribution": [0,1,3,8,12,6,2,0], "min": 8.0, "max": 20.0}
#}
{% macro class_stats_card(type, data, expanded=False, trimester_colors={}) %}
{# Configuration des icônes par type #}
{% set icons = {
'quantity': '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2v1a1 1 0 001 1h6a1 1 0 001-1V3a2 2 0 012 2v6a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h2a1 1 0 100-2H7z" clip-rule="evenodd"/></svg>',
'domains': '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/></svg>',
'competences': '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 6V5a3 3 0 013-3h2a3 3 0 013 3v1h2a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h2zm-1 5a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zm2-3a1 1 0 000 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/></svg>',
'results': '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"/><path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z"/></svg>'
} %}
{# Configuration des titres et couleurs par type #}
{% set config = {
'quantity': {
'title': 'Évaluations',
'color_class': 'blue',
'icon_color': 'text-blue-600',
'bg_color': 'bg-blue-50',
'border_color': 'border-blue-200'
},
'domains': {
'title': 'Domaines',
'color_class': 'purple',
'icon_color': 'text-purple-600',
'bg_color': 'bg-purple-50',
'border_color': 'border-purple-200'
},
'competences': {
'title': 'Compétences',
'color_class': 'indigo',
'icon_color': 'text-indigo-600',
'bg_color': 'bg-indigo-50',
'border_color': 'border-indigo-200'
},
'results': {
'title': 'Résultats',
'color_class': 'green',
'icon_color': 'text-green-600',
'bg_color': 'bg-green-50',
'border_color': 'border-green-200'
}
} %}
{% set card_config = config[type] %}
<div class="stats-card bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border {{ card_config.border_color }}"
data-type="{{ type }}"
data-expanded="{{ 'true' if expanded else 'false' }}">
<!-- Header - Toujours visible -->
<div class="p-6">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-12 h-12 {{ card_config.bg_color }} rounded-xl flex items-center justify-center mr-4 {{ card_config.icon_color }}">
{{ icons[type]|safe }}
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 mb-1">{{ card_config.title }}</h3>
{% if type == 'quantity' %}
<p class="text-2xl font-bold {{ card_config.icon_color }}">{{ data.total }} évaluations</p>
<p class="text-sm text-gray-500">{{ data.completed }} terminées</p>
{% elif type == 'domains' %}
<p class="text-2xl font-bold {{ card_config.icon_color }}">{{ data|length }} domaines</p>
<p class="text-sm text-gray-500">{{ data|sum(attribute='total_points')|round(1) }} points total</p>
{% elif type == 'competences' %}
<p class="text-2xl font-bold {{ card_config.icon_color }}">{{ data|length }} compétences</p>
<p class="text-sm text-gray-500">{{ data|sum(attribute='elements_count') }} éléments</p>
{% elif type == 'results' %}
<p class="text-2xl font-bold {{ card_config.icon_color }}">{{ data.mean|round(1) }}/20</p>
<p class="text-sm text-gray-500">Moyenne générale</p>
{% endif %}
</div>
</div>
<!-- Bouton d'expansion -->
<button type="button"
class="expand-btn text-gray-400 hover:text-gray-600 transition-colors p-2 rounded-lg hover:bg-gray-50"
onclick="toggleStatsCard(this)">
<svg class="w-5 h-5 transform transition-transform {{ 'rotate-180' if expanded else '' }}"
fill="currentColor" viewBox="0 0 20 20">
<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>
</button>
</div>
</div>
<!-- Contenu étendu -->
<div class="expanded-content {{ 'hidden' if not expanded else '' }} border-t {{ card_config.border_color }}">
<div class="p-6">
{% if type == 'quantity' %}
<!-- Détails des évaluations par statut -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-green-800">Terminées</p>
<p class="text-xl font-bold text-green-900">{{ data.completed }}</p>
</div>
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-orange-800">En cours</p>
<p class="text-xl font-bold text-orange-900">{{ data.in_progress }}</p>
</div>
<svg class="w-8 h-8 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM7 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H7z" clip-rule="evenodd"/>
</svg>
</div>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-red-800">Non commencées</p>
<p class="text-xl font-bold text-red-900">{{ data.not_started }}</p>
</div>
<svg class="w-8 h-8 text-red-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92z" clip-rule="evenodd"/>
</svg>
</div>
</div>
</div>
<!-- Actions contextuelles -->
<div class="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-200">
<a href="#" class="text-sm text-blue-600 hover:text-blue-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
</svg>
Voir toutes les évaluations
</a>
<a href="#" class="text-sm text-green-600 hover:text-green-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd"/>
</svg>
Créer une évaluation
</a>
</div>
{% elif type == 'domains' %}
<!-- Liste des domaines avec codes couleurs -->
<div class="space-y-3">
{% for domain in data %}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center">
<div class="w-4 h-4 rounded-full mr-3" style="background-color: {{ domain.color }}"></div>
<div>
<p class="font-medium text-gray-900">{{ domain.name }}</p>
<p class="text-sm text-gray-500">{{ domain.elements_count }} éléments</p>
</div>
</div>
<div class="text-right">
<p class="font-semibold text-gray-900">{{ domain.total_points|round(1) }} pts</p>
</div>
</div>
{% endfor %}
</div>
<!-- Actions contextuelles -->
<div class="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-200">
<a href="#" class="text-sm text-purple-600 hover:text-purple-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"/>
</svg>
Analyse par domaines
</a>
</div>
{% elif type == 'competences' %}
<!-- Liste des compétences avec badges -->
<div class="space-y-3">
{% for competence in data %}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center">
<div class="px-2 py-1 text-xs font-medium rounded-full text-white mr-3"
style="background-color: {{ competence.color }}">
{{ competence.name[:2]|upper }}
</div>
<div>
<p class="font-medium text-gray-900">{{ competence.name }}</p>
<p class="text-sm text-gray-500">{{ competence.elements_count }} éléments</p>
</div>
</div>
<div class="text-right">
<p class="font-semibold text-gray-900">{{ competence.total_points|round(1) }} pts</p>
</div>
</div>
{% endfor %}
</div>
<!-- Actions contextuelles -->
<div class="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-200">
<a href="#" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3z" clip-rule="evenodd"/>
</svg>
Évaluation par compétences
</a>
</div>
{% elif type == 'results' %}
<!-- Statistiques détaillées -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div class="text-center p-3 bg-blue-50 rounded-lg">
<p class="text-sm font-medium text-blue-800">Moyenne</p>
<p class="text-xl font-bold text-blue-900">{{ data.mean|round(1) }}</p>
</div>
<div class="text-center p-3 bg-green-50 rounded-lg">
<p class="text-sm font-medium text-green-800">Médiane</p>
<p class="text-xl font-bold text-green-900">{{ data.median|round(1) }}</p>
</div>
<div class="text-center p-3 bg-red-50 rounded-lg">
<p class="text-sm font-medium text-red-800">Minimum</p>
<p class="text-xl font-bold text-red-900">{{ data.min|round(1) }}</p>
</div>
<div class="text-center p-3 bg-purple-50 rounded-lg">
<p class="text-sm font-medium text-purple-800">Maximum</p>
<p class="text-xl font-bold text-purple-900">{{ data.max|round(1) }}</p>
</div>
</div>
<!-- Mini distribution des notes -->
<div class="mb-4">
<p class="text-sm font-medium text-gray-700 mb-2">Distribution des notes :</p>
<div class="flex items-end space-x-1 h-12">
{% for count in data.distribution %}
<div class="flex-1 bg-blue-200 rounded-t" style="height: {{ (count / (data.distribution|max or 1) * 100)|round }}%">
{% if count > 0 %}
<div class="text-xs text-center text-blue-800 pt-1">{{ count }}</div>
{% endif %}
</div>
{% endfor %}
</div>
<div class="flex justify-between text-xs text-gray-500 mt-1">
{% for i in range(data.distribution|length) %}
<span>{{ i }}-{{ i+1 }}</span>
{% endfor %}
</div>
</div>
<!-- Actions contextuelles -->
<div class="flex flex-wrap gap-2 pt-4 border-t border-gray-200">
<a href="#" class="text-sm text-green-600 hover:text-green-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"/>
</svg>
Analyse détaillée
</a>
<a href="#" class="text-sm text-blue-600 hover:text-blue-800 font-medium flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"/>
</svg>
Voir les graphiques
</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
{# JavaScript pour l'interaction d'expansion - À inclure dans la page qui utilise le composant #}
<script>
function toggleStatsCard(button) {
const card = button.closest('.stats-card');
const expandedContent = card.querySelector('.expanded-content');
const chevron = button.querySelector('svg');
const isExpanded = card.dataset.expanded === 'true';
if (isExpanded) {
expandedContent.classList.add('hidden');
chevron.classList.remove('rotate-180');
card.dataset.expanded = 'false';
} else {
expandedContent.classList.remove('hidden');
chevron.classList.add('rotate-180');
card.dataset.expanded = 'true';
}
}
</script>