feat(modules): batch module import in admin, module usage in journal
This commit is contained in:
@@ -114,6 +114,83 @@ document.getElementById('moduleForm').addEventListener('submit', async (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
function parseBatchModules(raw) {
|
||||
const items = [];
|
||||
const errors = [];
|
||||
const seen = new Set();
|
||||
String(raw).split('\n').forEach((line, idx) => {
|
||||
const text = line.trim();
|
||||
if (!text) return;
|
||||
const lineNo = idx + 1;
|
||||
let name = '';
|
||||
let lessonsRaw = '';
|
||||
const sepIndex = text.search(/[|;=\t]/);
|
||||
if (sepIndex !== -1) {
|
||||
name = text.slice(0, sepIndex).trim();
|
||||
lessonsRaw = text.slice(sepIndex + 1).trim();
|
||||
} else {
|
||||
const numMatch = text.match(/^(.*\S)\s+(\d+)$/);
|
||||
if (numMatch) {
|
||||
name = numMatch[1].trim();
|
||||
lessonsRaw = numMatch[2];
|
||||
}
|
||||
}
|
||||
if (!name && !lessonsRaw) { errors.push(`Строка ${lineNo}: «${text}» — укажите название и количество занятий, например: Основы Python | 10`); return; }
|
||||
if (!name) { errors.push(`Строка ${lineNo}: «${text}» — не указано название`); return; }
|
||||
if (!lessonsRaw) { errors.push(`Строка ${lineNo}: «${text}» — не указано количество занятий`); return; }
|
||||
const lessons = Number(lessonsRaw);
|
||||
if (!Number.isInteger(lessons) || lessons < 0 || lessons > 10000) {
|
||||
errors.push(`Строка ${lineNo}: «${text}» — количество занятий должно быть целым числом от 0 до 10000`);
|
||||
return;
|
||||
}
|
||||
const key = name.toLowerCase();
|
||||
if (seen.has(key)) { errors.push(`Строка ${lineNo}: «${text}» — дубликат названия в списке`); return; }
|
||||
seen.add(key);
|
||||
items.push({ name, lessons_count: lessons });
|
||||
});
|
||||
return { items, errors };
|
||||
}
|
||||
|
||||
function openBatchModuleModal() {
|
||||
document.getElementById('batchModuleForm').reset();
|
||||
document.getElementById('batchModuleModal').classList.add('open');
|
||||
setTimeout(() => document.getElementById('batchModuleText').focus(), 100);
|
||||
}
|
||||
|
||||
function closeBatchModuleModal() {
|
||||
document.getElementById('batchModuleModal').classList.remove('open');
|
||||
}
|
||||
|
||||
async function batchAddModules() {
|
||||
const submitBtn = document.getElementById('batchModuleSubmitBtn');
|
||||
const { items, errors } = parseBatchModules(document.getElementById('batchModuleText').value);
|
||||
if (errors.length) {
|
||||
alert('Исправьте строки:\n\n' + errors.join('\n'));
|
||||
return;
|
||||
}
|
||||
if (!items.length) { alert('Добавьте хотя бы один модуль'); return; }
|
||||
submitBtn.disabled = true;
|
||||
let added = 0, dupes = 0, failed = 0;
|
||||
for (const item of items) {
|
||||
const res = await fetch(`${API}/api/modules`, { method: 'POST', headers: hdrJson(), body: JSON.stringify(item) });
|
||||
if (res.ok) added++;
|
||||
else if (res.status === 409) dupes++;
|
||||
else failed++;
|
||||
}
|
||||
submitBtn.disabled = false;
|
||||
closeBatchModuleModal();
|
||||
loadModules();
|
||||
showToast(`Добавлено: ${added}${dupes ? `, дубликатов: ${dupes}` : ''}${failed ? `, ошибок: ${failed}` : ''}`);
|
||||
}
|
||||
|
||||
document.getElementById('batchModuleForm').addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
batchAddModules();
|
||||
});
|
||||
document.getElementById('batchModuleBtn').addEventListener('click', openBatchModuleModal);
|
||||
document.getElementById('batchModuleCancelBtn').addEventListener('click', closeBatchModuleModal);
|
||||
document.getElementById('batchModuleModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeBatchModuleModal(); });
|
||||
|
||||
document.getElementById('addModuleBtn').addEventListener('click', () => openModuleModal());
|
||||
document.getElementById('moduleCancelBtn').addEventListener('click', closeModuleModal);
|
||||
document.getElementById('moduleModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeModuleModal(); });
|
||||
|
||||
Reference in New Issue
Block a user