Refactor admin frontend into modular SPA
This commit is contained in:
102
frontend/admin/js/utils.js
Normal file
102
frontend/admin/js/utils.js
Normal file
@@ -0,0 +1,102 @@
|
||||
export const ESCAPE_MAP = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
export function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/[&<>"']/g, m => ESCAPE_MAP[m]);
|
||||
}
|
||||
|
||||
export function showAlert(elementId, msg, type) {
|
||||
const el = document.getElementById(elementId);
|
||||
if (!el) return;
|
||||
el.className = 'form-alert ' + type;
|
||||
el.textContent = msg;
|
||||
}
|
||||
|
||||
export function hideAlert(elementId) {
|
||||
const el = document.getElementById(elementId);
|
||||
if (!el) return;
|
||||
el.className = 'form-alert';
|
||||
el.textContent = '';
|
||||
}
|
||||
|
||||
export function applyRippleEffect() {
|
||||
document.addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('.btn-primary, .btn-delete, .btn-logout');
|
||||
if (!btn) return;
|
||||
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const ripple = document.createElement('span');
|
||||
ripple.classList.add('ripple');
|
||||
ripple.style.left = `${x}px`;
|
||||
ripple.style.top = `${y}px`;
|
||||
|
||||
if (getComputedStyle(btn).position === 'static') {
|
||||
btn.style.position = 'relative';
|
||||
}
|
||||
btn.style.overflow = 'hidden';
|
||||
|
||||
btn.appendChild(ripple);
|
||||
|
||||
setTimeout(() => ripple.remove(), 600);
|
||||
});
|
||||
}
|
||||
|
||||
export function initMultiSelect(boxId, menuId, textId, checkboxContainerId) {
|
||||
const box = document.getElementById(boxId);
|
||||
const menu = document.getElementById(menuId);
|
||||
const container = document.getElementById(checkboxContainerId);
|
||||
if (!box || !menu || !container) return;
|
||||
|
||||
// Remove old listeners to prevent duplication if re-initialized
|
||||
const newBox = box.cloneNode(true);
|
||||
box.parentNode.replaceChild(newBox, box);
|
||||
|
||||
newBox.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = menu.classList.contains('open');
|
||||
document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open'));
|
||||
document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active'));
|
||||
if (!isOpen) {
|
||||
menu.classList.add('open');
|
||||
newBox.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
container.addEventListener('change', () => {
|
||||
updateSelectText(checkboxContainerId, textId);
|
||||
});
|
||||
}
|
||||
|
||||
export function updateSelectText(containerId, textId) {
|
||||
const container = document.getElementById(containerId);
|
||||
const textEl = document.getElementById(textId);
|
||||
if (!container || !textEl) return;
|
||||
const checked = Array.from(container.querySelectorAll('input:checked'));
|
||||
if (checked.length === 0) {
|
||||
textEl.textContent = 'Выберите оборудование...';
|
||||
} else if (checked.length === 1) {
|
||||
textEl.textContent = checked[0].parentElement.textContent.trim();
|
||||
} else {
|
||||
textEl.textContent = `Выбрано: ${checked.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function closeAllDropdownsOnOutsideClick() {
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open'));
|
||||
document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active'));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user