feat(modules): module topics for student form, admin CRUD with pagination
- db: modules table (name, lessons_count) + entries.module_id (ON DELETE SET NULL), migration + idempotent startup ensure - api: GET /api/modules (public, search + limit/offset, entries_count), POST/PUT/DELETE (admin, audit-logged) - entries: accept/validate module_id on create/update, return module_name, module_id filter - backup/restore: include modules and entries.module_id - student form: required module select, hidden while no modules exist - admin: modules.html + js/modules.js list with pagination, search, create/edit/delete modal - journal: module filter, module select in edit modal, module badge, CSV column
This commit is contained in:
@@ -4,6 +4,8 @@ const preview = document.getElementById('preview');
|
||||
const placeholderSrc = preview.src;
|
||||
const removePhotoBtn = document.getElementById('removePhotoBtn');
|
||||
const groupInput = document.getElementById('groupInput');
|
||||
const moduleInput = document.getElementById('moduleInput');
|
||||
const moduleField = document.getElementById('moduleField');
|
||||
const toast = document.getElementById('toast');
|
||||
const camVideo = document.getElementById('camVideo');
|
||||
const camCanvas = document.getElementById('camCanvas');
|
||||
@@ -133,6 +135,23 @@ if (params.get('nameInput')) {
|
||||
}
|
||||
|
||||
(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 = '';
|
||||
}
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
const res = await fetch(`${API}/api/students`);
|
||||
const students = await res.json();
|
||||
const me = studentName ? students.find(s => s.name === studentName) : null;
|
||||
@@ -219,6 +238,12 @@ 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;
|
||||
}
|
||||
const btn = document.getElementById('submitBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Отправка...';
|
||||
@@ -228,6 +253,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('description', document.getElementById('descInput').value);
|
||||
fd.append('website', document.getElementById('hpWebsite').value);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user