488 lines
25 KiB
HTML
488 lines
25 KiB
HTML
{% extends 'base.html' %}
|
||
{% block title %}Bibliothèque — Admin OpenCare{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="admin-layout">
|
||
{% include 'admin/_sidebar.html' %}
|
||
|
||
<div class="admin-main admin-main--cockpit">
|
||
|
||
{# ── Header ─────────────────────────────────────────────────────── #}
|
||
<div class="lib-header px-4 py-3 border-bottom bg-white d-flex justify-content-between align-items-center flex-shrink-0">
|
||
<div>
|
||
<h1 class="admin-page-title mb-0">
|
||
<i class="fas fa-book-medical me-2 text-accent"></i>Bibliothèque
|
||
</h1>
|
||
<p class="text-muted mb-0 small">{{ recs|length }} recommandation(s) approuvée(s) et active(s)</p>
|
||
</div>
|
||
<div class="d-flex align-items-center gap-3">
|
||
<div class="d-flex gap-1 align-items-center">
|
||
<span class="evidence-badge ev-A">A <span class="ms-1 opacity-75">{{ ev_total.A }}</span></span>
|
||
<span class="evidence-badge ev-B">B <span class="ms-1 opacity-75">{{ ev_total.B }}</span></span>
|
||
<span class="evidence-badge ev-C">C <span class="ms-1 opacity-75">{{ ev_total.C }}</span></span>
|
||
</div>
|
||
<a href="{{ url_for('admin_review', status='pending') }}" class="btn btn-sm btn-primary">
|
||
<i class="fas fa-inbox me-1"></i>File d'attente
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
{% if recs %}
|
||
{# ── Cockpit body ────────────────────────────────────────────────── #}
|
||
<div class="lib-cockpit">
|
||
|
||
{# ── Left tree nav ──────────────────────────────────────────────── #}
|
||
<div class="lib-tree">
|
||
<div class="p-2">
|
||
|
||
<button class="lib-tree-node active" data-filter-domain="" data-filter-tag="">
|
||
<span><i class="fas fa-layer-group me-2 opacity-50" style="font-size:.8rem;"></i>Toutes</span>
|
||
<div class="d-flex align-items-center gap-1 flex-shrink-0">
|
||
{% if ev_total.A %}<span class="lib-tree-ev ev-A">{{ ev_total.A }}</span>{% endif %}
|
||
{% if ev_total.B %}<span class="lib-tree-ev ev-B">{{ ev_total.B }}</span>{% endif %}
|
||
{% if ev_total.C %}<span class="lib-tree-ev ev-C">{{ ev_total.C }}</span>{% endif %}
|
||
<span class="badge bg-secondary rounded-pill ms-1" style="font-size:.65rem;">{{ recs|length }}</span>
|
||
</div>
|
||
</button>
|
||
|
||
<div class="my-2" style="border-top:1px solid var(--border);"></div>
|
||
|
||
{% for domain_key, label in domain_labels.items() %}
|
||
{% if domain_key in tree %}
|
||
{% set node = tree[domain_key] %}
|
||
<div class="mb-1">
|
||
<button class="lib-tree-node lib-tree-domain-btn"
|
||
data-filter-domain="{{ domain_key }}" data-filter-tag=""
|
||
{% if node.tags %}data-bs-toggle="collapse" data-bs-target="#tt-{{ domain_key }}"{% endif %}>
|
||
<span class="text-truncate">{{ label[1] }} {{ label[0] }}</span>
|
||
<div class="d-flex align-items-center gap-1 flex-shrink-0">
|
||
{% if node.ev.A %}<span class="lib-tree-ev ev-A">{{ node.ev.A }}</span>{% endif %}
|
||
{% if node.ev.B %}<span class="lib-tree-ev ev-B">{{ node.ev.B }}</span>{% endif %}
|
||
{% if node.ev.C %}<span class="lib-tree-ev ev-C">{{ node.ev.C }}</span>{% endif %}
|
||
<span class="badge bg-secondary rounded-pill ms-1" style="font-size:.65rem;">{{ node.recs|length }}</span>
|
||
{% if node.tags %}<i class="fas fa-chevron-right lib-tree-chevron ms-1 text-muted" style="font-size:.6rem;transition:transform .2s;"></i>{% endif %}
|
||
</div>
|
||
</button>
|
||
|
||
{% if node.tags %}
|
||
<div class="collapse" id="tt-{{ domain_key }}">
|
||
{% for tag, tnode in node.tags.items()|sort %}
|
||
<button class="lib-tree-node lib-tree-tag"
|
||
data-filter-domain="{{ domain_key }}" data-filter-tag="{{ tag }}">
|
||
<span class="text-truncate">
|
||
<i class="fas fa-hashtag me-1 opacity-40" style="font-size:.6rem;"></i>{{ tag }}
|
||
</span>
|
||
<div class="d-flex align-items-center gap-1 flex-shrink-0">
|
||
{% if tnode.ev.A %}<span class="lib-tree-ev ev-A">{{ tnode.ev.A }}</span>{% endif %}
|
||
{% if tnode.ev.B %}<span class="lib-tree-ev ev-B">{{ tnode.ev.B }}</span>{% endif %}
|
||
{% if tnode.ev.C %}<span class="lib-tree-ev ev-C">{{ tnode.ev.C }}</span>{% endif %}
|
||
<span class="badge bg-secondary rounded-pill ms-1" style="font-size:.65rem;">{{ tnode.recs|length }}</span>
|
||
</div>
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{# ── Right cards panel ──────────────────────────────────────────── #}
|
||
<div class="lib-cards">
|
||
|
||
<div id="lib-empty" class="text-center py-5 text-muted d-none">
|
||
<i class="fas fa-filter fa-2x mb-3 opacity-25"></i>
|
||
<p class="mb-0">Aucune recommandation dans cette catégorie.</p>
|
||
</div>
|
||
|
||
{% for rec in recs %}
|
||
{% set is_universal = not rec.required_conditions and not rec.required_health_goals and not rec.required_diet_types and not rec.required_activity_levels %}
|
||
{% set has_gates = rec.not_for_under_18 or rec.not_for_pregnant or (rec.target_sex and rec.target_sex != 'all') or rec.target_age_min or rec.target_age_max or rec.excluded_conditions %}
|
||
|
||
<div class="lib-card mb-2"
|
||
data-domain="{{ rec.domain }}"
|
||
data-tags="{{ rec.tags | join(' ') | lower }}"
|
||
id="rec-{{ rec.id }}">
|
||
|
||
{# Card header — click to expand #}
|
||
<div class="lib-card-head d-flex align-items-start gap-2 p-3"
|
||
data-bs-toggle="collapse" data-bs-target="#cb-{{ rec.id }}"
|
||
aria-expanded="false" aria-controls="cb-{{ rec.id }}">
|
||
<span class="evidence-badge ev-{{ rec.evidence_level }} flex-shrink-0" style="margin-top:2px;">{{ rec.evidence_level }}</span>
|
||
<div class="flex-grow-1 min-w-0">
|
||
<div class="fw-semibold" style="line-height:1.3;">{{ rec.title }}</div>
|
||
<div class="d-flex flex-wrap gap-1 mt-1">
|
||
{% if is_universal %}
|
||
<span class="tag-pill tag-pill-green">🌍 Universel</span>
|
||
{% else %}
|
||
<span class="tag-pill tag-pill-blue">🎯 Ciblé</span>
|
||
{% endif %}
|
||
{% if rec.target_sex == 'male' %}<span class="tag-pill">♂ Hommes</span>
|
||
{% elif rec.target_sex == 'female' %}<span class="tag-pill">♀ Femmes</span>{% endif %}
|
||
{% if rec.target_age_min or rec.target_age_max %}
|
||
<span class="tag-pill">{{ rec.target_age_min or '?' }}–{{ rec.target_age_max or '∞' }} ans</span>
|
||
{% endif %}
|
||
{% for t in rec.tags[:4] %}<span class="tag-pill">{{ t }}</span>{% endfor %}
|
||
</div>
|
||
</div>
|
||
<i class="fas fa-chevron-down lib-chevron text-muted flex-shrink-0" style="margin-top:4px;"></i>
|
||
</div>
|
||
|
||
{# Card body — collapsible #}
|
||
<div class="collapse" id="cb-{{ rec.id }}">
|
||
<div class="lib-card-body border-top px-3 pb-3 pt-2">
|
||
|
||
{% if rec.summary %}
|
||
<p class="text-muted small mb-2">{{ rec.summary }}</p>
|
||
{% endif %}
|
||
{% if rec.details %}
|
||
<p class="small mb-3 text-secondary">{{ rec.details }}</p>
|
||
{% endif %}
|
||
|
||
{# Intervention + Effect #}
|
||
{% if rec.intervention or rec.effect %}
|
||
<div class="row g-2 mb-3">
|
||
{% if rec.intervention %}
|
||
<div class="col-md-6">
|
||
<div class="p-2 rounded-2" style="background:#f0fdf4;border:1px solid #bbf7d0;">
|
||
<div class="small fw-semibold text-success mb-1"><i class="fas fa-play-circle me-1"></i>Intervention</div>
|
||
<div class="small">{{ rec.intervention }}</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if rec.effect %}
|
||
<div class="col-md-6">
|
||
<div class="p-2 rounded-2" style="background:#eff6ff;border:1px solid #bfdbfe;">
|
||
<div class="small fw-semibold text-primary mb-1"><i class="fas fa-chart-line me-1"></i>Effet attendu</div>
|
||
<div class="small">{{ rec.effect }}</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Source citation #}
|
||
{% if rec.source_title %}
|
||
<div class="small text-muted mb-3 p-2 rounded-2" style="background:#f8fafc;border:1px solid var(--border);">
|
||
<i class="fas fa-book me-1"></i>
|
||
<em>{{ rec.source_title }}</em>
|
||
{% if rec.source_doi %} — <code class="small">{{ rec.source_doi }}</code>{% endif %}
|
||
{% if rec.study_type %}<span class="tag-pill ms-1">{{ rec.study_type.replace('_',' ') }}</span>{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# ── Targeting breakdown ─────────────────────────────────── #}
|
||
<div class="targeting-panel p-3 mb-3">
|
||
<div class="d-flex align-items-center gap-2 mb-2 flex-wrap">
|
||
<span class="text-uppercase fw-bold text-muted" style="font-size:.65rem;letter-spacing:.06em;">Ciblage profil</span>
|
||
{% if is_universal %}
|
||
<span class="tag-pill tag-pill-green">🌍 Universel — affiché à tous</span>
|
||
{% else %}
|
||
<span class="tag-pill tag-pill-blue">🎯 Conditionnel</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if has_gates %}
|
||
<div class="mb-2">
|
||
<div class="small fw-semibold text-danger mb-1" style="font-size:.75rem;">
|
||
<i class="fas fa-ban me-1"></i>Exclusions (portes dures — toutes doivent passer)
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-1">
|
||
{% if rec.not_for_under_18 %}<span class="tag-pill tag-pill-red">✗ Pas < 18 ans</span>{% endif %}
|
||
{% if rec.not_for_pregnant %}<span class="tag-pill tag-pill-red">✗ Pas grossesse</span>{% endif %}
|
||
{% if rec.target_sex == 'male' %}<span class="tag-pill tag-pill-red">♂ Hommes uniquement</span>{% endif %}
|
||
{% if rec.target_sex == 'female' %}<span class="tag-pill tag-pill-red">♀ Femmes uniquement</span>{% endif %}
|
||
{% if rec.target_age_min %}<span class="tag-pill tag-pill-red">≥ {{ rec.target_age_min }} ans</span>{% endif %}
|
||
{% if rec.target_age_max %}<span class="tag-pill tag-pill-red">≤ {{ rec.target_age_max }} ans</span>{% endif %}
|
||
{% for c in rec.excluded_conditions %}
|
||
<span class="tag-pill tag-pill-red">Exclu si: {{ condition_labels.get(c, c) }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if not is_universal %}
|
||
<div>
|
||
<div class="small fw-semibold text-primary mb-1" style="font-size:.75rem;">
|
||
<i class="fas fa-bolt me-1"></i>Déclencheurs — logique OU (un groupe suffit)
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-1">
|
||
{% for c in rec.required_conditions %}
|
||
<span class="tag-pill tag-pill-blue">Si: {{ condition_labels.get(c, c) }}</span>
|
||
{% endfor %}
|
||
{% for g in rec.required_health_goals %}
|
||
<span class="tag-pill tag-pill-blue">Objectif: {{ goal_labels.get(g, g) }}</span>
|
||
{% endfor %}
|
||
{% for d in rec.required_diet_types %}
|
||
<span class="tag-pill tag-pill-blue">Régime: {{ diet_labels.get(d, d) }}</span>
|
||
{% endfor %}
|
||
{% for a in rec.required_activity_levels %}
|
||
<span class="tag-pill tag-pill-blue">Activité: {{ activity_labels.get(a, a) }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="text-muted small mb-0">
|
||
<i class="fas fa-info-circle me-1"></i>Aucun critère de déclenchement — affichée à tous les utilisateurs (sous réserve des exclusions ci-dessus si définies).
|
||
</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Actions #}
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-sm btn-outline-primary"
|
||
data-bs-toggle="modal" data-bs-target="#modal-{{ rec.id }}">
|
||
<i class="fas fa-sliders-h me-1"></i>Modifier le ciblage
|
||
</button>
|
||
<form action="{{ url_for('admin_library_delete', rid=rec.id) }}" method="post"
|
||
onsubmit="return confirm('Archiver cette recommandation ?')">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||
<i class="fas fa-archive me-1"></i>Archiver
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ── Edit targeting modal ───────────────────────────────────── #}
|
||
<div class="modal fade" id="modal-{{ rec.id }}" tabindex="-1">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<form action="{{ url_for('admin_library_update', rid=rec.id) }}" method="POST">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-sliders-h me-2"></i>Ciblage
|
||
<span class="text-muted fw-normal small ms-1">— {{ rec.title[:60] }}{% if rec.title|length > 60 %}…{% endif %}</span>
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
|
||
{# Demographics #}
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Sexe cible</label>
|
||
<select name="target_sex" class="form-select form-select-sm">
|
||
<option value="all" {{ 'selected' if rec.target_sex in ('all', None, '') }}>Tous</option>
|
||
<option value="male" {{ 'selected' if rec.target_sex == 'male' }}>Hommes ♂</option>
|
||
<option value="female" {{ 'selected' if rec.target_sex == 'female' }}>Femmes ♀</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Âge min</label>
|
||
<input type="number" name="target_age_min" class="form-control form-control-sm"
|
||
value="{{ rec.target_age_min or '' }}" placeholder="—" min="0" max="120">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Âge max</label>
|
||
<input type="number" name="target_age_max" class="form-control form-control-sm"
|
||
value="{{ rec.target_age_max or '' }}" placeholder="—" min="0" max="120">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Priorité (1–10)</label>
|
||
<input type="number" name="priority" class="form-control form-control-sm"
|
||
value="{{ rec.priority }}" min="1" max="10">
|
||
</div>
|
||
<div class="col-md-8 d-flex align-items-end gap-4 pb-1">
|
||
<div class="form-check">
|
||
<input type="checkbox" name="not_for_under_18" class="form-check-input"
|
||
id="u18-{{ rec.id }}" {{ 'checked' if rec.not_for_under_18 }}>
|
||
<label class="form-check-label small" for="u18-{{ rec.id }}">
|
||
<span class="tag-pill tag-pill-red">Exclure < 18 ans</span>
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input type="checkbox" name="not_for_pregnant" class="form-check-input"
|
||
id="preg-{{ rec.id }}" {{ 'checked' if rec.not_for_pregnant }}>
|
||
<label class="form-check-label small" for="preg-{{ rec.id }}">
|
||
<span class="tag-pill tag-pill-red">Exclure grossesse</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-3">
|
||
|
||
{# Conditions #}
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold text-danger">
|
||
<i class="fas fa-ban me-1"></i>Conditions exclues
|
||
<span class="text-muted fw-normal">(porte dure)</span>
|
||
</label>
|
||
<div class="multi-check-grid">
|
||
{% for c in conditions %}
|
||
<label class="multi-check-item form-check">
|
||
<input type="checkbox" name="excluded_conditions" value="{{ c }}"
|
||
class="form-check-input" {{ 'checked' if c in rec.excluded_conditions }}>
|
||
{{ condition_labels.get(c, c) }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold text-primary">
|
||
<i class="fas fa-bolt me-1"></i>Conditions déclenchantes
|
||
<span class="text-muted fw-normal">(OU)</span>
|
||
</label>
|
||
<div class="multi-check-grid">
|
||
{% for c in conditions %}
|
||
<label class="multi-check-item form-check">
|
||
<input type="checkbox" name="required_conditions" value="{{ c }}"
|
||
class="form-check-input" {{ 'checked' if c in rec.required_conditions }}>
|
||
{{ condition_labels.get(c, c) }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Goals + Diet #}
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold text-primary">
|
||
<i class="fas fa-bullseye me-1"></i>Objectifs santé
|
||
<span class="text-muted fw-normal">(OU)</span>
|
||
</label>
|
||
<div class="multi-check-grid">
|
||
{% for g in health_goals %}
|
||
<label class="multi-check-item form-check">
|
||
<input type="checkbox" name="required_health_goals" value="{{ g }}"
|
||
class="form-check-input" {{ 'checked' if g in rec.required_health_goals }}>
|
||
{{ goal_labels.get(g, g) }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold text-primary">
|
||
<i class="fas fa-utensils me-1"></i>Types de régime
|
||
<span class="text-muted fw-normal">(OU)</span>
|
||
</label>
|
||
<div class="multi-check-grid">
|
||
{% for d in diet_types %}
|
||
<label class="multi-check-item form-check">
|
||
<input type="checkbox" name="required_diet_types" value="{{ d }}"
|
||
class="form-check-input" {{ 'checked' if d in rec.required_diet_types }}>
|
||
{{ diet_labels.get(d, d) }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Activity #}
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold text-primary">
|
||
<i class="fas fa-running me-1"></i>Niveaux d'activité
|
||
<span class="text-muted fw-normal">(OU)</span>
|
||
</label>
|
||
<div class="multi-check-grid">
|
||
{% for a in activity_levels %}
|
||
<label class="multi-check-item form-check">
|
||
<input type="checkbox" name="required_activity_levels" value="{{ a }}"
|
||
class="form-check-input" {{ 'checked' if a in rec.required_activity_levels }}>
|
||
{{ activity_labels.get(a, a) }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-3">
|
||
|
||
<div>
|
||
<label class="form-label small fw-semibold">Notes admin</label>
|
||
<textarea name="admin_notes" class="form-control form-control-sm" rows="2"
|
||
placeholder="Notes internes (non visibles par les utilisateurs)">{{ rec.admin_notes or '' }}</textarea>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal">Annuler</button>
|
||
<button type="submit" class="btn btn-sm btn-primary">
|
||
<i class="fas fa-save me-1"></i>Enregistrer le ciblage
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
{% else %}
|
||
<div class="text-center py-5 text-muted">
|
||
<i class="fas fa-book-medical fa-3x mb-3 opacity-25"></i>
|
||
<p class="mb-0">Aucune recommandation approuvée. Allez dans la file d'attente pour en valider.</p>
|
||
<a href="{{ url_for('admin_review', status='pending') }}" class="btn btn-primary mt-3">
|
||
<i class="fas fa-inbox me-1"></i>File d'attente
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
(function () {
|
||
// ── Tree filtering ────────────────────────────────────────────────
|
||
document.querySelectorAll('.lib-tree-node').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
var domain = this.dataset.filterDomain;
|
||
var tag = this.dataset.filterTag;
|
||
|
||
document.querySelectorAll('.lib-tree-node').forEach(function (b) {
|
||
b.classList.remove('active');
|
||
});
|
||
this.classList.add('active');
|
||
|
||
var visible = 0;
|
||
document.querySelectorAll('.lib-card').forEach(function (card) {
|
||
var show;
|
||
if (!domain) {
|
||
show = true;
|
||
} else if (!tag) {
|
||
show = card.dataset.domain === domain;
|
||
} else {
|
||
var tags = (card.dataset.tags || '').split(' ');
|
||
show = card.dataset.domain === domain && tags.indexOf(tag) !== -1;
|
||
}
|
||
card.style.display = show ? '' : 'none';
|
||
if (show) visible++;
|
||
});
|
||
|
||
var emptyEl = document.getElementById('lib-empty');
|
||
if (emptyEl) emptyEl.classList.toggle('d-none', visible > 0);
|
||
});
|
||
});
|
||
|
||
// ── Tree chevron rotation on collapse ────────────────────────────
|
||
document.querySelectorAll('.lib-tree-domain-btn[data-bs-toggle="collapse"]').forEach(function (btn) {
|
||
var target = document.querySelector(btn.dataset.bsTarget);
|
||
if (!target) return;
|
||
var chevron = btn.querySelector('.lib-tree-chevron');
|
||
if (!chevron) return;
|
||
target.addEventListener('show.bs.collapse', function () {
|
||
chevron.style.transform = 'rotate(90deg)';
|
||
});
|
||
target.addEventListener('hide.bs.collapse', function () {
|
||
chevron.style.transform = '';
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|