295 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| {# Macros pour composants réutilisables #}
 | |
| 
 | |
| {# Macro pour section hero avec gradient #}
 | |
| {% macro hero_section(title, subtitle=None, meta_info=[], primary_action=None, icon=None, gradient_class="from-purple-600 to-blue-600") %}
 | |
| <div class="bg-gradient-to-r {{ gradient_class }} text-white rounded-xl p-8 shadow-lg">
 | |
|     <div class="flex items-center justify-between">
 | |
|         <div class="flex-1">
 | |
|             {% if caller %}
 | |
|                 {{ caller() }}
 | |
|             {% endif %}
 | |
|             <h1 class="text-4xl font-bold mb-2">{{ title }}</h1>
 | |
|             {% if subtitle %}
 | |
|                 <p class="text-xl opacity-90 mb-1">{{ subtitle }}</p>
 | |
|             {% endif %}
 | |
|             {% if meta_info %}
 | |
|                 <div class="flex items-center space-x-6 text-sm opacity-75 mt-3">
 | |
|                     {% for info in meta_info %}
 | |
|                         <span class="flex items-center">
 | |
|                             {% if info.icon %}
 | |
|                                 {{ info.icon|safe }}
 | |
|                             {% endif %}
 | |
|                             {{ info.text }}
 | |
|                         </span>
 | |
|                     {% endfor %}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|         <div class="hidden md:block">
 | |
|             {% if primary_action %}
 | |
|                 <a href="{{ primary_action.url }}" 
 | |
|                    class="bg-white/20 hover:bg-white/30 text-white px-6 py-3 rounded-xl transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-105 flex items-center">
 | |
|                     {% if primary_action.icon %}
 | |
|                         {{ primary_action.icon|safe }}
 | |
|                     {% endif %}
 | |
|                     {{ primary_action.text }}
 | |
|                 </a>
 | |
|             {% elif icon %}
 | |
|                 <div class="w-24 h-24 bg-white/20 rounded-full flex items-center justify-center">
 | |
|                     {{ icon|safe }}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour card standardisée #}
 | |
| {% macro card(title=None, actions=[], classes="", body_classes="p-6") %}
 | |
| <div class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 {{ classes }}">
 | |
|     {% if title %}
 | |
|         <div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
 | |
|             <h2 class="text-lg font-semibold text-gray-900">{{ title }}</h2>
 | |
|             {% if actions %}
 | |
|                 <div class="flex space-x-2">
 | |
|                     {% for action in actions %}
 | |
|                         <a href="{{ action.url }}" class="{{ action.classes|default('text-blue-600 hover:text-blue-800 text-sm font-medium') }}">
 | |
|                             {{ action.text }}
 | |
|                         </a>
 | |
|                     {% endfor %}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     {% endif %}
 | |
|     <div class="{{ body_classes }}">
 | |
|         {{ caller() }}
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour bouton avec gradient #}
 | |
| {% macro gradient_button(url, text, icon=None, gradient_class="from-blue-500 to-blue-600", hover_gradient="from-blue-600 to-blue-700", size="md", classes="") %}
 | |
| {% set size_classes = {
 | |
|     'sm': 'px-4 py-2 text-sm',
 | |
|     'md': 'px-6 py-3 text-base',
 | |
|     'lg': 'px-8 py-4 text-lg'
 | |
| } %}
 | |
| <a href="{{ url }}" 
 | |
|    class="group bg-gradient-to-r {{ gradient_class }} hover:{{ hover_gradient }} text-white rounded-xl {{ size_classes[size] }} transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl font-semibold flex items-center {{ classes }}">
 | |
|     {% if icon %}
 | |
|         <div class="w-5 h-5 mr-2 group-hover:scale-110 transition-transform">
 | |
|             {{ icon|safe }}
 | |
|         </div>
 | |
|     {% endif %}
 | |
|     {{ text }}
 | |
| </a>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour action card (comme les cartes d'actions principales) #}
 | |
| {% macro action_card(url, title, subtitle, icon, gradient_class="from-green-500 to-green-600", hover_gradient="from-green-600 to-green-700", is_button=False) %}
 | |
| {% if is_button %}
 | |
|     <button onclick="{{ url }}" 
 | |
| {% else %}
 | |
|     <a href="{{ url }}" 
 | |
| {% endif %}
 | |
|            class="group bg-gradient-to-r {{ gradient_class }} hover:{{ hover_gradient }} text-white rounded-xl p-6 transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl">
 | |
|         <div class="flex items-center">
 | |
|             <div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
 | |
|                 {{ icon|safe }}
 | |
|             </div>
 | |
|             <div>
 | |
|                 <h3 class="text-lg font-bold mb-1">{{ title }}</h3>
 | |
|                 <p class="text-sm opacity-90">{{ subtitle }}</p>
 | |
|             </div>
 | |
|         </div>
 | |
| {% if is_button %}
 | |
|     </button>
 | |
| {% else %}
 | |
|     </a>
 | |
| {% endif %}
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour indicateur de progression #}
 | |
| {% macro progress_indicator(progress, clickable=True, assessment_id=None, compact=False) %}
 | |
| {% set base_classes = "inline-flex items-center rounded-full text-xs font-medium border" %}
 | |
| {% set size_classes = "px-3 py-2" if not compact else "px-2 py-1" %}
 | |
| 
 | |
| {% if progress.status == 'completed' %}
 | |
|     <div class="{{ base_classes }} {{ size_classes }} bg-green-100 text-green-800 border-green-200">
 | |
|         <svg class="w-4 h-4 mr-2" 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>
 | |
|         <span class="font-semibold">Correction 100%</span>
 | |
|     </div>
 | |
| {% elif progress.status == 'in_progress' %}
 | |
|     {% if clickable and assessment_id %}
 | |
|         <a href="{{ url_for('grading.assessment_grading', assessment_id=assessment_id) }}" 
 | |
|     {% else %}
 | |
|         <div 
 | |
|     {% endif %}
 | |
|            class="{{ base_classes }} {{ size_classes }} bg-orange-100 text-orange-800 border-orange-200 {% if clickable %}hover:bg-orange-200 transition-colors cursor-pointer{% endif %}">
 | |
|         <div class="relative w-4 h-4 mr-2">
 | |
|             <svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 16 16">
 | |
|                 <circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none" class="text-orange-300"/>
 | |
|                 <circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none" 
 | |
|                         class="text-orange-600" stroke-dasharray="37.7" 
 | |
|                         stroke-dashoffset="{{ 37.7 - (37.7 * progress.percentage / 100) }}"/>
 | |
|             </svg>
 | |
|         </div>
 | |
|         <span class="font-semibold">Correction {{ progress.percentage }}%</span>
 | |
|     {% if clickable and assessment_id %}</a>{% else %}</div>{% endif %}
 | |
| {% elif progress.status == 'not_started' %}
 | |
|     {% if clickable and assessment_id %}
 | |
|         <a href="{{ url_for('grading.assessment_grading', assessment_id=assessment_id) }}" 
 | |
|     {% else %}
 | |
|         <div 
 | |
|     {% endif %}
 | |
|            class="{{ base_classes }} {{ size_classes }} bg-red-100 text-red-800 border-red-200 {% if clickable %}hover:bg-red-200 transition-colors cursor-pointer{% endif %}">
 | |
|         <svg class="w-4 h-4 mr-2" 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.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
 | |
|         </svg>
 | |
|         <span class="font-semibold">Correction 0%</span>
 | |
|     {% if clickable and assessment_id %}</a>{% else %}</div>{% endif %}
 | |
| {% else %}
 | |
|     <div class="{{ base_classes }} {{ size_classes }} bg-gray-100 text-gray-800 border-gray-200">
 | |
|         <svg class="w-4 h-4 mr-2" 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>
 | |
|         <span class="font-semibold">Non définie</span>
 | |
|     </div>
 | |
| {% endif %}
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour carte de statistique cliquable #}
 | |
| {% macro stat_card(title, value, url, icon, color="blue", description=None) %}
 | |
| <a href="{{ url }}" class="block bg-white rounded-lg shadow hover:shadow-lg transition-all duration-300 p-6 transform hover:scale-105 group">
 | |
|     <div class="flex items-center justify-between">
 | |
|         <div>
 | |
|             <div class="text-sm font-medium text-gray-500 group-hover:text-{{ color }}-600 transition-colors">{{ title }}</div>
 | |
|             <div class="text-3xl font-bold text-gray-900 group-hover:text-{{ color }}-700 transition-colors">{{ value }}</div>
 | |
|             {% if description %}
 | |
|                 <div class="text-xs text-{{ color }}-600 flex items-center mt-1">
 | |
|                     <svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
 | |
|                         <path fill-rule="evenodd" d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
 | |
|                     </svg>
 | |
|                     {{ description }}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|         <div class="w-12 h-12 bg-{{ color }}-100 group-hover:bg-{{ color }}-200 rounded-full flex items-center justify-center transition-colors">
 | |
|             {{ icon|safe }}
 | |
|         </div>
 | |
|     </div>
 | |
| </a>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour filtres standardisés - Version classique (conservée pour compatibilité) #}
 | |
| {% macro filter_section(filters, current_values={}) %}
 | |
| <div class="filter-section">
 | |
|     <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
 | |
|         <div class="flex flex-col md:flex-row md:items-center space-y-3 md:space-y-0 md:space-x-4">
 | |
|             {% for filter in filters %}
 | |
|                 <div class="flex items-center space-x-2">
 | |
|                     <label class="text-sm font-medium text-gray-700">{{ filter.label }} :</label>
 | |
|                     <select data-filter="{{ filter.id.replace('-filter', '') }}" class="filter-control border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm hover:border-gray-400 transition-colors">
 | |
|                         {% for option in filter.options %}
 | |
|                             <option value="{{ option.value }}" {% if current_values.get(filter.id) == option.value %}selected{% endif %}>
 | |
|                                 {{ option.label }}
 | |
|                             </option>
 | |
|                         {% endfor %}
 | |
|                     </select>
 | |
|                 </div>
 | |
|             {% endfor %}
 | |
|         </div>
 | |
|         
 | |
|         <!-- Zone pour contenu additionnel -->
 | |
|         {% if caller %}
 | |
|             <div class="flex items-center space-x-4">
 | |
|                 {{ caller() }}
 | |
|             </div>
 | |
|         {% endif %}
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour filtres simplifiés et discrets #}
 | |
| {% macro simple_filter_section(filters_config, current_values={}, total_items=0, filtered_items=0) %}
 | |
| <div class="bg-gray-50 rounded-lg p-4 mb-6 border border-gray-200">
 | |
|     <!-- Filtres compacts en une ligne -->
 | |
|     <div class="flex flex-wrap items-center gap-4 text-sm">
 | |
|         <div class="flex items-center text-gray-600">
 | |
|             <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
 | |
|                 <path fill-rule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" clip-rule="evenodd"/>
 | |
|             </svg>
 | |
|             <span id="filtered-count">{{ filtered_items }}</span>/<span class="text-gray-500">{{ total_items }}</span>
 | |
|         </div>
 | |
|         <!-- Filtre Trimestre -->
 | |
|         {% if filters_config.trimester %}
 | |
|         <div class="flex items-center gap-1">
 | |
|             <span class="text-gray-500 text-xs">Trimestre:</span>
 | |
|             <button type="button" data-filter="trimester" data-value="" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if not current_values.get('trimester-filter') %}bg-blue-600 text-white border-blue-600{% else %}bg-white text-gray-600 border-gray-300 hover:bg-gray-50{% endif %} transition-colors">
 | |
|                 Tous
 | |
|             </button>
 | |
|             {% for i in range(1, 4) %}
 | |
|             <button type="button" data-filter="trimester" data-value="{{ i }}" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if current_values.get('trimester-filter') == i|string %}bg-blue-600 text-white border-blue-600{% else %}bg-white text-gray-600 border-gray-300 hover:bg-gray-50{% endif %} transition-colors">
 | |
|                 T{{ i }}
 | |
|             </button>
 | |
|             {% endfor %}
 | |
|         </div>
 | |
|         {% endif %}
 | |
|         
 | |
|         <!-- Filtre Classe -->
 | |
|         {% if filters_config.class_options %}
 | |
|         <div class="flex items-center gap-1">
 | |
|             <span class="text-gray-500 text-xs">Classe:</span>
 | |
|             {% for option in filters_config.class_options %}
 | |
|             <button type="button" data-filter="class" data-value="{{ option.value }}" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if current_values.get('class-filter') == option.value %}bg-blue-600 text-white border-blue-600{% else %}bg-white text-gray-600 border-gray-300 hover:bg-gray-50{% endif %} transition-colors">
 | |
|                 {{ option.label }}
 | |
|             </button>
 | |
|             {% endfor %}
 | |
|         </div>
 | |
|         {% endif %}
 | |
|         
 | |
|         <!-- Filtre État de correction -->
 | |
|         {% if filters_config.correction %}
 | |
|         <div class="flex items-center gap-1">
 | |
|             <span class="text-gray-500 text-xs">État:</span>
 | |
|             <button type="button" data-filter="correction" data-value="" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if not current_values.get('correction-filter') %}bg-blue-600 text-white border-blue-600{% else %}bg-white text-gray-600 border-gray-300 hover:bg-gray-50{% endif %} transition-colors">
 | |
|                 Toutes
 | |
|             </button>
 | |
|             <button type="button" data-filter="correction" data-value="not_started" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if current_values.get('correction-filter') == 'not_started' %}bg-red-500 text-white border-red-500{% else %}bg-white text-red-600 border-red-200 hover:bg-red-50{% endif %} transition-colors">
 | |
|                 Non commencées
 | |
|             </button>
 | |
|             <button type="button" data-filter="correction" data-value="incomplete" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if current_values.get('correction-filter') == 'incomplete' %}bg-orange-500 text-white border-orange-500{% else %}bg-white text-orange-600 border-orange-200 hover:bg-orange-50{% endif %} transition-colors">
 | |
|                 En cours
 | |
|             </button>
 | |
|             <button type="button" data-filter="correction" data-value="complete" 
 | |
|                     class="filter-btn px-2 py-1 text-xs rounded border {% if current_values.get('correction-filter') == 'complete' %}bg-green-500 text-white border-green-500{% else %}bg-white text-green-600 border-green-200 hover:bg-green-50{% endif %} transition-colors">
 | |
|                 Terminées
 | |
|             </button>
 | |
|         </div>
 | |
|         {% endif %}
 | |
|     </div>
 | |
|     
 | |
|     <!-- Reset discret et contenu additionnel -->
 | |
|     <div class="flex items-center justify-between mt-2">
 | |
|         <div id="active-filter-tags" class="flex flex-wrap gap-1">
 | |
|             <!-- Tags générés par JavaScript -->
 | |
|         </div>
 | |
|         <div class="flex items-center gap-2">
 | |
|             <button type="button" id="reset-filters" class="text-xs text-gray-500 hover:text-gray-700 hidden">
 | |
|                 Effacer
 | |
|             </button>
 | |
|             {% if caller %}
 | |
|                 {{ caller() }}
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %} |