feat: полная AJAX-загрузка списка заявлений с infinite scroll
- Обновлен requestListHandler.js: рендеринг в табличном формате - Сохранена структура таблицы req-table-header + req-row - Шаблон: удален SSR-цикл, сохранена шапка таблицы - Добавлены данные пользователя в dataset (userId, canProcess) - Включен useAjaxList режим в initRequestListPage - Удалены старые CSS для req-item (не используются) - Структура совместима с существующими стилями request-list.css
This commit is contained in:
parent
5d1fd340fb
commit
19757d2f98
@ -285,3 +285,122 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
AJAX-режим: стили для req-item
|
||||
======================================== */
|
||||
|
||||
.req-item {
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
padding: 1rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.req-item:hover {
|
||||
background-color: var(--bs-light);
|
||||
}
|
||||
|
||||
.req-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.req-header {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.req-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.req-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--bs-body-color);
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.req-link:hover {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.req-link strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.req-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.req-meta > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.req-comment {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bs-light);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.req-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--bs-border-color-translucent);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.req-approver,
|
||||
.req-recipients {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
/* Badge стили для статусов */
|
||||
.badge-warning {
|
||||
background-color: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: #198754;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background-color: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Спиннер загрузки */
|
||||
.load-more-trigger {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -22,11 +22,11 @@ export function initRequestListHandler(options) {
|
||||
headers = {}
|
||||
} = options;
|
||||
|
||||
const listElement = document.getElementById('requestList');
|
||||
const listElement = document.getElementById('reqList');
|
||||
const searchInput = document.getElementById('reqFilter');
|
||||
|
||||
if (!listElement) {
|
||||
console.warn('initRequestListHandler: #requestList not found');
|
||||
console.warn('initRequestListHandler: #reqList not found');
|
||||
return { load: () => {}, destroy: () => {} };
|
||||
}
|
||||
|
||||
@ -281,62 +281,177 @@ export function initRequestListHandler(options) {
|
||||
* @returns {string} HTML
|
||||
*/
|
||||
function renderRequestRow(req) {
|
||||
const statusClass = getStatusClass(req.status);
|
||||
const statusText = getStatusText(req.status);
|
||||
const detailUrl = detailUrlTemplate.replace('{id}', req.id);
|
||||
const type = (req.type || '').toLowerCase();
|
||||
const status = (req.status || '').toLowerCase();
|
||||
|
||||
// Иконка типа
|
||||
let typeIcon = 'bi-clipboard-check text-secondary';
|
||||
if (type === 'vacation') typeIcon = 'bi-calendar-check text-primary';
|
||||
else if (type === 'sick_leave') typeIcon = 'bi-capsule text-danger';
|
||||
else if (type === 'transfer') typeIcon = 'bi-box-arrow-right text-info';
|
||||
else if (type === 'dismissal') typeIcon = 'bi-door-open text-warning';
|
||||
|
||||
// Текст типа
|
||||
const typeText = {
|
||||
'vacation': 'Отпуск',
|
||||
'sick_leave': 'Больничный',
|
||||
'transfer': 'Перевод',
|
||||
'dismissal': 'Увольнение',
|
||||
'other': 'Другое'
|
||||
}[type] || req.type;
|
||||
|
||||
// Класс и текст статуса
|
||||
let statusBadgeClass = 'text-bg-light';
|
||||
let statusText = req.status;
|
||||
if (status === 'draft') { statusBadgeClass = 'text-bg-info'; statusText = 'Черновик'; }
|
||||
else if (status === 'pending') { statusBadgeClass = 'text-bg-warning'; statusText = 'На рассмотр.'; }
|
||||
else if (status === 'approved') { statusBadgeClass = 'text-bg-success'; statusText = 'Одобрено'; }
|
||||
else if (status === 'rejected') { statusBadgeClass = 'text-bg-danger'; statusText = 'Отклонено'; }
|
||||
else if (status === 'cancelled') { statusBadgeClass = 'text-bg-secondary'; statusText = 'Отменено'; }
|
||||
|
||||
// URL для сотрудника
|
||||
const employeeUrl = req.employee?.id === userId ? '/employees/profile/' : `/employees/${req.employee?.id}/`;
|
||||
const employeeName = req.employee?.id === userId ? 'Вы' : (req.employee?.full_name || req.employee?.display_name || 'Сотрудник');
|
||||
|
||||
// Кнопки действий
|
||||
let actionButtons = '';
|
||||
if (req.employee?.id !== userId && canProcess) {
|
||||
actionButtons = `
|
||||
<button type="button" class="btn btn-ghost btn-sm text-success"
|
||||
data-bs-toggle="modal" data-bs-target="#reqApproveModal"
|
||||
data-id="${req.id}" title="Одобрить">
|
||||
<i class="bi-hand-thumbs-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm text-danger"
|
||||
data-bs-toggle="modal" data-bs-target="#reqRejectModal"
|
||||
data-id="${req.id}" title="Отклонить">
|
||||
<i class="bi-hand-thumbs-down"></i>
|
||||
</button>
|
||||
`;
|
||||
} else if (req.employee?.id === userId) {
|
||||
actionButtons = `
|
||||
<button type="button" class="btn btn-ghost btn-sm text-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#reqEditModal"
|
||||
data-id="${req.id}"
|
||||
data-title="${escapeHtml(req.title || '')}"
|
||||
data-type="${req.type || ''}"
|
||||
data-date_from="${req.date_from || ''}"
|
||||
data-date_to="${req.date_to || ''}"
|
||||
data-comment="${escapeHtml(req.comment || '')}"
|
||||
data-status="${status}" title="Изменить">
|
||||
<i class="bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm text-secondary"
|
||||
data-bs-toggle="modal" data-bs-target="#reqCancelModal"
|
||||
data-id="${req.id}" title="Отменить">
|
||||
<i class="bi-slash-circle"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<article class="req-item" data-request-id="${req.id}">
|
||||
<header class="req-header">
|
||||
<div class="req-title-row">
|
||||
<a href="${detailUrl}" class="req-link">
|
||||
<i class="bi-file-earmark-text"></i>
|
||||
<strong>${escapeHtml(req.display_title || req.title || 'Заявление')}</strong>
|
||||
</a>
|
||||
<span class="badge badge-${statusClass}">${statusText}</span>
|
||||
<article class="req-row" id="req-${req.id}">
|
||||
<div class="req-table-row">
|
||||
<!-- Иконка типа -->
|
||||
<div class="req-cell req-cell-icon">
|
||||
<div class="card-icon d-flex align-items-center justify-content-center">
|
||||
<i class="${typeIcon}"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="req-meta">
|
||||
<span class="req-employee">
|
||||
<i class="bi-person"></i> ${escapeHtml(req.employee?.full_name || 'Неизвестно')}
|
||||
</span>
|
||||
${req.date_from ? `
|
||||
<span class="req-date">
|
||||
<i class="bi-calendar3"></i>
|
||||
${formatDate(req.date_from)}${req.date_to ? ' — ' + formatDate(req.date_to) : ''}
|
||||
</span>
|
||||
` : ''}
|
||||
<span class="req-created">
|
||||
<i class="bi-clock"></i> ${formatDate(req.created_at)}
|
||||
</span>
|
||||
<!-- Заголовок и тип -->
|
||||
<div class="req-cell req-cell-title">
|
||||
<div class="req-cell-content">
|
||||
<strong>${escapeHtml(req.title || 'Без заголовка')}</strong>
|
||||
${type ? `<span class="badge text-bg-light ms-1">${typeText}</span>` : ''}
|
||||
${req.comment ? `<div class="small text-body-secondary mt-1">${escapeHtml(req.comment.substring(0, 100))}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
${req.comment ? `
|
||||
<div class="req-comment">${escapeHtml(req.comment)}</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
<!-- Автор -->
|
||||
<div class="req-cell req-cell-author">
|
||||
<div class="req-cell-content">
|
||||
${req.employee ? `<a href="${employeeUrl}" class="text-decoration-none">${employeeName}</a>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Период -->
|
||||
<div class="req-cell req-cell-dates">
|
||||
<div class="req-cell-content text-mono">
|
||||
${req.date_from || req.date_to ? `
|
||||
${req.date_from ? formatDate(req.date_from) : '—'}<br>
|
||||
${req.date_to ? formatDate(req.date_to) : '—'}
|
||||
` : '<span class="text-body-secondary">—</span>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Статус -->
|
||||
<div class="req-cell req-cell-status">
|
||||
<div class="req-cell-content">
|
||||
${req.status ? `<span class="badge ${statusBadgeClass}">${statusText}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дата создания -->
|
||||
<div class="req-cell req-cell-created">
|
||||
<div class="req-cell-content small text-body-secondary">
|
||||
${req.created_at ? formatDate(req.created_at) : '<span class="text-body-secondary">—</span>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Действия -->
|
||||
<div class="req-cell req-cell-actions">
|
||||
<div class="req-cell-content">
|
||||
${actionButtons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Футер с комментариями -->
|
||||
<footer class="req-footer">
|
||||
${req.approver ? `
|
||||
<div class="req-approver">
|
||||
<i class="bi-person-check"></i>
|
||||
<span>Согласующий: ${escapeHtml(req.approver.full_name)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${req.recipient_count > 0 ? `
|
||||
<div class="req-recipients">
|
||||
<i class="bi-people"></i>
|
||||
<span>Получатели: ${req.recipient_count}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<button class="btn btn-ghost btn-sm d-inline-flex align-items-center gap-1 collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#rcoll-${req.id}"
|
||||
aria-controls="rcoll-${req.id}"
|
||||
aria-expanded="false">
|
||||
<i class="bi-chat-dots"></i>
|
||||
<span class="txt-open" data-role="count">${req.comments_count || 0}</span>
|
||||
<span class="txt-close">Скрыть</span>
|
||||
</button>
|
||||
${req.attachment_url ? `
|
||||
<a href="${req.attachment_url}" class="btn btn-ghost btn-sm d-inline-flex align-items-center gap-1" target="_blank">
|
||||
<i class="bi-paperclip"></i><span>Вложение</span>
|
||||
</a>
|
||||
` : ''}
|
||||
</footer>
|
||||
|
||||
<div id="rcoll-${req.id}" class="collapse" data-request-id="${req.id}" data-comments-collapse>
|
||||
<div class="comments-block p-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<strong class="small mb-0">Комментарии</strong>
|
||||
<div class="spinner-border spinner-border-sm text-secondary" role="status" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="comments-list" data-role="list"></div>
|
||||
<form class="comment-new mt-3 request-comment-form" data-role="form" autocomplete="off">
|
||||
<div class="message-field message-field--compact">
|
||||
<div class="dropdown message-emoji">
|
||||
<button type="button" class="btn btn-ghost btn-emoji message-icon-btn"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
title="Вставить смайлик">
|
||||
<i class="bi-emoji-smile"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-start message-emoji-menu">
|
||||
<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="form-control message-input" rows="1" placeholder="Написать…" data-role="text"></textarea>
|
||||
<button class="btn btn-primary message-send" type="submit" title="Отправить"><i class="bi-send"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -49,214 +49,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if requests %}
|
||||
{% for obj in requests %}
|
||||
<article class="req-row" id="req-{{ obj.id }}">
|
||||
<div class="req-table-row">
|
||||
<!-- Иконка типа -->
|
||||
<div class="req-cell req-cell-icon">
|
||||
<div class="card-icon d-flex align-items-center justify-content-center">
|
||||
{% with tp=obj.type|lower %}
|
||||
{% if tp == 'vacation' %}<i class="bi-calendar-check text-primary"></i>
|
||||
{% elif tp == 'sick_leave' %}<i class="bi-capsule text-danger"></i>
|
||||
{% elif tp == 'transfer' %}<i class="bi-box-arrow-right text-info"></i>
|
||||
{% elif tp == 'dismissal' %}<i class="bi-door-open text-warning"></i>
|
||||
{% else %}<i class="bi-clipboard-check text-secondary"></i>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Заголовок и тип -->
|
||||
<div class="req-cell req-cell-title">
|
||||
<div class="req-cell-content">
|
||||
<strong>{{ obj.title|default:"Без заголовка" }}</strong>
|
||||
{% if obj.type %}{% with tp=obj.type|lower %}
|
||||
<span class="badge text-bg-light ms-1">
|
||||
{% if tp == 'vacation' %}Отпуск
|
||||
{% elif tp == 'sick_leave' %}Больничный
|
||||
{% elif tp == 'transfer' %}Перевод
|
||||
{% elif tp == 'dismissal' %}Увольнение
|
||||
{% elif tp == 'other' %}Другое
|
||||
{% else %}{{ obj.type }}{% endif %}
|
||||
</span>
|
||||
{% endwith %}{% endif %}
|
||||
{% if obj.comment %}
|
||||
<div class="small text-body-secondary mt-1">{{ obj.comment|truncatewords:15 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Автор -->
|
||||
<div class="req-cell req-cell-author">
|
||||
<div class="req-cell-content">
|
||||
{% if obj.employee %}
|
||||
{% if obj.employee.id == user.id %}
|
||||
<a href="{% url 'employees:profile' %}" class="text-decoration-none">Вы</a>
|
||||
{% else %}
|
||||
<a href="{% url 'employees:employee_detail' obj.employee.id %}" class="text-decoration-none">
|
||||
{{ obj.employee.full_name|default:obj.employee.display_name|default:"Сотрудник" }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Период -->
|
||||
<div class="req-cell req-cell-dates">
|
||||
<div class="req-cell-content text-mono">
|
||||
{% if obj.date_from or obj.date_to %}
|
||||
{% if obj.date_from %}
|
||||
{{ obj.date_from|format_iso_date:"d.m.Y" }}<br>
|
||||
{% else %}
|
||||
—<br>
|
||||
{% endif %}
|
||||
{% if obj.date_to %}
|
||||
{{ obj.date_to|format_iso_date:"d.m.Y" }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-body-secondary">—</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Статус -->
|
||||
<div class="req-cell req-cell-status">
|
||||
<div class="req-cell-content">
|
||||
{% if obj.status %}{% with st=obj.status|lower %}
|
||||
<span class="badge
|
||||
{% if st == 'approved' %}text-bg-success
|
||||
{% elif st == 'rejected' %}text-bg-danger
|
||||
{% elif st == 'pending' %}text-bg-warning
|
||||
{% elif st == 'cancelled' %}text-bg-secondary
|
||||
{% elif st == 'draft' %}text-bg-info
|
||||
{% else %}text-bg-light{% endif %}">
|
||||
{% if st == 'draft' %}Черновик
|
||||
{% elif st == 'pending' %}На рассмотр.
|
||||
{% elif st == 'approved' %}Одобрено
|
||||
{% elif st == 'rejected' %}Отклонено
|
||||
{% elif st == 'cancelled' %}Отменено
|
||||
{% else %}{{ obj.status }}{% endif %}
|
||||
</span>
|
||||
{% endwith %}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дата создания -->
|
||||
<div class="req-cell req-cell-created">
|
||||
<div class="req-cell-content small text-body-secondary">
|
||||
{% if obj.created_at %}
|
||||
{{ obj.created_at|format_iso_date:"d.m.Y" }}
|
||||
{% else %}
|
||||
<span class="text-body-secondary">—</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Действия -->
|
||||
<div class="req-cell req-cell-actions">
|
||||
<div class="req-cell-content">
|
||||
{% if obj.employee.id != user.id and can_process %}
|
||||
<button type="button" class="btn btn-ghost btn-sm text-success"
|
||||
data-bs-toggle="modal" data-bs-target="#reqApproveModal"
|
||||
data-id="{{ obj.id }}" title="Одобрить">
|
||||
<i class="bi-hand-thumbs-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm text-danger"
|
||||
data-bs-toggle="modal" data-bs-target="#reqRejectModal"
|
||||
data-id="{{ obj.id }}" title="Отклонить">
|
||||
<i class="bi-hand-thumbs-down"></i>
|
||||
</button>
|
||||
{% elif obj.employee.id == user.id %}
|
||||
<button type="button" class="btn btn-ghost btn-sm text-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#reqEditModal"
|
||||
data-id="{{ obj.id }}"
|
||||
data-title="{{ obj.title|default_if_none:'' }}"
|
||||
data-type="{{ obj.type|default_if_none:'' }}"
|
||||
data-date_from="{{ obj.date_from|default_if_none:'' }}"
|
||||
data-date_to="{{ obj.date_to|default_if_none:'' }}"
|
||||
data-comment="{{ obj.comment|default_if_none:'' }}"
|
||||
data-status="{{ obj.status|lower }}" title="Изменить">
|
||||
<i class="bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm text-secondary"
|
||||
data-bs-toggle="modal" data-bs-target="#reqCancelModal"
|
||||
data-id="{{ obj.id }}" title="Отменить">
|
||||
<i class="bi-slash-circle"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Футер с комментариями -->
|
||||
<footer class="req-footer">
|
||||
<button class="btn btn-ghost btn-sm d-inline-flex align-items-center gap-1"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#rcoll-{{ obj.id }}"
|
||||
aria-controls="rcoll-{{ obj.id }}"
|
||||
aria-expanded="false">
|
||||
<i class="bi-chat-dots"></i>
|
||||
<span class="txt-open" data-role="count">{{ obj.comments_count|default:0 }}</span>
|
||||
<span class="txt-close">Скрыть</span>
|
||||
</button>
|
||||
{% if obj.attachment %}
|
||||
<a href="{% absolute_url obj.attachment.url %}" class="btn btn-ghost btn-sm d-inline-flex align-items-center gap-1" target="_blank">
|
||||
<i class="bi-paperclip"></i><span>Вложение</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
|
||||
<div id="rcoll-{{ obj.id }}" class="collapse" data-request-id="{{ obj.id }}" data-comments-collapse>
|
||||
<div class="comments-block p-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<strong class="small mb-0">Комментарии</strong>
|
||||
<div class="spinner-border spinner-border-sm text-secondary" role="status" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="comments-list" data-role="list"></div>
|
||||
<form class="comment-new mt-3 request-comment-form" data-role="form" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<div class="message-field message-field--compact">
|
||||
<!-- Кнопка выбора эмодзи -->
|
||||
<div class="dropdown message-emoji">
|
||||
<button type="button" class="btn btn-ghost btn-emoji message-icon-btn"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
title="Вставить смайлик">
|
||||
<i class="bi-emoji-smile"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-start message-emoji-menu">
|
||||
<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="form-control message-input" rows="1" placeholder="Написать…" data-role="text"></textarea>
|
||||
<button class="btn btn-primary message-send" type="submit" title="Отправить"><i class="bi-send"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-body-secondary">Нет заявлений.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Пагинация по next/prev ссылкам из API (если есть) #}
|
||||
{% if next_url or prev_url %}
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<a class="btn btn-outline-secondary {% if not prev_url %}disabled{% endif %}" href="{{ prev_url|default:'#' }}">
|
||||
<i class="bi-arrow-left"></i> Назад
|
||||
</a>
|
||||
<span class="text-body-secondary small">Всего: {{ count|default:'—' }}</span>
|
||||
<a class="btn btn-outline-secondary {% if not next_url %}disabled{% endif %}" href="{{ next_url|default:'#' }}">
|
||||
Вперёд <i class="bi-arrow-right"></i>
|
||||
</a>
|
||||
{# Список загружается через JavaScript из requestListHandler.js #}
|
||||
<div class="text-center py-4" id="reqListLoading">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# ===== Modals ===== #}
|
||||
|
||||
@ -558,6 +357,11 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Передаем данные для инициализации страницы
|
||||
document.body.dataset.userId = '{{ user.id }}';
|
||||
document.body.dataset.canProcess = '{{ can_process|yesno:"true,false" }}';
|
||||
</script>
|
||||
<script type="module">
|
||||
import { initRequestListPage } from "{% static 'js/pages/requestList.js' %}";
|
||||
|
||||
@ -566,7 +370,8 @@
|
||||
apiDetailBase: "{% url 'api:v1:request-list' %}",
|
||||
commentsUrlTemplate: "/api/v1/requests/0/comments/",
|
||||
addCommentUrlTemplate: "/api/v1/requests/0/comments/",
|
||||
autoShowComments: {% if comments_for %}true{% else %}false{% endif %}
|
||||
autoShowComments: {% if comments_for %}true{% else %}false{% endif %},
|
||||
useAjaxList: true // Включаем AJAX режим для списка
|
||||
});
|
||||
</script>
|
||||
<script src="{% static 'js/components/requestComments.js' %}"></script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user