[ УЧЕБНАЯ ЛАБОРАТОРИЯ ]
🔒
Добро пожаловать
Введи логин и пароль для входа
[ Учебная лаборатория ]ADMIN
Вошёл: —
ученики
Загрузка...
Новая карточка
пул карточек
Загрузка...
Новый спринт
список спринтов
Загрузка...
собрать спринт из карточек
Добавь карточки из пула → они прикрепятся к спринту
📦 Все карточки (без спринта)
📎 В этом спринте
открыть спринт ученику
Карточки в спринте:
ℹ️ Спринт разблокируется, все его карточки появятся у ученика в To Do
апрув карточки
💬 Сообщения учеников
Загрузка...
← Выбери ученика из списка
импорт карточек из JSON
📋 Формат (один файл = одна карточка):
{ "title":"...", "description":"...", "difficulty":"easy|medium|hard",
  "card_type":"theory|practice", "sprint_id":1,
  "checklist":[{"text":"Пункт","url":"https://..."}] }
📂
Перетащи файлы или папку сюда
Предпросмотр: 0 карточек
0/0
Переключись на вкладку 📚 Docs чтобы сгенерировать документацию
[ Учебная лаборатория ]
Привет,—
Мой прогресс
0
всего
0
в работе
0
выполнено
0
апрув
Загружаем задачи...
Описание
Чек-лист
    Заметки
    Переместить:
    , bind here var sBtn=document.getElementById('s-chat-send'); var sInp=document.getElementById('s-chat-input'); if(sBtn&&!sBtn._bound){ sBtn._bound=true; sBtn.addEventListener('click',sndStudent); if(sInp){ sInp.addEventListener('keydown',function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sndStudent();}}); sInp.addEventListener('input',function(){ this.style.height='38px'; var h=Math.min(this.scrollHeight,120); this.style.height=h+'px'; this.style.overflowY=this.scrollHeight>118?'auto':'hidden'; }); } // Media attach var mBtn=document.getElementById('s-media-btn'); var mInp=document.getElementById('s-media-inp'); if(mBtn&&mInp&&!mBtn._bound){ mBtn._bound=true; mBtn.addEventListener('click',function(){mInp.click();}); mInp.addEventListener('change',function(){ var file=mInp.files[0];if(!file)return; if(file.size>5*1024*1024){showToast('Файл > 5 МБ','err');return;} var reader=new FileReader(); reader.onload=function(ev){ window._sChatPendingMedia={base64:ev.target.result,type:file.type}; var sBar=document.getElementById('s-media-bar'); if(!sBar)return; sBar.innerHTML=''; if(file.type.startsWith('image')){var img=document.createElement('img');img.src=ev.target.result;img.style.cssText='width:48px;height:48px;object-fit:cover;border-radius:7px;';sBar.appendChild(img);} else{var sp=document.createElement('span');sp.style.cssText='font-size:1.4rem;';sp.textContent='🎬';sBar.appendChild(sp);} var inf=document.createElement('div');inf.className='tg-media-bar-info';inf.textContent=file.type.startsWith('image')?'Фото готово к отправке':'Видео готово'; var cx=document.createElement('button');cx.className='tg-mpcancel';cx.textContent='×'; cx.onclick=function(){window._sChatPendingMedia=null;sBar.innerHTML='';sBar.classList.remove('has-media');}; sBar.appendChild(inf);sBar.appendChild(cx);sBar.classList.add('has-media'); }; reader.readAsDataURL(file);mInp.value=''; }); } } } bindStudentChat(); // Hook chat tab → init document.querySelectorAll('.tab').forEach(function(t){ if(t.dataset.tab==='chat')t.addEventListener('click',function(){tgInit();}); }); // Load after unlock var _origUnlock=unlock; unlock=function(name,role){ _origUnlock(name,role); if(role==='student'){setTimeout(function(){bindStudentChat();loadStudentChat();},500);} if(role==='admin'){setTimeout(tgInit,1000);} }; // ══════════════════════════════════════════════════════════ // DOCS TAB // ══════════════════════════════════════════════════════════ function generateDocs(){ var el=document.getElementById('docs-body'); if(!el)return; var h=''; // Sprints h+='
    🏃 Спринты'+_sprints.length+' всего
    '; if(!_sprints.length){ h+='
    Спринтов пока нет
    '; } else { _sprints.forEach(function(s){ var cnt=_cards.filter(function(c){return c.sprint_id===s.id;}).length; h+='
    ' +''+esc(s.name)+'' +''+(s.is_locked?'🔒 закрыт':'🟢 открыт')+'' +(s.description?''+esc(s.description.slice(0,65))+'':'') +''+cnt+' карт.' +'
    '; }); } h+='
    '; // Cards h+='
    🗂 Карточки'+_cards.length+' всего
    '; if(!_cards.length){ h+='
    Карточек пока нет
    '; } else { _cards.forEach(function(c){ var links=[];try{links=JSON.parse(c.links||'[]');}catch(ex){} h+='
    ' +''+esc(c.title)+'' +''+esc(c.difficulty||'medium')+'' +''+esc(c.card_type||'practice')+'' +(c.sprint_name?''+esc(c.sprint_name)+'':'') +(links.length?'📋 '+links.length+'':'') +'
    '; }); } h+='
    '; // Students h+='
    👥 Ученики'+_students.length+' всего
    '; if(!_students.length){ h+='
    Учеников пока нет
    '; } else { h+='
    УченикВсегоВ работеDoneАпрув
    '; _students.forEach(function(u){ h+='
    ' +''+esc(u.display_name)+'' +''+(u.total_cards||0)+'' +''+(u.inprogress_count||0)+'' +''+(u.done_count||0)+'' +''+(u.approved_count||0)+'' +'
    '; }); } h+='
    '; el.innerHTML=h; var ts=document.getElementById('docs-ts'); if(ts)ts.textContent='Обновлено: '+new Date().toLocaleTimeString('ru',{hour:'2-digit',minute:'2-digit',second:'2-digit'}); } function docsDownloadMd(){ var md='# Учебная лаборатория — Документация\n\n> Сгенерировано: '+new Date().toLocaleString('ru')+'\n\n'; md+='## Спринты\n\n'; _sprints.forEach(function(s){ var cnt=_cards.filter(function(c){return c.sprint_id===s.id;}).length; md+='### '+s.name+' '+(s.is_locked?'🔒':'🟢')+'\n\n'; if(s.description)md+=s.description+'\n\n'; md+='**Карточек:** '+cnt+(s.start_date?' | **Период:** '+s.start_date+' — '+(s.end_date||'?'):'')+'\n\n'; var spCards=_cards.filter(function(c){return c.sprint_id===s.id;}); if(spCards.length){ md+='| Карточка | Сложность | Тип |\n|---|---|---|\n'; spCards.forEach(function(c){md+='| '+c.title+' | '+(c.difficulty||'medium')+' | '+(c.card_type||'practice')+' |\n';}); md+='\n'; } }); md+='## Все карточки\n\n'; _cards.forEach(function(c){ md+='### '+c.title+'\n- **Сложность:** '+(c.difficulty||'medium')+'\n- **Тип:** '+(c.card_type||'practice')+'\n'; if(c.sprint_name)md+='- **Спринт:** '+c.sprint_name+'\n'; if(c.description)md+='\n'+c.description+'\n'; var links=[];try{links=JSON.parse(c.links||'[]');}catch(ex){} if(links.length){md+='\n**Чеклист:**\n';links.forEach(function(l){md+='- '+(l.text||l.url||'')+(l.url&&l.url!==l.text?' ('+l.url+')':'')+'\n';});} md+='\n'; }); md+='## Статистика учеников\n\n'; if(_students.length){ md+='| Ученик | Всего | В работе | Done | Апрув |\n|---|---|---|---|---|\n'; _students.forEach(function(u){md+='| '+u.display_name+' | '+(u.total_cards||0)+' | '+(u.inprogress_count||0)+' | '+(u.done_count||0)+' | '+(u.approved_count||0)+' |\n';}); } var blob=new Blob([md],{type:'text/markdown;charset=utf-8'}); var url=URL.createObjectURL(blob); var a=document.createElement('a');a.href=url;a.download='qa-lab-docs-'+new Date().toISOString().slice(0,10)+'.md'; document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url); } function docsDownloadJson(){ var snapshot={generated_at:new Date().toISOString(),sprints:_sprints,cards:_cards,students:_students}; var blob=new Blob([JSON.stringify(snapshot,null,2)],{type:'application/json'}); var url=URL.createObjectURL(blob); var a=document.createElement('a');a.href=url;a.download='qa-lab-snapshot-'+new Date().toISOString().slice(0,10)+'.json'; document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url); } document.addEventListener('keydown', function(e){ if(e.key==='Escape'){ document.getElementById('mat-modal').classList.remove('open'); document.getElementById('modal').classList.remove('open'); var openKcard=document.querySelector('.kcard.kcd-open'); if(openKcard){ openKcard.classList.remove('kcd-open'); setTimeout(function(){var d=openKcard.querySelector('.kcard-detail');if(d)d.remove();},230); } _activeCard=null; } }); }());
    М
    Наставник
    ожидает вопрос
    Пока вопросов нет. Напиши первый! 👇