feat(requests): улучшена UI списка заявлений и добавлены расширенные фильтры
Стили комментариев:
- Приведены к единому стилю с лентой новостей
- Убрана фоновая подложка блока комментариев
- Убрана разделительная линия между заявлением и комментариями
- Обновлена структура: comment-header, comment-body, comment-separator
- Добавлены кликабельные ссылки на профили авторов
Исправления формы комментариев:
- Исправлена перезагрузка страницы при нажатии Enter
- Улучшена обработка submit события (preventDefault + stopPropagation)
- Правильная проверка формы через matches() вместо closest()
API endpoints:
- Использование правильных API endpoints для комментариев
- Переход с /requests/comments/{id}/ на /api/v1/requests/{id}/comments/
- Устранено дублирование запросов к старым и новым endpoints
CSRF токены:
- Добавлена функция getCsrfToken() для извлечения из cookie
- CSRF токен добавлен во все POST запросы к API
- Исправлены ошибки 403 Forbidden для действий (cancel, approve, reject)
Расширенные фильтры:
- Добавлен фильтр 'Все доступные' (свои + адресованные + с правами)
- Добавлен фильтр 'Мои заявления' (только созданные мной)
- Добавлен фильтр 'Мне адресовано' (recipients + cc_users + sent_to_all_department)
- Поддержка параметра addressed_to_me=true в API
- Обновлена логика views_front.py для обработки новых фильтров
- Исправлены lint ошибки (длинные строки)
This commit is contained in:
parent
cce465d44b
commit
5ca0feb9eb
@ -185,13 +185,17 @@ class RequestsView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
# --- фильтры API ---
|
||||
params: Dict[str, Any] = {}
|
||||
scope = (request.GET.get("view") or "").lower()
|
||||
scope = (request.GET.get("view") or "").lower().strip()
|
||||
mine_raw = (request.GET.get("mine") or "").strip().lower()
|
||||
|
||||
if scope == "mine" or mine_raw in {"1", "true", "yes", "on"}:
|
||||
params["view"] = "mine"
|
||||
scope = "mine"
|
||||
elif scope == "addressed":
|
||||
params["addressed_to_me"] = "true"
|
||||
scope = "addressed"
|
||||
else:
|
||||
scope = "all"
|
||||
scope = ""
|
||||
|
||||
type_ = (request.GET.get("type") or "").strip()
|
||||
status_ = (request.GET.get("status") or "").strip()
|
||||
@ -218,7 +222,11 @@ class RequestsView(LoginRequiredMixin, TemplateView):
|
||||
"count": None,
|
||||
"scope": scope,
|
||||
"can_process": _user_can_process_requests(request.user),
|
||||
"filters": {"type": type_, "status": status_, "view": scope},
|
||||
"filters": {
|
||||
"type": type_,
|
||||
"status": status_,
|
||||
"view": scope,
|
||||
},
|
||||
}
|
||||
)
|
||||
return ctx
|
||||
@ -233,7 +241,11 @@ class RequestsView(LoginRequiredMixin, TemplateView):
|
||||
"count": count,
|
||||
"scope": scope,
|
||||
"can_process": _user_can_process_requests(request.user),
|
||||
"filters": {"type": type_, "status": status_, "view": scope},
|
||||
"filters": {
|
||||
"type": type_,
|
||||
"status": status_,
|
||||
"view": scope,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -245,7 +257,12 @@ class RequestsView(LoginRequiredMixin, TemplateView):
|
||||
)
|
||||
if ok_c:
|
||||
comments, _, _, _ = _parse_page_payload(data_c)
|
||||
ctx.update({"comments_for": int(comments_for), "comments": comments})
|
||||
ctx.update(
|
||||
{
|
||||
"comments_for": int(comments_for),
|
||||
"comments": comments,
|
||||
}
|
||||
)
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
|
||||
@ -176,8 +176,6 @@
|
||||
|
||||
/* Блок комментариев */
|
||||
#reqList .comments-block {
|
||||
border-top: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 70%);
|
||||
background: color-mix(in srgb, var(--bs-tertiary-bg), transparent 50%);
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
margin-left: calc(48px + 0.75rem); /* Выравнивание с содержимым */
|
||||
|
||||
@ -35,15 +35,24 @@ export function initRequestCommentsHandler(options) {
|
||||
* @returns {string} HTML строка
|
||||
*/
|
||||
function renderComment(comment) {
|
||||
const author = (comment.author &&
|
||||
const authorName = (comment.author &&
|
||||
(comment.author.full_name || comment.author.display_name || comment.author.username)) || '—';
|
||||
const authorId = comment.author?.id;
|
||||
const authorLink = authorId
|
||||
? `/employees/employee/${authorId}/`
|
||||
: '#';
|
||||
const commentDate = esc(comment.created_at || '');
|
||||
|
||||
return `<div class="comment-item">
|
||||
<div class="comment-meta">
|
||||
<span class="comment-author">${esc(author)}</span>
|
||||
<span class="text-mono">${esc(comment.created_at || '')}</span>
|
||||
<div class="comment-header">
|
||||
${authorId
|
||||
? `<a href="${authorLink}" class="comment-author">${esc(authorName)}</a>`
|
||||
: `<span class="comment-author">${esc(authorName)}</span>`
|
||||
}
|
||||
<span class="comment-separator">·</span>
|
||||
<time class="comment-date">${commentDate}</time>
|
||||
</div>
|
||||
<div class="comment-text">${esc(comment.text || '')}</div>
|
||||
<div class="comment-body">${esc(comment.text || '')}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@ -142,10 +151,13 @@ export function initRequestCommentsHandler(options) {
|
||||
* Обработчик отправки формы комментария
|
||||
*/
|
||||
async function handleCommentSubmit(e) {
|
||||
const form = e.target.closest(`${collapseSelector} form[data-role=form]`);
|
||||
if (!form) return;
|
||||
const form = e.target;
|
||||
|
||||
// Проверяем, что это форма комментария внутри блока заявки
|
||||
if (!form.matches(`${collapseSelector} form[data-role=form]`)) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const block = form.closest('.comments-block');
|
||||
const textarea = form.querySelector('[data-role=text]');
|
||||
|
||||
@ -28,7 +28,31 @@ export function initRequestListPage(config) {
|
||||
// Получаем токен доступа
|
||||
const accessMeta = document.querySelector('meta[name="api-access"]');
|
||||
const ACCESS = accessMeta ? accessMeta.content : '';
|
||||
const headers = ACCESS ? { 'Authorization': 'Bearer ' + ACCESS } : {};
|
||||
|
||||
// Функция для получения CSRF токена из cookie
|
||||
function getCsrfToken() {
|
||||
const name = 'csrftoken';
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
};
|
||||
|
||||
if (ACCESS) {
|
||||
headers['Authorization'] = 'Bearer ' + ACCESS;
|
||||
}
|
||||
|
||||
// Инициализация обработчиков модальных окон
|
||||
initRequestModalHandler({
|
||||
|
||||
@ -3,8 +3,9 @@
|
||||
<div class="col-12 col-sm-3">
|
||||
<label class="form-label mb-1">Область</label>
|
||||
<select name="view" class="form-select">
|
||||
<option value="mine" {% if scope == 'mine' %}selected{% endif %}>Мои</option>
|
||||
<option value="all" {% if scope == 'all' %}selected{% endif %}>Все</option>
|
||||
<option value="" {% if not filters.view or filters.view == '' %}selected{% endif %}>Все доступные</option>
|
||||
<option value="mine" {% if filters.view == 'mine' %}selected{% endif %}>Мои заявления</option>
|
||||
<option value="addressed" {% if filters.view == 'addressed' %}selected{% endif %}>Мне адресовано</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-sm-3">
|
||||
|
||||
@ -148,8 +148,7 @@
|
||||
<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" }}<br>
|
||||
{{ obj.created_at|format_iso_date:"H:i" }}
|
||||
{{ obj.created_at|format_iso_date:"d.m.Y" }}
|
||||
{% else %}
|
||||
<span class="text-body-secondary">—</span>
|
||||
{% endif %}
|
||||
@ -565,8 +564,8 @@
|
||||
initRequestListPage({
|
||||
apiListUrl: "{% url 'api:v1:request-list' %}",
|
||||
apiDetailBase: "{% url 'api:v1:request-list' %}",
|
||||
commentsUrlTemplate: "{% url 'requests:request_comments' 0 %}",
|
||||
addCommentUrlTemplate: "{% url 'requests:request_comment_add' 0 %}",
|
||||
commentsUrlTemplate: "/api/v1/requests/0/comments/",
|
||||
addCommentUrlTemplate: "/api/v1/requests/0/comments/",
|
||||
autoShowComments: {% if comments_for %}true{% else %}false{% endif %}
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user