Files
notytex/templates/components/common/macros.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 %}