feat: requests detail view - stage 5: функциональность изменения статуса
- Добавлен обработчик для смены статуса заявления в JavaScript
- Интегрирована форма смены статуса в шаблон request_detail.html
- Форма видна только администраторам (can_process=True)
- Поддерживаются переходы: pending->approved/rejected, *->cancelled
- Добавлена валидация и подтверждение перед изменением статуса
- Используются API endpoints: /api/v1/requests/{id}/approve/, reject/, cancel/
- Автоматическое обновление содержимого модали после смены статуса
- Отображение статуса обновления (loading, success, error) в UI
This commit is contained in:
parent
48a96f495e
commit
240f734c92
@ -120,6 +120,12 @@ export class RequestDetailModal {
|
||||
});
|
||||
}
|
||||
|
||||
// Обработчик для кнопок смены статуса
|
||||
const statusForm = this.container?.querySelector('.request-status-form');
|
||||
if (statusForm) {
|
||||
statusForm.addEventListener('submit', (e) => this.handleStatusChange(e));
|
||||
}
|
||||
|
||||
// Инициализация emoji picker если доступна
|
||||
this.initEmojiPicker();
|
||||
|
||||
@ -280,6 +286,108 @@ export class RequestDetailModal {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик изменения статуса заявления
|
||||
*/
|
||||
async handleStatusChange(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.target;
|
||||
const selectField = form.querySelector('select[name="status"]');
|
||||
const newStatus = selectField?.value;
|
||||
|
||||
if (!newStatus) {
|
||||
alert('Пожалуйста, выберите новый статус');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmMsg = form.dataset.confirmMsg ||
|
||||
`Вы уверены, что хотите изменить статус на "${form.querySelector(`option[value="${newStatus}"]`)?.textContent}"?`;
|
||||
|
||||
if (!confirm(confirmMsg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Показать состояние загрузки
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalBtnText = submitBtn?.textContent || 'Применить';
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Обновление...';
|
||||
}
|
||||
|
||||
try {
|
||||
// Использовать правильный endpoint для изменения статуса
|
||||
const action = newStatus === 'approved' ? 'approve' :
|
||||
newStatus === 'rejected' ? 'reject' :
|
||||
newStatus === 'cancelled' ? 'cancel' : null;
|
||||
|
||||
if (!action) {
|
||||
throw new Error('Неподдерживаемый статус');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`/api/v1/requests/${this.currentRequestId}/${action}/`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]')?.value || ''
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
// Показать успешное сообщение
|
||||
if (submitBtn) {
|
||||
const originalClass = submitBtn.className;
|
||||
submitBtn.className = submitBtn.className.replace('btn-primary', 'btn-success');
|
||||
submitBtn.innerHTML = '<i class="bi-check-circle me-2"></i>Статус обновлен!';
|
||||
|
||||
// Вернуть кнопку в исходное состояние
|
||||
setTimeout(() => {
|
||||
submitBtn.className = originalClass;
|
||||
submitBtn.innerHTML = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Перезагрузить содержимое модали
|
||||
if (this.currentRequestId) {
|
||||
setTimeout(() => {
|
||||
this.loadData(this.currentRequestId);
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error changing status:', error);
|
||||
|
||||
// Восстановить кнопку при ошибке
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalBtnText;
|
||||
}
|
||||
|
||||
// Показать ошибку в UI
|
||||
const alertDiv = form.querySelector('.alert');
|
||||
if (alertDiv) {
|
||||
alertDiv.remove();
|
||||
}
|
||||
|
||||
const errorAlert = document.createElement('div');
|
||||
errorAlert.className = 'alert alert-danger alert-dismissible fade show mb-2';
|
||||
errorAlert.innerHTML = `
|
||||
<strong>Ошибка:</strong> ${error.message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
form.parentNode.insertBefore(errorAlert, form);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть модальное окно
|
||||
*/
|
||||
|
||||
@ -85,6 +85,26 @@
|
||||
<i class="bi-x-circle"></i> Отозвать
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Форма смены статуса — для администраторов #}
|
||||
{% if can_process %}
|
||||
<form class="request-status-form ms-auto d-flex gap-2" method="post">
|
||||
{% csrf_token %}
|
||||
<select class="form-select form-select-sm" name="status" style="max-width: 200px;">
|
||||
<option value="">Изменить статус...</option>
|
||||
{% if request_obj.status == 'pending' %}
|
||||
<option value="approved">✓ Одобрить</option>
|
||||
<option value="rejected">✗ Отклонить</option>
|
||||
{% endif %}
|
||||
{% if not request_obj.is_final %}
|
||||
<option value="cancelled">⊗ Отменить</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-primary" {% if not request_obj.status == 'pending' and request_obj.is_final %}disabled{% endif %}>
|
||||
Применить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
<div class="card-body pt-0">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user