feat(chat): рефакторинг мобильной навигации и шапки чата
- Реализован современный нижний navbar для мобильных (5 кнопок) - Скрытие нижнего navbar на странице чата - Новая шапка чата с аватарами и бейджами типа чата - Переработана HTML структура чата (убраны лишние Bootstrap контейнеры) - Добавлены стили для аватаров и бейджей типов чатов - Условное отображение: аватар OR иконка категории
This commit is contained in:
parent
0b96c48e8c
commit
85ce134bac
@ -195,5 +195,19 @@
|
||||
body {
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
/* Скрыть bottom navbar на странице чата - композер заменяет навигацию */
|
||||
.chat-container ~ .bottom-navbar,
|
||||
.chat-page ~ .bottom-navbar,
|
||||
body:has(.chat-container) .bottom-navbar,
|
||||
body:has(.chat-page) .bottom-navbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Убрать padding, когда navbar скрыт */
|
||||
body:has(.chat-container),
|
||||
body:has(.chat-page) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,6 +75,48 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ===== CHAT AVATAR WITH TYPE BADGE ===== */
|
||||
.chat-avatar-wrapper {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
.chat-type-badge {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--bs-primary);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
border: 2px solid var(--bs-body-bg);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chat-type-badge i {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .chat-type-badge {
|
||||
border-color: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* При скролле - более выраженный фон */
|
||||
.page-header.scrolled {
|
||||
background: linear-gradient(
|
||||
@ -88,6 +130,55 @@
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* ===== TOP BAR (заголовок + действия) ===== */
|
||||
.page-header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-header-title h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--bs-body-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.page-header-title i {
|
||||
font-size: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.page-header-title h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.page-header-title i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== ЗАГОЛОВОК ===== */
|
||||
.page-title {
|
||||
font-weight: 800;
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
{% block title %}Чат{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/components/chat-detail.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/message-reactions.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/message-context-menu.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/message-selection.css' %}">
|
||||
@ -12,74 +14,91 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container chat-root"
|
||||
id="chatDetailApp"
|
||||
data-chat-id="{{ chat.id }}"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-user-name="{{ user.get_full_name|default:user.username }}"
|
||||
data-user-avatar="{% if user.avatar %}{{ user.avatar.url }}{% endif %}"
|
||||
data-upload-url="{% url 'api:v1:upload_message' %}"
|
||||
data-messages-url="{% url 'api:v1:chat_messages' chat.pk %}"
|
||||
data-mark-read-url="{% url 'communications:chat_mark_read' chat.pk %}"
|
||||
data-edit-url-template="/api/v1/communications/messages/{id}/edit/"
|
||||
data-last-read-timestamp="{{ chat.last_read_timestamp|default:'0' }}">
|
||||
<div class="row gx-0">
|
||||
<div class="col-12 chat-col">
|
||||
|
||||
<!-- Заголовок -->
|
||||
<div class="section-header">
|
||||
{% if chat.type == 'global' %}
|
||||
<i class="bi-globe fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Глобальный чат</h2>
|
||||
{% elif chat.type == 'department' %}
|
||||
<i class="bi-diagram-3 fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Чат отдела: {{ chat.department.name }}</h2>
|
||||
{% elif chat.type == 'announcement' %}
|
||||
<i class="bi-megaphone fs-2 text-warning"></i>
|
||||
<h2 class="title mb-0">{{ chat.name|default:"Объявление" }}</h2>
|
||||
{% if chat.is_blocked %}
|
||||
<span class="badge bg-danger ms-2">
|
||||
<i class="bi-lock-fill"></i> Заблокировано
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if not is_announcement_creator %}
|
||||
<span class="badge bg-info ms-2">
|
||||
<i class="bi-eye"></i> Только просмотр
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i class="bi-person fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">
|
||||
{% for u in chat.participants.all %}
|
||||
{% if u != user %}
|
||||
{% if not forloop.first %}, {% endif %}
|
||||
<a href="{% url 'employees:employee_detail' u.pk %}" class="text-decoration-none">
|
||||
{{ u.get_full_name|default:u.username }}
|
||||
</a>
|
||||
<!-- Чат без лишних контейнеров -->
|
||||
<div class="chat-page">
|
||||
|
||||
<!-- Шапка чата -->
|
||||
<div class="chat-header">
|
||||
<div class="chat-header-content">
|
||||
<div class="chat-header-title">
|
||||
{% if chat.avatar %}
|
||||
<!-- Аватар чата с маленькой иконкой типа -->
|
||||
<div class="chat-avatar-wrapper">
|
||||
<img src="{{ chat.avatar.url }}" alt="{{ chat.name }}" class="chat-avatar">
|
||||
<span class="chat-type-badge">
|
||||
{% if chat.type == 'global' %}
|
||||
<i class="bi-globe"></i>
|
||||
{% elif chat.type == 'department' %}
|
||||
<i class="bi-diagram-3"></i>
|
||||
{% elif chat.type == 'announcement' %}
|
||||
<i class="bi-megaphone"></i>
|
||||
{% elif chat.type == 'group' %}
|
||||
<i class="bi-people"></i>
|
||||
{% elif chat.type == 'channel' %}
|
||||
<i class="bi-broadcast"></i>
|
||||
{% else %}
|
||||
<i class="bi-person"></i>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</h2>
|
||||
{% endif %}
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
{% if chat.type == 'announcement' and user.is_staff %}
|
||||
{% if chat.is_blocked %}
|
||||
<button type="button" class="btn btn-success btn-sm" onclick="unblockAnnouncement({{ chat.id }})">
|
||||
<i class="bi-unlock"></i> Разблокировать
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="blockAnnouncement({{ chat.id }})">
|
||||
<i class="bi-lock"></i> Заблокировать
|
||||
</button>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<h1>{{ chat.name|default:"Чат" }}</h1>
|
||||
{% else %}
|
||||
<!-- Иконка категории, если нет аватара -->
|
||||
{% if chat.type == 'global' %}
|
||||
<i class="bi-globe text-primary"></i>
|
||||
<h1>{{ chat.name|default:"Глобальный чат" }}</h1>
|
||||
{% elif chat.type == 'department' %}
|
||||
<i class="bi-diagram-3 text-primary"></i>
|
||||
<h1>{{ chat.department.name|default:chat.name }}</h1>
|
||||
{% elif chat.type == 'announcement' %}
|
||||
<i class="bi-megaphone text-warning"></i>
|
||||
<h1>{{ chat.name|default:"Объявление" }}</h1>
|
||||
{% else %}
|
||||
<i class="bi-person text-primary"></i>
|
||||
<h1>
|
||||
{% if chat.name %}
|
||||
{{ chat.name }}
|
||||
{% else %}
|
||||
{% for u in chat.participants.all %}
|
||||
{% if u != user %}{{ u.get_full_name|default:u.username }}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% endif %}
|
||||
<a href="{% url 'communications:chat_list' %}" class="btn btn-ghost">
|
||||
<i class="bi-arrow-left"></i> К чатам
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="chat-header-actions">
|
||||
{% if chat.type == 'announcement' and user.is_staff %}
|
||||
{% if chat.is_blocked %}
|
||||
<button type="button" class="btn btn-success btn-sm" onclick="unblockAnnouncement({{ chat.id }})">
|
||||
<i class="bi-unlock"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="blockAnnouncement({{ chat.id }})">
|
||||
<i class="bi-lock"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<a href="{% url 'communications:chat_list' %}" class="btn btn-ghost">
|
||||
<i class="bi-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Корпус чата -->
|
||||
<article class="card chat-box">
|
||||
<!-- Контейнер чата -->
|
||||
<div class="chat-container"
|
||||
id="chatDetailApp"
|
||||
data-chat-id="{{ chat.id }}"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-user-name="{{ user.get_full_name|default:user.username }}"
|
||||
data-user-avatar="{% if user.avatar %}{{ user.avatar.url }}{% endif %}"
|
||||
data-upload-url="{% url 'api:v1:upload_message' %}"
|
||||
data-messages-url="{% url 'api:v1:chat_messages' chat.pk %}"
|
||||
data-mark-read-url="{% url 'communications:chat_mark_read' chat.pk %}"
|
||||
data-edit-url-template="/api/v1/communications/messages/{id}/edit/"
|
||||
data-last-read-timestamp="{{ chat.last_read_timestamp|default:'0' }}">
|
||||
|
||||
<!-- Предупреждение о блокировке -->
|
||||
{% if chat.is_blocked %}
|
||||
@ -117,7 +136,7 @@
|
||||
|
||||
<!-- Лента сообщений -->
|
||||
<div id="chatScroll"
|
||||
class="chat-scroll"
|
||||
class="chat-messages-scroll"
|
||||
data-me-id="{{ user.id }}"
|
||||
data-me-avatar="{% if user.avatar %}{{ user.avatar.url }}{% endif %}"
|
||||
data-profile-url="{% url 'employees:profile' %}"
|
||||
@ -130,7 +149,7 @@
|
||||
data-has-more="{% if messages_has_more %}1{% else %}0{% endif %}"
|
||||
data-last-read-ts="{% if last_read_at %}{{ last_read_at|date:'U' }}000{% endif %}">
|
||||
|
||||
<!-- Индикатор загрузки истории (для прокрутки вверх) -->
|
||||
<!-- Индикатор загрузки истории -->
|
||||
<div class="history-loader d-none" data-history-loader>
|
||||
<div class="spinner-border spinner-border-sm text-secondary" role="status" aria-hidden="true"></div>
|
||||
<span class="small">Загружаем ранние сообщения…</span>
|
||||
@ -143,21 +162,18 @@
|
||||
</div>
|
||||
<p class="text-secondary">Загружаем сообщения...</p>
|
||||
</div>
|
||||
|
||||
<!-- Сообщения будут добавлены через JavaScript -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Индикатор печати -->
|
||||
<div id="typing" class="small text-secondary ps-3 pt-2 d-none">
|
||||
<span class="typing-indicator">
|
||||
<div id="typing" class="chat-typing-indicator d-none">
|
||||
<span class="typing-dots">
|
||||
<span></span><span></span><span></span>
|
||||
</span>
|
||||
Собеседник печатает…
|
||||
<span class="typing-text">Собеседник печатает…</span>
|
||||
</div>
|
||||
|
||||
<!-- Композер -->
|
||||
<div class="chat-composer">
|
||||
<div class="chat-composer-wrapper">
|
||||
{% if can_send_messages %}
|
||||
<form id="chatForm"
|
||||
class="composer-wrap"
|
||||
@ -270,11 +286,14 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
</div> <!-- .chat-container -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Кнопка «вниз» -->
|
||||
<button id="scrollBtn" class="scroll-to-bottom" type="button" aria-label="Прокрутить вниз">
|
||||
<i class="bi-arrow-down"></i>
|
||||
</button>
|
||||
|
||||
</div> <!-- .chat-page -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -427,6 +446,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- .chat-page -->
|
||||
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user