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
+62 -21
View File
@@ -5,7 +5,10 @@ const placeholderSrc = preview.src;
const removePhotoBtn = document.getElementById('removePhotoBtn');
const groupInput = document.getElementById('groupInput');
const moduleInput = document.getElementById('moduleInput');
const moduleInputList = document.getElementById('moduleInputList');
const moduleField = document.getElementById('moduleField');
const moduleNames = new Map();
let moduleSearchTimer = null;
const toast = document.getElementById('toast');
const camVideo = document.getElementById('camVideo');
const camCanvas = document.getElementById('camCanvas');
@@ -134,22 +137,46 @@ if (params.get('nameInput')) {
field.innerHTML = `<label>Фамилия и имя</label><div class="fixed-name" id="fixedName">${escName(studentName)}</div>`;
}
function renderModuleOptions(list) {
list.forEach(m => moduleNames.set(m.name, m.id));
moduleInputList.innerHTML = '';
list.forEach(m => {
const opt = document.createElement('option');
opt.value = m.name;
moduleInputList.appendChild(opt);
});
}
async function fetchModules(q, limit) {
const p = new URLSearchParams();
if (q) p.set('search', q);
p.set('limit', String(limit));
try {
const res = await fetch(`${API}/api/modules?${p}`);
if (!res.ok) return [];
const data = await res.json();
return data.modules || [];
} catch (e) { return []; }
}
async function resolveModuleId(name) {
const n = (name || '').trim();
if (!n) return null;
if (moduleNames.has(n)) return moduleNames.get(n);
const list = await fetchModules(n, 5);
renderModuleOptions(list);
const lower = n.toLowerCase();
const exact = list.find(m => m.name.toLowerCase() === lower);
return exact ? exact.id : null;
}
(async () => {
try {
const mRes = await fetch(`${API}/api/modules`);
if (mRes.ok) {
const mData = await mRes.json();
const list = mData.modules || [];
if (list.length) {
list.forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.lessons_count > 0 ? `${m.name} · ${m.lessons_count} зан.` : m.name;
moduleInput.appendChild(opt);
});
moduleInput.required = true;
moduleField.style.display = '';
}
const list = await fetchModules('', 50);
if (list.length) {
renderModuleOptions(list);
moduleInput.required = true;
moduleField.style.display = '';
}
} catch (e) { /* ignore */ }
const res = await fetch(`${API}/api/students`);
@@ -238,11 +265,15 @@ form.addEventListener('submit', async (e) => {
setTimeout(() => toast.classList.remove('show', 'error'), 3200);
return;
}
if (moduleField.style.display !== 'none' && !moduleInput.value) {
toast.textContent = 'Выберите тему модуля';
toast.classList.add('show', 'error');
setTimeout(() => toast.classList.remove('show', 'error'), 3200);
return;
let moduleId = '';
if (moduleField.style.display !== 'none') {
moduleId = await resolveModuleId(moduleInput.value);
if (!moduleId) {
toast.textContent = 'Выберите тему модуля из списка подсказок';
toast.classList.add('show', 'error');
setTimeout(() => toast.classList.remove('show', 'error'), 3200);
return;
}
}
const btn = document.getElementById('submitBtn');
btn.disabled = true;
@@ -253,7 +284,7 @@ form.addEventListener('submit', async (e) => {
selectedFiles.forEach(f => fd.append('files', f, f.name));
fd.append('student_name', studentName || document.getElementById('nameInput').value);
fd.append('group_id', groupInput.value);
fd.append('module_id', moduleInput.value);
fd.append('module_id', moduleId || '');
fd.append('description', document.getElementById('descInput').value);
fd.append('website', document.getElementById('hpWebsite').value);
@@ -285,4 +316,14 @@ document.getElementById('openCamBtn').addEventListener('click', openCam);
filesClearBtn.addEventListener('click', clearFiles);
document.getElementById('filesBtn').addEventListener('click', () => document.getElementById('filesInput').click());
document.getElementById('cancelCamBtn').addEventListener('click', closeCam);
document.getElementById('captureBtn').addEventListener('click', capture);
document.getElementById('captureBtn').addEventListener('click', capture);
moduleInput.addEventListener('input', () => {
clearTimeout(moduleSearchTimer);
const q = moduleInput.value.trim();
moduleSearchTimer = setTimeout(async () => {
renderModuleOptions(await fetchModules(q, 20));
}, 250);
});
moduleInput.addEventListener('focus', async () => {
if (!moduleInput.value.trim()) renderModuleOptions(await fetchModules('', 50));
});