/**
* Модуль вкладок "Реестры": исключения, удаленщики, командировки, флигель.
*/
window.openAddExceptionModal = function(category) {
const modal = document.getElementById("exception-modal");
const catInput = document.getElementById("exception-category-input");
const valInput = document.getElementById("exception-value-input");
const commInput = document.getElementById("exception-comment-input");
const headerEl = document.getElementById("exception-modal-header-text");
const labelEl = document.getElementById("exception-value-label");
const errEl = document.getElementById("exception-error-msg");
if (!modal) return;
if (errEl) errEl.classList.add("hidden");
if (catInput) catInput.value = category;
if (valInput) { valInput.value = ""; valInput.focus(); }
if (commInput) commInput.value = "";
const titles = {
'include_fio': 'Белый список (ФИО)',
'fio': 'Исключенный сотрудник (ФИО)',
'departments': 'Исключенный отдел',
'positions': 'Исключенная должность',
'turnstile_fio': 'Правый турникет (ФИО)',
'turnstile_departments': 'Правый турникет (Отделы)',
'fligel_fio': 'Флигель (ФИО)',
'fligel_departments': 'Флигель (Отделы)'
};
const isDept = category.includes('department');
const isPos = category.includes('position');
if (headerEl) headerEl.innerText = "Добавить в " + (titles[category] || "реестр");
if (labelEl) {
labelEl.innerText = isDept ? 'Название подразделения:' : (isPos ? 'Название должности:' : 'ФИО сотрудника:');
}
modal.classList.remove("hidden");
};
window.closeExceptionModal = function() {
const modal = document.getElementById("exception-modal");
if (modal) modal.classList.add("hidden");
};
window.submitExceptionModalForm = async function(e) {
e.preventDefault();
const cat = document.getElementById("exception-category-input")?.value;
const val = document.getElementById("exception-value-input")?.value.trim();
const comm = document.getElementById("exception-comment-input")?.value.trim() || "";
const errEl = document.getElementById("exception-error-msg");
if (!cat || !val) {
if (errEl) { errEl.innerText = "Заполните поле"; errEl.classList.remove("hidden"); }
return;
}
try {
const res = await fetch("/api/v1/exceptions", {
method: "POST",
headers: AuthManager.getAuthHeaders(),
body: JSON.stringify({ category: cat, value: val, comment: comm })
});
if (res.ok) {
closeExceptionModal();
loadExceptionsView();
} else {
const data = await res.json();
if (errEl) { errEl.innerText = data.detail || "Ошибка сохранения"; errEl.classList.remove("hidden"); }
}
} catch (err) {
if (errEl) { errEl.innerText = "Ошибка соединения"; errEl.classList.remove("hidden"); }
}
};
function switchRegistrySubTab(subTab) {
if (window.SidebarManager) {
SidebarManager.currentRegistrySubTab = subTab;
}
document.querySelectorAll('.registry-subtab-btn').forEach(btn => {
const isActive = btn.dataset.subtab === subTab;
btn.classList.toggle('text-indigo-600', isActive);
btn.classList.toggle('bg-white', isActive);
btn.classList.toggle('shadow-xs', isActive);
btn.classList.toggle('font-bold', isActive);
btn.classList.toggle('text-slate-600', !isActive);
});
if (subTab === 'exceptions') loadExceptionsView();
else if (subTab === 'remote') loadRemoteWorkersView();
else if (subTab === 'local_trip') loadManualAbsencesView('LOCAL_TRIP');
else if (subTab === 'other') loadManualAbsencesView('OTHER');
}
async function loadExceptionsView() {
const container = document.getElementById('registry-content-container');
if (!container) return;
container.innerHTML = '
Загрузка правил...
';
try {
const res = await fetch('/api/v1/exceptions', { headers: AuthManager.getAuthHeaders() });
if (!res.ok) throw new Error('Ошибка сети');
const data = await res.json();
renderExceptionsView(data || {});
} catch (e) {
container.innerHTML = 'Не удалось загрузить реестры исключений
';
}
}
function renderExceptionsView(exceptions) {
const container = document.getElementById('registry-content-container');
if (!container) return;
const renderCard = (title, list, category, badgeClass, subBadge) => {
badgeClass = badgeClass || 'bg-slate-100 text-slate-700';
subBadge = subBadge || '';
let tags = 'Список пуст';
if (list && list.length > 0) {
tags = list.map(item => {
const val = escapeHtml(item);
return '' +
'' + val + '' +
'' +
'';
}).join('');
}
const count = list ? list.length : 0;
return '' +
'
' +
'
' +
'' + title + ' (' + count + ')' +
subBadge +
'
' +
'
' +
'
' +
'
' + tags + '
' +
'
';
};
container.innerHTML =
renderCard('Белый список (ФИО)', exceptions.include_fio, 'include_fio', 'bg-indigo-50 text-indigo-700') +
renderCard('Исключенные сотрудники (ФИО)', exceptions.fio, 'fio') +
renderCard('Исключенные отделы', exceptions.departments, 'departments') +
renderCard('Исключенные должности', exceptions.positions, 'positions') +
renderCard('Пр. турникет (ФИО)', exceptions.turnstile_fio, 'turnstile_fio', 'bg-emerald-50 text-emerald-700 border border-emerald-200', 'Оба турникета') +
renderCard('Пр. турникет (Отделы)', exceptions.turnstile_departments, 'turnstile_departments', 'bg-emerald-50 text-emerald-700 border border-emerald-200', 'Оба турникета') +
renderCard('Флигель (ФИО)', exceptions.fligel_fio, 'fligel_fio', 'bg-indigo-50 text-indigo-700 border border-indigo-200', 'Дверь 23') +
renderCard('Флигель (Отделы)', exceptions.fligel_departments, 'fligel_departments', 'bg-indigo-50 text-indigo-700 border border-indigo-200', 'Дверь 23');
container.querySelectorAll('.btn-add-exc').forEach(btn => {
btn.addEventListener('click', e => {
openAddExceptionModal(e.currentTarget.getAttribute('data-add-cat'));
});
});
container.querySelectorAll('.btn-remove-exc').forEach(btn => {
btn.addEventListener('click', e => {
const target = e.currentTarget;
removeExceptionItem(target.getAttribute('data-cat'), target.getAttribute('data-val'));
});
});
}
async function removeExceptionItem(category, value) {
if (!confirm('Удалить "' + value + '" из категории "' + category + '"?')) return;
try {
const res = await fetch('/api/v1/exceptions?category=' + encodeURIComponent(category) + '&value=' + encodeURIComponent(value), {
method: 'DELETE',
headers: AuthManager.getAuthHeaders()
});
if (res.ok) loadExceptionsView();
else alert('Ошибка при удалении');
} catch (e) {
alert('Ошибка сети');
}
}
async function loadRemoteWorkersView() {
const container = document.getElementById('registry-content-container');
if (!container) return;
container.innerHTML = ' Загрузка удаленщиков...
';
try {
const res = await fetch('/api/v1/remote-workers', { headers: AuthManager.getAuthHeaders() });
const data = await res.json();
const workers = data.workers || [];
let listHtml = '';
if (workers.length === 0) {
listHtml = 'Список удаленщиков пуст
';
} else {
listHtml = workers.map(w => {
const fio = escapeHtml(w.fio);
const dept = escapeHtml(w.department || 'Все');
const dFrom = escapeHtml(w.date_from || '—');
const dTo = escapeHtml(w.date_to || 'бессрочно');
return '' +
'
' +
'
' + fio + '' +
'
' +
'' +
'' +
'
' +
'
' +
'
' +
'' + dept + '' +
'' + dFrom + ' по ' + dTo + '' +
'
' +
'
';
}).join('');
}
container.innerHTML =
'' +
'Удаленные сотрудники: ' + workers.length + '' +
'' +
'
' +
'' + listHtml + '
';
const addBtn = document.getElementById('btn-add-remote');
if (addBtn) {
addBtn.addEventListener('click', () => openRemoteWorkerModal('ADD'));
}
container.querySelectorAll('.btn-edit-remote').forEach(btn => {
btn.addEventListener('click', e => {
const t = e.currentTarget;
openRemoteWorkerModal('EDIT', t.getAttribute('data-edit-fio'), t.getAttribute('data-edit-dept'), t.getAttribute('data-edit-from'), t.getAttribute('data-edit-to'));
});
});
container.querySelectorAll('.btn-del-remote').forEach(btn => {
btn.addEventListener('click', e => {
deleteRemoteWorkerItem(e.currentTarget.getAttribute('data-del-fio'));
});
});
} catch (e) {
container.innerHTML = 'Не удалось загрузить удаленщиков
';
}
}
async function deleteRemoteWorkerItem(fio) {
if (!confirm('Удалить сотрудника "' + fio + '"?')) return;
try {
const res = await fetch('/api/v1/remote-workers?fio=' + encodeURIComponent(fio), {
method: 'DELETE',
headers: AuthManager.getAuthHeaders()
});
if (res.ok) loadRemoteWorkersView();
} catch (e) {
alert('Ошибка сети');
}
}
async function loadManualAbsencesView(type) {
const container = document.getElementById('registry-content-container');
if (!container) return;
const isTrip = (type === 'LOCAL_TRIP');
const titleText = isTrip ? 'Местные командировки' : 'Иные причины';
const btnColor = isTrip ? 'bg-indigo-600 hover:bg-indigo-700' : 'bg-purple-600 hover:bg-purple-700';
container.innerHTML = ' Загрузка...
';
try {
const res = await fetch('/api/v1/manual-absences/?type=' + encodeURIComponent(type), {
headers: AuthManager.getAuthHeaders()
});
const data = await res.json();
const items = data.items || [];
let listHtml = '';
if (items.length === 0) {
listHtml = 'Нет записей
';
} else {
listHtml = items.map(it => {
const fio = escapeHtml(it.fio);
const reason = escapeHtml(it.reason || '');
const dept = escapeHtml(it.department || '—');
const dStart = it.date_start ? it.date_start.replace(/-/g, '.') : '';
const dEnd = it.date_end ? it.date_end.replace(/-/g, '.') : '';
// Форматирование срока
let dateBadge = '';
if (dStart && dEnd && dStart === dEnd) {
dateBadge = dStart;
} else if (dStart && dEnd) {
dateBadge = `${dStart} — ${dEnd}`;
} else if (dEnd) {
dateBadge = `по ${dEnd}`;
} else if (dStart) {
dateBadge = `с ${dStart}`;
} else {
dateBadge = 'бессрочно';
}
const badgeBg = isTrip
? 'bg-indigo-50 text-indigo-700 border-indigo-200'
: 'bg-purple-50 text-purple-700 border-purple-200';
return `
${!isTrip && reason ? `
${reason}
` : ''}
${dept}
${dateBadge}
`;
}).join('');
}
container.innerHTML = `
${titleText}: ${items.length}
${listHtml}
`;
const addBtn = document.getElementById('btn-add-absence');
if (addBtn) {
addBtn.addEventListener('click', () => openManualAbsenceModal(type));
}
// Слушатели кнопок редактирования
container.querySelectorAll('.btn-edit-abs').forEach(btn => {
btn.addEventListener('click', e => {
const t = e.currentTarget;
const id = t.getAttribute('data-edit-id');
const fio = t.getAttribute('data-edit-fio');
const start = t.getAttribute('data-edit-start');
const end = t.getAttribute('data-edit-end');
openManualAbsenceModal(type, {
id: id,
fio: fio,
date_start: start,
date_end: end
});
});
});
// Слушатели кнопок удаления
container.querySelectorAll('.btn-del-abs').forEach(btn => {
btn.addEventListener('click', e => {
deleteManualAbsenceRecord(e.currentTarget.getAttribute('data-del-id'), type);
});
});
} catch (e) {
container.innerHTML = 'Ошибка загрузки
';
}
}
// Быстрое модальное окно / диалог изменения дат
async function openEditAbsenceDatesModal(id, fio, dateStart, dateEnd, type) {
const newEnd = prompt(`Укажите новую дату окончания для сотрудника:\n${fio}\n(формат ГГГГ-ММ-ДД):`, dateEnd || '');
if (newEnd === null) return;
try {
const res = await fetch(`/api/v1/manual-absences/${id}`, {
method: 'PUT',
headers: { ...AuthManager.getAuthHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({
id: parseInt(id),
date_start: dateStart || null,
date_end: newEnd.trim() || null
})
});
if (res.ok) {
loadManualAbsencesView(type);
} else {
alert('Не удалось обновить дату');
}
} catch (err) {
alert('Ошибка сети при обновлении');
}
}
async function deleteManualAbsenceRecord(id, type) {
if (!confirm('Удалить эту запись?')) return;
try {
const res = await fetch('/api/v1/manual-absences/' + encodeURIComponent(id), {
method: 'DELETE',
headers: AuthManager.getAuthHeaders()
});
if (res.ok) loadManualAbsencesView(type);
} catch (e) {
alert('Ошибка сети');
}
}