215 lines
9.2 KiB
JavaScript
215 lines
9.2 KiB
JavaScript
/**
|
|
* ===============================================================================
|
|
* FILE: modules/web_api/static/js/manual_absences.js
|
|
* ROLE: Модальные окна "Мест. командир.", "Иное", универсальный автокомплит ФИО
|
|
* и синхронизация с боковой панелью SidebarManager.
|
|
* ===============================================================================
|
|
*/
|
|
|
|
let activeAbsenceType = 'LOCAL_TRIP'; // 'LOCAL_TRIP' или 'OTHER'
|
|
let reasonsCache = [];
|
|
|
|
async function loadAbsenceReasons() {
|
|
try {
|
|
const res = await fetch('/api/v1/manual-absences/reasons');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
reasonsCache = data.reasons || [];
|
|
}
|
|
} catch (e) {
|
|
console.error('Ошибка загрузки причин:', e);
|
|
}
|
|
}
|
|
|
|
// ⭐️ Открытие окна: режим создания (item = null) или редактирования (item = {...})
|
|
function openManualAbsenceModal(type, editItem = null) {
|
|
activeAbsenceType = type;
|
|
const isTrip = (type === 'LOCAL_TRIP');
|
|
const modal = document.getElementById('manual-absence-modal');
|
|
const titleEl = document.getElementById('manual-absence-modal-title-text');
|
|
const reasonBlock = document.getElementById('manual-absence-reason-block');
|
|
const reasonSelect = document.getElementById('manual-absence-reason-select');
|
|
|
|
const idInput = document.getElementById('manual-absence-id');
|
|
const fioInput = document.getElementById('manual-absence-fio-input');
|
|
const deptInput = document.getElementById('manual-absence-dept');
|
|
const posInput = document.getElementById('manual-absence-pos');
|
|
const startDateInput = document.getElementById('manual-absence-start-date');
|
|
const endDateInput = document.getElementById('manual-absence-end-date');
|
|
const errEl = document.getElementById('manual-absence-error');
|
|
|
|
if (errEl) errEl.classList.add('hidden');
|
|
|
|
if (reasonBlock && reasonSelect) {
|
|
if (isTrip) {
|
|
reasonBlock.classList.add('hidden');
|
|
} else {
|
|
reasonBlock.classList.remove('hidden');
|
|
reasonSelect.innerHTML = reasonsCache.map(r => `<option value="${r}">${r}</option>`).join('');
|
|
}
|
|
}
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
if (editItem) {
|
|
// Режим РЕДАКТИРОВАНИЯ
|
|
if (titleEl) titleEl.innerText = isTrip ? 'Изменение сроков командировки' : 'Изменение сроков отсутствия';
|
|
if (idInput) idInput.value = editItem.id;
|
|
if (fioInput) {
|
|
fioInput.value = editItem.fio;
|
|
fioInput.readOnly = true;
|
|
fioInput.classList.add('bg-slate-100', 'text-slate-500', 'cursor-not-allowed');
|
|
}
|
|
if (deptInput) deptInput.value = editItem.department || '';
|
|
if (posInput) posInput.value = editItem.position || '';
|
|
if (startDateInput) startDateInput.value = editItem.date_start ? editItem.date_start.replace(/\./g, '-') : today;
|
|
if (endDateInput) endDateInput.value = editItem.date_end ? editItem.date_end.replace(/\./g, '-') : today;
|
|
if (reasonSelect && editItem.reason) reasonSelect.value = editItem.reason;
|
|
} else {
|
|
// Режим СОЗДАНИЯ
|
|
if (titleEl) titleEl.innerText = isTrip ? 'Добавить в командировки' : 'Добавить отсутствие';
|
|
if (idInput) idInput.value = '';
|
|
if (fioInput) {
|
|
fioInput.value = '';
|
|
fioInput.readOnly = false;
|
|
fioInput.classList.remove('bg-slate-100', 'text-slate-500', 'cursor-not-allowed');
|
|
}
|
|
if (deptInput) deptInput.value = '';
|
|
if (posInput) posInput.value = '';
|
|
if (startDateInput) startDateInput.value = today;
|
|
if (endDateInput) endDateInput.value = today;
|
|
}
|
|
|
|
if (modal) modal.classList.remove('hidden');
|
|
}
|
|
|
|
function closeManualAbsenceModal() {
|
|
const modal = document.getElementById('manual-absence-modal');
|
|
if (modal) modal.classList.add('hidden');
|
|
}
|
|
|
|
async function submitManualAbsence(event) {
|
|
if (event) event.preventDefault();
|
|
const idVal = document.getElementById('manual-absence-id')?.value.trim();
|
|
const fio = document.getElementById('manual-absence-fio-input')?.value.trim();
|
|
const deptVal = document.getElementById('manual-absence-dept')?.value.trim() || '';
|
|
const posVal = document.getElementById('manual-absence-pos')?.value.trim() || '';
|
|
const startDateVal = document.getElementById('manual-absence-start-date')?.value || null;
|
|
const endDateVal = document.getElementById('manual-absence-end-date')?.value || null;
|
|
const reasonSelect = document.getElementById('manual-absence-reason-select');
|
|
const errEl = document.getElementById('manual-absence-error');
|
|
|
|
if (!fio) {
|
|
if (errEl) { errEl.innerText = 'Укажите ФИО сотрудника'; errEl.classList.remove('hidden'); }
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
absence_type: activeAbsenceType,
|
|
fio: fio,
|
|
department: deptVal,
|
|
position: posVal,
|
|
date_start: startDateVal,
|
|
date_end: endDateVal,
|
|
reason: activeAbsenceType === 'LOCAL_TRIP' ? 'Местная командировка' : (reasonSelect ? reasonSelect.value : 'Иное')
|
|
};
|
|
|
|
try {
|
|
let res;
|
|
if (idVal) {
|
|
// Редактирование
|
|
res = await fetch(`/api/v1/manual-absences/${idVal}`, {
|
|
method: 'PUT',
|
|
headers: { ...AuthManager.getAuthHeaders(), 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: parseInt(idVal), date_start: startDateVal, date_end: endDateVal })
|
|
});
|
|
} else {
|
|
// Создание
|
|
res = await fetch('/api/v1/manual-absences/', {
|
|
method: 'POST',
|
|
headers: { ...AuthManager.getAuthHeaders(), 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
if (res.ok) {
|
|
closeManualAbsenceModal();
|
|
if (typeof loadManualAbsencesView === 'function') {
|
|
loadManualAbsencesView(activeAbsenceType);
|
|
}
|
|
} else {
|
|
const data = await res.json();
|
|
if (errEl) { errEl.innerText = data.detail || 'Ошибка сохранения'; errEl.classList.remove('hidden'); }
|
|
}
|
|
} catch (e) {
|
|
if (errEl) { errEl.innerText = 'Сетевая ошибка при сохранении'; errEl.classList.remove('hidden'); }
|
|
}
|
|
}
|
|
|
|
// Универсальный автокомплит сотрудников
|
|
let searchTimeout = null;
|
|
function setupStaffAutocomplete(inputEl, suggestionsBoxId) {
|
|
const box = document.getElementById(suggestionsBoxId);
|
|
if (!inputEl || !box) return;
|
|
|
|
inputEl.addEventListener('input', function() {
|
|
const val = this.value.trim();
|
|
clearTimeout(searchTimeout);
|
|
if (val.length < 2) {
|
|
box.classList.add('hidden');
|
|
box.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
searchTimeout = setTimeout(async () => {
|
|
try {
|
|
const res = await fetch(`/api/v1/manual-absences/staff-autocomplete?q=${encodeURIComponent(val)}`);
|
|
if (!res.ok) return;
|
|
const items = await res.json();
|
|
if (items.length === 0) {
|
|
box.classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
box.innerHTML = items.map(it => `
|
|
<div class="p-2 hover:bg-indigo-50 cursor-pointer border-b border-slate-100 flex flex-col text-xs"
|
|
onmousedown="selectStaffSuggestion('${escapeHtml(it.fio)}', '${escapeHtml(it.department)}', '${escapeHtml(it.position)}', '${inputEl.id}', '${suggestionsBoxId}')">
|
|
<span class="font-bold text-slate-800">${escapeHtml(it.fio)}</span>
|
|
<span class="text-[10px] text-slate-500">${escapeHtml(it.department)} · ${escapeHtml(it.position)}</span>
|
|
</div>
|
|
`).join('');
|
|
box.classList.remove('hidden');
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}, 200);
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (!inputEl.contains(e.target) && !box.contains(e.target)) {
|
|
box.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function selectStaffSuggestion(fio, dept, pos, targetInputId, boxId) {
|
|
const targetFioEl = document.getElementById(targetInputId);
|
|
if (targetFioEl) targetFioEl.value = fio;
|
|
|
|
if (targetInputId === 'rw-fio') {
|
|
const rwDept = document.getElementById('rw-dept');
|
|
if (rwDept && dept && dept !== '—') rwDept.value = dept;
|
|
} else if (targetInputId === 'manual-absence-fio-input') {
|
|
const deptInput = document.getElementById('manual-absence-dept');
|
|
const posInput = document.getElementById('manual-absence-pos');
|
|
if (deptInput) deptInput.value = dept;
|
|
if (posInput) posInput.value = pos;
|
|
}
|
|
|
|
const box = document.getElementById(boxId);
|
|
if (box) box.classList.add('hidden');
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadAbsenceReasons();
|
|
}); |