diff --git a/backend/feed/views_front.py b/backend/feed/views_front.py index 9eb41004..40e1387f 100644 --- a/backend/feed/views_front.py +++ b/backend/feed/views_front.py @@ -133,18 +133,11 @@ def _post_back_url(post: dict, user) -> str: def feed_list(request): """ - Лента компании - посты загружаются через API на клиенте. - Блок «новые сотрудники» загружается с сервера. + Лента компании - посты и новые сотрудники загружаются через API на клиенте. """ - # Загружаем только новых сотрудников с сервера - new_employees = _api_list_new_employees(request, limit=10) - return render( request, "feed/feed_list.html", - { - "new_employees": new_employees, - }, ) diff --git a/backend/static/js/components/feedList.js b/backend/static/js/components/feedList.js index 4d4ed581..83d1e72b 100644 --- a/backend/static/js/components/feedList.js +++ b/backend/static/js/components/feedList.js @@ -365,10 +365,11 @@ export class FeedList { collapseDiv.id = `ccoll-${post.id}`; collapseDiv.className = 'collapse'; - // Заглушка - показываем кнопку "Показать все комментарии" + // Контейнер с padding const contentDiv = document.createElement('div'); contentDiv.className = 'px-3 pt-2'; + // Кнопка "Показать все комментарии" const linkContainer = document.createElement('div'); linkContainer.className = 'd-flex justify-content-between align-items-center mb-2'; @@ -381,11 +382,141 @@ export class FeedList { linkContainer.appendChild(showAllLink); contentDiv.appendChild(linkContainer); + + // Последний комментарий (если есть) + if (post.last_comment) { + const commentDiv = this.createLastComment(post.last_comment); + contentDiv.appendChild(commentDiv); + } + collapseDiv.appendChild(contentDiv); + // Форма быстрого комментария + const form = this.createQuickCommentForm(post.id); + collapseDiv.appendChild(form); + return collapseDiv; } + /** + * Создание последнего комментария + */ + createLastComment(comment) { + const commentDiv = document.createElement('div'); + commentDiv.className = 'comment-item'; + + // Header с автором и датой + const headerDiv = document.createElement('div'); + headerDiv.className = 'comment-header'; + + // Автор + if (comment.author?.id) { + const authorLink = document.createElement('a'); + authorLink.href = `/employees/${comment.author.id}/`; + authorLink.className = 'comment-author'; + authorLink.textContent = comment.author.full_name || 'Сотрудник'; + headerDiv.appendChild(authorLink); + } else { + const authorSpan = document.createElement('span'); + authorSpan.className = 'comment-author'; + authorSpan.textContent = comment.author?.full_name || 'Сотрудник'; + headerDiv.appendChild(authorSpan); + } + + // Разделитель + const separator = document.createElement('span'); + separator.className = 'comment-separator'; + separator.textContent = '·'; + headerDiv.appendChild(separator); + + // Дата + const timeEl = document.createElement('time'); + timeEl.className = 'comment-date'; + timeEl.textContent = comment.created_at_display || comment.created_at || ''; + headerDiv.appendChild(timeEl); + + commentDiv.appendChild(headerDiv); + + // Тело комментария (обрезанное до 220 символов) + const bodyDiv = document.createElement('div'); + bodyDiv.className = 'comment-body'; + let text = comment.text || ''; + if (text.length > 220) { + text = text.substring(0, 217) + '...'; + } + bodyDiv.textContent = text; + commentDiv.appendChild(bodyDiv); + + return commentDiv; + } + + /** + * Создание формы быстрого комментария + */ + createQuickCommentForm(postId) { + const form = document.createElement('form'); + form.className = 'px-3 pb-3 pt-2 comment-form-quick'; + form.dataset.postId = postId; + + // Message field container + const messageField = document.createElement('div'); + messageField.className = 'message-field message-field--compact'; + + // Emoji picker dropdown + const emojiDropdown = document.createElement('div'); + emojiDropdown.className = 'dropdown message-emoji'; + + const emojiBtn = document.createElement('button'); + emojiBtn.type = 'button'; + emojiBtn.className = 'btn btn-ghost btn-emoji message-icon-btn'; + emojiBtn.dataset.bsToggle = 'dropdown'; + emojiBtn.setAttribute('aria-expanded', 'false'); + emojiBtn.title = 'Вставить смайлик'; + + const emojiIcon = document.createElement('i'); + emojiIcon.className = 'bi-emoji-smile'; + emojiBtn.appendChild(emojiIcon); + + const emojiMenu = document.createElement('div'); + emojiMenu.className = 'dropdown-menu dropdown-menu-start message-emoji-menu'; + + const emojiPicker = document.createElement('emoji-picker'); + emojiPicker.dataset.emojiPicker = ''; + emojiPicker.className = 'chat-emoji-picker'; + emojiMenu.appendChild(emojiPicker); + + emojiDropdown.appendChild(emojiBtn); + emojiDropdown.appendChild(emojiMenu); + + messageField.appendChild(emojiDropdown); + + // Textarea + const textarea = document.createElement('textarea'); + textarea.name = 'text'; + textarea.className = 'form-control message-input'; + textarea.rows = 1; + textarea.placeholder = 'Оставить комментарий…'; + textarea.required = true; + + messageField.appendChild(textarea); + + // Send button + const sendBtn = document.createElement('button'); + sendBtn.className = 'btn btn-primary message-send'; + sendBtn.type = 'submit'; + sendBtn.title = 'Отправить'; + + const sendIcon = document.createElement('i'); + sendIcon.className = 'bi-send'; + sendBtn.appendChild(sendIcon); + + messageField.appendChild(sendBtn); + + form.appendChild(messageField); + + return form; + } + /** * Показать лоадер */ diff --git a/backend/static/js/components/newEmployeesScroller.js b/backend/static/js/components/newEmployeesScroller.js new file mode 100644 index 00000000..d8a48744 --- /dev/null +++ b/backend/static/js/components/newEmployeesScroller.js @@ -0,0 +1,153 @@ +/** + * Модуль для загрузки и отображения новых сотрудников + * Использует API для получения данных с корректными глобальными URL + */ + +export class NewEmployeesScroller { + constructor(containerId = 'newEmployeesContainer') { + this.container = document.getElementById(containerId); + if (!this.container) { + console.warn(`Container #${containerId} not found`); + return; + } + this.apiUrl = '/api/v1/employees/'; + } + + /** + * Инициализация - загрузка и рендеринг + */ + async init() { + try { + const employees = await this.fetchNewEmployees(); + if (employees && employees.length > 0) { + this.render(employees); + } else { + this.container.style.display = 'none'; + } + } catch (error) { + console.error('Failed to load new employees:', error); + this.container.style.display = 'none'; + } + } + + /** + * Загрузка новых сотрудников через API + */ + async fetchNewEmployees() { + // Дата 14 дней назад + const since = new Date(); + since.setDate(since.getDate() - 14); + const sinceISO = since.toISOString(); + + const params = new URLSearchParams({ + active: 'true', + created_at__gte: sinceISO, + ordering: '-created_at', + page_size: '10' + }); + + const response = await fetch(`${this.apiUrl}?${params}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.results || data; + } + + /** + * Рендеринг списка сотрудников + */ + render(employees) { + const scrollerHtml = this.createScrollerHtml(employees); + this.container.innerHTML = scrollerHtml; + this.container.style.display = 'block'; + } + + /** + * Создание HTML разметки скроллера + */ + createScrollerHtml(employees) { + const items = employees.map(emp => this.createEmployeeItem(emp)).join(''); + + return ` +