// ═══════════════════════════════════════════ // STEELPULSE PREMIUM CALENDAR v3 // /static/calendar.js // ═══════════════════════════════════════════ var _calInited=false, _calView='month', _calDate=new Date(), _calEvs=[]; var _cemKat='Gym', _cemCol='#E63946'; var _calTz=Intl.DateTimeFormat().resolvedOptions().timeZone; var _calNowTimer=null; var _MJN=['Siječanj','Veljača','Ožujak','Travanj','Svibanj','Lipanj', 'Srpanj','Kolovoz','Rujan','Listopad','Studeni','Prosinac']; var _ROWN=['Pon','Uto','Sri','Čet','Pet','Sub','Ned']; var _KAT_BOJE={ 'Gym':'#E63946','Posao':'#3B82F6','Zdravlje':'#22C55E', 'Prehrana':'#F59E0B','Odmor':'#A855F7','Osobno':'#06B6D4','Trening':'#E63946' }; function calInit(){ if(_calInited)return; _calInited=true; _calDate=new Date(); calDetectTz(); calFillYears(); calLoad(); if(window.innerWidth<=640) setTimeout(function(){calMobView('agenda');},200); } function calDetectTz(){ _calTz=Intl.DateTimeFormat().resolvedOptions().timeZone; var off=-new Date().getTimezoneOffset(); var h=Math.floor(Math.abs(off)/60), m=Math.abs(off)%60; var s=off>=0?'+':'-'; var lbl=_calTz.split('/').pop().replace('_',' ')+' UTC'+s+String(h).padStart(2,'0')+':'+String(m).padStart(2,'0'); var el=document.getElementById('calTzLbl'); if(el)el.textContent='⏱ '+lbl; } function calFillYears(){ var sel=document.getElementById('calYearSel'); if(!sel||sel.options.length>10)return; for(var y=2025;y<=5000;y++){ var o=document.createElement('option'); o.value=y; o.text=y; if(y===new Date().getFullYear())o.selected=true; sel.appendChild(o); } } function calSetYear(y){ _calDate.setFullYear(parseInt(y)); calLoad(); } async function calLoad(){ calDetectTz(); var y=_calDate.getFullYear(), m=_calDate.getMonth(); var od=new Date(y,m-1,1).toISOString().slice(0,10)+'T00:00:00'; var do_=new Date(y,m+2,0).toISOString().slice(0,10)+'T23:59:59'; try{ var r=await fetchAuth('/api/kalendar?od='+od+'&do='+do_); if(r&&r.ok){ var d=await r.json(); _calEvs=Array.isArray(d)?d:[]; } else _calEvs=[]; }catch(e){_calEvs=[];} var ys=document.getElementById('calYearSel'); if(ys)ys.value=_calDate.getFullYear(); calRender(); calRenderMini(); calRenderUpcoming(); } function calNav(dir){ if(_calView==='month') _calDate.setMonth(_calDate.getMonth()+dir); else _calDate.setDate(_calDate.getDate()+dir*7); calLoad(); } function calGoToday(){ _calDate=new Date(); calLoad(); } function calView(v){ _calView=v; document.getElementById('cvbM').className='cal-vbtn'+(v==='month'?' on':''); document.getElementById('cvbW').className='cal-vbtn'+(v==='week'?' on':''); var g=document.getElementById('calGrid'); if(g)g.style.display=v==='month'?'flex':'none'; var w=document.getElementById('calWeekGrid'); if(w)w.style.display=v==='week'?'flex':'none'; var dr=document.getElementById('calDowRow'); if(dr)dr.style.display=v==='month'?'':'none'; if(v==='month') calRenderMonth(); else if(v==='week') calRenderWeek(); } function calMobView(v){ ['calMobM','calMobW','calMobA'].forEach(function(id,i){ var el=document.getElementById(id); if(!el)return; var active=(v==='month'&&i===0)||(v==='week'&&i===1)||(v==='agenda'&&i===2); el.className='cal-mob-vbtn'+(active?' on':''); }); if(v==='agenda'){ _calView='agenda'; _showAgendaLayout(); calRenderAgenda(); } else calView(v); } function _showAgendaLayout(){ var g=document.getElementById('calGrid'); if(g)g.style.display='flex'; var w=document.getElementById('calWeekGrid'); if(w)w.style.display='none'; var dr=document.getElementById('calDowRow'); if(dr)dr.style.display='none'; } function calRender(){ if(_calView==='month') calRenderMonth(); else if(_calView==='week') calRenderWeek(); else calRenderAgenda(); } function _ds(d){ return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); } // ── MONTH ────────────────────────────────── function calRenderMonth(){ var y=_calDate.getFullYear(), m=_calDate.getMonth(); document.getElementById('calMonthLbl').textContent=_MJN[m].toUpperCase()+' '+y; var today=new Date(); var fd=new Date(y,m,1).getDay(); fd=fd===0?6:fd-1; var ld=new Date(y,m+1,0).getDate(); var pl=new Date(y,m,0).getDate(); var total=Math.ceil((fd+ld)/7)*7; var cells=document.getElementById('calCells'); cells.innerHTML=''; cells.style.gridTemplateRows='repeat('+Math.ceil(total/7)+',1fr)'; for(var i=0;i=fd+ld){day=i-fd-ld+1;inM=false;} else day=i-fd+1; var cm=inM?m:(i=3)return; var ev=document.createElement('div'); ev.className='cal-ev'; ev.style.background=(e.boja||_KAT_BOJE[e.kategorija]||'#E63946'); ev.title=e.naslov+(e.pocetak?' '+e.pocetak.slice(11,16):''); ev.textContent=e.naslov; ev.onclick=(function(ed){return function(evt){evt.stopPropagation();calOpenEdit(ed);};})(e); cell.appendChild(ev); shown++; }); if(dayEvs.length>3){ var more=document.createElement('div'); more.className='cal-more'; more.textContent='+'+(dayEvs.length-3)+' više'; cell.appendChild(more); } cells.appendChild(cell); } } // ── WEEK ────────────────────────────────── function calRenderWeek(){ var today=new Date(), d=new Date(_calDate); var dow=d.getDay(); dow=dow===0?6:dow-1; d.setDate(d.getDate()-dow); var days=[]; for(var i=0;i<7;i++){var dd=new Date(d);dd.setDate(d.getDate()+i);days.push(dd);} var s=days[0].toLocaleDateString('hr-HR',{day:'2-digit',month:'short'}); var e=days[6].toLocaleDateString('hr-HR',{day:'2-digit',month:'short'}); document.getElementById('calMonthLbl').textContent=s+' – '+e+' '+days[0].getFullYear(); var head=document.getElementById('calWeekHead'); head.style.gridTemplateColumns='48px repeat(7,1fr)'; head.innerHTML='
'; days.forEach(function(dd,i){ var isT=dd.toDateString()===today.toDateString(); var div=document.createElement('div'); div.className='cal-whd'+(isT?' cal-wtoday':''); div.innerHTML='
'+_ROWN[i]+'
' +'
'+dd.getDate()+'
'; head.appendChild(div); }); var H=56; // px per hour var cols=document.getElementById('calWeekCols'); cols.style.gridTemplateColumns='48px repeat(7,1fr)'; cols.style.minHeight=(24*H)+'px'; cols.innerHTML=''; // Time column var tc=document.createElement('div'); tc.className='cal-time-col'; for(var h=0;h<24;h++){ var ts=document.createElement('div'); ts.className='cal-ts'; ts.textContent=String(h).padStart(2,'0')+':00'; ts.style.height=H+'px'; tc.appendChild(ts); } cols.appendChild(tc); // Day columns days.forEach(function(dd){ var ds=_ds(dd); var col=document.createElement('div'); col.className='cal-wday'; col.style.height=(24*H)+'px'; for(var h=0;h<24;h++){ var ln=document.createElement('div'); ln.className='cal-wline'; ln.style.height=H+'px'; col.appendChild(ln); } _calEvs.filter(function(ev){return ev&&ev.pocetak&&ev.pocetak.slice(0,10)===ds;}).forEach(function(ev){ var sh=parseInt(ev.pocetak.slice(11,13)),sm=parseInt(ev.pocetak.slice(14,16)); var eh=ev.kraj?parseInt(ev.kraj.slice(11,13)):sh+1; var em=ev.kraj?parseInt(ev.kraj.slice(14,16)):sm; var top=sh*H+(sm/60*H), height=Math.max(20,(eh-sh)*H+((em-sm)/60*H)); var blk=document.createElement('div'); blk.className='cal-evblock'; blk.style.cssText='top:'+top+'px;height:'+height+'px;background:'+(ev.boja||_KAT_BOJE[ev.kategorija]||'#E63946'); blk.innerHTML='
'+ev.naslov+'
' +(ev.pocetak?'
'+ev.pocetak.slice(11,16)+(ev.kraj?' – '+ev.kraj.slice(11,16):'')+'
':''); blk.onclick=(function(ed){return function(){calOpenEdit(ed);};})(ev); col.appendChild(blk); }); col.onclick=(function(s){return function(e){if(e.target===col||e.target.classList.contains('cal-wline'))calNewEvent(s);};})(ds); cols.appendChild(col); }); // Current time line calDrawNowLine(days, H); if(_calNowTimer) clearInterval(_calNowTimer); _calNowTimer=setInterval(function(){ calDrawNowLine(days, H); }, 60000); } function calDrawNowLine(days, H){ // Remove existing lines document.querySelectorAll('.cal-now-line').forEach(function(el){el.remove();}); var now=new Date(); var nowStr=_ds(now); // Find which column var cols=document.getElementById('calWeekCols'); if(!cols)return; var dayCols=cols.querySelectorAll('.cal-wday'); days.forEach(function(dd,i){ if(_ds(dd)!==nowStr)return; var col=dayCols[i]; if(!col)return; var top=(now.getHours()*H)+(now.getMinutes()/60*H); var line=document.createElement('div'); line.className='cal-now-line'; line.style.top=top+'px'; col.appendChild(line); // Scroll to current time var scroll=document.getElementById('calWeekScroll'); if(scroll) scroll.scrollTop=Math.max(0,top-100); }); } // ── MINI ────────────────────────────────── function calRenderMini(){ var y=_calDate.getFullYear(), m=_calDate.getMonth(); var el=document.getElementById('calMiniTitle'); if(el)el.textContent=_MJN[m].slice(0,3)+' '+y; var fd=new Date(y,m,1).getDay(); fd=fd===0?6:fd-1; var ld=new Date(y,m+1,0).getDate(), total=Math.ceil((fd+ld)/7)*7; var cont=document.getElementById('calMiniCells'); if(!cont)return; cont.innerHTML=''; var today=new Date(); for(var i=0;ild){cont.appendChild(document.createElement('div'));continue;} var cd=new Date(y,m,day), cs=_ds(cd); var isT=cd.toDateString()===today.toDateString(); var hasEv=_calEvs.some(function(e){return e&&e.pocetak&&e.pocetak.slice(0,10)===cs;}); var mc=document.createElement('div'); mc.className='cal-mc'+(isT?' mc-today':'')+(hasEv?' mc-has':''); mc.textContent=day; mc.onclick=(function(s){return function(){_calDate=new Date(s);calLoad();};})(cs); cont.appendChild(mc); } } // ── UPCOMING ────────────────────────────── function calRenderUpcoming(){ var el=document.getElementById('calUpcoming'); if(!el)return; var now=new Date(); var ups=_calEvs.filter(function(e){return e&&e.pocetak&&new Date(e.pocetak)>=now;}) .sort(function(a,b){return new Date(a.pocetak)-new Date(b.pocetak);}).slice(0,10); if(!ups.length){el.innerHTML='
Nema nadolazecih eventa
';return;} el.innerHTML=''; ups.forEach(function(e){ var d=new Date(e.pocetak); var isT=d.toDateString()===now.toDateString(); var isM=d.toDateString()===new Date(now.getTime()+86400000).toDateString(); var lbl=isT?'Danas':(isM?'Sutra':d.toLocaleDateString('hr-HR',{day:'2-digit',month:'short'})); var time=e.cijeli_dan?'':' '+e.pocetak.slice(11,16); var row=document.createElement('div'); row.className='cal-uev'; row.innerHTML='
' +'
' +'
'+e.naslov+'
' +'
'+lbl+time+(e.lokacija?' · '+e.lokacija:'')+'
' +'
'; row.onclick=(function(ed){return function(){calOpenEdit(ed);};})(e); el.appendChild(row); }); } // ── AGENDA ────────────────────────────────── function calRenderAgenda(){ _showAgendaLayout(); var today=new Date(); var y=_calDate.getFullYear(), m=_calDate.getMonth(); document.getElementById('calMonthLbl').textContent=_MJN[m].toUpperCase()+' '+y; var monthEvs=_calEvs.filter(function(e){return e&&e.pocetak&&e.pocetak.slice(0,7)===(y+'-'+String(m+1).padStart(2,'0'));}) .sort(function(a,b){return new Date(a.pocetak)-new Date(b.pocetak);}); var cells=document.getElementById('calCells'); cells.style.gridTemplateColumns='1fr'; cells.style.gridTemplateRows='auto'; cells.innerHTML=''; if(!monthEvs.length){ var emp=document.createElement('div'); emp.style.cssText='padding:32px 16px;text-align:center;color:var(--t3);font-size:11px;letter-spacing:2px'; emp.textContent='NEMA EVENTA U OVOM MJESECU'; cells.appendChild(emp); return; } var lastDate=''; monthEvs.forEach(function(e){ var d=new Date(e.pocetak), ds=e.pocetak.slice(0,10); var isToday=d.toDateString()===today.toDateString(); if(ds!==lastDate){ lastDate=ds; var dayN=d.toLocaleDateString('hr-HR',{weekday:'short'}).toUpperCase(); var hdr=document.createElement('div'); hdr.style.cssText='display:flex;align-items:center;gap:12px;padding:10px 12px 4px;border-top:1px solid var(--b2);margin-top:4px'; hdr.innerHTML='
' +'
'+dayN+'
' +'
'+d.getDate()+'
' +'
'+_MJN[d.getMonth()].slice(0,3)+'
' +'
' +'
'; cells.appendChild(hdr); } var time=e.cijeli_dan?'Cijeli dan':e.pocetak.slice(11,16)+(e.kraj?' – '+e.kraj.slice(11,16):''); var boja=e.boja||'#E63946'; var row=document.createElement('div'); row.style.cssText='display:flex;align-items:center;gap:10px;padding:10px 12px 10px 64px;' +'border-radius:8px;margin:2px 8px;cursor:pointer;background:var(--bg-card);' +'border-left:3px solid '+boja+';transition:background .1s'; row.innerHTML='
' +'
'+e.naslov+'
' +'
'+time+(e.lokacija?' · '+e.lokacija:'')+'
' +'
'; row.onclick=(function(ed){return function(){calOpenEdit(ed);};})(e); cells.appendChild(row); }); } // ── MODAL ────────────────────────────────── function calNewEvent(dateStr){ document.getElementById('cemId').value=''; document.getElementById('cemTitle').textContent='Novi event'; document.getElementById('cemDelBtn').style.display='none'; ['cemNaslov','cemOpis','cemLok'].forEach(function(id){document.getElementById(id).value='';}); document.getElementById('cemCijeli').checked=false; document.getElementById('cemPods').value='15'; _cemKat='Gym'; _cemCol='#E63946'; document.querySelectorAll('.cem-kat').forEach(function(b,i){b.className='cem-kat'+(i===0?' on':'');}); document.querySelectorAll('.cem-col').forEach(function(d,i){d.className='cem-col'+(i===0?' on':'');}); var base=dateStr?new Date(dateStr):new Date(); base.setHours(10,0,0,0); var end=new Date(base); end.setHours(11); document.getElementById('cemOd').type='datetime-local'; document.getElementById('cemDo').type='datetime-local'; document.getElementById('cemOd').value=_iso(base); document.getElementById('cemDo').value=_iso(end); document.getElementById('calEvModal').className='open'; setTimeout(function(){document.getElementById('cemNaslov').focus();},100); } function calOpenEdit(ev){ if(ev.auto)return; document.getElementById('cemId').value=ev.id; document.getElementById('cemTitle').textContent='Uredi event'; document.getElementById('cemDelBtn').style.display='inline-block'; document.getElementById('cemNaslov').value=ev.naslov||''; document.getElementById('cemOpis').value=ev.opis||''; document.getElementById('cemLok').value=ev.lokacija||''; document.getElementById('cemCijeli').checked=!!ev.cijeli_dan; document.getElementById('cemPods').value=String(ev.podsjetnik||15); _cemKat=ev.kategorija||'Gym'; _cemCol=ev.boja||'#E63946'; document.querySelectorAll('.cem-kat').forEach(function(b){b.className='cem-kat'+(b.textContent.indexOf(_cemKat)>=0?' on':'');}); document.querySelectorAll('.cem-col').forEach(function(d){d.className='cem-col'+(d.style.background===_cemCol?' on':'');}); var po=ev.pocetak?ev.pocetak.replace(' ','T').slice(0,16):''; var kr=ev.kraj?ev.kraj.replace(' ','T').slice(0,16):''; if(ev.cijeli_dan){ document.getElementById('cemOd').type='date'; document.getElementById('cemDo').type='date'; document.getElementById('cemOd').value=po.slice(0,10); document.getElementById('cemDo').value=kr.slice(0,10); }else{ document.getElementById('cemOd').type='datetime-local'; document.getElementById('cemDo').type='datetime-local'; document.getElementById('cemOd').value=po; document.getElementById('cemDo').value=kr; } document.getElementById('calEvModal').className='open'; } function calCloseModal(){document.getElementById('calEvModal').className='';} function cemKat(el,kat){ _cemKat=kat; document.querySelectorAll('.cem-kat').forEach(function(b){b.className='cem-kat';}); el.className='cem-kat on'; var ab=_KAT_BOJE[kat]; if(ab){_cemCol=ab;document.querySelectorAll('.cem-col').forEach(function(d){d.className='cem-col'+(d.style.background===ab?' on':'');});} } function cemColor(el,col){_cemCol=col;document.querySelectorAll('.cem-col').forEach(function(d){d.className='cem-col';});el.className='cem-col on';} function cemTogCijeli(){var cd=document.getElementById('cemCijeli').checked;document.getElementById('cemOd').type=cd?'date':'datetime-local';document.getElementById('cemDo').type=cd?'date':'datetime-local';} async function cemSave(){ var id=document.getElementById('cemId').value; var od=document.getElementById('cemOd').value; if(!od){alert('Unesite datum pocetka!');return;} var payload={ naslov:document.getElementById('cemNaslov').value||'Bez naziva', opis:document.getElementById('cemOpis').value, kategorija:_cemKat, boja:_cemCol, pocetak:od.replace('T',' '), kraj:(document.getElementById('cemDo').value||'').replace('T',' ')||null, cijeli_dan:document.getElementById('cemCijeli').checked?1:0, lokacija:document.getElementById('cemLok').value, podsjetnik:parseInt(document.getElementById('cemPods').value)||0, ponavljanje:'nikad' }; try{ var r=await fetchAuth(id?'/api/kalendar/'+id:'/api/kalendar',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); if(!r||!r.ok){var err={};try{err=await r.json();}catch(x){};alert('Greska: '+(err.greska||r.status));return;} calCloseModal(); if(!id&&od) _calDate=new Date(od.replace(' ','T')); calLoad(); }catch(e){alert('Greska: '+e);} } async function cemDelete(){ var id=document.getElementById('cemId').value; if(!id||!confirm('Obrisati event?'))return; try{await fetchAuth('/api/kalendar/'+id,{method:'DELETE'});calCloseModal();calLoad();}catch(e){alert('Greska: '+e);} } async function calExport(){ try{var r=await fetchAuth('/api/kalendar/export.ics');if(!r||!r.ok){alert('Greska');return;} var t=await r.text();var b=new Blob([t],{type:'text/calendar'});var a=document.createElement('a'); a.href=URL.createObjectURL(b);a.download='steelpulse.ics';a.click();URL.revokeObjectURL(a.href);} catch(e){alert('Greska: '+e);} } // ── FAB ────────────────────────────────── function calFabToggle(){ var m=document.getElementById('calFabMenu'), b=document.getElementById('calFabMainBtn'); if(!m)return; var open=m.className==='open'; m.className=open?'':'open'; b.textContent=open?'+':'✕'; } function calFabClose(){ var m=document.getElementById('calFabMenu'), b=document.getElementById('calFabMainBtn'); if(m)m.className=''; if(b)b.textContent='+'; } // ── AI GENERIRAJ ────────────────────────── async function calAiGeneriraj(sync){ // Pitaj za preferirano vrijeme var savedTime = localStorage.getItem('sp_gym_time') || '17:00'; var time = prompt('U koje vrijeme obično ideš u gym?\n(Format: HH:MM)', savedTime); if(!time) return; // Validiraj format if(!/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(time)){ alert('Neispravan format! Unesi npr: 17:00 ili 08:30'); return; } localStorage.setItem('sp_gym_time', time); var btn=document.getElementById('calAiBtn'); if(btn){btn.textContent='Generira...';btn.disabled=true;} try{ var url=sync?'/api/kalendar/ai-sync':'/api/kalendar/ai-generiraj'; var r=await fetchAuth(url,{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({gym_time: time})}); if(!r||!r.ok){var err={};try{err=await r.json();}catch(x){}; alert('Greska: '+(err.greska||r.status));} else{var d=await r.json(); alert(d.kreirani===0?'Svi gym eventi vec postoje za 4 tjedna!':'OK '+d.poruka); calLoad();} }catch(e){alert('Greska: '+e);} finally{if(btn){btn.textContent='AI GENERIRAJ GYM';btn.disabled=false;}} } // ── UTILS ──────────────────────────────── function _iso(d){var p=function(n){return String(n).padStart(2,'0');};return d.getFullYear()+'-'+p(d.getMonth()+1)+'-'+p(d.getDate())+'T'+p(d.getHours())+':'+p(d.getMinutes());} // Swipe (function(){ var sx=0, sy=0; document.addEventListener('touchstart',function(e){if(document.getElementById('calEvModal').className==='open')return;sx=e.touches[0].clientX;sy=e.touches[0].clientY;},{passive:true}); document.addEventListener('touchend',function(e){ if(document.getElementById('calEvModal').className==='open')return; if(!document.getElementById('tab-cal').classList.contains('active'))return; var dx=e.changedTouches[0].clientX-sx, dy=e.changedTouches[0].clientY-sy; if(Math.abs(dx)>Math.abs(dy)&&Math.abs(dx)>50) calNav(dx<0?1:-1); },{passive:true}); })(); // Timezone poll setInterval(function(){var tz=Intl.DateTimeFormat().resolvedOptions().timeZone;if(tz!==_calTz){_calTz=tz;calDetectTz();}},60000); // ESC + click outside document.addEventListener('keydown',function(e){if(e.key==='Escape')calCloseModal();}); document.addEventListener('click',function(e){var m=document.getElementById('calEvModal');if(m&&e.target===m)calCloseModal();});