Игорь Надеин 737f0a9623 refactor: Extract sidebar and navbar CSS components
- Created static/css/components/sidebar.css (200 lines)
  * Navigation layout, active states, badges
  * Theme switcher button

- Created static/css/components/navbar.css (30 lines)
  * Menu toggle button
  * Dropdown avatar customization

- Removed ~95 lines of inline styles from sidebar.html
- Removed ~23 lines of inline styles from navbar.html
- All tests passing (17 OK)
2025-10-28 10:03:31 +03:00

165 lines
6.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% load static req_tags %}
<link rel="stylesheet" href="{% static 'css/components/sidebar.css' %}">
<div id="sidebar"
class="offcanvas offcanvas-start offcanvas-lg"
tabindex="-1" aria-labelledby="sidebarLabel"
data-bs-backdrop="false" data-bs-scroll="true">
<div class="offcanvas-header border-bottom d-lg-none">
<h5 class="offcanvas-title" id="sidebarLabel">Меню</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
</div>
<div class="offcanvas-body d-flex flex-column h-100 px-0">
<nav class="side-nav">
<a href="{% url 'feed:feed_list' %}"
class="nav-link {% if request.resolver_match.url_name == 'feed_list' %}active{% endif %}"
{% if request.resolver_match.url_name == 'feed_list' %}aria-current="page"{% endif %}>
<span class="side-icon"><i class="bi-megaphone"></i></span>
<span>Новости</span>
</a>
<a href="{% url 'employees:employees_list' %}"
class="nav-link {% if request.resolver_match.url_name == 'employees_list' %}active{% endif %}"
{% if request.resolver_match.url_name == 'employees_list' %}aria-current="page"{% endif %}>
<span class="side-icon"><i class="bi-people"></i></span>
<span>Сотрудники</span>
</a>
<a href="{% url 'employees:department_list' %}"
class="nav-link {% if request.resolver_match.url_name == 'department_list' %}active{% endif %}"
{% if request.resolver_match.url_name == 'department_list' %}aria-current="page"{% endif %}>
<span class="side-icon"><i class="bi-diagram-3"></i></span>
<span>Отделы</span>
</a>
<a href="{% url 'requests:request_list' %}"
class="nav-link {% if request.resolver_match.url_name == 'request_list' %}active{% endif %}"
{% if request.resolver_match.url_name == 'request_list' %}aria-current="page"{% endif %}>
<span class="side-icon"><i class="bi-clipboard-check"></i></span>
<span>Заявления</span>
</a>
{% with total=chat_unread_total|default:0 %}
<a href="{% url 'communications:chat_list' %}"
class="nav-link {% if request.resolver_match.app_name == 'communications' %}active{% endif %}"
{% if request.resolver_match.app_name == 'communications' %}aria-current="page"{% endif %}
aria-label="Чаты{% if total|add:0 > 0 %}, непрочитанных {{ total }}{% endif %}">
<span class="side-icon icon-wrap">
<i class="bi-chat-dots"></i>
{% if total|add:0 > 0 %}
<span class="nav-unread" id="sidebarChatBadge" data-count="{{ total }}">{{ total }}</span>
{% else %}
<span class="nav-unread d-none" id="sidebarChatBadge" data-count="0">0</span>
{% endif %}
</span>
<span>Чаты</span>
</a>
{% endwith %}
<a href="{% url 'documents:document_list' %}"
class="nav-link {% if request.resolver_match.url_name == 'document_list' %}active{% endif %}"
{% if request.resolver_match.url_name == 'document_list' %}aria-current="page"{% endif %}>
<span class="side-icon"><i class="bi-file-earmark-text"></i></span>
<span>Документы</span>
</a>
</nav>
<div class="mt-auto pt-3 small">
<button type="button" class="btn-theme js-theme-btn" id="themeBtnSidebar"
aria-label="Сменить тему" title="Сменить тему">
<i class="bi bi-circle-half" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<script>
// компактный апдейтер бейджа "Чаты" (форматирует 99+)
(function(){
const badge = document.getElementById('sidebarChatBadge');
if (!badge) return;
function render(n){
const v = Number(n)||0;
badge.dataset.count = String(v);
badge.textContent = v > 99 ? '99+' : String(v);
badge.classList.toggle('d-none', v <= 0);
}
render(badge.dataset.count);
// глобальное событие для real-time апдейта
window.addEventListener('chats:unread-total', (e)=> render(e?.detail?.total));
// fallback: суммируем [data-unread-count] на страницах списка чатов
document.addEventListener('DOMContentLoaded', ()=>{
const rows = document.querySelectorAll('[data-unread-count]');
if (!rows.length) return;
const total = Array.from(rows).reduce((acc, el)=> acc + (Number(el.textContent)||0), 0);
render(total);
});
})();
</script>
<script>
(function(){
const media = window.matchMedia('(prefers-color-scheme: dark)');
const root = document.documentElement;
const MODES = ['light','auto','dark'];
const ICONS = { light:'bi-sun-fill', auto:'bi-circle-half', dark:'bi-moon-stars' };
const LABEL = { light:'Светлая', auto:'Авто', dark:'Темная' };
// какие-то проекты любят хранить 'theme' в LS — используем тот же ключ
function getSavedMode(){ return localStorage.getItem('theme') || 'auto'; }
function setSavedMode(m){ try{ localStorage.setItem('theme', m); }catch(_){} }
function effective(mode){ return mode === 'auto' ? (media.matches ? 'dark' : 'light') : mode; }
function applyMode(mode, persist=true){
const eff = effective(mode);
root.setAttribute('data-bs-theme', eff);
if (persist) setSavedMode(mode);
updateButtons(mode);
}
function updateButtons(mode){
document.querySelectorAll('.js-theme-btn').forEach(btn=>{
const icon = btn.querySelector('.bi');
const lab = btn.querySelector('.mode-label');
// сброс иконок
if (icon){
icon.className = 'bi ' + (ICONS[mode] || ICONS.auto);
}
if (lab){ lab.textContent = LABEL[mode] || LABEL.auto; }
btn.dataset.mode = mode;
btn.title = `Тема: ${LABEL[mode]}. Нажмите, чтобы переключить`;
btn.setAttribute('aria-label', `Тема: ${LABEL[mode]}. Нажмите, чтобы переключить`);
});
}
function nextMode(cur){
const i = MODES.indexOf(cur); return MODES[(i >= 0 ? i : 0) + 1 === MODES.length ? 0 : i + 1];
}
document.addEventListener('DOMContentLoaded', ()=>{
const start = getSavedMode();
applyMode(start, false);
document.body.addEventListener('click', (e)=>{
const btn = e.target.closest('.js-theme-btn'); if (!btn) return;
e.preventDefault();
const cur = btn.dataset.mode || getSavedMode() || 'auto';
const nxt = nextMode(cur);
applyMode(nxt, true);
});
// если системная тема поменялась и у нас «Авто», нужно обновить
media.addEventListener('change', ()=>{ if ((localStorage.getItem('theme') || 'auto') === 'auto'){ applyMode('auto', false); } });
});
})();
</script>