feat(modules): batch module import in admin, module usage in journal

This commit is contained in:
dev
2026-09-18 19:36:32 +03:00
parent 12a527b3ee
commit d3dd922e32
7 changed files with 254 additions and 56 deletions
+85 -28
View File
@@ -5,6 +5,10 @@ let highlightClearTimer = null;
let editId = null;
let editGroups = [];
let editModules = [];
let moduleNames = new Map();
let moduleFilterId = '';
let moduleSearchTimer = null;
let editModuleSearchTimer = null;
let modulesLoaded = false;
let groupsLoaded = false;
let studentsLoaded = false;
@@ -219,29 +223,64 @@ async function loadGroupOptions() {
groupsLoaded = true;
}
async function loadModuleOptions() {
const res = await fetch(`${API}/api/modules`);
if (!res.ok) return;
const data = await res.json();
editModules = data.modules || [];
modulesLoaded = true;
const filt = document.getElementById('moduleFilter');
if (filt) {
while (filt.options.length > 1) filt.remove(1);
editModules.forEach(m => {
const o = document.createElement('option');
o.value = m.id; o.textContent = m.name;
filt.appendChild(o);
});
}
function renderModuleOptions(datalistId, list) {
list.forEach(m => moduleNames.set(m.name, m.id));
const dl = document.getElementById(datalistId);
if (!dl) return;
dl.innerHTML = '';
list.forEach(m => {
const opt = document.createElement('option');
opt.value = m.name;
dl.appendChild(opt);
});
}
function fillEditModuleSelect(selected) {
const sel = document.getElementById('editModule');
if (!sel) return;
sel.innerHTML = '<option value="">— не выбрано —</option>' +
editModules.map(m => `<option value="${m.id}">${esc(m.name)}</option>`).join('');
sel.value = selected ? String(selected) : '';
async function fetchModules(q, limit) {
const p = new URLSearchParams();
if (q) p.set('search', q);
p.set('limit', String(limit));
const res = await fetch(`${API}/api/modules?${p}`, { headers: hdr() });
if (!res.ok) return [];
const data = await res.json().catch(() => ({}));
return data.modules || [];
}
async function resolveModuleId(name) {
const n = (name || '').trim();
if (!n) return '';
if (moduleNames.has(n)) return String(moduleNames.get(n));
const list = await fetchModules(n, 10);
const lower = n.toLowerCase();
const exact = list.find(m => m.name.toLowerCase() === lower);
return exact ? String(exact.id) : null;
}
async function loadModuleOptions() {
editModules = await fetchModules('', 50);
modulesLoaded = true;
renderModuleOptions('moduleFilterList', editModules);
}
async function applyModuleFilter() {
const inp = document.getElementById('moduleFilter');
const resolved = await resolveModuleId(inp.value);
if (resolved === null) {
inp.value = '';
moduleFilterId = '';
showToast('Модуль не найден — фильтр сброшен');
} else {
moduleFilterId = resolved;
}
page = 1;
loadEntries();
}
function fillEditModuleInput(entry) {
const inp = document.getElementById('editModule');
if (!inp) return;
if (entry.module_name && entry.module_id) moduleNames.set(entry.module_name, entry.module_id);
inp.value = entry.module_name || '';
renderModuleOptions('editModuleList', editModules);
}
async function loadStudentOptions() {
@@ -265,8 +304,7 @@ async function loadEntries() {
if (gid) p.set('group_id', gid);
if (df) p.set('date_from', df);
if (dt) p.set('date_to', dt);
const mid = document.getElementById('moduleFilter') ? document.getElementById('moduleFilter').value : '';
if (mid) p.set('module_id', mid);
if (moduleFilterId) p.set('module_id', moduleFilterId);
p.set('limit', PAGE_SIZE);
p.set('offset', (page - 1) * PAGE_SIZE);
const res = await fetch(`${API}/api/entries?${p}`, { headers: hdr() });
@@ -368,7 +406,7 @@ async function openEdit(id) {
}
sel.value = e.group_id;
if (!modulesLoaded) await loadModuleOptions();
fillEditModuleSelect(e.module_id);
fillEditModuleInput(e);
renderEditPhotos(e.photos || [], e.photo_path);
document.getElementById('editModal').classList.add('open');
}
@@ -1158,11 +1196,14 @@ async function saveEdit() {
const description = document.getElementById('editDesc').value.trim();
if (!name || !group_id || !description) { alert('Заполните все поля'); return; }
const moduleId = await resolveModuleId(document.getElementById('editModule').value);
if (moduleId === null) { alert('Тема модуля не найдена. Выберите из списка подсказок.'); return; }
const formData = new FormData();
formData.append('student_name', name);
formData.append('group_id', group_id);
formData.append('description', description);
formData.append('module_id', document.getElementById('editModule').value);
formData.append('module_id', moduleId);
const photoFiles = document.getElementById('editPhoto').files;
for (const f of photoFiles) formData.append('photo', f);
@@ -1206,7 +1247,24 @@ async function delEntry(id) {
document.getElementById('studentFilter').addEventListener('change', () => { page = 1; loadEntries(); });
document.getElementById('groupFilter').addEventListener('change', () => { page = 1; loadEntries(); });
document.getElementById('moduleFilter').addEventListener('change', () => { page = 1; loadEntries(); });
document.getElementById('moduleFilter').addEventListener('change', applyModuleFilter);
document.getElementById('moduleFilter').addEventListener('keydown', e => {
if (e.key === 'Enter') { e.preventDefault(); applyModuleFilter(); }
});
document.getElementById('moduleFilter').addEventListener('input', () => {
clearTimeout(moduleSearchTimer);
const q = document.getElementById('moduleFilter').value.trim();
moduleSearchTimer = setTimeout(async () => {
renderModuleOptions('moduleFilterList', await fetchModules(q, 20));
}, 250);
});
document.getElementById('editModule').addEventListener('input', () => {
clearTimeout(editModuleSearchTimer);
const q = document.getElementById('editModule').value.trim();
editModuleSearchTimer = setTimeout(async () => {
renderModuleOptions('editModuleList', await fetchModules(q, 20));
}, 250);
});
document.getElementById('dateFrom').addEventListener('change', () => { page = 1; loadEntries(); });
document.getElementById('dateTo').addEventListener('change', () => { page = 1; loadEntries(); });
document.getElementById('searchInput').addEventListener('input', () => {
@@ -1261,8 +1319,7 @@ async function exportCSV() {
if (gid) p.set('group_id', gid);
if (df) p.set('date_from', df);
if (dt) p.set('date_to', dt);
const mid = document.getElementById('moduleFilter') ? document.getElementById('moduleFilter').value : '';
if (mid) p.set('module_id', mid);
if (moduleFilterId) p.set('module_id', moduleFilterId);
const res = await fetch(`${API}/api/entries?${p}`, { headers: hdr() });
const entries = (await res.json()).entries;
if (!entries.length) { alert('Нет данных для экспорта'); return; }