From 598f20f95a1219ee841fe1ab98c1e7b00dccb04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 30 Nov 2025 09:44:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20chat=5Flist.html:=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D1=8F,=20=D1=81=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D0=B8=D0=B5=20=D0=BF=D1=83=D1=81=D1=82=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA,=20=D0=BE=D0=BF=D1=82?= =?UTF-8?q?=D0=B8=D0=BC=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20JS=20=D0=B8?= =?UTF-8?q?=20CSS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHAT_LIST_SIDEBAR_FILTER.md | 121 +++++++++++ .../MODERN_CHAT_LIST_REDESIGN.md | 0 .../css/components/chat-list-enhanced.css | 173 +++++++++++++-- .../css/components/chat-list-modern.css | 1 + .../css/components/chat-list-modern.css.map | 1 + .../scss/components/_chat-list-modern.scss | 0 .../templates/communications/chat_list.html | 203 +++++++++++++++--- 7 files changed, 448 insertions(+), 51 deletions(-) create mode 100644 CHAT_LIST_SIDEBAR_FILTER.md create mode 100644 backend/communications/MODERN_CHAT_LIST_REDESIGN.md create mode 100644 backend/static/css/components/chat-list-modern.css create mode 100644 backend/static/css/components/chat-list-modern.css.map create mode 100644 backend/static/scss/components/_chat-list-modern.scss diff --git a/CHAT_LIST_SIDEBAR_FILTER.md b/CHAT_LIST_SIDEBAR_FILTER.md new file mode 100644 index 00000000..f7f082e0 --- /dev/null +++ b/CHAT_LIST_SIDEBAR_FILTER.md @@ -0,0 +1,121 @@ +# Фильтрация чатов с боковой панелью типов + +## Описание изменений + +Страница списка чатов (`/communications/chats/`) была реорганизована с добавлением боковой панели для фильтрации по типам чатов. + +## Структура + +### Двухколоночный макет + +1. **Левая колонка (боковая панель)** - фиксированная ширина (120px на desktop, адаптивно до 60px на mobile) + - Кнопки-иконки для каждого типа чата + - Бейджи с количеством чатов в каждой категории + - Sticky-позиционирование (прилипает при скролле) + +2. **Правая колонка (основной список)** - flex-grow для занятия оставшегося пространства + - Все секции чатов (глобальный, отделы, личные, группы, каналы, объявления) + - Поиск и существующие фильтры + +## Типы чатов + +Доступные типы для фильтрации: + +- **Все** (`all`) - показывает все чаты +- **Глобальный** (`global`) - глобальный чат организации +- **Отделы** (`department`) - чаты отделов +- **Личные** (`private`) - личные чаты между пользователями +- **Группы** (`group`) - групповые чаты +- **Каналы** (`channel`) - каналы для широковещательных сообщений +- **Объявления** (`announcement`) - каналы объявлений + +## Функциональность + +### Фильтрация + +- Клик по кнопке типа чата показывает только чаты этого типа +- Кнопка "Все" показывает все чаты +- Активная кнопка подсвечивается синим цветом +- При наведении иконка увеличивается (scale 1.1) + +### Счетчики + +- Каждая кнопка показывает количество чатов в категории +- Счетчики обновляются динамически при поиске +- Пустые категории скрывают бейдж (display: none) + +### Адаптивность + +**Desktop (>992px):** +- Ширина панели: 120px +- Отображаются иконки + текст + бейджи + +**Tablet (768-992px):** +- Ширина панели: 90px +- Уменьшенный шрифт текста + +**Mobile (576-768px):** +- Ширина панели: 70px +- Еще более компактный вид + +**Small Mobile (<576px):** +- Ширина панели: 60px +- Скрывается текст, остаются только иконки +- Только title атрибут для понимания назначения + +## Файлы изменений + +### HTML +`backend/templates/communications/chat_list.html` +- Добавлена структура `.d-flex` с `.chat-type-sidebar` и `.chat-list-main` +- Кнопки фильтрации с иконками и бейджами +- JavaScript функции для подсчета и фильтрации + +### CSS +`backend/static/css/components/chat-list-enhanced.css` +- Стили для `.chat-type-sidebar` +- Стили для `.chat-type-btn` (обычное, hover, active состояния) +- Адаптивные медиа-запросы для всех размеров экрана +- Sticky-позиционирование боковой панели + +## Интеграция с существующими функциями + +### Поиск +- Поиск работает в рамках отфильтрованных секций +- Счетчики обновляются после применения поиска +- При смене фильтра поиск переприменяется + +### WebSocket +- Обновления чатов в реальном времени сохраняются +- Счетчики могут быть обновлены при получении новых сообщений + +### Создание чата +- Модальное окно создания чата не изменено +- После создания нового чата рекомендуется добавить обновление счетчиков + +## Будущие улучшения + +1. **Динамическое обновление счетчиков при WebSocket событиях** + - Обновлять бейджи при получении новых сообщений + - Показывать непрочитанные сообщения в каждой категории + +2. **Сохранение выбранного фильтра в localStorage** + - Запоминать последний выбранный тип + - Восстанавливать при следующем посещении + +3. **Анимация при переключении** + - Плавное появление/исчезновение секций + - Transition для смены активной кнопки + +4. **Drag & Drop для переупорядочивания кнопок** + - Позволить пользователю настроить порядок типов + - Сохранять настройку в профиле + +## Тестирование + +- ✅ Переключение между типами чатов +- ✅ Подсчет количества чатов +- ✅ Адаптивность на разных экранах +- ✅ Совместимость с поиском +- ✅ Sticky-позиционирование боковой панели +- ⏳ WebSocket обновления счетчиков (требует доработки) diff --git a/backend/communications/MODERN_CHAT_LIST_REDESIGN.md b/backend/communications/MODERN_CHAT_LIST_REDESIGN.md new file mode 100644 index 00000000..e69de29b diff --git a/backend/static/css/components/chat-list-enhanced.css b/backend/static/css/components/chat-list-enhanced.css index b8f2fda5..e892b86f 100644 --- a/backend/static/css/components/chat-list-enhanced.css +++ b/backend/static/css/components/chat-list-enhanced.css @@ -1,10 +1,12 @@ +/* ===== CHAT LIST ENHANCED STYLES ===== */ + /* Context Menu Styles */ .context-menu { position: absolute; background: white; border: 1px solid #dee2e6; border-radius: 8px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 4px 0; min-width: 200px; z-index: 9999; @@ -32,12 +34,31 @@ border-color: #dee2e6; } -/* Chat List Update Animation */ +/* Chat List Animations */ +.chat-row { + border-top: 1px solid var(--bs-border-color); +} + .chat-row.updated { background: #e3f2fd; transition: background 0.3s ease-out; } +/* Chat Section Headers */ +.chat-section .card-header { + text-align: center; + border-bottom: 1px solid var(--bs-border-color); +} + +.chat-section .card-header .card-icon { + display: none; +} + +.chat-section .card-header .card-meta { + width: 100%; + text-align: center; +} + /* Pinned Chat Indicator */ .chat-row.pinned { background: #fffbeb; @@ -61,12 +82,13 @@ } /* Chat Avatars */ -.chat-avatar { +.chat-avatar, +.card-icon img { width: 44px; height: 44px; - border-radius: 50%; + border-radius: 8px; /* Овальная форма вместо круглой */ object-fit: cover; - border: 2px solid #dee2e6; + border: 2px solid var(--bs-border-color); } .chat-avatar-group { @@ -77,29 +99,17 @@ .chat-avatar-group img { margin-left: -8px; border: 2px solid white; + border-radius: 8px; } .chat-avatar-group img:first-child { margin-left: 0; } -/* Chat Actions Menu Button */ +/* Chat Actions Menu */ .chat-actions-menu { display: flex; align-items: center; - position: static !important; -} - -.chat-row { - overflow: visible !important; -} - -.list-chats { - overflow: visible !important; -} - -.card { - overflow: visible !important; } .chat-actions-menu .btn { @@ -119,8 +129,7 @@ .chat-actions-menu .dropdown-menu { font-size: 0.9rem; min-width: 200px; - position: absolute !important; - z-index: 1050 !important; + z-index: 1050; } .chat-actions-menu .dropdown-item { @@ -131,3 +140,125 @@ width: 20px; text-align: center; } + +/* ===== CHAT TYPE SIDEBAR ===== */ + +:root { + --chat-sidebar-offset: 120px; +} + +.chat-type-sidebar { + flex-shrink: 0; + align-self: flex-start; + position: sticky; + top: calc(var(--navbar-h, 60px) + var(--chat-sidebar-offset, 120px)); +} + +.chat-type-sidebar .card { + max-height: calc(100vh - var(--navbar-h, 60px) - var(--chat-sidebar-offset, 120px) - 24px); + overflow-y: auto; +} + +.chat-type-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.35rem; + position: relative; + cursor: pointer; + transition: background-color 0.2s ease, opacity 0.2s ease; +} + +.chat-type-btn[style*="display: none"] { + display: none !important; +} + +.chat-type-btn:hover { + background-color: var(--bs-light) !important; +} + +.chat-type-btn.active { + background-color: var(--bs-primary) !important; + color: white !important; +} + +.chat-type-btn.active i { + color: white; +} + +.chat-type-btn .badge { + position: absolute; + top: 0.25rem; + right: 0.25rem; + min-width: 1.25rem; + height: 1.25rem; + padding: 0.15rem 0.3rem; + border-radius: 50%; + font-size: 0.65rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.chat-type-btn.active .badge { + background-color: rgba(255, 255, 255, 0.3) !important; + color: white !important; + border-color: white !important; +} + +/* ===== MOBILE VERSION ===== */ + +@media (max-width: 768px) { + .chat-list-wrapper { + flex-direction: column !important; + } + + .chat-type-sidebar { + width: 100% !important; + order: -1; + position: static; + } + + .chat-type-sidebar .card { + max-height: none; + overflow: hidden; + padding: 0.25rem 0.5rem; + width: 100%; + } + + .chat-type-sidebar .list-group { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: 0.5rem; + width: 100%; + overflow-x: auto; + overflow-y: hidden; + padding: 0 0.25rem 0.25rem; + scrollbar-width: thin; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; + } + + .chat-type-btn { + flex: 0 0 auto; + min-width: 60px; + max-width: 80px; + padding: 0.5rem 0.25rem !important; + border-radius: 0.75rem !important; + border: 1px solid var(--bs-border-color) !important; + scroll-snap-align: start; + } + + .chat-type-btn i { + font-size: 1.25rem !important; + } + + .chat-type-btn .badge { + font-size: 0.6rem; + min-width: 1.1rem; + height: 1.1rem; + } +} diff --git a/backend/static/css/components/chat-list-modern.css b/backend/static/css/components/chat-list-modern.css new file mode 100644 index 00000000..1b2f7f26 --- /dev/null +++ b/backend/static/css/components/chat-list-modern.css @@ -0,0 +1 @@ +.chat-list-container{display:flex;gap:0;min-height:calc(100vh - 150px);background:var(--bs-body-bg)}@media(max-width: 767.98px){.chat-list-container{flex-direction:column;gap:1rem}}.chat-types-panel{width:80px;background:var(--bs-card-bg, #ffffff);border-right:1px solid var(--bs-border-color);border-radius:12px 0 0 12px;padding:1rem 0;display:flex;flex-direction:column;gap:.5rem;position:sticky;top:80px;align-self:flex-start;max-height:calc(100vh - 120px);overflow-y:auto}@media(max-width: 767.98px){.chat-types-panel{width:100%;flex-direction:row;border-radius:12px;border-right:none;border-bottom:1px solid var(--bs-border-color);position:static;max-height:none;overflow-x:auto;overflow-y:hidden;padding:.75rem;gap:.75rem}.chat-types-panel::-webkit-scrollbar{height:4px}}.chat-type-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.25rem;padding:.75rem .5rem;border:none;background:rgba(0,0,0,0);color:var(--bs-secondary);cursor:pointer;border-radius:8px;transition:all .2s ease;position:relative;min-width:64px}.chat-type-btn:hover{background:rgba(var(--bs-primary-rgb), 0.1);color:var(--bs-primary)}.chat-type-btn.active{background:var(--bs-primary);color:#fff;box-shadow:0 2px 8px rgba(var(--bs-primary-rgb), 0.3)}.chat-type-btn.active .chat-type-badge{background:#fff;color:var(--bs-primary)}.chat-type-btn i{font-size:1.5rem;line-height:1}.chat-type-btn .chat-type-label{font-size:.7rem;font-weight:500;text-align:center;line-height:1.2;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-type-btn .chat-type-badge{position:absolute;top:8px;right:8px;background:var(--bs-danger);color:#fff;border-radius:10px;padding:0 6px;font-size:.65rem;font-weight:600;min-width:18px;height:18px;display:flex;align-items:center;justify-content:center;line-height:1}.chat-type-btn .chat-type-badge.zero{display:none}@media(max-width: 767.98px){.chat-type-btn{flex-direction:row;gap:.5rem;padding:.5rem 1rem;min-width:auto}.chat-type-btn i{font-size:1.25rem}.chat-type-btn .chat-type-label{font-size:.85rem;max-width:none;white-space:nowrap}.chat-type-btn .chat-type-badge{position:relative;top:auto;right:auto;margin-left:.25rem}}.chat-list-panel{flex:1;background:var(--bs-card-bg, #ffffff);border-radius:0 12px 12px 0;padding:0;min-width:0}@media(max-width: 767.98px){.chat-list-panel{border-radius:12px}}.chat-list-header{padding:1.25rem 1.5rem;border-bottom:1px solid var(--bs-border-color);display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap}.chat-list-header h5{margin:0;font-size:1.25rem;font-weight:600;color:var(--bs-body-color);display:flex;align-items:center;gap:.5rem}.chat-list-header h5 i{font-size:1.5rem;color:var(--bs-primary)}.chat-list-header .chat-count-badge{background:rgba(var(--bs-primary-rgb), 0.1);color:var(--bs-primary);padding:.25rem .75rem;border-radius:12px;font-size:.85rem;font-weight:600}.chat-list-body{padding:0;max-height:calc(100vh - 300px);overflow-y:auto}@media(max-width: 767.98px){.chat-list-body{max-height:none}}.chat-list-body::-webkit-scrollbar{width:8px}.chat-list-body::-webkit-scrollbar-track{background:rgba(0,0,0,0)}.chat-list-body::-webkit-scrollbar-thumb{background:var(--bs-border-color);border-radius:4px}.chat-list-body::-webkit-scrollbar-thumb:hover{background:var(--bs-secondary)}.chat-row-modern{display:flex;align-items:start;gap:.75rem;padding:1rem 1.5rem;border-bottom:1px solid var(--bs-border-color-translucent);transition:all .2s ease;position:relative;cursor:pointer}.chat-row-modern:last-child{border-bottom:none}.chat-row-modern:hover{background:rgba(var(--bs-primary-rgb), 0.03)}.chat-row-modern.pinned{background:linear-gradient(90deg, rgba(255, 193, 7, 0.08) 0%, transparent 100%);border-left:3px solid var(--bs-warning)}.chat-row-modern.pinned::before{content:"";font-family:"bootstrap-icons";position:absolute;top:.75rem;right:1rem;color:var(--bs-warning);font-size:.875rem;z-index:1}.chat-row-modern.unread{background:rgba(var(--bs-info-rgb), 0.05);border-left:3px solid var(--bs-info)}.chat-row-modern.updated{animation:pulse-update .6s ease-out}@media(max-width: 767.98px){.chat-row-modern{padding:.875rem 1rem;gap:.625rem}}@keyframes pulse-update{0%{background:rgba(var(--bs-success-rgb), 0.2)}100%{background:rgba(0,0,0,0)}}.chat-row-avatar{width:48px;height:48px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:1.25rem;background:linear-gradient(135deg, rgba(var(--bs-primary-rgb), 0.1), rgba(var(--bs-primary-rgb), 0.05));color:var(--bs-primary);overflow:hidden;position:relative}.chat-row-avatar img{width:100%;height:100%;object-fit:cover}.chat-row-avatar.status-online::after{content:"";position:absolute;bottom:2px;right:2px;width:12px;height:12px;background:var(--bs-success);border:2px solid var(--bs-card-bg, white);border-radius:50%}@media(max-width: 767.98px){.chat-row-avatar{width:44px;height:44px;font-size:1.1rem}}.chat-row-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.25rem}.chat-row-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.chat-row-title{font-size:.95rem;font-weight:600;color:var(--bs-body-color);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-decoration:none}.chat-row-title:hover{color:var(--bs-primary)}.chat-row-time{font-size:.75rem;color:var(--bs-secondary);white-space:nowrap;flex-shrink:0}.chat-row-preview{display:flex;align-items:center;gap:.5rem;font-size:.85rem;color:var(--bs-secondary)}.chat-row-preview .preview-author{font-weight:500;color:var(--bs-body-color);flex-shrink:0}.chat-row-preview .preview-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.chat-row-meta{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.chat-row-meta .badge{font-size:.7rem;padding:.25rem .5rem;font-weight:500}.chat-row-meta .unread-badge{background:var(--bs-danger);color:#fff;border-radius:10px;padding:.125rem .5rem;font-size:.7rem;font-weight:600;min-width:20px;text-align:center}.chat-row-actions{position:absolute;top:1rem;right:1rem;opacity:0;transition:opacity .2s;z-index:10}.chat-row-modern:hover .chat-row-actions{opacity:1}.chat-row-actions .btn{padding:.25rem .5rem;font-size:.875rem;color:var(--bs-secondary)}.chat-row-actions .btn:hover{color:var(--bs-body-color)}.chat-list-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem 1.5rem;text-align:center;color:var(--bs-secondary)}.chat-list-empty i{font-size:4rem;color:var(--bs-secondary);margin-bottom:1rem;opacity:.5}.chat-list-empty h5{font-size:1.1rem;font-weight:600;color:var(--bs-body-color);margin-bottom:.5rem}.chat-list-empty p{font-size:.9rem;margin-bottom:1rem;max-width:400px}.chat-list-empty .btn{margin-top:.5rem}.chat-list-loading{display:flex;align-items:center;justify-content:center;padding:2rem;color:var(--bs-secondary)}.chat-list-loading .spinner-border{width:2rem;height:2rem;margin-right:.75rem}[data-bs-theme=dark] .chat-types-panel,[data-bs-theme=dark] .chat-list-panel{background:var(--bs-body-bg)}[data-bs-theme=dark] .chat-type-btn:hover{background:rgba(var(--bs-primary-rgb), 0.2)}[data-bs-theme=dark] .chat-row-modern:hover{background:rgba(var(--bs-primary-rgb), 0.1)}[data-bs-theme=dark] .chat-row-modern.pinned{background:linear-gradient(90deg, rgba(255, 193, 7, 0.15) 0%, transparent 100%)}[data-bs-theme=dark] .chat-row-modern.unread{background:rgba(var(--bs-info-rgb), 0.1)}[data-bs-theme=dark] .chat-row-avatar{background:linear-gradient(135deg, rgba(var(--bs-primary-rgb), 0.2), rgba(var(--bs-primary-rgb), 0.1))}@media(max-width: 575.98px){.chat-list-header{padding:1rem}.chat-list-header h5{font-size:1.1rem}.chat-list-header h5 i{font-size:1.25rem}.chat-row-modern{padding:.75rem}.chat-row-avatar{width:40px;height:40px;font-size:1rem}.chat-row-title{font-size:.9rem}.chat-row-preview{font-size:.8rem}}.chat-type-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.25rem;padding:.75rem .5rem;border:none;background:rgba(0,0,0,0);color:var(--bs-secondary);cursor:pointer;border-radius:8px;transition:all .2s ease;position:relative;min-width:64px}.chat-type-btn:hover{background:rgba(var(--bs-primary-rgb), 0.1);color:var(--bs-primary)}.chat-type-btn.active{background:var(--bs-primary);color:#fff;box-shadow:0 2px 8px rgba(var(--bs-primary-rgb), 0.3)}.chat-type-btn.active .chat-type-badge{background:#fff;color:var(--bs-primary)}.chat-type-btn i{font-size:1.5rem;line-height:1}.chat-type-btn .chat-type-label{font-size:.7rem;font-weight:500;text-align:center;line-height:1.2;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-type-btn .chat-type-badge{position:absolute;top:8px;right:8px;background:var(--bs-danger);color:#fff;border-radius:10px;padding:0 6px;font-size:.65rem;font-weight:600;min-width:18px;height:18px;display:flex;align-items:center;justify-content:center;line-height:1}.chat-type-btn .chat-type-badge.zero{display:none}@media(max-width: 767.98px){.chat-type-btn{flex-direction:row;gap:.5rem;padding:.5rem 1rem;min-width:auto}.chat-type-btn i{font-size:1.25rem}.chat-type-btn .chat-type-label{font-size:.85rem;max-width:none;white-space:nowrap}.chat-type-btn .chat-type-badge{position:relative;top:auto;right:auto;margin-left:.25rem}}.chat-list-panel{flex:1;background:#fff;border-radius:0 12px 12px 0;padding:0;min-width:0}@media(max-width: 767.98px){.chat-list-panel{border-radius:12px}}.chat-list-header{padding:1.25rem 1.5rem;border-bottom:1px solid var(--bs-border-color);display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap}.chat-list-header h5{margin:0;font-size:1.25rem;font-weight:600;color:var(--bs-dark);display:flex;align-items:center;gap:.5rem}.chat-list-header h5 i{font-size:1.5rem;color:var(--bs-primary)}.chat-list-header .chat-count-badge{background:rgba(var(--bs-primary-rgb), 0.1);color:var(--bs-primary);padding:.25rem .75rem;border-radius:12px;font-size:.85rem;font-weight:600}.chat-list-body{padding:0;max-height:calc(100vh - 300px);overflow-y:auto}@media(max-width: 767.98px){.chat-list-body{max-height:none}}.chat-list-body::-webkit-scrollbar{width:8px}.chat-list-body::-webkit-scrollbar-track{background:rgba(0,0,0,0)}.chat-list-body::-webkit-scrollbar-thumb{background:#dee2e6;border-radius:4px}.chat-list-body::-webkit-scrollbar-thumb:hover{background:#adb5bd}.chat-row-modern{display:flex;align-items:start;gap:.75rem;padding:1rem 1.5rem;border-bottom:1px solid var(--bs-border-color-translucent);transition:all .2s ease;position:relative;cursor:pointer}.chat-row-modern:last-child{border-bottom:none}.chat-row-modern:hover{background:rgba(var(--bs-primary-rgb), 0.03)}.chat-row-modern.pinned{background:linear-gradient(90deg, rgba(255, 193, 7, 0.08) 0%, transparent 100%);border-left:3px solid var(--bs-warning)}.chat-row-modern.pinned::before{content:"";font-family:"bootstrap-icons";position:absolute;top:.75rem;right:1rem;color:var(--bs-warning);font-size:.875rem;z-index:1}.chat-row-modern.unread{background:rgba(var(--bs-info-rgb), 0.05);border-left:3px solid var(--bs-info)}.chat-row-modern.updated{animation:pulse-update .6s ease-out}@media(max-width: 767.98px){.chat-row-modern{padding:.875rem 1rem;gap:.625rem}}@keyframes pulse-update{0%{background:rgba(var(--bs-success-rgb), 0.2)}100%{background:rgba(0,0,0,0)}}.chat-row-avatar{width:48px;height:48px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:1.25rem;background:linear-gradient(135deg, rgba(var(--bs-primary-rgb), 0.1), rgba(var(--bs-primary-rgb), 0.05));color:var(--bs-primary);overflow:hidden;position:relative}.chat-row-avatar img{width:100%;height:100%;object-fit:cover}.chat-row-avatar.status-online::after{content:"";position:absolute;bottom:2px;right:2px;width:12px;height:12px;background:var(--bs-success);border:2px solid #fff;border-radius:50%}@media(max-width: 767.98px){.chat-row-avatar{width:44px;height:44px;font-size:1.1rem}}.chat-row-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.25rem}.chat-row-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.chat-row-title{font-size:.95rem;font-weight:600;color:var(--bs-dark);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-decoration:none}.chat-row-title:hover{color:var(--bs-primary)}.chat-row-time{font-size:.75rem;color:var(--bs-secondary);white-space:nowrap;flex-shrink:0}.chat-row-preview{display:flex;align-items:center;gap:.5rem;font-size:.85rem;color:var(--bs-secondary)}.chat-row-preview .preview-author{font-weight:500;color:var(--bs-dark);flex-shrink:0}.chat-row-preview .preview-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.chat-row-meta{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.chat-row-meta .badge{font-size:.7rem;padding:.25rem .5rem;font-weight:500}.chat-row-meta .unread-badge{background:var(--bs-danger);color:#fff;border-radius:10px;padding:.125rem .5rem;font-size:.7rem;font-weight:600;min-width:20px;text-align:center}.chat-row-actions{position:absolute;top:1rem;right:1rem;opacity:0;transition:opacity .2s;z-index:10}.chat-row-modern:hover .chat-row-actions{opacity:1}.chat-row-actions .btn{padding:.25rem .5rem;font-size:.875rem}.chat-list-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem 1.5rem;text-align:center;color:var(--bs-secondary)}.chat-list-empty i{font-size:4rem;color:var(--bs-gray-400);margin-bottom:1rem;opacity:.5}.chat-list-empty h5{font-size:1.1rem;font-weight:600;color:var(--bs-dark);margin-bottom:.5rem}.chat-list-empty p{font-size:.9rem;margin-bottom:1rem;max-width:400px}.chat-list-empty .btn{margin-top:.5rem}.chat-list-loading{display:flex;align-items:center;justify-content:center;padding:2rem;color:var(--bs-secondary)}.chat-list-loading .spinner-border{width:2rem;height:2rem;margin-right:.75rem}@media(max-width: 575.98px){.chat-list-header{padding:1rem}.chat-list-header h5{font-size:1.1rem}.chat-list-header h5 i{font-size:1.25rem}.chat-row-modern{padding:.75rem}.chat-row-avatar{width:40px;height:40px;font-size:1rem}.chat-row-title{font-size:.9rem}.chat-row-preview{font-size:.8rem}} diff --git a/backend/static/css/components/chat-list-modern.css.map b/backend/static/css/components/chat-list-modern.css.map new file mode 100644 index 00000000..7af0a024 --- /dev/null +++ b/backend/static/css/components/chat-list-modern.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../scss/components/_chat-list-modern.scss"],"names":[],"mappings":"CAIA,qBACE,aACA,MACA,+BACA,6BAEA,4BANF,qBAOI,sBACA,UAKJ,kBACE,WACA,gBACA,8CACA,4BACA,eACA,aACA,sBACA,UACA,gBACA,SACA,sBACA,+BACA,gBAEA,4BAfF,kBAgBI,WACA,mBACA,mBACA,kBACA,+CACA,gBACA,gBACA,gBACA,kBACA,eACA,WAGA,qCACE,YAKN,eACE,aACA,sBACA,mBACA,uBACA,WACA,qBACA,YACA,yBACA,0BACA,eACA,kBACA,wBACA,kBACA,eAEA,qBACE,4CACA,wBAGF,sBACE,6BACA,WACA,sDAEA,uCACE,gBACA,wBAIJ,iBACE,iBACA,cAGF,gCACE,gBACA,gBACA,kBACA,gBACA,eACA,gBACA,uBACA,mBAGF,gCACE,kBACA,QACA,UACA,4BACA,WACA,mBACA,cACA,iBACA,gBACA,eACA,YACA,aACA,mBACA,uBACA,cAEA,qCACE,aAIJ,4BAtEF,eAuEI,mBACA,UACA,mBACA,eAEA,iBACE,kBAGF,gCACE,iBACA,eACA,mBAGF,gCACE,kBACA,SACA,WACA,oBAMN,iBACE,OACA,gBACA,4BACA,UACA,YAEA,4BAPF,iBAQI,oBAIJ,kBACE,uBACA,+CACA,aACA,mBACA,8BACA,SACA,eAEA,qBACE,SACA,kBACA,gBACA,qBACA,aACA,mBACA,UAEA,uBACE,iBACA,wBAIJ,oCACE,4CACA,wBACA,sBACA,mBACA,iBACA,gBAIJ,gBACE,UACA,+BACA,gBAEA,4BALF,gBAMI,iBAIF,mCACE,UAGF,yCACE,yBAGF,yCACE,mBACA,kBAEA,+CACE,mBAMN,iBACE,aACA,kBACA,WACA,oBACA,2DACA,wBACA,kBACA,eAEA,4BACE,mBAGF,uBACE,6CAGF,wBACE,gFACA,wCAEA,gCACE,YACA,8BACA,kBACA,WACA,WACA,wBACA,kBACA,UAIJ,wBACE,0CACA,qCAGF,yBACE,oCAGF,4BA3CF,iBA4CI,qBACA,aAIJ,wBACE,GACE,4CAEF,KACE,0BAIJ,iBACE,WACA,YACA,mBACA,cACA,aACA,mBACA,uBACA,kBACA,wGACA,wBACA,gBACA,kBAEA,qBACE,WACA,YACA,iBAGF,sCACE,WACA,kBACA,WACA,UACA,WACA,YACA,6BACA,sBACA,kBAGF,4BAhCF,iBAiCI,WACA,YACA,kBAIJ,kBACE,OACA,YACA,aACA,sBACA,WAGF,iBACE,aACA,mBACA,8BACA,UAGF,gBACE,iBACA,gBACA,qBACA,SACA,gBACA,uBACA,mBACA,OACA,qBAEA,sBACE,wBAIJ,eACE,iBACA,0BACA,mBACA,cAGF,kBACE,aACA,mBACA,UACA,iBACA,0BAEA,kCACE,gBACA,qBACA,cAGF,gCACE,gBACA,uBACA,mBACA,OACA,YAIJ,eACE,aACA,mBACA,UACA,eAEA,sBACE,gBACA,qBACA,gBAGF,6BACE,4BACA,WACA,mBACA,sBACA,gBACA,gBACA,eACA,kBAIJ,kBACE,kBACA,SACA,WACA,UACA,uBACA,WAEA,yCACE,UAGF,uBACE,qBACA,kBAKJ,iBACE,aACA,sBACA,mBACA,uBACA,oBACA,kBACA,0BAEA,mBACE,eACA,yBACA,mBACA,WAGF,oBACE,iBACA,gBACA,qBACA,oBAGF,mBACE,gBACA,mBACA,gBAGF,sBACE,iBAKJ,mBACE,aACA,mBACA,uBACA,aACA,0BAEA,mCACE,WACA,YACA,oBAKJ,4BACE,kBACE,aAEA,qBACE,iBAEA,uBACE,kBAKN,iBACE,eAGF,iBACE,WACA,YACA,eAGF,gBACE,gBAGF,kBACE","file":"chat-list-modern.css"} \ No newline at end of file diff --git a/backend/static/scss/components/_chat-list-modern.scss b/backend/static/scss/components/_chat-list-modern.scss new file mode 100644 index 00000000..e69de29b diff --git a/backend/templates/communications/chat_list.html b/backend/templates/communications/chat_list.html index 8c444ae7..cf01099e 100644 --- a/backend/templates/communications/chat_list.html +++ b/backend/templates/communications/chat_list.html @@ -17,7 +17,56 @@
{# ===== НОВАЯ ШАПКА С КОМПОНЕНТОМ ===== #} - {% include "includes/components/page_header.html" with icon="bi-chat-dots" title="Мои чаты" search_id="chatSearch" search_placeholder="Поиск по названию или участникам" has_filters=True filters_target="chatFilters" filters_template="communications/_filters.html" create_as_card=True create_modal="chatCreateModal" create_text="Создать чат" %} + {% include "includes/components/page_header.html" with icon="bi-chat-dots" title="Мои чаты" search_id="chatSearch" search_placeholder="Поиск по названию или участникам" create_as_card=True create_modal="chatCreateModal" create_text="Создать чат" %} + + {# ===== ДВУХКОЛОНОЧНАЯ СТРУКТУРА ===== #} +
+ + {# Левая колонка - иконки типов чатов #} +
+
+
+ + + + + + + +
+
+
+ + {# Правая колонка - список чатов #} +
{# ===== 1) ГЛОБАЛЬНЫЙ ===== #}
@@ -33,7 +82,7 @@ {% for chat in chats %} {% if chat.type == 'global' %} {% with last=chat.last_message.0 %} -
{% endif %} +
{# конец chat-list-main #} +
{# конец d-flex #} +
@@ -523,6 +575,28 @@ const ACCESS = accessMeta ? accessMeta.content : ''; const headersAuth = ACCESS ? { 'Authorization': 'Bearer ' + ACCESS } : {}; + // Динамически рассчитываем высоту page-header для смещения липкой панели + const chatListWrapper = document.querySelector('.chat-list-wrapper'); + + function updateSidebarOffset() { + const pageHeader = document.querySelector('.page-header'); + if (!pageHeader) return; + + const headerStyles = window.getComputedStyle(pageHeader); + const marginBottom = parseFloat(headerStyles.marginBottom) || 0; + const offset = pageHeader.offsetHeight + marginBottom; + + document.documentElement.style.setProperty('--chat-sidebar-offset', `${offset}px`); + } + + let resizeTimeout; + window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(updateSidebarOffset, 150); + }); + + updateSidebarOffset(); + // Инициализация RecipientPicker для создания чата const chatPicker = new RecipientPicker( document.getElementById('chatRecipients'), @@ -646,40 +720,109 @@ rowSelector: '.chat-row' }); - // Обработка селекта фильтра типа чата - const chatTypeFilter = document.getElementById('chatTypeFilter'); - const applyBtn = document.getElementById('applyChatFilter'); - const resetBtn = document.getElementById('resetChatFilter'); - - function applyTypeFilter() { - const selectedType = chatTypeFilter?.value || 'all'; - const sections = document.querySelectorAll('.chat-section'); + // ===== ФИЛЬТРАЦИЯ ПО ТИПАМ ЧАТОВ ===== + let activeFilter = 'all'; + + const chatTypeButtons = document.querySelectorAll('.chat-type-btn'); + const chatSections = document.querySelectorAll('.chat-section'); + + // Функция подсчета чатов по типам и управления видимостью + function updateChatCounts() { + const counts = { + all: 0, + global: 0, + department: 0, + private: 0, + group: 0, + channel: 0, + announcement: 0 + }; - sections.forEach(section => { + // Подсчитываем чаты в каждой секции + chatSections.forEach(section => { const sectionType = section.dataset.sec; - if (selectedType === 'all' || selectedType === sectionType) { - section.style.display = ''; - } else { + const chatRows = section.querySelectorAll('.chat-row'); + + // Считаем только видимые (не скрытые поиском) чаты + const visibleChats = Array.from(chatRows).filter(row => { + return row.style.display !== 'none'; + }).length; + + counts[sectionType] = visibleChats; + counts.all += visibleChats; + + // Управление видимостью секции + // 1. Если чатов нет (скрыты поиском или отсутствуют) -> скрываем секцию + if (visibleChats === 0) { section.style.display = 'none'; + } + // 2. Если чаты есть, проверяем активный фильтр + else { + if (activeFilter === 'all' || activeFilter === sectionType) { + section.style.display = ''; + } else { + section.style.display = 'none'; + } } }); - // Переприменяем поиск после изменения видимости секций + // Обновляем бейджи и кнопки + Object.keys(counts).forEach(type => { + const badge = document.querySelector(`[data-count="${type}"]`); + const button = document.querySelector(`[data-chat-type="${type}"]`); + + if (badge) { + badge.textContent = counts[type]; + // Скрываем бейдж если 0 + badge.style.display = counts[type] === 0 ? 'none' : ''; + } + + // Скрываем кнопку категории, если в ней нет чатов (кроме 'all') + if (button && type !== 'all') { + button.style.display = counts[type] === 0 ? 'none' : ''; + } + }); + } + + function applyTypeFilter(filterType) { + activeFilter = filterType; + + // Обновляем активную кнопку + chatTypeButtons.forEach(btn => { + if (btn.dataset.chatType === filterType) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); + + // Переприменяем поиск (если есть), это обновит видимость строк if (filterApi && filterApi.reapply) { filterApi.reapply(); } + + // Обновляем видимость секций и счетчики + updateChatCounts(); } - - function resetFilter() { - if(chatTypeFilter) chatTypeFilter.value = 'all'; - applyTypeFilter(); - } - - applyBtn?.addEventListener('click', applyTypeFilter); - resetBtn?.addEventListener('click', resetFilter); - // Применяем фильтр при загрузке - applyTypeFilter(); + // Обработка кликов по кнопкам типов + chatTypeButtons.forEach(btn => { + btn.addEventListener('click', () => { + const filterType = btn.dataset.chatType; + applyTypeFilter(filterType); + }); + }); + + // Применяем начальный фильтр и подсчитываем чаты + applyTypeFilter('all'); + + // Обновляем счетчики после поиска + const searchInput = document.getElementById('chatSearch'); + if (searchInput) { + searchInput.addEventListener('input', () => { + setTimeout(updateChatCounts, 100); + }); + } {# Подключаем chat-list-enhanced.js для WebSocket и контекстного меню #}