diff --git a/backend/static/js/components/employeeFormHandler.js b/backend/static/js/components/employeeFormHandler.js
index bb7341b3..95ab9ee9 100644
--- a/backend/static/js/components/employeeFormHandler.js
+++ b/backend/static/js/components/employeeFormHandler.js
@@ -1,84 +1,180 @@
/**
* employeeFormHandler.js
- * Минимальный стаб, оставленный ради обратной совместимости после
- * отказа от кастомного JS-сабмита. Теперь форма отправляется браузером
- * напрямую в фронтовую Django-вьюху, а модуль лишь возвращает
- * совместимое API без вмешательства в submit.
+ * Обработка отправки формы редактирования сотрудника через AJAX
+ * с отображением ошибок валидации inline без перезагрузки страницы
*/
export function initEmployeeForm(options = {}) {
const formId = options.formId || 'apiEditForm';
const form = document.getElementById(formId);
+
if (!form) {
console.warn('[EmployeeForm] Форма не найдена, инициализация пропущена');
return null;
}
- // Новый обработчик: submit через fetch
- form.addEventListener('submit', async function (e) {
+ console.info('[EmployeeForm] Инициализация AJAX обработчика формы');
+
+ // Перехватываем submit формы
+ form.addEventListener('submit', async (e) => {
e.preventDefault();
- // Очищаем старые ошибки
- form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
- form.querySelectorAll('.invalid-feedback').forEach(el => el.textContent = '');
-
- const endpoint = form.dataset.endpoint || form.action;
- const formData = new FormData(form);
- let payload = {};
- formData.forEach((value, key) => {
- if (payload[key]) {
- if (!Array.isArray(payload[key])) payload[key] = [payload[key]];
- payload[key].push(value);
- } else {
- payload[key] = value;
- }
- });
-
- // Если есть файл, отправляем multipart, иначе json
- let fetchOptions = {
- method: 'POST',
- headers: {
- 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
- }
- };
- if (form.enctype === 'multipart/form-data') {
- fetchOptions.body = formData;
- delete fetchOptions.headers['Content-Type'];
- } else {
- fetchOptions.headers['Content-Type'] = 'application/json';
- fetchOptions.body = JSON.stringify(payload);
- }
-
+
+ const submitBtn = form.querySelector('button[type="submit"]');
+ const btnLabel = submitBtn?.querySelector('.btn-label');
+ const btnSpinner = submitBtn?.querySelector('.btn-spinner');
+
+ // Очищаем предыдущие ошибки
+ clearFormErrors(form);
+
+ // Показываем индикатор загрузки
+ if (submitBtn) submitBtn.disabled = true;
+ if (btnLabel) btnLabel.classList.add('d-none');
+ if (btnSpinner) btnSpinner.classList.remove('d-none');
+
try {
- const response = await fetch(endpoint, fetchOptions);
- const data = await response.json();
- if (response.ok) {
- // Успех: можно обновить данные или закрыть форму
- if (options.onSuccess) options.onSuccess(data);
- else location.reload();
- } else {
- // Ошибки: показываем в форме
- if (data && typeof data === 'object') {
- for (const [field, errors] of Object.entries(data)) {
- const input = form.querySelector(`[name="${field}"]`);
- if (input) {
- input.classList.add('is-invalid');
- let feedback = input.parentElement.querySelector('.invalid-feedback');
- if (!feedback) {
- feedback = document.createElement('div');
- feedback.className = 'invalid-feedback';
- input.parentElement.appendChild(feedback);
- }
- feedback.textContent = Array.isArray(errors) ? errors.join(' ') : errors;
- }
- }
+ const endpoint = form.dataset.endpoint || form.action;
+ const formData = new FormData(form);
+
+ console.log('[EmployeeForm] Отправка данных на:', endpoint);
+
+ const response = await fetch(endpoint, {
+ method: 'PATCH',
+ body: formData,
+ headers: {
+ 'X-CSRFToken': formData.get('csrfmiddlewaretoken') || ''
}
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ console.log('[EmployeeForm] Успешно сохранено');
+ showSuccessMessage('Изменения сохранены');
+
+ // Обновляем страницу через 1 секунду
+ setTimeout(() => {
+ location.reload();
+ }, 1000);
+ } else {
+ console.warn('[EmployeeForm] Ошибка валидации:', data);
+ displayFormErrors(form, data);
+ showErrorMessage('Исправьте ошибки в форме');
}
- } catch (err) {
- alert('Ошибка отправки формы: ' + err);
+ } catch (error) {
+ console.error('[EmployeeForm] Ошибка отправки:', error);
+ showErrorMessage('Произошла ошибка при сохранении');
+ } finally {
+ // Возвращаем кнопку в нормальное состояние
+ if (submitBtn) submitBtn.disabled = false;
+ if (btnLabel) btnLabel.classList.remove('d-none');
+ if (btnSpinner) btnSpinner.classList.add('d-none');
}
});
return {
- setSubmitting: (v) => {},
+ setSubmitting: (state) => {
+ const submitBtn = form.querySelector('button[type="submit"]');
+ if (submitBtn) submitBtn.disabled = state;
+ },
refresh: () => location.reload()
};
}
+
+/**
+ * Очистка всех ошибок в форме
+ */
+function clearFormErrors(form) {
+ // Убираем класс is-invalid со всех полей
+ form.querySelectorAll('.is-invalid').forEach(el => {
+ el.classList.remove('is-invalid');
+ });
+
+ // Очищаем все сообщения об ошибках
+ form.querySelectorAll('.invalid-feedback').forEach(el => {
+ el.textContent = '';
+ el.style.display = 'none';
+ });
+}
+
+/**
+ * Отображение ошибок валидации в полях формы
+ */
+function displayFormErrors(form, errors) {
+ // errors может быть объектом с полями или массивом строк
+ if (Array.isArray(errors)) {
+ // Общие ошибки (non_field_errors)
+ showErrorMessage(errors.join(', '));
+ return;
+ }
+
+ if (typeof errors === 'object') {
+ Object.keys(errors).forEach(fieldName => {
+ const errorMessages = Array.isArray(errors[fieldName])
+ ? errors[fieldName]
+ : [errors[fieldName]];
+
+ // Ищем поле по имени
+ const field = form.querySelector(`[name="${fieldName}"]`);
+
+ if (field) {
+ // Добавляем класс ошибки
+ field.classList.add('is-invalid');
+
+ // Ищем или создаём контейнер для ошибки
+ let feedbackEl = field.parentElement.querySelector('.invalid-feedback');
+
+ if (!feedbackEl) {
+ feedbackEl = document.createElement('div');
+ feedbackEl.className = 'invalid-feedback';
+ field.parentElement.appendChild(feedbackEl);
+ }
+
+ // Показываем сообщение об ошибке
+ feedbackEl.textContent = errorMessages.join(', ');
+ feedbackEl.style.display = 'block';
+
+ console.log(`[EmployeeForm] Ошибка в поле ${fieldName}:`, errorMessages);
+ } else {
+ console.warn(`[EmployeeForm] Поле ${fieldName} не найдено в форме`);
+ }
+ });
+ }
+}
+
+/**
+ * Показ сообщения об успехе
+ */
+function showSuccessMessage(message) {
+ // Можно использовать toast или простой alert
+ const alertDiv = document.createElement('div');
+ alertDiv.className = 'alert alert-success alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3';
+ alertDiv.style.zIndex = '9999';
+ alertDiv.innerHTML = `
+ ${message}
+
+ `;
+ document.body.appendChild(alertDiv);
+
+ // Автоматически убираем через 3 секунды
+ setTimeout(() => {
+ alertDiv.remove();
+ }, 3000);
+}
+
+/**
+ * Показ сообщения об ошибке
+ */
+function showErrorMessage(message) {
+ const alertDiv = document.createElement('div');
+ alertDiv.className = 'alert alert-danger alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3';
+ alertDiv.style.zIndex = '9999';
+ alertDiv.innerHTML = `
+ ${message}
+
+ `;
+ document.body.appendChild(alertDiv);
+
+ // Автоматически убираем через 5 секунд
+ setTimeout(() => {
+ alertDiv.remove();
+ }, 5000);
+}