refactor: textbooks assign modal → LS.modal (−120 строк)
Та же миграция что и в exam9: убран inline-overlay HTML, дубликаты CSS (.ex-overlay/.ex-panel/.ex-panel-* + .ax-error/.ax-success/ .ax-actions/.ax-btn) — всё это теперь .ls-mod-* из LS.modal. Глобальные window.openAssignModal/closeAssignModal/onAssignOverlayClick/ onAssignEsc/setAssignTab/submitAssign и assignSlug/assignTitle/assignTab переменные заменены на одну window.openAssignModal с локальным closure по slug/title/currentTab. Сохранены внутренние form-классы (.ax-form/.ax-classes/.ax-class/ .ax-tabs/.ax-tab/.ax-student-results/.ax-input/.ax-hint) — они используются в body модалки. Student search и tab-switching теперь обработчики на элементах модалки (m.body.querySelector), а не глобальные document-listener'ы — автоматически очищаются вместе с модалкой при close(). textbooks.html: 945 → 824 строки
This commit is contained in:
+142
-240
@@ -255,30 +255,6 @@
|
|||||||
.am-saved.show { opacity:1; }
|
.am-saved.show { opacity:1; }
|
||||||
|
|
||||||
/* ── Assign modal (reused styling from exam9) ── */
|
/* ── Assign modal (reused styling from exam9) ── */
|
||||||
.ex-overlay {
|
|
||||||
display:none; position:fixed; inset:0;
|
|
||||||
background:rgba(15,23,42,.55); z-index:300;
|
|
||||||
align-items:flex-start; justify-content:center; padding-top:80px;
|
|
||||||
backdrop-filter:blur(2px);
|
|
||||||
}
|
|
||||||
.ex-overlay.visible { display:flex; }
|
|
||||||
.ex-panel {
|
|
||||||
background:var(--surface); border:1.5px solid var(--border);
|
|
||||||
border-radius:16px; box-shadow:0 24px 64px rgba(0,0,0,.32);
|
|
||||||
width:min(520px, 94vw); max-height:calc(100vh - 120px);
|
|
||||||
overflow-y:auto; padding:22px 22px 26px;
|
|
||||||
}
|
|
||||||
.ex-panel-head {
|
|
||||||
display:flex; align-items:center; justify-content:space-between; margin-bottom:18px;
|
|
||||||
}
|
|
||||||
.ex-panel-head h2 { font-family:'Unbounded',sans-serif; font-size:1rem; font-weight:800; }
|
|
||||||
.ex-panel-close {
|
|
||||||
width:32px; height:32px; border:none; background:none;
|
|
||||||
color:var(--text-2); cursor:pointer; border-radius:8px;
|
|
||||||
display:flex; align-items:center; justify-content:center; transition:background .15s;
|
|
||||||
}
|
|
||||||
.ex-panel-close:hover { background:var(--border); color:var(--text); }
|
|
||||||
.ex-panel-close svg { width:18px; height:18px; }
|
|
||||||
.ax-form { display:flex; flex-direction:column; gap:14px; }
|
.ax-form { display:flex; flex-direction:column; gap:14px; }
|
||||||
.ax-field label {
|
.ax-field label {
|
||||||
display:block; font-size:.78rem; font-weight:700; color:var(--text-2);
|
display:block; font-size:.78rem; font-weight:700; color:var(--text-2);
|
||||||
@@ -304,22 +280,6 @@
|
|||||||
}
|
}
|
||||||
.ax-input:focus { outline:none; border-color:var(--violet); }
|
.ax-input:focus { outline:none; border-color:var(--violet); }
|
||||||
.ax-hint { font-size:.74rem; color:var(--text-3); margin-top:4px; }
|
.ax-hint { font-size:.74rem; color:var(--text-3); margin-top:4px; }
|
||||||
.ax-actions { display:flex; gap:10px; justify-content:flex-end; margin-top:6px; }
|
|
||||||
.ax-btn {
|
|
||||||
padding:9px 18px; border-radius:10px; border:1.5px solid var(--border-h);
|
|
||||||
background:transparent; color:var(--text);
|
|
||||||
font-family:'Manrope',sans-serif; font-size:.88rem; font-weight:700;
|
|
||||||
cursor:pointer; transition:all .15s;
|
|
||||||
}
|
|
||||||
.ax-btn:hover { border-color:var(--text-2); }
|
|
||||||
.ax-btn-primary { background:var(--violet); border-color:var(--violet); color:#fff; }
|
|
||||||
.ax-btn-primary:hover { background:#7e3eca; border-color:#7e3eca; }
|
|
||||||
.ax-btn-primary:disabled { opacity:.5; cursor:not-allowed; }
|
|
||||||
.ax-error, .ax-success {
|
|
||||||
padding:9px 12px; border-radius:8px; font-size:.84rem; display:none;
|
|
||||||
}
|
|
||||||
.ax-error.visible { display:block; background:rgba(241,91,68,.1); border:1px solid rgba(241,91,68,.3); color:#F94144; }
|
|
||||||
.ax-success.visible { display:block; background:rgba(6,214,160,.1); border:1px solid rgba(6,214,160,.3); color:#06D6A0; }
|
|
||||||
|
|
||||||
.ax-tabs { display:flex; gap:6px; background:var(--border); padding:4px; border-radius:10px; }
|
.ax-tabs { display:flex; gap:6px; background:var(--border); padding:4px; border-radius:10px; }
|
||||||
.ax-tab {
|
.ax-tab {
|
||||||
@@ -419,51 +379,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ex-overlay" id="assign-overlay" onclick="onAssignOverlayClick(event)">
|
|
||||||
<div class="ex-panel" onclick="event.stopPropagation()">
|
|
||||||
<div class="ex-panel-head">
|
|
||||||
<h2 id="assign-title">Назначить чтение</h2>
|
|
||||||
<button class="ex-panel-close" onclick="closeAssignModal()">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form class="ax-form" id="assign-form" onsubmit="event.preventDefault(); submitAssign()">
|
|
||||||
<div class="ax-field">
|
|
||||||
<label>Кому</label>
|
|
||||||
<div class="ax-tabs">
|
|
||||||
<button type="button" class="ax-tab active" data-tab="class" onclick="setAssignTab('class')">Классу</button>
|
|
||||||
<button type="button" class="ax-tab" data-tab="student" onclick="setAssignTab('student')">Ученику</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ax-field" id="ax-class-field">
|
|
||||||
<label>Классы</label>
|
|
||||||
<div class="ax-classes" id="ax-classes-list">Загрузка…</div>
|
|
||||||
</div>
|
|
||||||
<div class="ax-field" id="ax-student-field" style="display:none">
|
|
||||||
<label>Ученик</label>
|
|
||||||
<input type="text" class="ax-input" id="ax-student-search" placeholder="Поиск по имени или email…" autocomplete="off" />
|
|
||||||
<div class="ax-student-results" id="ax-student-results"></div>
|
|
||||||
<input type="hidden" id="ax-student-id" />
|
|
||||||
</div>
|
|
||||||
<div class="ax-field">
|
|
||||||
<label>Параграфы</label>
|
|
||||||
<input type="text" class="ax-input" id="ax-paragraphs" placeholder="например: 1-5 или 1,3,7" />
|
|
||||||
<div class="ax-hint">Диапазон («15-18») или список через запятую («1,3,5»). Пустое = весь учебник.</div>
|
|
||||||
</div>
|
|
||||||
<div class="ax-field">
|
|
||||||
<label>Срок сдачи</label>
|
|
||||||
<input type="datetime-local" class="ax-input" id="ax-deadline" />
|
|
||||||
</div>
|
|
||||||
<div class="ax-error" id="ax-error"></div>
|
|
||||||
<div class="ax-success" id="ax-success"></div>
|
|
||||||
<div class="ax-actions">
|
|
||||||
<button type="button" class="ax-btn" onclick="closeAssignModal()">Отмена</button>
|
|
||||||
<button type="submit" class="ax-btn ax-btn-primary" id="ax-submit">Назначить</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
||||||
<script src="/js/api.js"></script>
|
<script src="/js/api.js"></script>
|
||||||
<script src="/js/sidebar.js"></script>
|
<script src="/js/sidebar.js"></script>
|
||||||
@@ -573,178 +488,165 @@
|
|||||||
if (window.lucide) lucide.createIcons();
|
if (window.lucide) lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Assign modal ── */
|
/* ── Assign modal (via LS.modal) ── */
|
||||||
let assignSlug = null;
|
let teacherStudents = null;
|
||||||
let assignTitle = null;
|
|
||||||
let assignTab = 'class'; // 'class' or 'student'
|
|
||||||
let teacherStudents = null; // cached list of students-in-teacher's-classes
|
|
||||||
|
|
||||||
async function loadTeacherClasses() {
|
async function loadTeacherClasses() {
|
||||||
if (teacherClasses) return teacherClasses;
|
if (teacherClasses) return teacherClasses;
|
||||||
try {
|
try { const list = await LS.api('/api/classes'); teacherClasses = Array.isArray(list) ? list : []; }
|
||||||
const list = await LS.api('/api/classes');
|
catch { teacherClasses = []; }
|
||||||
teacherClasses = Array.isArray(list) ? list : [];
|
|
||||||
} catch { teacherClasses = []; }
|
|
||||||
return teacherClasses;
|
return teacherClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadTeacherStudents() {
|
async function loadTeacherStudents() {
|
||||||
if (teacherStudents) return teacherStudents;
|
if (teacherStudents) return teacherStudents;
|
||||||
try {
|
try { const r = await LS.api('/api/classes/students'); teacherStudents = Array.isArray(r) ? r : (r.students || []); }
|
||||||
const r = await LS.api('/api/classes/students');
|
catch { teacherStudents = []; }
|
||||||
teacherStudents = Array.isArray(r) ? r : (r.students || []);
|
|
||||||
} catch { teacherStudents = []; }
|
|
||||||
return teacherStudents;
|
return teacherStudents;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setAssignTab = function (tab) {
|
|
||||||
assignTab = tab;
|
|
||||||
document.querySelectorAll('.ax-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
|
|
||||||
document.getElementById('ax-class-field').style.display = tab === 'class' ? '' : 'none';
|
|
||||||
document.getElementById('ax-student-field').style.display = tab === 'student' ? '' : 'none';
|
|
||||||
document.getElementById('ax-student-id').value = '';
|
|
||||||
document.getElementById('ax-student-search').value = '';
|
|
||||||
document.getElementById('ax-student-results').classList.remove('visible');
|
|
||||||
};
|
|
||||||
|
|
||||||
window.openAssignModal = async function (slug, title) {
|
window.openAssignModal = async function (slug, title) {
|
||||||
assignSlug = slug;
|
|
||||||
assignTitle = title;
|
|
||||||
document.getElementById('assign-title').textContent = `Назначить чтение: «${title}»`;
|
|
||||||
['ax-error', 'ax-success'].forEach(id => document.getElementById(id).classList.remove('visible'));
|
|
||||||
document.getElementById('ax-paragraphs').value = '';
|
|
||||||
document.getElementById('ax-deadline').value = '';
|
|
||||||
document.getElementById('ax-submit').disabled = false;
|
|
||||||
document.getElementById('ax-submit').textContent = 'Назначить';
|
|
||||||
setAssignTab('class');
|
|
||||||
|
|
||||||
const listEl = document.getElementById('ax-classes-list');
|
|
||||||
listEl.textContent = 'Загрузка…';
|
|
||||||
const classes = await loadTeacherClasses();
|
const classes = await loadTeacherClasses();
|
||||||
if (!classes.length) {
|
const classesHtml = classes.length
|
||||||
listEl.innerHTML = '<div style="padding:14px;color:var(--text-3);font-size:.85rem">У вас пока нет классов</div>';
|
? classes.map(c => `
|
||||||
} else {
|
<label class="ax-class">
|
||||||
listEl.innerHTML = classes.map(c => `
|
<input type="checkbox" name="cls" value="${c.id}" />
|
||||||
<label class="ax-class">
|
<span class="ax-cname">${esc(c.name)}</span>
|
||||||
<input type="checkbox" name="cls" value="${c.id}" />
|
<span class="ax-cmeta">${c.member_count || 0} учеников</span>
|
||||||
<span class="ax-cname">${esc(c.name)}</span>
|
</label>`).join('')
|
||||||
<span class="ax-cmeta">${c.member_count || 0} учеников</span>
|
: '<div style="padding:14px;color:var(--text-3);font-size:.85rem">У вас пока нет классов</div>';
|
||||||
</label>`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('assign-overlay').classList.add('visible');
|
const body = `
|
||||||
document.addEventListener('keydown', onAssignEsc);
|
<form class="ax-form" onsubmit="event.preventDefault()">
|
||||||
};
|
<div class="ax-field">
|
||||||
|
<label>Кому</label>
|
||||||
|
<div class="ax-tabs">
|
||||||
|
<button type="button" class="ax-tab active" data-tab="class">Классу</button>
|
||||||
|
<button type="button" class="ax-tab" data-tab="student">Ученику</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ax-field" data-pane="class">
|
||||||
|
<label>Классы</label>
|
||||||
|
<div class="ax-classes">${classesHtml}</div>
|
||||||
|
</div>
|
||||||
|
<div class="ax-field" data-pane="student" style="display:none">
|
||||||
|
<label>Ученик</label>
|
||||||
|
<input type="text" class="ax-input" name="student-search" placeholder="Поиск по имени или email…" autocomplete="off" />
|
||||||
|
<div class="ax-student-results"></div>
|
||||||
|
<input type="hidden" name="student-id" />
|
||||||
|
</div>
|
||||||
|
<div class="ax-field">
|
||||||
|
<label>Параграфы</label>
|
||||||
|
<input type="text" class="ax-input" name="paragraphs" placeholder="например: 1-5 или 1,3,7" />
|
||||||
|
<div class="ax-hint">Диапазон («15-18») или список через запятую («1,3,5»). Пустое = весь учебник.</div>
|
||||||
|
</div>
|
||||||
|
<div class="ax-field">
|
||||||
|
<label>Срок сдачи</label>
|
||||||
|
<input type="datetime-local" class="ax-input" name="deadline" />
|
||||||
|
</div>
|
||||||
|
</form>`;
|
||||||
|
|
||||||
window.closeAssignModal = function () {
|
let currentTab = 'class';
|
||||||
document.getElementById('assign-overlay').classList.remove('visible');
|
const m = LS.modal({
|
||||||
document.removeEventListener('keydown', onAssignEsc);
|
title: `Назначить чтение: «${title}»`,
|
||||||
};
|
content: body,
|
||||||
|
size: 'sm',
|
||||||
window.onAssignOverlayClick = function (e) {
|
actions: [
|
||||||
if (e.target === document.getElementById('assign-overlay')) closeAssignModal();
|
{ label: 'Отмена', onClick: () => m.close() },
|
||||||
};
|
{
|
||||||
function onAssignEsc(e) { if (e.key === 'Escape') closeAssignModal(); }
|
label: 'Назначить', primary: true,
|
||||||
|
onClick: async () => {
|
||||||
/* Student search (debounced) */
|
const f = m.body.querySelector('form');
|
||||||
let stSearchTimer = null;
|
const paragraphs = f['paragraphs'].value.trim();
|
||||||
document.addEventListener('input', e => {
|
const deadline = f['deadline'].value || null;
|
||||||
if (e.target?.id !== 'ax-student-search') return;
|
const titleSuffix = paragraphs ? ` (§${paragraphs})` : '';
|
||||||
clearTimeout(stSearchTimer);
|
const btns = m.root.querySelectorAll('.ls-mod-btn');
|
||||||
stSearchTimer = setTimeout(() => filterStudents(e.target.value), 200);
|
btns.forEach(b => b.disabled = true);
|
||||||
});
|
btns[1].textContent = 'Назначаю…';
|
||||||
|
try {
|
||||||
async function filterStudents(q) {
|
if (currentTab === 'class') {
|
||||||
const resultsEl = document.getElementById('ax-student-results');
|
const checked = [...f.querySelectorAll('input[name="cls"]:checked')].map(el => Number(el.value));
|
||||||
q = q.trim().toLowerCase();
|
if (!checked.length) throw new Error('Выберите хотя бы один класс');
|
||||||
if (q.length < 2) { resultsEl.classList.remove('visible'); return; }
|
const r = await LS.api('/api/assignments/bulk', {
|
||||||
const students = await loadTeacherStudents();
|
method: 'POST',
|
||||||
const matches = students.filter(s =>
|
body: {
|
||||||
(s.name && s.name.toLowerCase().includes(q)) ||
|
title: `Учебник: ${title}${titleSuffix}`,
|
||||||
(s.email && s.email.toLowerCase().includes(q))
|
class_ids: checked,
|
||||||
).slice(0, 12);
|
mode: 'exam', count: 1, subject_slug: 'other', is_homework: 1,
|
||||||
if (!matches.length) {
|
deadline, textbook_slug: slug, textbook_paragraphs: paragraphs || null,
|
||||||
resultsEl.innerHTML = '<div class="ax-student-row" style="color:var(--text-3);cursor:default">Не найдено</div>';
|
},
|
||||||
} else {
|
});
|
||||||
resultsEl.innerHTML = matches.map(s => `
|
LS.toast(`Назначено в ${r.count || checked.length} класс(ах)`, 'success');
|
||||||
<div class="ax-student-row" data-id="${s.id}" data-name="${esc(s.name)}">
|
} else {
|
||||||
<span>${esc(s.name)}</span>
|
const studentId = Number(f['student-id'].value);
|
||||||
<span class="ax-student-email">${esc(s.email || '')}</span>
|
if (!studentId) throw new Error('Выберите ученика');
|
||||||
</div>`).join('');
|
await LS.api('/api/assignments', {
|
||||||
}
|
method: 'POST',
|
||||||
resultsEl.classList.add('visible');
|
body: {
|
||||||
}
|
title: `Учебник: ${title}${titleSuffix}`,
|
||||||
|
student_id: studentId,
|
||||||
document.addEventListener('click', e => {
|
mode: 'exam', count: 1, subject_slug: 'other', is_homework: 1,
|
||||||
const row = e.target.closest('.ax-student-row');
|
deadline, textbook_slug: slug, textbook_paragraphs: paragraphs || null,
|
||||||
if (!row || !row.dataset.id) return;
|
},
|
||||||
document.querySelectorAll('.ax-student-row').forEach(r => r.classList.remove('selected'));
|
});
|
||||||
row.classList.add('selected');
|
LS.toast('Личное задание создано', 'success');
|
||||||
document.getElementById('ax-student-id').value = row.dataset.id;
|
}
|
||||||
document.getElementById('ax-student-search').value = row.dataset.name;
|
m.close();
|
||||||
document.getElementById('ax-student-results').classList.remove('visible');
|
} catch (e) {
|
||||||
});
|
m.setError(e.message || 'Не удалось создать задание');
|
||||||
|
btns.forEach(b => b.disabled = false);
|
||||||
window.submitAssign = async function () {
|
btns[1].textContent = 'Назначить';
|
||||||
const errorEl = document.getElementById('ax-error');
|
}
|
||||||
const successEl = document.getElementById('ax-success');
|
|
||||||
const submitBtn = document.getElementById('ax-submit');
|
|
||||||
errorEl.classList.remove('visible');
|
|
||||||
successEl.classList.remove('visible');
|
|
||||||
|
|
||||||
const paragraphs = document.getElementById('ax-paragraphs').value.trim();
|
|
||||||
const deadline = document.getElementById('ax-deadline').value || null;
|
|
||||||
const titleSuffix = paragraphs ? ` (§${paragraphs})` : '';
|
|
||||||
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
submitBtn.textContent = 'Назначаю…';
|
|
||||||
|
|
||||||
try {
|
|
||||||
let resultMsg;
|
|
||||||
if (assignTab === 'class') {
|
|
||||||
const checked = [...document.querySelectorAll('#ax-classes-list input[name="cls"]:checked')]
|
|
||||||
.map(el => Number(el.value));
|
|
||||||
if (!checked.length) throw new Error('Выберите хотя бы один класс');
|
|
||||||
|
|
||||||
const r = await LS.api('/api/assignments/bulk', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
title: `Учебник: ${assignTitle}${titleSuffix}`,
|
|
||||||
class_ids: checked,
|
|
||||||
mode: 'exam', count: 1, subject_slug: 'other', is_homework: 1,
|
|
||||||
deadline,
|
|
||||||
textbook_slug: assignSlug,
|
|
||||||
textbook_paragraphs: paragraphs || null,
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
resultMsg = `Назначено в ${r.count || checked.length} класс(е/ах)`;
|
],
|
||||||
} else {
|
});
|
||||||
const studentId = Number(document.getElementById('ax-student-id').value);
|
|
||||||
if (!studentId) throw new Error('Выберите ученика');
|
|
||||||
|
|
||||||
await LS.api('/api/assignments', {
|
// Tab switching within modal
|
||||||
method: 'POST',
|
m.body.querySelectorAll('.ax-tab').forEach(tab => {
|
||||||
body: {
|
tab.addEventListener('click', () => {
|
||||||
title: `Учебник: ${assignTitle}${titleSuffix}`,
|
currentTab = tab.dataset.tab;
|
||||||
student_id: studentId,
|
m.body.querySelectorAll('.ax-tab').forEach(t => t.classList.toggle('active', t === tab));
|
||||||
mode: 'exam', count: 1, subject_slug: 'other', is_homework: 1,
|
m.body.querySelectorAll('[data-pane]').forEach(p => p.style.display = p.dataset.pane === currentTab ? '' : 'none');
|
||||||
deadline,
|
});
|
||||||
textbook_slug: assignSlug,
|
});
|
||||||
textbook_paragraphs: paragraphs || null,
|
|
||||||
},
|
// Student search (debounced, scoped to this modal)
|
||||||
});
|
let stTimer = null;
|
||||||
resultMsg = 'Личное задание создано';
|
const searchInput = m.body.querySelector('input[name="student-search"]');
|
||||||
}
|
const resultsEl = m.body.querySelector('.ax-student-results');
|
||||||
successEl.textContent = resultMsg;
|
const idInput = m.body.querySelector('input[name="student-id"]');
|
||||||
successEl.classList.add('visible');
|
|
||||||
submitBtn.textContent = 'Готово';
|
searchInput.addEventListener('input', () => {
|
||||||
setTimeout(closeAssignModal, 1500);
|
clearTimeout(stTimer);
|
||||||
} catch (e) {
|
stTimer = setTimeout(async () => {
|
||||||
errorEl.textContent = e.message || 'Не удалось создать задание';
|
const q = searchInput.value.trim().toLowerCase();
|
||||||
errorEl.classList.add('visible');
|
if (q.length < 2) { resultsEl.classList.remove('visible'); return; }
|
||||||
submitBtn.disabled = false;
|
const students = await loadTeacherStudents();
|
||||||
submitBtn.textContent = 'Назначить';
|
const matches = students.filter(s =>
|
||||||
}
|
(s.name && s.name.toLowerCase().includes(q)) ||
|
||||||
|
(s.email && s.email.toLowerCase().includes(q))
|
||||||
|
).slice(0, 12);
|
||||||
|
resultsEl.innerHTML = matches.length
|
||||||
|
? matches.map(s => `
|
||||||
|
<div class="ax-student-row" data-id="${s.id}" data-name="${esc(s.name)}">
|
||||||
|
<span>${esc(s.name)}</span>
|
||||||
|
<span class="ax-student-email">${esc(s.email || '')}</span>
|
||||||
|
</div>`).join('')
|
||||||
|
: '<div class="ax-student-row" style="color:var(--text-3);cursor:default">Не найдено</div>';
|
||||||
|
resultsEl.classList.add('visible');
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsEl.addEventListener('click', e => {
|
||||||
|
const row = e.target.closest('.ax-student-row');
|
||||||
|
if (!row || !row.dataset.id) return;
|
||||||
|
m.body.querySelectorAll('.ax-student-row').forEach(r => r.classList.remove('selected'));
|
||||||
|
row.classList.add('selected');
|
||||||
|
idInput.value = row.dataset.id;
|
||||||
|
searchInput.value = row.dataset.name;
|
||||||
|
resultsEl.classList.remove('visible');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user