/** * Модуль вкладок "Реестры": исключения, удаленщики, командировки, флигель. */ 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 `
${fio}
${!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('Ошибка сети'); } }