fix: Manual accordion toggle without Bootstrap events

- Removed data-bs-toggle="collapse" to prevent Bootstrap interference
- Handle click manually: toggle class "show" and update icon
- Cookie stores 'expanded'/'collapsed' state per group
- Chevron icon updates correctly on click
This commit is contained in:
mirivlad 2026-04-17 17:32:51 +08:00
parent 68cb135322
commit 32894447f3
1 changed files with 43 additions and 50 deletions

View File

@ -82,8 +82,8 @@
{% set groupSlug = groupName|lower|replace({' ': '-'}) %} {% set groupSlug = groupName|lower|replace({' ': '-'}) %}
<div class="card mb-3 border-0 shadow-sm"> <div class="card mb-3 border-0 shadow-sm">
<div class="card-header text-white py-2 accordion-header" <div class="card-header text-white py-2 accordion-header"
data-bs-toggle="collapse" data-group="{{ groupSlug }}"
data-bs-target="#group-{{ loop.index }}" data-target="#group-{{ loop.index }}"
style="cursor: pointer; background-color: {{ group.color|default('#6c757d') }};"> style="cursor: pointer; background-color: {{ group.color|default('#6c757d') }};">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"> <h5 class="mb-0">
@ -94,7 +94,7 @@
<i class="fas accordion-icon"></i> <i class="fas accordion-icon"></i>
</div> </div>
</div> </div>
<div id="group-{{ loop.index }}" class="collapse" data-group-slug="{{ groupSlug }}"> <div id="group-{{ loop.index }}" class="collapse" data-group="{{ groupSlug }}">
<div class="card-body p-2"> <div class="card-body p-2">
<div class="row g-2"> <div class="row g-2">
{% for server in group.servers %} {% for server in group.servers %}
@ -201,66 +201,59 @@
<script> <script>
(function() { (function() {
const COOKIE_NAME = 'dashboard_accordion'; const COOKIE_NAME = 'dashboard_accordion';
let accordionState = {};
function getAccordionState() { function getCookie(name) {
const cookies = document.cookie.split(';'); const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
for (let c of cookies) { return match ? JSON.parse(decodeURIComponent(match[2])) : {};
c = c.trim();
if (c.startsWith(COOKIE_NAME + '=')) {
try {
return JSON.parse(decodeURIComponent(c.substring(COOKIE_NAME.length + 1)));
} catch(e) {
console.log('Cookie parse error:', e);
}
}
}
return {};
} }
function saveAccordionState(state) { function saveCookie(state) {
document.cookie = COOKIE_NAME + '=' + encodeURIComponent(JSON.stringify(state)) + document.cookie = COOKIE_NAME + '=' + encodeURIComponent(JSON.stringify(state)) +
'; path=/; max-age=' + (30 * 24 * 60 * 60); '; path=/; max-age=' + (30 * 24 * 60 * 60);
} }
function updateIcon(header, isOpen) {
const icon = header ? header.querySelector('.accordion-icon') : null;
if (icon) {
icon.className = isOpen ? 'fas fa-chevron-up' : 'fas fa-chevron-down';
}
}
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const state = getAccordionState(); accordionState = getCookie(COOKIE_NAME);
console.log('Initial state from cookies:', state);
// Инициализация иконок из cookies // Инициализация: устанавливаем начальное состояние из cookies
document.querySelectorAll('.accordion-header').forEach(header => { document.querySelectorAll('.accordion-header').forEach(function(header) {
const groupId = header.dataset.groupSlug; const groupId = header.dataset.group;
const target = header.dataset.bsTarget; const targetId = header.dataset.target;
const isOpen = state[groupId] === 'open'; const target = document.querySelector(targetId);
console.log('Group:', groupId, 'isOpen:', isOpen);
updateIcon(header, isOpen); if (target) {
if (accordionState[groupId] === 'collapsed') {
target.classList.remove('show');
header.querySelector('.accordion-icon').className = 'fas fa-chevron-down';
} else {
// По умолчанию открыто
target.classList.add('show');
header.querySelector('.accordion-icon').className = 'fas fa-chevron-up';
}
}
}); });
// Слушаем события Bootstrap collapse // Клик на заголовке accordion
document.querySelectorAll('.collapse').forEach(el => { document.querySelectorAll('.accordion-header').forEach(function(header) {
el.addEventListener('hidden.bs.collapse', function() { header.addEventListener('click', function(e) {
const groupId = this.dataset.groupSlug; const groupId = this.dataset.group;
state[groupId] = 'closed'; const targetId = this.dataset.target;
saveAccordionState(state); const target = document.querySelector(targetId);
console.log('Hidden:', groupId, 'state:', state); const icon = this.querySelector('.accordion-icon');
const header = document.querySelector('.accordion-header[data-bs-target="#' + this.id + '"]');
updateIcon(header, false);
});
el.addEventListener('shown.bs.collapse', function() { if (target.classList.contains('show')) {
const groupId = this.dataset.groupSlug; // Сворачиваем
state[groupId] = 'open'; target.classList.remove('show');
saveAccordionState(state); icon.className = 'fas fa-chevron-down';
console.log('Shown:', groupId, 'state:', state); accordionState[groupId] = 'collapsed';
const header = document.querySelector('.accordion-header[data-bs-target="#' + this.id + '"]'); } else {
updateIcon(header, true); // Разворачиваем
target.classList.add('show');
icon.className = 'fas fa-chevron-up';
accordionState[groupId] = 'expanded';
}
saveCookie(accordionState);
}); });
}); });