Corp/backend/static/js/modules/requestDetail.js
Игорь Надеин 2ab5920386 feat: requests detail view - stage 3: frontend разработка
Этап 3 разработки включает:

✅ Создан JavaScript модуль requestDetail.js:
  - Класс RequestDetailModal для управления модальным окном
  - Функция open(requestId) для открытия заявления
  - Функция loadData(requestId) для загрузки через AJAX
  - Обработка ошибок (404, 403)
  - Поддержка добавления комментариев в модали
  - initRequestDetailModal() для инициализации на странице список

✅ Добавлено модальное окно в request_list_full.html:
  - HTML структура модального окна requestDetailModal
  - ID контейнера для содержимого: requestDetailContent
  - Bootstrap 5 modal с размером modal-lg
  - Scrollable модаль для больших содержимо

✅ Обновлена инициализация скриптов:
  - Подключение модуля requestDetail.js
  - Инициализация RequestDetailModal при загрузке страницы
  - Сохранение экземпляра для использования на странице

✅ Интеграция с кликами:
  - Подготовка к интеграции с существующей логикой списка
  - Возможность открытия заявления из списка или по прямой ссылке

Готово для следующего этапа 4: Функциональность комментариев
2025-12-26 16:03:25 +03:00

212 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Module: Request Detail Modal
*
* Отвечает за открытие и управление модальным окном с деталями заявления.
* Используется для открытия заявления из списка или прямой ссылки.
*/
export class RequestDetailModal {
constructor(modalId = 'requestDetailModal', containerId = 'requestDetailContent') {
this.modalId = modalId;
this.containerId = containerId;
this.modal = null;
this.container = null;
this.currentRequestId = null;
this.init();
}
/**
* Инициализация модального окна
*/
init() {
const modalElement = document.getElementById(this.modalId);
if (!modalElement) {
console.warn(`Modal element with id "${this.modalId}" not found`);
return;
}
this.container = document.getElementById(this.containerId);
this.modal = new bootstrap.Modal(modalElement);
// Обработчики событий модали
modalElement.addEventListener('hidden.bs.modal', () => {
this.currentRequestId = null;
});
}
/**
* Открыть модальное окно с заявлением
* @param {number} requestId - ID заявления
*/
async open(requestId) {
this.currentRequestId = requestId;
// Показать loading
if (this.container) {
this.container.innerHTML = '<div class="d-flex justify-content-center p-4"><div class="spinner-border" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
}
// Загрузить данные
await this.loadData(requestId);
// Показать модаль
if (this.modal) {
this.modal.show();
}
}
/**
* Загрузить данные заявления через AJAX
* @param {number} requestId - ID заявления
*/
async loadData(requestId) {
try {
const response = await fetch(`/requests/${requestId}/`, {
method: 'GET',
headers: {
'Accept': 'text/html',
},
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const html = await response.text();
// Извлечь содержимое из полной страницы (ищем .card элемент)
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const content = doc.querySelector('.container > .row > .col-12');
if (content && this.container) {
this.container.innerHTML = content.innerHTML;
this.attachEventListeners();
}
} catch (error) {
console.error('Error loading request details:', error);
if (this.container) {
this.container.innerHTML = `
<div class="alert alert-danger mb-0">
<strong>Ошибка при загрузке:</strong> ${error.message}
</div>
`;
}
}
}
/**
* Присоединить обработчики событий к элементам в модали
*/
attachEventListeners() {
// Обработчик для формы добавления комментария
const commentForm = this.container?.querySelector('.request-comment-form');
if (commentForm) {
commentForm.addEventListener('submit', (e) => this.handleCommentSubmit(e));
}
// Обработчик для кнопки отозвать
const cancelBtn = this.container?.querySelector('[href*="request_cancel"]');
if (cancelBtn) {
cancelBtn.addEventListener('click', (e) => this.handleCancel(e));
}
}
/**
* Обработчик отправки комментария
*/
async handleCommentSubmit(e) {
e.preventDefault();
const form = e.target;
const textarea = form.querySelector('textarea[name="text"]');
const text = textarea?.value.trim();
if (!text) {
alert('Пожалуйста, введите текст комментария');
return;
}
try {
const formData = new FormData(form);
const response = await fetch(form.action, {
method: 'POST',
body: formData,
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// Очистить форму
textarea.value = '';
// Перезагрузить содержимое модали
if (this.currentRequestId) {
await this.loadData(this.currentRequestId);
}
} catch (error) {
console.error('Error submitting comment:', error);
alert('Ошибка при добавлении комментария: ' + error.message);
}
}
/**
* Обработчик отозвания заявления
*/
handleCancel(e) {
e.preventDefault();
if (confirm('Вы уверены, что хотите отозвать это заявление?')) {
// Перейти по ссылке или отправить форму
window.location.href = e.target.href;
}
}
/**
* Закрыть модальное окно
*/
close() {
if (this.modal) {
this.modal.hide();
}
}
}
/**
* Инициализировать обработчики для открытия заявлений
* Должна быть вызвана после загрузки страницы с списком заявлений
*/
export function initRequestDetailModal() {
const detailModal = new RequestDetailModal();
// Обработчик клика по строкам в таблице заявлений
document.addEventListener('click', (e) => {
// Проверить если клик был по элементу с data-request-id
let target = e.target.closest('[data-request-id]');
if (target) {
const requestId = target.dataset.requestId;
if (requestId) {
e.preventDefault();
detailModal.open(parseInt(requestId));
}
}
// Или клик по ссылке на заявление
if (e.target.classList.contains('request-link')) {
const requestId = e.target.dataset.requestId;
if (requestId) {
e.preventDefault();
detailModal.open(parseInt(requestId));
}
}
});
return detailModal;
}
export default RequestDetailModal;