722 lines
22 KiB
JavaScript
722 lines
22 KiB
JavaScript
(function () {
|
|
const body = document.body;
|
|
const menuButton = document.getElementById('mobileMenuBtn');
|
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
|
|
function initNotiflix() {
|
|
if (!window.Notiflix) return;
|
|
|
|
window.Notiflix.Notify.init({
|
|
width: '320px',
|
|
position: 'right-top',
|
|
distance: '16px',
|
|
timeout: 2600,
|
|
borderRadius: '8px',
|
|
fontFamily: 'Inter, sans-serif',
|
|
fontSize: '13px',
|
|
messageMaxLength: 180,
|
|
clickToClose: true,
|
|
pauseOnHover: true,
|
|
cssAnimationStyle: 'from-right',
|
|
useIcon: true,
|
|
zindex: 5000,
|
|
success: {
|
|
background: '#067647',
|
|
textColor: '#ffffff'
|
|
},
|
|
failure: {
|
|
background: '#b42318',
|
|
textColor: '#ffffff'
|
|
},
|
|
warning: {
|
|
background: '#b54708',
|
|
textColor: '#ffffff'
|
|
},
|
|
info: {
|
|
background: '#3755c3',
|
|
textColor: '#ffffff'
|
|
}
|
|
});
|
|
|
|
window.Notiflix.Confirm.init({
|
|
width: '360px',
|
|
borderRadius: '8px',
|
|
fontFamily: 'Inter, sans-serif',
|
|
titleColor: '#111827',
|
|
titleFontSize: '16px',
|
|
messageColor: '#475569',
|
|
messageFontSize: '13px',
|
|
okButtonBackground: '#3755c3',
|
|
okButtonColor: '#ffffff',
|
|
cancelButtonBackground: '#e2e8f0',
|
|
cancelButtonColor: '#334155',
|
|
backOverlayColor: 'rgba(15, 23, 42, 0.42)',
|
|
zindex: 5001,
|
|
cssAnimationStyle: 'zoom'
|
|
});
|
|
}
|
|
|
|
function notify(type, message) {
|
|
if (!message) return;
|
|
|
|
if (!window.Notiflix) {
|
|
console.info(message);
|
|
return;
|
|
}
|
|
|
|
const Notify = window.Notiflix.Notify;
|
|
|
|
if (type === 'success') {
|
|
Notify.success(message);
|
|
return;
|
|
}
|
|
|
|
if (type === 'failure') {
|
|
Notify.failure(message);
|
|
return;
|
|
}
|
|
|
|
if (type === 'warning') {
|
|
Notify.warning(message);
|
|
return;
|
|
}
|
|
|
|
Notify.info(message);
|
|
}
|
|
|
|
function confirmAction(message, onConfirm) {
|
|
if (!window.Notiflix) {
|
|
if (window.confirm(message || 'Xác nhận thao tác?') && typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
return;
|
|
}
|
|
|
|
window.Notiflix.Confirm.show(
|
|
'Xác nhận thao tác',
|
|
message,
|
|
'Xác nhận',
|
|
'Hủy',
|
|
() => {
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
return;
|
|
}
|
|
notify('success', 'Đã xác nhận thao tác');
|
|
},
|
|
() => {
|
|
notify('info', 'Đã hủy thao tác');
|
|
}
|
|
);
|
|
}
|
|
|
|
function setMobileNav(open) {
|
|
body.classList.toggle('mobile-nav-open', open);
|
|
if (menuButton) {
|
|
menuButton.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
}
|
|
}
|
|
|
|
function openModal(id) {
|
|
const modal = document.getElementById(id);
|
|
if (!modal) return;
|
|
|
|
modal.classList.add('open');
|
|
const focusTarget = modal.querySelector('input, select, textarea, button');
|
|
if (focusTarget) {
|
|
window.setTimeout(() => focusTarget.focus(), 60);
|
|
}
|
|
}
|
|
|
|
function closeModal(modal) {
|
|
if (!modal) return;
|
|
modal.classList.remove('open');
|
|
}
|
|
|
|
function applyTableFilters(tableId) {
|
|
const table = document.getElementById(tableId);
|
|
if (!table) return;
|
|
|
|
const searchInput = document.querySelector(`[data-table-search="${tableId}"]`);
|
|
const query = searchInput ? searchInput.value.trim().toLowerCase() : '';
|
|
const selects = document.querySelectorAll(`[data-filter-select][data-filter-table="${tableId}"]`);
|
|
|
|
table.querySelectorAll('tbody tr').forEach((row) => {
|
|
const matchesSearch = !query || (row.dataset.search || '').includes(query);
|
|
let matchesSelects = true;
|
|
|
|
selects.forEach((select) => {
|
|
const column = select.dataset.filterColumn;
|
|
const value = select.value;
|
|
if (value && row.dataset[column] !== value) {
|
|
matchesSelects = false;
|
|
}
|
|
});
|
|
|
|
row.hidden = !(matchesSearch && matchesSelects);
|
|
});
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
|
|
|
const units = ['B', 'KB', 'MB', 'GB'];
|
|
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
const value = bytes / Math.pow(1024, index);
|
|
|
|
return `${value.toFixed(value >= 10 || index === 0 ? 0 : 1)} ${units[index]}`;
|
|
}
|
|
|
|
function isAllowedPackageFile(file) {
|
|
if (!file) return false;
|
|
|
|
const name = file.name.toLowerCase();
|
|
const allowedExtensions = ['.deb', '.tar', '.tar.gz', '.tgz', '.zip', '.gz'];
|
|
|
|
return allowedExtensions.some((extension) => name.endsWith(extension));
|
|
}
|
|
|
|
function renderSelectedFile(zone, file) {
|
|
const preview = zone.querySelector('[data-file-preview]');
|
|
const fileName = zone.querySelector('[data-file-name]');
|
|
const fileMeta = zone.querySelector('[data-file-meta]');
|
|
|
|
if (!preview || !fileName || !fileMeta) return;
|
|
|
|
if (!file) {
|
|
preview.hidden = true;
|
|
fileName.textContent = 'Chưa chọn file';
|
|
fileMeta.textContent = '';
|
|
zone.classList.remove('has-file');
|
|
return;
|
|
}
|
|
|
|
fileName.textContent = file.name;
|
|
fileMeta.textContent = `${formatFileSize(file.size)} • ${file.type || 'package file'}`;
|
|
preview.hidden = false;
|
|
zone.classList.add('has-file');
|
|
notify('success', `Đã chọn file: ${file.name}`);
|
|
}
|
|
|
|
function setInputFiles(input, files) {
|
|
try {
|
|
input.files = files;
|
|
} catch (error) {
|
|
console.info('Browser does not allow assigning dropped files to input.files.', error);
|
|
}
|
|
}
|
|
|
|
function handlePackageFiles(zone, files) {
|
|
const input = zone.querySelector('[data-file-input]');
|
|
const file = files && files[0];
|
|
|
|
if (!input || !file) return;
|
|
|
|
if (!isAllowedPackageFile(file)) {
|
|
notify('warning', 'File chưa đúng định dạng. Vui lòng chọn .deb, .tar, .tgz, .zip hoặc .gz.');
|
|
return;
|
|
}
|
|
|
|
setInputFiles(input, files);
|
|
renderSelectedFile(zone, file);
|
|
}
|
|
|
|
function initFileDropzones() {
|
|
document.querySelectorAll('[data-file-dropzone]').forEach((zone) => {
|
|
const input = zone.querySelector('[data-file-input]');
|
|
const browseButton = zone.querySelector('[data-file-browse]');
|
|
const clearButton = zone.querySelector('[data-file-clear]');
|
|
|
|
if (!input) return;
|
|
|
|
if (browseButton) {
|
|
browseButton.addEventListener('click', () => input.click());
|
|
}
|
|
|
|
if (clearButton) {
|
|
clearButton.addEventListener('click', () => {
|
|
input.value = '';
|
|
renderSelectedFile(zone, null);
|
|
notify('info', 'Đã bỏ file đã chọn');
|
|
});
|
|
}
|
|
|
|
input.addEventListener('change', () => {
|
|
handlePackageFiles(zone, input.files);
|
|
});
|
|
|
|
['dragenter', 'dragover'].forEach((eventName) => {
|
|
zone.addEventListener(eventName, (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
zone.classList.add('dragover');
|
|
});
|
|
});
|
|
|
|
['dragleave', 'drop'].forEach((eventName) => {
|
|
zone.addEventListener(eventName, (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
zone.classList.remove('dragover');
|
|
});
|
|
});
|
|
|
|
zone.addEventListener('drop', (event) => {
|
|
handlePackageFiles(zone, event.dataTransfer.files);
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateRegisterSubmit(form) {
|
|
const submitButton = form.querySelector('[data-register-submit]');
|
|
if (!submitButton) return;
|
|
|
|
const uniqueInputs = form.querySelectorAll('[data-unique-check]');
|
|
const isBlocked = Array.from(uniqueInputs).some((input) => (
|
|
input.dataset.uniqueStatus === 'error' || input.dataset.uniqueStatus === 'checking'
|
|
));
|
|
|
|
submitButton.disabled = isBlocked;
|
|
}
|
|
|
|
function setUniqueState(input, state, message) {
|
|
const field = input.closest('.form-field');
|
|
const form = input.closest('[data-register-form]');
|
|
const feedback = form
|
|
? form.querySelector(`[data-unique-feedback="${input.dataset.uniqueCheck}"]`)
|
|
: null;
|
|
|
|
input.dataset.uniqueStatus = state;
|
|
|
|
if (field) {
|
|
field.classList.toggle('has-error', state === 'error');
|
|
field.classList.toggle('has-success', state === 'success');
|
|
}
|
|
|
|
if (feedback) {
|
|
feedback.textContent = message || '';
|
|
feedback.style.display = message ? 'block' : '';
|
|
}
|
|
|
|
input.setCustomValidity(state === 'error' ? message : '');
|
|
|
|
if (form) {
|
|
updateRegisterSubmit(form);
|
|
}
|
|
}
|
|
|
|
function checkUniqueInput(input) {
|
|
const field = input.dataset.uniqueCheck;
|
|
const value = input.value.trim();
|
|
|
|
if (!field || !value) {
|
|
setUniqueState(input, 'idle', '');
|
|
return;
|
|
}
|
|
|
|
if (field === 'email' && !input.validity.valid) {
|
|
setUniqueState(input, 'idle', '');
|
|
return;
|
|
}
|
|
|
|
const requestId = String(Date.now());
|
|
input.dataset.uniqueRequestId = requestId;
|
|
setUniqueState(input, 'checking', 'Đang kiểm tra...');
|
|
|
|
fetch(`/register/check?field=${encodeURIComponent(field)}&value=${encodeURIComponent(value)}`, {
|
|
headers: {
|
|
Accept: 'application/json'
|
|
}
|
|
})
|
|
.then((response) => response.ok ? response.json() : Promise.reject(new Error('Cannot check field')))
|
|
.then((data) => {
|
|
if (input.dataset.uniqueRequestId !== requestId) return;
|
|
|
|
setUniqueState(
|
|
input,
|
|
data.available ? 'success' : 'error',
|
|
data.message || (data.available ? 'Có thể sử dụng.' : 'Đã tồn tại.')
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.info('Cannot check registration field:', error);
|
|
if (input.dataset.uniqueRequestId === requestId) {
|
|
setUniqueState(input, 'idle', '');
|
|
}
|
|
});
|
|
}
|
|
|
|
function initRegistrationUniqueChecks() {
|
|
document.querySelectorAll('[data-register-form]').forEach((form) => {
|
|
const timers = new Map();
|
|
|
|
form.querySelectorAll('[data-unique-check]').forEach((input) => {
|
|
input.addEventListener('input', () => {
|
|
window.clearTimeout(timers.get(input));
|
|
setUniqueState(input, 'idle', '');
|
|
timers.set(input, window.setTimeout(() => checkUniqueInput(input), 450));
|
|
});
|
|
|
|
input.addEventListener('blur', () => {
|
|
window.clearTimeout(timers.get(input));
|
|
checkUniqueInput(input);
|
|
});
|
|
|
|
if (input.value.trim()) {
|
|
checkUniqueInput(input);
|
|
}
|
|
});
|
|
|
|
form.addEventListener('submit', (event) => {
|
|
const blockedInput = form.querySelector('[data-unique-status="error"], [data-unique-status="checking"]');
|
|
if (!blockedInput) return;
|
|
|
|
event.preventDefault();
|
|
notify('warning', blockedInput.dataset.uniqueStatus === 'checking'
|
|
? 'Đang kiểm tra username/email, vui lòng chờ một chút.'
|
|
: 'Vui lòng đổi username/email đang bị trùng.');
|
|
blockedInput.focus();
|
|
});
|
|
});
|
|
}
|
|
|
|
function getUserDataFromRow(row) {
|
|
return {
|
|
id: row.dataset.userId || '',
|
|
name: row.dataset.userName || '',
|
|
username: row.dataset.userUsername || '',
|
|
email: row.dataset.userEmail || '',
|
|
fullName: row.dataset.userFullName || '',
|
|
role: row.dataset.userRole || 'User',
|
|
status: row.dataset.userStatus || '',
|
|
isActive: row.dataset.userActive === 'true',
|
|
createdAt: row.dataset.userCreatedAt || '',
|
|
updatedAt: row.dataset.userUpdatedAt || '',
|
|
packageCount: row.dataset.userPackageCount || '0',
|
|
applicationCount: row.dataset.userApplicationCount || '0'
|
|
};
|
|
}
|
|
|
|
function parseJsonAttribute(value, fallback) {
|
|
try {
|
|
return JSON.parse(value || '');
|
|
} catch (error) {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function getAppDataFromTrigger(trigger) {
|
|
return {
|
|
id: trigger.dataset.appId || '',
|
|
code: trigger.dataset.appCode || '',
|
|
name: trigger.dataset.appName || '',
|
|
version: trigger.dataset.appVersion || '',
|
|
status: trigger.dataset.appStatus || 'Draft',
|
|
openUrl: trigger.dataset.appOpenUrl || '',
|
|
notes: trigger.dataset.appNotes || '',
|
|
packages: parseJsonAttribute(trigger.dataset.appPackages, [])
|
|
};
|
|
}
|
|
|
|
function findEditAppVersionSelect(form, packageId) {
|
|
return Array.from(form.querySelectorAll('[data-edit-app-version]'))
|
|
.find((select) => select.dataset.editAppVersion === packageId);
|
|
}
|
|
|
|
function setEditAppPackageEnabled(form, checkbox) {
|
|
const select = findEditAppVersionSelect(form, checkbox.dataset.editAppPackage);
|
|
if (select) {
|
|
select.disabled = !checkbox.checked;
|
|
}
|
|
}
|
|
|
|
function setEditAppField(form, field, value) {
|
|
const input = form.querySelector(`[data-edit-app-field="${field}"]`);
|
|
if (input) {
|
|
input.value = value || '';
|
|
}
|
|
}
|
|
|
|
function openAppEdit(trigger) {
|
|
const app = getAppDataFromTrigger(trigger);
|
|
const form = document.getElementById('editAppForm');
|
|
if (!form || !app.id) return;
|
|
|
|
form.action = `/applications/${encodeURIComponent(app.id)}/edit`;
|
|
setEditAppField(form, 'appCode', app.code);
|
|
setEditAppField(form, 'appName', app.name);
|
|
setEditAppField(form, 'appVersion', app.version);
|
|
setEditAppField(form, 'status', app.status);
|
|
setEditAppField(form, 'openUrl', app.openUrl);
|
|
setEditAppField(form, 'notes', app.notes);
|
|
|
|
form.querySelectorAll('[data-edit-app-package]').forEach((checkbox) => {
|
|
checkbox.checked = false;
|
|
setEditAppPackageEnabled(form, checkbox);
|
|
});
|
|
|
|
app.packages.forEach((packageItem) => {
|
|
const packageId = packageItem.packageId || '';
|
|
const checkbox = Array.from(form.querySelectorAll('[data-edit-app-package]'))
|
|
.find((input) => input.dataset.editAppPackage === packageId);
|
|
const select = findEditAppVersionSelect(form, packageId);
|
|
|
|
if (checkbox) {
|
|
checkbox.checked = true;
|
|
}
|
|
|
|
if (select) {
|
|
select.disabled = false;
|
|
select.value = packageItem.selectedVersionId || '';
|
|
}
|
|
});
|
|
|
|
openModal('editAppModal');
|
|
}
|
|
|
|
function openPackageUpdate(packageId) {
|
|
const modalId = 'updatePackageModal';
|
|
const modal = document.getElementById(modalId);
|
|
const packageSelect = modal ? modal.querySelector('select[name="packageId"]') : null;
|
|
|
|
if (packageSelect && packageId) {
|
|
packageSelect.value = packageId;
|
|
}
|
|
|
|
openModal(modalId);
|
|
}
|
|
|
|
function setText(selector, value) {
|
|
const element = document.querySelector(selector);
|
|
if (element) {
|
|
element.textContent = value || '';
|
|
}
|
|
}
|
|
|
|
function openUserDetail(row) {
|
|
const user = getUserDataFromRow(row);
|
|
|
|
setText('[data-user-detail="name"]', user.name);
|
|
setText('[data-user-detail="username"]', user.username);
|
|
setText('[data-user-detail="email"]', user.email);
|
|
setText('[data-user-detail="role"]', user.role);
|
|
setText('[data-user-detail="status"]', user.status);
|
|
setText('[data-user-detail="createdAt"]', user.createdAt);
|
|
setText('[data-user-detail="updatedAt"]', user.updatedAt || 'Chưa cập nhật');
|
|
setText('[data-user-detail="ownedData"]', `${user.packageCount} packages, ${user.applicationCount} apps`);
|
|
|
|
openModal('userDetailModal');
|
|
}
|
|
|
|
function openUserEdit(row) {
|
|
const user = getUserDataFromRow(row);
|
|
const form = document.getElementById('editUserForm');
|
|
if (!form) return;
|
|
|
|
form.action = `/users/${encodeURIComponent(user.id)}/edit`;
|
|
form.querySelector('[data-edit-user-field="username"]').value = user.username;
|
|
form.querySelector('[data-edit-user-field="fullName"]').value = user.fullName;
|
|
form.querySelector('[data-edit-user-field="email"]').value = user.email;
|
|
form.querySelector('[data-edit-user-field="role"]').value = user.role;
|
|
form.querySelector('[data-edit-user-field="isActive"]').checked = user.isActive;
|
|
form.querySelector('[data-edit-user-field="newPassword"]').value = '';
|
|
form.querySelector('[data-edit-user-field="confirmPassword"]').value = '';
|
|
|
|
openModal('editUserModal');
|
|
}
|
|
|
|
function validateProfileForm(form, shouldNotify) {
|
|
const email = form.querySelector('[data-profile-email]');
|
|
const confirmEmail = form.querySelector('[data-profile-confirm-email]');
|
|
const newPassword = form.querySelector('[data-profile-new-password]');
|
|
const confirmPassword = form.querySelector('[data-profile-confirm-password]');
|
|
const emailFeedback = form.querySelector('[data-profile-feedback="email"]');
|
|
const passwordFeedback = form.querySelector('[data-profile-feedback="password"]');
|
|
|
|
const emailMismatch = Boolean(email && confirmEmail
|
|
&& email.value.trim().toLowerCase() !== confirmEmail.value.trim().toLowerCase());
|
|
const passwordMismatch = Boolean(newPassword && confirmPassword
|
|
&& (newPassword.value || confirmPassword.value)
|
|
&& newPassword.value !== confirmPassword.value);
|
|
|
|
if (confirmEmail) {
|
|
confirmEmail.setCustomValidity(emailMismatch ? 'Confirm email mới chưa khớp.' : '');
|
|
const field = confirmEmail.closest('.form-field');
|
|
if (field) {
|
|
field.classList.toggle('has-error', emailMismatch);
|
|
}
|
|
}
|
|
|
|
if (emailFeedback) {
|
|
emailFeedback.textContent = emailMismatch ? 'Confirm email mới chưa khớp.' : '';
|
|
emailFeedback.style.display = emailMismatch ? 'block' : '';
|
|
}
|
|
|
|
if (confirmPassword) {
|
|
confirmPassword.setCustomValidity(passwordMismatch ? 'Xác nhận mật khẩu mới chưa khớp.' : '');
|
|
const field = confirmPassword.closest('.form-field');
|
|
if (field) {
|
|
field.classList.toggle('has-error', passwordMismatch);
|
|
}
|
|
}
|
|
|
|
if (passwordFeedback) {
|
|
passwordFeedback.textContent = passwordMismatch ? 'Xác nhận mật khẩu mới chưa khớp.' : '';
|
|
passwordFeedback.style.display = passwordMismatch ? 'block' : '';
|
|
}
|
|
|
|
if (shouldNotify && emailMismatch) {
|
|
notify('warning', 'Confirm email mới chưa khớp.');
|
|
} else if (shouldNotify && passwordMismatch) {
|
|
notify('warning', 'Xác nhận mật khẩu mới chưa khớp.');
|
|
}
|
|
|
|
return !emailMismatch && !passwordMismatch;
|
|
}
|
|
|
|
function initProfileForms() {
|
|
document.querySelectorAll('[data-profile-form]').forEach((form) => {
|
|
form.querySelectorAll('input').forEach((input) => {
|
|
input.addEventListener('input', () => validateProfileForm(form, false));
|
|
});
|
|
|
|
form.addEventListener('submit', (event) => {
|
|
if (!validateProfileForm(form, true)) {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function initEditAppForms() {
|
|
document.querySelectorAll('#editAppForm [data-edit-app-package]').forEach((checkbox) => {
|
|
const form = checkbox.closest('form');
|
|
setEditAppPackageEnabled(form, checkbox);
|
|
checkbox.addEventListener('change', () => setEditAppPackageEnabled(form, checkbox));
|
|
});
|
|
}
|
|
|
|
initNotiflix();
|
|
initFileDropzones();
|
|
initRegistrationUniqueChecks();
|
|
initProfileForms();
|
|
initEditAppForms();
|
|
|
|
if (body.dataset.notice) {
|
|
notify(body.dataset.noticeType || 'info', body.dataset.notice);
|
|
const url = new URL(window.location.href);
|
|
if (url.searchParams.has('notice') || url.searchParams.has('noticeType')) {
|
|
url.searchParams.delete('notice');
|
|
url.searchParams.delete('noticeType');
|
|
window.history.replaceState({}, document.title, `${url.pathname}${url.search}${url.hash}`);
|
|
}
|
|
delete body.dataset.notice;
|
|
delete body.dataset.noticeType;
|
|
}
|
|
|
|
if (menuButton) {
|
|
menuButton.addEventListener('click', () => {
|
|
setMobileNav(!body.classList.contains('mobile-nav-open'));
|
|
});
|
|
}
|
|
|
|
if (sidebarBackdrop) {
|
|
sidebarBackdrop.addEventListener('click', () => setMobileNav(false));
|
|
}
|
|
|
|
document.querySelectorAll('[data-table-search]').forEach((input) => {
|
|
input.addEventListener('input', () => applyTableFilters(input.dataset.tableSearch));
|
|
});
|
|
|
|
document.querySelectorAll('[data-filter-select]').forEach((select) => {
|
|
select.addEventListener('change', () => applyTableFilters(select.dataset.filterTable));
|
|
});
|
|
|
|
document.addEventListener('click', (event) => {
|
|
const appEditButton = event.target.closest('[data-app-edit]');
|
|
if (appEditButton) {
|
|
openAppEdit(appEditButton);
|
|
return;
|
|
}
|
|
|
|
const packageUpdateButton = event.target.closest('[data-package-update]');
|
|
if (packageUpdateButton) {
|
|
openPackageUpdate(packageUpdateButton.dataset.packageUpdate);
|
|
return;
|
|
}
|
|
|
|
const refreshButton = event.target.closest('[data-refresh-page]');
|
|
if (refreshButton) {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
|
|
const userViewButton = event.target.closest('[data-user-view]');
|
|
if (userViewButton) {
|
|
const row = userViewButton.closest('tr');
|
|
if (row) {
|
|
openUserDetail(row);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const userEditButton = event.target.closest('[data-user-edit]');
|
|
if (userEditButton) {
|
|
const row = userEditButton.closest('tr');
|
|
if (row) {
|
|
openUserEdit(row);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const modalOpenButton = event.target.closest('[data-modal-open]');
|
|
if (modalOpenButton) {
|
|
openModal(modalOpenButton.dataset.modalOpen);
|
|
return;
|
|
}
|
|
|
|
const modalCloseButton = event.target.closest('[data-modal-close]');
|
|
if (modalCloseButton) {
|
|
closeModal(modalCloseButton.closest('.modal-backdrop'));
|
|
}
|
|
|
|
const modalBackdrop = event.target.classList.contains('modal-backdrop') ? event.target : null;
|
|
if (modalBackdrop) {
|
|
closeModal(modalBackdrop);
|
|
}
|
|
|
|
const toastButton = event.target.closest('[data-toast]');
|
|
if (toastButton) {
|
|
notify(toastButton.dataset.toastType || 'info', toastButton.dataset.toast);
|
|
}
|
|
|
|
const confirmButton = event.target.closest('[data-confirm]');
|
|
if (confirmButton) {
|
|
confirmAction(confirmButton.dataset.confirm);
|
|
}
|
|
});
|
|
|
|
document.addEventListener('submit', (event) => {
|
|
const form = event.target.closest('form[data-confirm-submit]');
|
|
if (!form || form.dataset.confirmed === 'true') return;
|
|
|
|
event.preventDefault();
|
|
confirmAction(form.dataset.confirmSubmit, () => {
|
|
form.dataset.confirmed = 'true';
|
|
form.submit();
|
|
});
|
|
});
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key !== 'Escape') return;
|
|
|
|
const openModalNode = document.querySelector('.modal-backdrop.open');
|
|
if (openModalNode) {
|
|
closeModal(openModalNode);
|
|
return;
|
|
}
|
|
|
|
setMobileNav(false);
|
|
});
|
|
})();
|