From bc3487639d1e35f9f88c18f74ee5367fd0f51be3 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 9 Sep 2026 12:25:49 +0300 Subject: [PATCH] Fix user edit modal: pass full user object instead of just ID --- public/js/users.js | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 public/js/users.js diff --git a/public/js/users.js b/public/js/users.js new file mode 100644 index 0000000..fa739ce --- /dev/null +++ b/public/js/users.js @@ -0,0 +1,127 @@ +async function init() { + if (!(await checkAuth())) return; + if (!isAdmin()) { location.href = 'journal.html'; return; } + buildSidebar('users'); + document.getElementById('addUserBtn').addEventListener('click', () => openModal(null)); + document.getElementById('uCancel').addEventListener('click', closeModal); + document.getElementById('uSave').addEventListener('click', saveUser); + document.getElementById('uRole').addEventListener('change', () => { + document.getElementById('uBranchesField').style.display = document.getElementById('uRole').value === 'admin' ? 'none' : ''; + }); + await loadUsers(); +} + +let branches = []; +let editingId = null; + +async function loadUsers() { + const [uRes, bRes] = await Promise.all([ + fetch(`${API}/api/users`, { headers: hdr() }), + fetch(`${API}/api/branches`, { headers: hdr() }), + ]); + if (!uRes.ok) { showToast('Ошибка загрузки пользователей'); return; } + branches = await bRes.json(); + renderUsers(await uRes.json()); +} + +function renderUsers(users) { + const tbody = document.querySelector('#usersTable tbody'); + const empty = document.getElementById('usersEmpty'); + tbody.innerHTML = ''; + empty.style.display = users.length ? 'none' : 'block'; + const branchById = {}; + branches.forEach(b => { branchById[b.id] = b.name; }); + users.forEach(u => { + const tr = document.createElement('tr'); + const branchNames = (u.branch_ids || []).map(id => branchById[id] || `#${id}`).join(', ') || '—'; + const roleCls = u.role === 'admin' ? 'badge-admin' : 'badge-tutor'; + const roleTxt = u.role === 'admin' ? 'Администратор' : 'Тутор'; + const status = u.is_active ? 'Активен' : 'Заблокирован'; + const delBtn = u.id === currentUser.id + ? '' + : ``; + tr.innerHTML = ` + ${u.id} + ${esc(u.username)} + ${esc(u.name || '')} + ${roleTxt} + ${esc(branchNames)} + ${status} + + + ${delBtn} + `; + tbody.appendChild(tr); + }); + tbody.querySelectorAll('[data-edit]').forEach(b => { + const userId = Number(b.dataset.edit); + const user = users.find(u => u.id === userId); + b.addEventListener('click', () => openModal(user)); + }); + tbody.querySelectorAll('[data-del]').forEach(b => b.addEventListener('click', () => delUser(Number(b.dataset.del)))); +} + +function openModal(user) { + editingId = user ? user.id : null; + document.getElementById('userModalTitle').textContent = user ? `Пользователь: ${user.username}` : 'Новый пользователь'; + document.getElementById('uUsername').value = user ? user.username : ''; + document.getElementById('uName').value = user ? (user.name || '') : ''; + document.getElementById('uRole').value = user ? user.role : 'tutor'; + document.getElementById('uPassword').value = ''; + document.getElementById('uActive').checked = user ? user.is_active : true; + document.getElementById('pwdHint').textContent = user ? '(оставьте пустым, чтобы не менять)' : '(мин. 6 символов)'; + document.getElementById('uRole').disabled = !!user && user.id === currentUser.id; + const sel = document.getElementById('uBranches'); + sel.innerHTML = ''; + branches.forEach(b => { + const o = document.createElement('option'); + o.value = b.id; + o.textContent = b.name; + if (user && (user.branch_ids || []).includes(b.id)) o.selected = true; + sel.appendChild(o); + }); + document.getElementById('uBranchesField').style.display = document.getElementById('uRole').value === 'admin' ? 'none' : ''; + document.getElementById('userModal').classList.add('open'); +} + +function closeModal() { + document.getElementById('userModal').classList.remove('open'); +} + +async function saveUser() { + const username = document.getElementById('uUsername').value.trim(); + const name = document.getElementById('uName').value.trim() || null; + const role = document.getElementById('uRole').value; + const password = document.getElementById('uPassword').value; + const is_active = document.getElementById('uActive').checked; + const branchSel = document.getElementById('uBranches'); + const branch_ids = role === 'admin' ? [] : [...branchSel.selectedOptions].map(o => Number(o.value)); + if (!username) { showToast('Укажите логин'); return; } + if (!editingId && password.length < 6) { showToast('Пароль должен быть не короче 6 символов'); return; } + const payload = { username, name, role, is_active, branch_ids }; + if (password) payload.password = password; + const res = await fetch(editingId ? `${API}/api/users/${editingId}` : `${API}/api/users`, { + method: editingId ? 'PUT' : 'POST', + headers: hdrJson(), + body: JSON.stringify(payload), + }); + const data = await res.json().catch(() => ({})); + if (!res.ok) { showToast(data.error || 'Ошибка сохранения'); return; } + showToast('Сохранено'); + closeModal(); + loadUsers(); +} + +async function delUser(id) { + if (!confirm('Удалить пользователя?')) return; + const res = await fetch(`${API}/api/users/${id}`, { method: 'DELETE', headers: hdr() }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + showToast(data.error || 'Ошибка удаления'); + return; + } + showToast('Пользователь удалён'); + loadUsers(); +} + +init(); \ No newline at end of file