Corp/backend/static/js/components/employeeGroupsManager.js

341 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* employeeGroupsManager.js
* Управление членством сотрудника в группах (добавление, удаление, создание, удаление групп)
*
* @module employeeGroupsManager
* @version 1.0.0
*/
/**
* Инициализация менеджера групп сотрудника
* @param {Object} options - Опции конфигурации
* @param {string} options.employeeId - ID сотрудника
* @param {string} options.bulkAddUrl - URL для массового добавления
* @param {string} options.bulkRemoveUrl - URL для массового удаления
* @param {string} options.createGroupUrl - URL для создания группы
* @param {string} options.deleteGroupUrl - URL для удаления группы
*/
export function initEmployeeGroupsManager(options = {}) {
// Элементы
const elems = {
assignBtn: document.getElementById('empGroupsAssignBtn'),
removeBtn: document.getElementById('empGroupsRemoveBtn'),
createBtn: document.getElementById('empGroupsCreateBtn'),
deleteBtn: document.getElementById('empGroupsDeleteBtn'),
msg: document.getElementById('empGroupsMsg'),
createMsg: document.getElementById('empGroupsCreateMsg'),
deleteMsg: document.getElementById('empGroupsDeleteMsg'),
groupsList: document.getElementById('empGroupsList'),
assignedList: document.getElementById('empGroupsAssigned'),
searchInput: document.getElementById('empGroupsSearch'),
clearBtn: document.getElementById('empGroupsClear'),
doneBtn: document.getElementById('empGroupsDone'),
createNameInput: document.getElementById('empGroupsCreateName'),
deleteSelect: document.getElementById('empGroupsDeleteSelect')
};
// Если основных элементов нет - функционал не используется на этой странице
if (!elems.assignBtn && !elems.groupsList) {
console.log('employeeGroupsManager: skip (no elements)');
return null;
}
console.log('employeeGroupsManager: initializing with elements:', {
assignBtn: !!elems.assignBtn,
groupsList: !!elems.groupsList,
assignedList: !!elems.assignedList
});
// Конфигурация из data-атрибутов блока
const block = document.getElementById('empGroupsBlock');
const config = {
bulkUrl: options.bulkUrl || block?.dataset.bulkUrl || '',
createGroupUrl: options.createGroupUrl || block?.dataset.createUrl || '',
deleteGroupUrl: options.deleteGroupUrl || block?.dataset.deleteUrl || ''
};
if (!config.bulkUrl) {
console.warn('employeeGroupsManager: bulkUrl not configured');
}
// ===== Helpers =====
function busy(btn, on) {
if (!btn) return;
btn.disabled = on;
const spinner = btn.querySelector('.spinner-border');
if (spinner) {
spinner.classList.toggle('d-none', !on);
}
}
function showMsg(text, success = true, targetElem = elems.msg) {
if (!targetElem) return;
targetElem.textContent = text;
targetElem.className = success ? 'alert alert-success mt-2' : 'alert alert-danger mt-2';
targetElem.classList.remove('d-none');
setTimeout(() => targetElem.classList.add('d-none'), 4000);
}
// Получить выбранные ID из модального окна
function selectedIds() {
const checks = elems.groupsList?.querySelectorAll('.emp-group-check:checked') || [];
return Array.from(checks).map((ch) => ch.value);
}
// Получить карту имен групп
function getGroupNames() {
const nameById = {};
const checks = elems.groupsList?.querySelectorAll('.emp-group-check') || [];
checks.forEach((ch) => {
nameById[ch.value] = ch.dataset.cn || `#${ch.value}`;
});
return nameById;
}
// Сбросить выбор в модале
function clearSelection() {
const checks = elems.groupsList?.querySelectorAll('.emp-group-check') || [];
checks.forEach((ch) => (ch.checked = false));
}
async function postJSON(url, body) {
const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]');
const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfInput?.value || ''
},
body: JSON.stringify(body)
});
if (!resp.ok) {
const txt = await resp.text();
throw new Error(txt || resp.statusText);
}
return await resp.json();
}
// ===== Pills rendering =====
function addAssignedBadges(groupIds, groupNames = {}) {
if (!elems.assignedList || !groupIds.length) return;
// Удалим "Нет персональных групп" если есть
const emptyMsg = elems.assignedList.querySelector('.text-secondary.small');
if (emptyMsg) emptyMsg.remove();
groupIds.forEach((gid) => {
const existing = elems.assignedList.querySelector(`[data-gid="${gid}"]`);
if (existing) return; // уже есть
const badge = document.createElement('span');
badge.className = 'badge rounded-pill bg-primary-subtle text-primary-emphasis border';
badge.dataset.gid = gid;
badge.textContent = groupNames[gid] || `#${gid}`;
elems.assignedList.appendChild(badge);
});
}
function removeAssignedBadges(groupIds) {
if (!elems.assignedList) return;
groupIds.forEach((gid) => {
const badge = elems.assignedList.querySelector(`[data-gid="${gid}"]`);
if (badge) badge.remove();
});
// Если больше нет badges, покажем "Нет персональных групп"
if (!elems.assignedList.querySelector('[data-gid]')) {
const emptyMsg = document.createElement('span');
emptyMsg.className = 'text-secondary small';
emptyMsg.textContent = 'Нет персональных групп';
elems.assignedList.appendChild(emptyMsg);
}
}
// ===== Assign/Remove groups =====
async function assignGroups() {
const ids = selectedIds();
if (!ids.length) {
showMsg('Не выбрано ни одной группы', false);
return;
}
if (!config.bulkUrl) {
showMsg('URL не настроен', false);
return;
}
busy(elems.assignBtn, true);
try {
const data = await postJSON(config.bulkUrl, {
action: 'add',
group_ids: ids
});
showMsg(data.message || 'Группы назначены', true);
// Добавляем badges
const groupNames = getGroupNames();
addAssignedBadges(ids, groupNames);
clearSelection();
} catch (err) {
showMsg('Ошибка: ' + err.message, false);
} finally {
busy(elems.assignBtn, false);
}
}
async function removeGroups() {
const ids = selectedIds();
if (!ids.length) {
showMsg('Не выбрано ни одной группы', false);
return;
}
if (!config.bulkUrl) {
showMsg('URL не настроен', false);
return;
}
busy(elems.removeBtn, true);
try {
const data = await postJSON(config.bulkUrl, {
action: 'remove',
group_ids: ids
});
showMsg(data.message || 'Группы сняты', true);
removeAssignedBadges(ids);
clearSelection();
} catch (err) {
showMsg('Ошибка: ' + err.message, false);
} finally {
busy(elems.removeBtn, false);
}
}
// ===== Create group =====
async function createGroup() {
const name = elems.createNameInput?.value.trim();
if (!name) {
showMsg('Введите имя группы', false, elems.createMsg);
return;
}
if (!config.createGroupUrl) {
showMsg('URL создания не настроен', false, elems.createMsg);
return;
}
busy(elems.createBtn, true);
try {
const data = await postJSON(config.createGroupUrl, { name });
showMsg(data.message || 'Группа создана', true, elems.createMsg);
if (elems.createNameInput) elems.createNameInput.value = '';
// Перезагрузим страницу через секунду
setTimeout(() => location.reload(), 1500);
} catch (err) {
showMsg('Ошибка: ' + err.message, false, elems.createMsg);
} finally {
busy(elems.createBtn, false);
}
}
// ===== Delete group =====
async function deleteGroup() {
const gid = elems.deleteSelect?.value;
if (!gid) {
showMsg('Выберите группу для удаления', false, elems.deleteMsg);
return;
}
const groupName = elems.deleteSelect?.selectedOptions[0]?.textContent || '';
if (!confirm(`Удалить группу "${groupName}"?`)) return;
if (!config.deleteGroupUrl) {
showMsg('URL удаления не настроен', false, elems.deleteMsg);
return;
}
busy(elems.deleteBtn, true);
try {
const data = await postJSON(config.deleteGroupUrl, { group_id: gid });
showMsg(data.message || 'Группа удалена', true, elems.deleteMsg);
// Удалим из селекта
const option = elems.deleteSelect?.querySelector(`option[value="${gid}"]`);
if (option) option.remove();
// Перезагрузим страницу через секунду
setTimeout(() => location.reload(), 1500);
} catch (err) {
showMsg('Ошибка: ' + err.message, false, elems.deleteMsg);
} finally {
busy(elems.deleteBtn, false);
}
}
// ===== Search in modal =====
function filterGroups() {
const query = elems.searchInput?.value.toLowerCase().trim() || '';
const items = elems.groupsList?.querySelectorAll('.list-group-item') || [];
items.forEach((item) => {
const text = item.textContent.toLowerCase();
const match = !query || text.includes(query);
item.style.display = match ? '' : 'none';
});
}
// ===== Event listeners =====
elems.assignBtn?.addEventListener('click', assignGroups);
elems.removeBtn?.addEventListener('click', removeGroups);
elems.createBtn?.addEventListener('click', createGroup);
elems.deleteBtn?.addEventListener('click', deleteGroup);
// Поиск в модале
elems.searchInput?.addEventListener('input', filterGroups);
// Кнопки модала
elems.clearBtn?.addEventListener('click', clearSelection);
elems.doneBtn?.addEventListener('click', () => {
// Просто закрываем модал (Bootstrap сделает это сам через data-bs-dismiss)
});
// Переместим модал в body (важно для правильного z-index)
const modal = document.getElementById('empGroupsPickerModal');
if (modal) {
console.log('employeeGroupsManager: moving modal to body, current parent:', modal.parentNode.nodeName);
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
console.log('employeeGroupsManager: modal moved to body');
} else {
console.log('employeeGroupsManager: modal already in body');
}
// Убедимся, что у модала правильные атрибуты
if (!modal.hasAttribute('data-bs-backdrop')) {
modal.setAttribute('data-bs-backdrop', 'true');
}
if (!modal.hasAttribute('data-bs-keyboard')) {
modal.setAttribute('data-bs-keyboard', 'true');
}
} else {
console.warn('employeeGroupsManager: modal #empGroupsPickerModal not found');
}
// Публичный API
return {
assign: assignGroups,
remove: removeGroups,
createGroup,
deleteGroup,
refresh: () => location.reload()
};
}