540 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			540 lines
		
	
	
		
			27 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 page layout standardisé avec hero section #}
 | |
| {% macro page_layout(title, subtitle=None, breadcrumbs=[], actions=[]) %}
 | |
| {% if breadcrumbs %}
 | |
| <nav class="flex mb-6" aria-label="Breadcrumb">
 | |
|     <ol class="flex items-center space-x-2 text-sm">
 | |
|         {% for crumb in breadcrumbs %}
 | |
|             <li class="flex items-center">
 | |
|                 {% if not loop.last %}
 | |
|                     <a href="{{ crumb.url }}" class="text-gray-500 hover:text-gray-700 transition-colors">
 | |
|                         {{ crumb.text }}
 | |
|                     </a>
 | |
|                     <svg class="w-4 h-4 mx-2 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
 | |
|                         <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
 | |
|                     </svg>
 | |
|                 {% else %}
 | |
|                     <span class="text-gray-900 font-medium">{{ crumb.text }}</span>
 | |
|                 {% endif %}
 | |
|             </li>
 | |
|         {% endfor %}
 | |
|     </ol>
 | |
| </nav>
 | |
| {% endif %}
 | |
| 
 | |
| <!-- Hero Section avec gradient -->
 | |
| <div class="bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl p-8 shadow-lg mb-8">
 | |
|     <div class="flex items-center justify-between">
 | |
|         <div class="flex-1">
 | |
|             <h1 class="text-4xl font-bold mb-2">{{ title }}</h1>
 | |
|             {% if subtitle %}
 | |
|                 <p class="text-xl opacity-90 mb-1">{{ subtitle }}</p>
 | |
|             {% endif %}
 | |
|             <p class="text-sm opacity-75">{{ moment().format('dddd D MMMM YYYY') if moment else 'Lundi 16 août 2025' }}</p>
 | |
|         </div>
 | |
|         <div class="hidden md:block">
 | |
|             {% if actions %}
 | |
|                 <div class="flex items-center space-x-3">
 | |
|                     {% for action in actions %}
 | |
|                         <a href="{{ 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 action.icon %}{{ action.icon|safe }}{% endif %}
 | |
|                             {{ action.text }}
 | |
|                         </a>
 | |
|                     {% endfor %}
 | |
|                 </div>
 | |
|             {% else %}
 | |
|                 <div class="w-24 h-24 bg-white/20 rounded-full flex items-center justify-center">
 | |
|                     <svg class="w-12 h-12" fill="currentColor" viewBox="0 0 20 20">
 | |
|                         <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
 | |
|                     </svg>
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     </div>
 | |
| </div>
 | |
| 
 | |
| {{ caller() }}
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour section de contenu standardisée #}
 | |
| {% macro content_section(title=None, description=None, actions=[], classes="") %}
 | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden {{ classes }}">
 | |
|     {% if title %}
 | |
|         <div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
 | |
|             <div>
 | |
|                 <h2 class="text-lg font-semibold text-gray-900">{{ title }}</h2>
 | |
|                 {% if description %}
 | |
|                     <p class="text-sm text-gray-600 mt-1">{{ description }}</p>
 | |
|                 {% endif %}
 | |
|             </div>
 | |
|             {% if actions %}
 | |
|                 <div class="flex items-center space-x-2">
 | |
|                     {% for action in actions %}
 | |
|                         <a href="{{ action.url }}" 
 | |
|                            class="text-blue-600 hover:text-blue-800 text-sm font-medium">
 | |
|                             {% if action.icon %}{{ action.icon|safe }}{% endif %}
 | |
|                             {{ action.text }}
 | |
|                         </a>
 | |
|                     {% endfor %}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     {% endif %}
 | |
|     
 | |
|     <div class="p-6">
 | |
|         {{ caller() }}
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour état vide unifié #}
 | |
| {% macro empty_state(title, description, icon, action=None, secondary_action=None) %}
 | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden">
 | |
|     <div class="p-6 text-center py-12">
 | |
|         <div class="w-24 h-24 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center mx-auto mb-6">
 | |
|             {{ icon|safe }}
 | |
|         </div>
 | |
|         
 | |
|         <h3 class="text-xl font-semibold text-gray-900 mb-2">{{ title }}</h3>
 | |
|         <p class="text-base text-gray-600 mb-6 max-w-md mx-auto">{{ description }}</p>
 | |
|         
 | |
|         <div class="space-y-4">
 | |
|             {% if action %}
 | |
|                 <a href="{{ action.url }}" 
 | |
|                    class="inline-flex items-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold">
 | |
|                     {% if action.icon %}{{ action.icon|safe }}{% endif %}
 | |
|                     {{ action.text }}
 | |
|                 </a>
 | |
|             {% endif %}
 | |
|             
 | |
|             {% if secondary_action %}
 | |
|                 <div class="text-sm text-gray-600">
 | |
|                     <p><strong>💡 Astuce :</strong> {{ secondary_action.text }}</p>
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     </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 %}
 | |
| 
 | |
| {# Macro pour tableau standardisé #}
 | |
| {% macro data_table(headers, rows, actions_column=False, zebra=True) %}
 | |
| <div class="overflow-x-auto">
 | |
|     <table class="min-w-full divide-y divide-gray-200">
 | |
|         <thead class="bg-gray-50">
 | |
|             <tr>
 | |
|                 {% for header in headers %}
 | |
|                     <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
 | |
|                         {{ header }}
 | |
|                     </th>
 | |
|                 {% endfor %}
 | |
|                 {% if actions_column %}
 | |
|                     <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
 | |
|                         Actions
 | |
|                     </th>
 | |
|                 {% endif %}
 | |
|             </tr>
 | |
|         </thead>
 | |
|         <tbody class="bg-white divide-y divide-gray-200">
 | |
|             {% for row in rows %}
 | |
|                 <tr class="{% if zebra and loop.index % 2 == 0 %}bg-gray-50{% endif %} hover:bg-blue-50 transition-colors">
 | |
|                     {% for cell in row.cells %}
 | |
|                         <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
 | |
|                             {{ cell|safe }}
 | |
|                         </td>
 | |
|                     {% endfor %}
 | |
|                     {% if actions_column and row.actions %}
 | |
|                         <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
 | |
|                             <div class="flex justify-end space-x-2">
 | |
|                                 {% for action in row.actions %}
 | |
|                                     <a href="{{ action.url }}" 
 | |
|                                        class="text-{{ action.color|default('blue') }}-600 hover:text-{{ action.color|default('blue') }}-900 transition-colors">
 | |
|                                         {{ action.text }}
 | |
|                                     </a>
 | |
|                                 {% endfor %}
 | |
|                             </div>
 | |
|                         </td>
 | |
|                     {% endif %}
 | |
|                 </tr>
 | |
|             {% endfor %}
 | |
|         </tbody>
 | |
|     </table>
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour liste d'éléments uniformisée #}
 | |
| {% macro item_list(items, show_meta=True, show_actions=True) %}
 | |
| <div class="space-tight">
 | |
|     {% for item in items %}
 | |
|         <div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-blue-50 rounded-lg transition-colors group">
 | |
|             <a href="{{ item.url }}" class="flex items-center space-x-4 flex-1 min-w-0">
 | |
|                 {% if item.avatar %}
 | |
|                     <div class="w-10 h-10 {{ item.avatar.classes|default('bg-gradient-to-br from-blue-500 to-purple-600') }} rounded-full flex items-center justify-center text-white font-bold text-sm">
 | |
|                         {{ item.avatar.content|safe }}
 | |
|                     </div>
 | |
|                 {% endif %}
 | |
|                 
 | |
|                 <div class="min-w-0 flex-1">
 | |
|                     <h3 class="text-sm font-semibold text-gray-900 group-hover:text-blue-700 transition-colors truncate">
 | |
|                         {{ item.title }}
 | |
|                     </h3>
 | |
|                     {% if show_meta and item.meta %}
 | |
|                         <div class="flex items-center text-xs text-gray-500 space-x-2 mt-1">
 | |
|                             {% for meta in item.meta %}
 | |
|                                 <span class="flex items-center">
 | |
|                                     {% if meta.icon %}{{ meta.icon|safe }}{% endif %}
 | |
|                                     {{ meta.text }}
 | |
|                                 </span>
 | |
|                                 {% if not loop.last %}<span>•</span>{% endif %}
 | |
|                             {% endfor %}
 | |
|                         </div>
 | |
|                     {% endif %}
 | |
|                 </div>
 | |
|             </a>
 | |
|             
 | |
|             {% if show_actions and item.indicators %}
 | |
|                 <div class="flex items-center space-x-3 flex-shrink-0">
 | |
|                     {% for indicator in item.indicators %}
 | |
|                         {{ indicator|safe }}
 | |
|                     {% endfor %}
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     {% endfor %}
 | |
| </div>
 | |
| {% endmacro %}
 | |
| 
 | |
| {# Macro pour notifications/alertes uniformisées #}
 | |
| {% macro alert(type, title, message, dismissible=True, icon=True) %}
 | |
| {% set alert_classes = {
 | |
|     'info': 'bg-blue-50 border-blue-200 text-blue-800',
 | |
|     'success': 'bg-green-50 border-green-200 text-green-800', 
 | |
|     'warning': 'bg-orange-50 border-orange-200 text-orange-800',
 | |
|     'error': 'bg-red-50 border-red-200 text-red-800'
 | |
| } %}
 | |
| 
 | |
| {% set icons = {
 | |
|     'info': '<svg class="w-5 h-5" 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>',
 | |
|     'success': '<svg class="w-5 h-5" 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>',
 | |
|     'warning': '<svg class="w-5 h-5" 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>',
 | |
|     'error': '<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
 | |
| } %}
 | |
| 
 | |
| <div class="border rounded-lg p-4 {{ alert_classes[type] }} {% if dismissible %}relative{% endif %}" role="alert">
 | |
|     <div class="flex items-start">
 | |
|         {% if icon %}
 | |
|             <div class="flex-shrink-0 mr-3 mt-0.5">
 | |
|                 {{ icons[type]|safe }}
 | |
|             </div>
 | |
|         {% endif %}
 | |
|         <div class="flex-1">
 | |
|             <h3 class="text-sm font-medium">{{ title }}</h3>
 | |
|             <p class="text-sm mt-1">{{ message }}</p>
 | |
|         </div>
 | |
|         {% if dismissible %}
 | |
|             <button type="button" class="flex-shrink-0 ml-3 hover:opacity-75 transition-opacity" 
 | |
|                     onclick="this.parentElement.remove()">
 | |
|                 <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
 | |
|                     <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
 | |
|                 </svg>
 | |
|             </button>
 | |
|         {% endif %}
 | |
|     </div>
 | |
| </div>
 | |
| {% endmacro %} |