Schichtplanung
Staff
?
Hallo,
Meine nächsten Schichten
Events
Team
Team-Chat
Profil
Meine Skills
E-Mail Benachrichtigungen
Schicht zugewiesen
Wenn du eingeplant wirst
Schicht bestätigt
Wenn deine Schicht bestätigt wird
Erinnerung
Vor Schichtbeginn
Stempel-Bestätigung
Nach dem Stempeln
Passwort ändern
Verwaltung
// ════ STAMP (shift-linked) ════ let ACTIVE_STAMP = null; async function startStampFlow() { const today = new Date().toISOString().slice(0,10); let myShifts = []; for (const ev of EVENTS) { const d = await api('/shifts.php?event='+ev.id).catch(()=>null); if (!d?.shifts) continue; d.shifts.forEach(s => { if (s.date === today && s.my_status === 'confirmed') { myShifts.push({...s, event_name: ev.name}); } }); } if (!myShifts.length) { toast('Keine bestätigten Schichten für heute gefunden.','err'); return; } document.getElementById('stamp-step1').style.display = 'none'; document.getElementById('stamp-step2').style.display = ''; const list = document.getElementById('stamp-shift-list'); list.innerHTML = myShifts.map(s => { const from = s.time_from ? s.time_from.slice(0,5) : '--:--'; const to = s.time_to ? s.time_to.slice(0,5) : '--:--'; return ''; }).join(''); } function cancelStampFlow() { document.getElementById('stamp-step1').style.display = ''; document.getElementById('stamp-step2').style.display = 'none'; } async function doStampIn(shiftId) { const d = await api('/shifts.php?action=stamp&id='+shiftId,'POST',{type:'in'}).catch(()=>null); if (!d?.ok) { toast(d?.error||'Fehler beim Einstempeln','err'); return; } const btn = document.querySelector('#stamp-shift-list button[onclick="doStampIn('+shiftId+')"]'); const title = btn ? btn.querySelector('div').textContent : 'Schicht'; ACTIVE_STAMP = {shift_id: shiftId, shift_title: title, stamp_in_time: new Date()}; document.getElementById('stamp-step2').style.display = 'none'; document.getElementById('stamp-active').style.display = ''; document.getElementById('stamp-active-shift').textContent = title; document.getElementById('stamp-active-since').textContent = 'seit ' + new Date().toLocaleTimeString('de',{hour:'2-digit',minute:'2-digit'}) + ' Uhr'; toast('Eingestempelt!','ok'); loadShifts(); } async function doStampOut() { if (!ACTIVE_STAMP) return; const d = await api('/shifts.php?action=stamp&id='+ACTIVE_STAMP.shift_id,'POST',{type:'out'}).catch(()=>null); if (!d?.ok) { toast(d?.error||'Fehler','err'); return; } const now = new Date(); const dur = Math.round((now - ACTIVE_STAMP.stamp_in_time) / 60000); document.getElementById('stamp-active').style.display = 'none'; document.getElementById('stamp-step1').style.display = ''; document.getElementById('stamp-info').textContent = 'Ausgestempelt. ' + Math.floor(dur/60) + 'h ' + (dur%60) + 'm. Gute Arbeit!'; ACTIVE_STAMP = null; toast('Ausgestempelt. Gute Arbeit!','ok'); loadShifts(); } async function checkActiveStamp() { for (const ev of EVENTS) { const d = await api('/shifts.php?event='+ev.id).catch(()=>null); if (!d?.shifts) continue; const active = d.shifts.find(s => s.my_status === 'checkedin'); if (active) { ACTIVE_STAMP = {shift_id: active.id, shift_title: active.title, stamp_in_time: new Date()}; document.getElementById('stamp-step1').style.display = 'none'; document.getElementById('stamp-active').style.display = ''; document.getElementById('stamp-active-shift').textContent = active.title; document.getElementById('stamp-active-since').textContent = 'bereits eingecheckt'; break; } } } // ════ REPEATING SHIFTS ════ // ════ DAY OVERVIEW ════ const PERSON_COLORS = ['#4a7c3f','#c85a1a','#2c6b8a','#8a3a7c','#c4a010','#3a7a7a','#7c3a3a','#5a6e2c','#2c4a8a']; const personColorMap = {}; let colorIdx = 0; function personColor(name) { if (!personColorMap[name]) { personColorMap[name] = PERSON_COLORS[colorIdx++ % PERSON_COLORS.length]; } return personColorMap[name]; } async function loadDayOverview() { const el = document.getElementById('overview-container'); el.innerHTML = '
'; const d = await api('/events.php?action=plan&id='+CUR_EVENT_ID).catch(()=>null); if (!d?.ok) { el.innerHTML='
Fehler.
'; return; } renderDayOverview(d.plan, d.dates, d.stations); } function renderDayOverview(plan, dates, stations) { const el = document.getElementById('overview-container'); let html = ''; dates.forEach(function(date) { const dlbl = new Date(date+'T12:00').toLocaleDateString('de-DE',{weekday:'long',day:'numeric',month:'long'}); html += '
' + dlbl + '
'; html += '
'; stations.forEach(function(st) { html += ''; }); html += ''; const slots = {}; stations.forEach(function(st) { ((plan[date] || {})[st.id] || []).forEach(function(s) { slots[s.time_from+'-'+s.time_to] = true; }); }); const sorted = Object.keys(slots).sort(); if (!sorted.length) { html += ''; } else { sorted.forEach(function(slot) { const parts = slot.split('-'); const tf = parts[0]; const tt = parts[1]; html += ''; stations.forEach(function(st) { const shifts = ((plan[date] || {})[st.id] || []).filter(function(s) { return s.time_from===tf && s.time_to===tt; }); if (!shifts.length) { html += ''; return; } html += ''; }); html += ''; }); } html += '
Zeit' + st.emoji + ' ' + st.label + '
Keine Schichten
' + tf + '
' + tt + '
'; shifts.forEach(function(sh) { (sh.persons || []).forEach(function(p) { const col = personColor(p.name); html += '' + p.name + ''; }); if (sh.filled < sh.slots) html += '+' + (sh.slots-sh.filled) + ' frei'; }); html += '
'; }); el.innerHTML = html || '
Keine Daten.
'; } // ════ INSTALL HINT ════ function showInstallHint() { if (localStorage.getItem('install_dismissed')) return; const hint = document.getElementById('install-hint'); const txt = document.getElementById('install-hint-text'); if (!hint || !txt) return; const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; const isAndroid = /Android/.test(navigator.userAgent); if (window.matchMedia('(display-mode: standalone)').matches) return; if (isIOS) { txt.innerHTML = 'Tippe auf das Teilen-Symbol in Safari (↑) und dann auf Zum Home-Bildschirm.'; hint.style.display = 'flex'; } else if (isAndroid) { txt.innerHTML = 'Tippe auf die drei Punkte oben rechts und dann auf App installieren.'; hint.style.display = 'flex'; } } function dismissInstallHint() { localStorage.setItem('install_dismissed','1'); document.getElementById('install-hint').style.display='none'; }