From 5ca0feb9ebb10f4743c432f2e5db41a7c6e6740a 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: Wed, 3 Dec 2025 08:49:23 +0300 Subject: [PATCH] =?UTF-8?q?feat(requests):=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B0=20UI=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стили комментариев: - Приведены к единому стилю с лентой новостей - Убрана фоновая подложка блока комментариев - Убрана разделительная линия между заявлением и комментариями - Обновлена структура: 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 ошибки (длинные строки) --- backend/requests_app/views_front.py | 27 +++++++++++++++---- .../static/css/components/request-list.css | 2 -- .../js/components/requestCommentsHandler.js | 26 +++++++++++++----- backend/static/js/pages/requestList.js | 26 +++++++++++++++++- backend/templates/requests_app/_filters.html | 5 ++-- .../requests_app/request_list_full.html | 7 +++-- 6 files changed, 72 insertions(+), 21 deletions(-) diff --git a/backend/requests_app/views_front.py b/backend/requests_app/views_front.py index a1c11e10..d5b8f847 100644 --- a/backend/requests_app/views_front.py +++ b/backend/requests_app/views_front.py @@ -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, diff --git a/backend/static/css/components/request-list.css b/backend/static/css/components/request-list.css index 6d80a8e1..b9805397 100644 --- a/backend/static/css/components/request-list.css +++ b/backend/static/css/components/request-list.css @@ -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); /* Выравнивание с содержимым */ diff --git a/backend/static/js/components/requestCommentsHandler.js b/backend/static/js/components/requestCommentsHandler.js index 71564334..6e3137bf 100644 --- a/backend/static/js/components/requestCommentsHandler.js +++ b/backend/static/js/components/requestCommentsHandler.js @@ -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 `
-
- ${esc(author)} - ${esc(comment.created_at || '')} +
+ ${authorId + ? `${esc(authorName)}` + : `${esc(authorName)}` + } + · +
-
${esc(comment.text || '')}
+
${esc(comment.text || '')}
`; } @@ -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]'); diff --git a/backend/static/js/pages/requestList.js b/backend/static/js/pages/requestList.js index c5efc0fe..a1b7cf99 100644 --- a/backend/static/js/pages/requestList.js +++ b/backend/static/js/pages/requestList.js @@ -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({ diff --git a/backend/templates/requests_app/_filters.html b/backend/templates/requests_app/_filters.html index bb8a2285..2964fcdf 100644 --- a/backend/templates/requests_app/_filters.html +++ b/backend/templates/requests_app/_filters.html @@ -3,8 +3,9 @@
diff --git a/backend/templates/requests_app/request_list_full.html b/backend/templates/requests_app/request_list_full.html index d5b6b53d..e02d4201 100644 --- a/backend/templates/requests_app/request_list_full.html +++ b/backend/templates/requests_app/request_list_full.html @@ -148,8 +148,7 @@
{% if obj.created_at %} - {{ obj.created_at|format_iso_date:"d.m.Y" }}
- {{ obj.created_at|format_iso_date:"H:i" }} + {{ obj.created_at|format_iso_date:"d.m.Y" }} {% else %} — {% 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 %} });