const state={token:'',password:null,showNames:true,cdTarget:0,cdTimer:null,cdBound:false,systemName:'WhatIDo'}; function loadBrand(){ return fetch('/api/public-settings').then(function(res){ return res.ok?res.json():null; }).then(function(s){ if(!s)return; if((s.system_name||'').trim())state.systemName=s.system_name; var logo=document.getElementById('brandLogo'); if(logo&&s.system_logo){ logo.src=s.system_logo; logo.hidden=false; var mark=document.getElementById('brandMark'); if(mark)mark.style.display='none'; } var name=document.getElementById('brandName'); if(name&&(s.system_name||'').trim()){ name.textContent=s.system_name; name.hidden=false; } }).catch(function(){}); } function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''')} function pad(n){return String(n).padStart(2,'0')} function toDate(v){if(!v)return null;const d=new Date(v);return isNaN(d.getTime())?null:d} function dtIsDateOnly(v){return typeof v==='string'&&/^\d{4}-\d{2}-\d{2}/.test(v)} function fmtDate(v){return dtIsDateOnly(v)?fmtDateOnlyIso(v):(v?fmtDateFull(v):'')} function fmtShort(v){return dtIsDateOnly(v)?fmtShortIso(v):(v?fmtDateShort(v):'')} function fmtLong(v){return dtIsDateOnly(v)?fmtDateOnlyLongIso(v):(v?fmtDateLong(v):'')} function plural(n,one,few,many){const m10=n%10,m100=n%100;if(m100>=11&&m100<=14)return many;if(m10===1)return one;if(m10>=2&&m10<=4)return few;return many} function words(n,one,few,many){return n+' '+plural(n,one,few,many)} function isImageFile(name){return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')} function extLabel(name){const m=String(name||'').match(/\.([a-z0-9]{1,5})$/i);return m?m[1].toUpperCase():'FILE'} function baseName(name){return String(name||'файл').replace(/^.*[\\/]/,'').slice(0,80)} function thumbSrc(p){if(!p)return '';if(p.indexOf('/uploads/')===0)return '/uploads/thumb/'+p.slice(9);if(p.indexOf('/api/files/')>=0)return p+(p.indexOf('?')>=0?'&':'?')+'thumb=1';return p} function fileUrl(token){return '/api/share/'+encodeURIComponent(state.token)+'/files/'+encodeURIComponent(token)+(state.password?'?password='+encodeURIComponent(state.password):'')} function initials(name){const parts=String(name||'').trim().split(/\s+/).filter(Boolean);if(!parts.length)return 'WI';const a=parts[0].charAt(0);const b=parts.length>1?parts[1].charAt(0):(parts[0].length>1?parts[0].charAt(1):'');return (a+b).toUpperCase()} function setText(id,text){const el=document.getElementById(id);if(el)el.textContent=text==null?'':String(text)} function setHTML(id,html){const el=document.getElementById(id);if(el)el.innerHTML=html} function hideEl(el,on){if(el)el.classList.toggle('rep-hidden',!!on)} function node(html){const t=document.createElement('template');t.innerHTML=html.trim();return t.content.firstElementChild} function periodLabel(from,to){ if(!from&&!to)return ''; if(from&&to)return fmtDate(from)+' — '+fmtDate(to); return fmtDate(from)||fmtDate(to); } function dtIsoYear(v){ if(!v)return null; if(dtIsDateOnly(v))return String(v).slice(0,4); const d=toDate(v); if(!d)return null; try{return new Intl.DateTimeFormat('en-CA',{timeZone:DateTime.tz,year:'numeric'}).format(d).slice(0,4)}catch{return String(d.getFullYear())} } function yearLabel(from,to){ if(!from&&!to)return ''; const ay=dtIsoYear(from),by=dtIsoYear(to); if(ay&&by)return ay===by?String(ay):ay+' — '+String(by).slice(2); return String(ay||by); } function countsLabel(photos,files){ const out=[]; if(photos)out.push(words(photos,'фото','фото','фото')); if(files)out.push(words(files,'файл','файла','файлов')); return out.join(' · '); } async function fetchShare(token,password){ const headers={}; if(password)headers['x-share-password']=password; const res=await fetch('/api/share/'+encodeURIComponent(token),{headers}); const data=await res.json().catch(function(){return {}}); return {ok:res.ok,status:res.status,data:data}; } function showError(msg){ stopCountdown(); setText('errorText',msg); const box=document.getElementById('errorBox'); if(box)box.style.display='flex'; const content=document.getElementById('content'); if(content)content.style.display='none'; } function stopCountdown(){ if(state.cdTimer){clearInterval(state.cdTimer);state.cdTimer=null} state.cdTarget=0; const el=document.getElementById('countdown'); if(el)el.style.display='none'; } function formatRemaining(ms){ const total=Math.floor(ms/1000); const days=Math.floor(total/86400); const hours=Math.floor((total%86400)/3600); const minutes=Math.floor((total%3600)/60); const seconds=total%60; const parts=[]; if(days>0)parts.push(words(days,'день','дня','дней')); if(hours>0)parts.push(words(hours,'час','часа','часов')); if(minutes>0)parts.push(words(minutes,'минуту','минуты','минут')); if(days===0)parts.push(words(seconds,'секунду','секунды','секунд')); return parts.slice(0,2).join(' '); } function syncCountdown(){ const el=document.getElementById('countdown'); if(!el||!state.cdTarget)return; if(state.cdTarget-Date.now()<=0){el.style.display='none';return} const hero=document.getElementById('hero'); const passed=hero?window.scrollY>hero.offsetHeight*0.5:true; el.style.display=passed?'inline-flex':'none'; } function tickCountdown(){ const el=document.getElementById('countdown'); if(!el||!state.cdTarget)return; const left=state.cdTarget-Date.now(); if(left<=0){stopCountdown();return} el.classList.toggle('rep-urgent',left<3600000); el.classList.toggle('rep-warn',left>=3600000&&left<86400000); const txt=el.querySelector('.rep-cd-text'); if(txt)txt.innerHTML='Отчёт доступен ещё '+esc(formatRemaining(left))+''; syncCountdown(); } function startCountdown(expiresAt){ stopCountdown(); const t=toDate(expiresAt); if(!t)return; state.cdTarget=t.getTime(); if(state.cdTarget-Date.now()<=0){state.cdTarget=0;return} if(!state.cdBound){state.cdBound=true;window.addEventListener('scroll',syncCountdown,{passive:true})} tickCountdown(); state.cdTimer=setInterval(tickCountdown,1000); } function initSlider(slider,update){ if(!slider)return; const track=slider.querySelector('.blocklist__list'); if(!track)return; const prev=slider.querySelector('[data-slider-prev]'); const next=slider.querySelector('[data-slider-next]'); const step=function(){ const first=track.firstElementChild; const w=first?first.getBoundingClientRect().width:track.clientWidth; const visible=Math.max(1,Math.round(track.clientWidth/Math.max(1,w))); return {width:Math.max(1,w),visible:visible}; }; const sync=function(){ const s=step(); const max=track.scrollWidth-track.clientWidth; const one=track.scrollWidth<=track.clientWidth+4; hideEl(prev,one||track.scrollLeft<=4); hideEl(next,one||track.scrollLeft>=max-4); }; const move=function(dir){ const s=step(); track.scrollBy({left:dir*s.width*s.visible,behavior:'smooth'}); }; if(prev)prev.addEventListener('click',function(){move(-1)}); if(next)next.addEventListener('click',function(){move(1)}); track.addEventListener('scroll',sync,{passive:true}); window.addEventListener('resize',sync); sync(); if(update)update(sync); } function stepSlider(slider,dir){ const track=slider.querySelector('.blocklist__list'); if(!track)return; const first=track.firstElementChild; const w=first?first.getBoundingClientRect().width:track.clientWidth; const visible=Math.max(1,Math.round(track.clientWidth/Math.max(1,w))); track.scrollBy({left:dir*w*visible,behavior:'smooth'}); } function scrollSliderTo(slider,index){ const track=slider.querySelector('.blocklist__list'); if(!track)return; const child=track.children[index]; if(child)track.scrollTo({left:child.offsetLeft-track.offsetLeft,behavior:'smooth'}); } function renderSlider(slider,items){ const track=slider.querySelector('.blocklist__list'); if(!track)return; track.innerHTML=items.join(''); window.requestAnimationFrame(function(){initSlider(slider)}); } function setNavVisible(name,on){ document.querySelectorAll('[data-nav="'+name+'"]').forEach(function(el){hideEl(el,!on)}); } function makeNavLinks(){ document.querySelectorAll('.rep-nav .menu__link-lvl1').forEach(function(a){ a.addEventListener('click',function(){closeMenu()}); }); } function openMenu(){ const panel=document.getElementById('navPanel'); const overlay=document.getElementById('navOverlay'); const close=document.getElementById('navClose'); if(panel)panel.classList.add('is-open'); if(overlay)overlay.classList.add('is-open'); if(close)close.classList.add('is-open'); document.documentElement.style.overflow='hidden'; } function closeMenu(){ const panel=document.getElementById('navPanel'); const overlay=document.getElementById('navOverlay'); const close=document.getElementById('navClose'); if(panel)panel.classList.remove('is-open'); if(overlay)overlay.classList.remove('is-open'); if(close)close.classList.remove('is-open'); document.documentElement.style.overflow=''; } function openModal(){const m=document.getElementById('passwordModal');if(m)m.classList.add('is-open');modalError('');const i=document.getElementById('passwordInput');if(i){i.value='';i.focus()}} function closeModal(){const m=document.getElementById('passwordModal');if(m)m.classList.remove('is-open')} function modalError(msg){const e=document.getElementById('passwordError');if(!e)return;e.textContent=msg||'';e.style.display=msg?'block':'none'} function openLightbox(src){const lb=document.getElementById('lightbox');const img=document.getElementById('lightboxImg');if(!lb||!img)return;img.src=src;lb.classList.add('is-open')} function photoPool(data){ const pool=[]; (data.entries||[]).forEach(function(e){if(e.photo_path)pool.push(e.photo_path)}); (data.photos||[]).forEach(function(p){if(p.photo_path)pool.push(p.photo_path)}); return pool; } function fileChips(files){ if(!files||!files.length)return ''; const items=files.map(function(f){ const url=fileUrl(f.token); const label=baseName(f.name); if(isImageFile(f.name))return ''+esc(label)+''; return ''+esc(label)+''; }).join(''); return '
'+items+'
'; } function hideSection(id){ const el=document.getElementById(id); if(el)el.style.display='none'; } function renderHero(data,heading,pool){ const entries=data.entries||[]; setText('heroName',heading); setText('heroRole',data.group_name?'Ученик группы «'+data.group_name+'»':'Отчёт о проделанной работе'); setText('heroPeriod',yearLabel(data.date_from,data.date_to)||yearLabel(data.created_at)); const lead=(data.message&&String(data.message).trim())||('Здесь собраны все занятия за выбранный период: '+words(entries.length,'запись','записи','записей')+'.'); setText('heroLead',lead); const box=document.getElementById('heroPhotoBox'); const img=document.getElementById('heroPhoto'); if(box&&img&&pool.length){ img.src=thumbSrc(pool[0]); img.alt=heading; box.setAttribute('data-img',pool[0]); }else if(box){ if(img)img.remove(); const mono=document.createElement('span'); mono.className='rep-monogram'; mono.textContent=initials(heading); box.appendChild(mono); box.classList.add('rep-hero-photo'); } } function statItem(num,cap,variant){ const sets=[ {item:'list__item list__item--u-ix00fc47p',num:'text text--u-iif5jnrfz text-number-1-script',cap:'text t2 text--u-ih2l1ywk8'}, {item:'list__item list__item--u-ic8gt20s1',num:'text text--u-ipk87vesx text-number-1-script',cap:'text t2 text--u-i53xasxdu'}, {item:'list__item list__item--u-ici0pcuqz',num:'text text--u-ir07nprxn text-number-1-script',cap:'text t2 text--u-ise2s2948'}, {item:'list__item list__item--u-ici0pcuqz list__item--u-irn5njhv8',num:'text text--u-ir07nprxn text-number-1-script',cap:'text t2 text--u-ise2s2948'} ]; const s=sets[variant]||sets[0]; return '
'+ '
'+esc(num)+'
'+ '
'+esc(cap)+'
'+ '
'; } function statSep(variant){ const extra=variant===1?' list__item--u-ip5wdmggw':(variant===2?' list__item--u-ixebr6v8q':''); return '
'; } function dtYearMonth(d){ try{return new Intl.DateTimeFormat('en-CA',{timeZone:DateTime.tz,year:'numeric',month:'2-digit'}).format(d).slice(0,7)}catch{return d.getFullYear()+'-'+d.getMonth()} } function renderAbout(data,pool){ const entries=data.entries||[]; const photos=(data.photos||[]).length; const files=entries.reduce(function(n,e){return n+((e.files||[]).length)},0); const months={}; entries.forEach(function(e){const d=toDate(e.created_at);if(d)months[dtYearMonth(d)]=1}); const photosTotal=entries.filter(function(e){return e.photo_path}).length+photos; setText('aboutTitle',(data.name&&String(data.name).trim())||'Отчёт'); setText('aboutLead',(data.message&&String(data.message).trim())||('Мы собрали '+words(entries.length,'запись','записи','записей')+' о работе за этот период — с фотографиями, файлами и описанием каждого занятия.')); const parts=[]; if(periodLabel(data.date_from,data.date_to))parts.push('Период: '+periodLabel(data.date_from,data.date_to)); if(data.group_name)parts.push('Группа «'+data.group_name+'»'); setText('aboutNote',parts.join(' · ')); setHTML('statsList', statItem(entries.length,'записей о проделанной работе',0)+statSep(0)+ statItem(photosTotal,'фото с занятий',1)+statSep(1)+ statItem(files,'проектных файлов',2)+statSep(2)+ statItem(Object.keys(months).length,'месяцев активных занятий',3) ); const box=document.getElementById('aboutPhotoBox'); const img=document.getElementById('aboutPhoto'); const photo=pool[1]||pool[0]; if(box&&img&&photo){img.src=thumbSrc(photo);img.alt='';box.setAttribute('data-img',photo);} else if(box){ if(img)img.remove(); const mono=document.createElement('span'); mono.className='rep-about-monogram'; mono.textContent=initials((data.name&&String(data.name).trim())||data.student_name||'WhatIDo'); box.appendChild(mono); } } function renderTimeline(data){ const entries=data.entries||[]; const list=document.getElementById('timelineList'); if(!list)return; if(!entries.length){hideSection('timeline');setNavVisible('timeline',false);return} const showDate=data.show_entry_date!==false; const showDesc=!!data.show_student_message; list.innerHTML=entries.map(function(e,index){ const files=e.files||[]; const stamp=showDate?fmtShort(e.created_at):pad(index+1); const aside=[]; if(data.group_name)aside.push(data.group_name); const counts=countsLabel(e.photo_path?1:0,files.length); if(counts)aside.push(counts); return '
'+ '
'+esc(stamp)+'
'+ '
'+ '
'+ (e.photo_path?'
':'')+ '
'+ '
'+ '
'+ ''+esc(e.student_name||'Занятие')+''+ (showDesc&&e.description?'
'+esc(e.description)+'
':'')+ fileChips(files)+ '
'+ '
'+ aside.map(function(a){return '
'+esc(a)+'
'}).join('')+ '
'+ '
'; }).join(''); } function workTitle(e){ const d=String(e.description||'').replace(/\s+/g,' ').trim(); if(d)return d.length>88?d.slice(0,88).trim()+'…':d; return e.student_name||'Работа'; } function renderWorks(data){ const slider=document.querySelector('.rep-slider[data-slider="works"]'); if(!slider)return; const entries=(data.entries||[]).filter(function(e){return e.photo_path}); if(!entries.length){hideSection('works');setNavVisible('works',false);return} const items=entries.slice(0,12).map(function(e,index){ return '
'+ '
'+ '
'+ '
'+ '
'+ '
'+esc(pad(index+1))+'
'+ '
'+ '
'+ '
'+esc(workTitle(e))+'
'+ '
'+esc(data.show_entry_date!==false?fmtDate(e.created_at):(e.student_name||''))+'
'+ '
'+ (data.show_student_message&&e.description&&workTitle(e)!==String(e.description).trim()?'
'+esc(e.description)+'
':'')+ fileChips(e.files)+ '
'+ '
'+ '
'+ '
'+ '
'; }); renderSlider(slider,items); } function renderFiles(data){ const list=document.getElementById('filesList'); if(!list)return; const rows=[]; (data.entries||[]).forEach(function(e){ (e.files||[]).forEach(function(f){rows.push({file:f,entry:e})}); }); if(!rows.length){hideSection('files');setNavVisible('files',false);return} list.innerHTML=rows.map(function(row){ const f=row.file; const url=fileUrl(f.token); const label=baseName(f.name); const isImg=isImageFile(f.name); const meta=[]; if(data.show_entry_date!==false)meta.push(fmtDate(row.entry.created_at)); if(row.entry.student_name)meta.push(row.entry.student_name); if(data.group_name)meta.push(data.group_name); const icon=isImg ?'
' :'
'+esc(extLabel(f.name))+'
'; const head=isImg ?'' :''; return head+ '
'+esc(fmtDate(row.entry.created_at))+'
'+ '
'+ '
'+ '
'+ icon+ '
'+ '
'+ '
'+ '
'+esc(label)+'
'+ '
'+ ''+esc(meta.join(' · '))+''+ ''+(isImg?'Посмотреть':'Скачать')+''+ '
'+ '
'; }).join(''); } function renderGallery(data){ const slider=document.querySelector('.rep-slider[data-slider="gallery"]'); if(!slider)return; const photos=data.photos||[]; if(!photos.length){hideSection('gallery');setNavVisible('gallery',false);return} const items=photos.slice(0,12).map(function(p){ const caption=String(p.caption||'').trim(); return '
'+ '
'+ '
'+ '
'+ '
'+ ''+ '
'+ '
'+ '
'+ '
'+ ''+ ''+ ''+ '
'+ '
'+ '
'+ '
'+ '
'; }); renderSlider(slider,items); } function renderFooter(data,heading,pool){ setText('brandMark',initials(heading)); setText('brandMarkFooter',initials(heading)); setText('footGroup',data.group_name?'Группа «'+data.group_name+'»':'Группа не указана'); setText('footPeriod',periodLabel(data.date_from,data.date_to)?'Период: '+periodLabel(data.date_from,data.date_to):'Период: весь отчёт'); setText('footCreated','Обновлён: '+fmtDate(data.created_at)); const expires=toDate(data.expires_at); setText('footExpires',expires?'Доступно до '+fmtDate(data.expires_at):'Срок не ограничен'); const link=document.getElementById('footLink'); if(link&&data.link_url&&/^https?:\/\//i.test(data.link_url)){link.href=data.link_url;link.style.display='inline-flex'} else if(link){link.style.display='none'} const note=document.getElementById('contactNote'); if(note)note.innerHTML='Если появятся вопросы по отчёту — напишите преподавателю — мы ответим в течение 24 часов.'; setText('contactLead',(data.name&&String(data.name).trim())||'Отчёт о работе'); const sub=[]; if(periodLabel(data.date_from,data.date_to))sub.push('Период: '+periodLabel(data.date_from,data.date_to)); sub.push('Обновлён: '+fmtDate(data.created_at)); if(expires)sub.push('Доступен до: '+fmtDate(data.expires_at)); setText('contactSub',sub.join('\n')); const btn=document.getElementById('contactBtn'); if(btn&&data.link_url&&/^https?:\/\//i.test(data.link_url)){ btn.href=data.link_url; btn.target='_blank'; btn.rel='noopener noreferrer'; const label=btn.querySelector('.text-block-wrap-div'); if(label)label.textContent='Перейти по ссылке'; } setText('footCopy',state.systemName+' © '+nowYear()); setText('footBrand',heading); const fbox=document.getElementById('footerPhotoBox'); const fimg=document.getElementById('footerPhoto'); const photo=pool[2]||pool[0]; if(fbox&&fimg&&photo){fimg.src=thumbSrc(photo);fimg.alt='';fbox.setAttribute('data-img',photo)} else if(fbox){hideEl(fbox,true)} } function renderReport(data){ const heading=(data.name&&String(data.name).trim())||data.student_name||data.group_name||'Отчёт'; document.title=heading; const pool=photoPool(data); renderHero(data,heading,pool); renderAbout(data,pool); renderTimeline(data); renderWorks(data); renderFiles(data); renderGallery(data); renderFooter(data,heading,pool); startCountdown(data.expires_at); const content=document.getElementById('content'); if(content)content.style.display='block'; } async function loadReport(){ const token=location.pathname.split('/').filter(Boolean).pop()||''; state.token=token; state.password=null; const result=await fetchShare(token,null); if(result.ok){ state.showNames=result.data.show_student_names!==false; renderReport(result.data); return; } if(result.status===401&&result.data.passwordRequired){ openModal(); return; } if(result.status===410)showError('Срок действия ссылки истёк.'); else showError('Ссылка не найдена или была удалена.'); setText('brandMark',initials('WhatIDo')); setText('brandMarkFooter',initials('WhatIDo')); } function bindEvents(){ const burger=document.getElementById('navBurger'); const close=document.getElementById('navClose'); const overlay=document.getElementById('navOverlay'); if(burger)burger.addEventListener('click',openMenu); if(close)close.addEventListener('click',closeMenu); if(overlay)overlay.addEventListener('click',closeMenu); document.querySelectorAll('.rep-nav .menu__link-lvl1, .rep-nav .link--u-i2ifdcrc4').forEach(function(a){ a.addEventListener('click',function(){closeMenu()}); }); document.addEventListener('click',function(ev){ const prev=ev.target.closest('[data-slider-prev]'); const next=ev.target.closest('[data-slider-next]'); if(prev||next){ const slider=(prev||next).closest('.rep-slider'); if(slider){ev.preventDefault();stepSlider(slider,prev?-1:1)} return; } const imgEl=ev.target.closest('[data-img]'); if(imgEl){ev.preventDefault();openLightbox(imgEl.getAttribute('data-img'));return} const lb=document.getElementById('lightbox'); if(lb&&lb.classList.contains('is-open'))closeLightbox(); }); const submit=document.getElementById('passwordSubmitBtn'); const input=document.getElementById('passwordInput'); if(submit)submit.addEventListener('click',async function(){ const value=input?input.value.trim():''; if(!value)return; submit.disabled=true; const result=await fetchShare(state.token,value); submit.disabled=false; if(result.ok){ state.password=value; closeModal(); state.showNames=result.data.show_student_names!==false; renderReport(result.data); }else{ modalError(result.status===410?'Срок действия ссылки истёк':'Неверный пароль'); if(input){input.value='';input.focus()} } }); if(input)input.addEventListener('keydown',function(ev){ if(ev.key==='Enter'&&submit)submit.click(); }); const modal=document.getElementById('passwordModal'); if(modal)modal.addEventListener('click',function(ev){if(ev.target===modal)closeModal()}); document.addEventListener('keydown',function(ev){ if(ev.key!=='Escape')return; closeLightbox(); closeMenu(); }); window.addEventListener('resize',function(){ if(window.innerWidth>991)closeMenu(); }); } bindEvents(); makeNavLinks(); initDateTime().then(function(){return loadBrand()}).then(function(){loadReport()}); function closeLightbox(){const lb=document.getElementById('lightbox');if(lb)lb.classList.remove('is-open')}