/**
* groupPickers.js
* Управление выбором групп с чипсами и модальными окнами
* Используется в формах создания/редактирования должностей и сотрудников
*
* @module groupPickers
* @version 1.0.0
*/
/**
* Инициализация пикера групп для создания должности
*/
export function initPositionGroupPicker() {
const chipsBox = document.getElementById('pos_groups_chips');
const hiddenInp = document.getElementById('pos_groups_hidden');
const listBox = document.getElementById('groupsList');
const searchInp = document.getElementById('groupsSearch');
const clearBtn = document.getElementById('groupsClear');
const doneBtn = document.getElementById('groupsDone');
if (!listBox || !hiddenInp) {
return null; // Тихий выход, если элементы не найдены
}
// Текущее множество выбранных id
const sel = new Set(
(hiddenInp.value || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
);
// Рендер чипсов
function renderChips() {
if (!chipsBox) return;
chipsBox.innerHTML = '';
if (sel.size === 0) {
return;
}
// Соберём карту id->название из списка
const nameById = {};
listBox.querySelectorAll('.group-check').forEach((ch) => {
nameById[ch.value] =
ch.closest('.list-group-item').querySelector('span.flex-grow-1')?.textContent ||
'#' + ch.value;
});
for (const id of sel) {
const chip = document.createElement('span');
chip.className = 'chip';
chip.dataset.id = id;
chip.innerHTML = `${nameById[id] || '#' + id} `;
chipsBox.appendChild(chip);
}
}
// Синхронизация чекбоксов ↔ выбранные
function syncChecksFromSel() {
listBox.querySelectorAll('.group-check').forEach((ch) => {
ch.checked = sel.has(ch.value);
});
}
function syncSelFromChecks() {
sel.clear();
listBox.querySelectorAll('.group-check:checked').forEach((ch) => sel.add(ch.value));
}
function saveHidden() {
hiddenInp.value = Array.from(sel).join(',');
}
// Интерактив: клик по чекбоксу
listBox.addEventListener('change', (e) => {
const ch = e.target.closest('.group-check');
if (!ch) return;
if (ch.checked) sel.add(ch.value);
else sel.delete(ch.value);
});
// Фильтр
searchInp?.addEventListener('input', () => {
const q = (searchInp.value || '').toLowerCase().trim();
listBox.querySelectorAll('.list-group-item').forEach((row) => {
const name = row.querySelector('.flex-grow-1')?.textContent.toLowerCase() || '';
const id = row.querySelector('.group-check')?.value || '';
row.classList.toggle('d-none', !(name.includes(q) || id.includes(q)));
});
});
// Сброс
clearBtn?.addEventListener('click', () => {
sel.clear();
syncChecksFromSel();
renderChips();
saveHidden();
});
// Готово
doneBtn?.addEventListener('click', () => {
syncSelFromChecks();
renderChips();
saveHidden();
});
// Удаление из чипса
chipsBox?.addEventListener('click', (e) => {
const btn = e.target.closest('button');
const chip = btn?.closest('.chip');
if (!chip) return;
sel.delete(chip.dataset.id);
renderChips();
syncChecksFromSel();
saveHidden();
});
// Инициализация
renderChips();
syncChecksFromSel();
// Связь с кнопкой "Создать и назначить"
const posCreateBtn = document.getElementById('posCreateBtn');
if (posCreateBtn) {
posCreateBtn.addEventListener(
'click',
() => {
if (hiddenInp) {
posCreateBtn.dataset.groupsSelected = hiddenInp.value;
}
},
{ capture: true }
);
}
// Переместим модал в body (важно для правильного z-index)
const modal = document.getElementById('groupsPickerModal');
if (modal) {
console.log('groupPicker: moving modal to body, current parent:', modal.parentNode.nodeName);
// Проверяем, что модал еще не перемещен
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
console.log('groupPicker: modal moved to body');
} else {
console.log('groupPicker: 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('groupPicker: modal #groupsPickerModal not found');
}
return {
renderChips,
getSelectedIds: () => Array.from(sel)
};
}
/**
* Инициализация пикера групп для редактирования должности
*/
export function initPositionEditGroupPicker() {
const chipsBox = document.getElementById('pos_edit_groups_chips');
const hidden = document.getElementById('pos_edit_groups_hidden');
const listBox = document.getElementById('posEditGroupsList');
const search = document.getElementById('posEditGroupsSearch');
const clearBtn = document.getElementById('posEditGroupsClear');
const doneBtn = document.getElementById('posEditGroupsDone');
const clearGroupsBtn = document.getElementById('posEditClearGroups');
if (!listBox || !hidden) {
return null; // Тихий выход, если элементы не найдены
}
// Переместим модал в body (важно для правильного z-index)
const modalEl = document.getElementById('posEditGroupsPickerModal');
if (modalEl) {
console.log('editGroupPicker: moving modal to body, current parent:', modalEl.parentNode.nodeName);
if (modalEl.parentNode !== document.body) {
document.body.appendChild(modalEl);
console.log('editGroupPicker: modal moved to body');
} else {
console.log('editGroupPicker: modal already in body');
}
// Убедимся, что у модала правильные атрибуты
if (!modalEl.hasAttribute('data-bs-backdrop')) {
modalEl.setAttribute('data-bs-backdrop', 'true');
}
if (!modalEl.hasAttribute('data-bs-keyboard')) {
modalEl.setAttribute('data-bs-keyboard', 'true');
}
} else {
console.warn('editGroupPicker: modal #posEditGroupsPickerModal not found');
}
function currentIds() {
const v = (hidden?.value || '').trim();
return v
? v
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: [];
}
function setIds(ids) {
if (hidden) hidden.value = (ids || []).join(',');
renderChips();
syncChecksFromHidden();
}
function renderChips() {
if (!chipsBox) return;
chipsBox.innerHTML = '';
const ids = currentIds();
if (!ids.length) return;
const nameById = {};
listBox?.querySelectorAll('.pos-edit-group-check').forEach((ch) => {
const nm =
ch.closest('.list-group-item')?.querySelector('.flex-grow-1')?.textContent || '#' + ch.value;
nameById[ch.value] = nm;
});
ids.forEach((id) => {
const chip = document.createElement('span');
chip.className = 'chip';
chip.dataset.id = id;
chip.innerHTML = `${nameById[id] || '#' + id} `;
chipsBox.appendChild(chip);
});
}
chipsBox?.addEventListener('click', (e) => {
const btn = e.target.closest('button');
const chip = btn?.closest('.chip');
if (!chip) return;
const ids = new Set(currentIds());
ids.delete(chip.dataset.id);
setIds(Array.from(ids));
});
function syncChecksFromHidden() {
const ids = new Set(currentIds());
listBox?.querySelectorAll('.pos-edit-group-check').forEach((ch) => (ch.checked = ids.has(ch.value)));
}
function syncHiddenFromChecks() {
const ids = [];
listBox?.querySelectorAll('.pos-edit-group-check:checked').forEach((ch) => ids.push(ch.value));
setIds(ids);
}
search?.addEventListener('input', () => {
const q = (search.value || '').toLowerCase().trim();
listBox?.querySelectorAll('.list-group-item').forEach((row) => {
const name = row.querySelector('.flex-grow-1')?.textContent.toLowerCase() || '';
const id = row.querySelector('.pos-edit-group-check')?.value || '';
row.classList.toggle('d-none', !(name.includes(q) || id.includes(q)));
});
});
clearBtn?.addEventListener('click', () => {
listBox?.querySelectorAll('.pos-edit-group-check').forEach((ch) => (ch.checked = false));
setIds([]);
});
doneBtn?.addEventListener('click', syncHiddenFromChecks);
clearGroupsBtn?.addEventListener('click', () => setIds([]));
// Expose render function globally for position manager
window.renderPositionChips = renderChips;
return {
renderChips,
setIds,
getIds: currentIds
};
}