// Netz-Verbindungsüberwachung & PWA Registrierung if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('./sw.js').catch(err => { console.warn('ServiceWorker Info:', err.message); }); }); } window.addEventListener('online', handleNetworkStatus); window.addEventListener('offline', handleNetworkStatus); function handleNetworkStatus() { const offlineBar = document.getElementById('offline-status-bar'); if (!navigator.onLine) { if (offlineBar) offlineBar.classList.remove('hidden'); } else { if (offlineBar) offlineBar.classList.add('hidden'); syncOfflineQueue(); } } function queueOfflineAction(action) { const queue = JSON.parse(localStorage.getItem('mainpark_offline_queue') || '[]'); queue.push(action); localStorage.setItem('mainpark_offline_queue', JSON.stringify(queue)); } async function syncOfflineQueue() { const queue = JSON.parse(localStorage.getItem('mainpark_offline_queue') || '[]'); if (queue.length === 0) return; for (const item of queue) { try { if (item.type === 'toggleTask') { await supabaseClient.from('tasks').update({ is_completed: item.isCompleted }).eq('id', item.taskId); } else if (item.type === 'addTask') { delete item.taskData.id; delete item.taskData.is_offline_pending; await supabaseClient.from('tasks').insert([item.taskData]); } else if (item.type === 'updateStatus') { await supabaseClient.from('rooms').update({ status: item.status }).eq('id', item.roomId); } else if (item.type === 'updateNotes') { await supabaseClient.from('rooms').update({ notes: item.notes }).eq('id', item.roomId); } } catch (e) { console.error('Fehler beim Nach-Synchronisieren:', e); } } localStorage.removeItem('mainpark_offline_queue'); loadRooms(); } // Dynamische Changelog-Steuerung async function fetchLatestChangelog() { if (!navigator.onLine) return; const { data } = await supabaseClient .from('activity_logs') .select('*') .eq('user_alias', 'CHANGELOG_UPDATE') .order('created_at', { ascending: false }); const footerBtn = document.getElementById('footer-changelog-btn'); const profileLink = document.getElementById('profile-changelog-link'); if (data && data.length > 0) { window.publishedChangelogs = data; if (footerBtn) footerBtn.classList.remove('hidden'); if (profileLink) profileLink.classList.remove('hidden'); const latest = data[0]; const versionLabel = document.getElementById('footer-version-label'); if (versionLabel) versionLabel.textContent = `v${latest.room_name}`; } else { window.publishedChangelogs = []; if (footerBtn) footerBtn.classList.add('hidden'); if (profileLink) profileLink.classList.add('hidden'); } } function openChangelogModal() { const list = document.getElementById('changelog-modal-list'); list.innerHTML = ''; const changelogs = window.publishedChangelogs || []; if (changelogs.length === 0) { list.innerHTML = '

Kein veröffentlichter Changelog vorhanden.

'; } else { changelogs.forEach(item => { const dateStr = new Date(item.created_at).toLocaleDateString('de-DE'); const card = document.createElement('div'); card.className = "bg-slate-900 border border-slate-700/80 p-4 rounded-xl space-y-2 text-xs"; card.innerHTML = `
Version ${item.room_name} ${dateStr}
${item.action_text}
`; list.appendChild(card); }); } document.getElementById('changelog-modal').classList.remove('hidden'); } function closeChangelogModal() { document.getElementById('changelog-modal').classList.add('hidden'); } // Login & Auth Handler document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('email').value; const password = document.getElementById('password').value; const errorEl = document.getElementById('login-error'); const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password }); if (error) { errorEl.textContent = error.message; errorEl.classList.remove('hidden'); } else { checkAuth(); } }); document.getElementById('logout-btn').addEventListener('click', async () => { if (pingInterval) clearInterval(pingInterval); await supabaseClient.auth.signOut(); checkAuth(); }); async function checkAuth() { handleNetworkStatus(); const { data: { session } } = await supabaseClient.auth.getSession(); if (session) { currentUser = session.user; const { data: prof } = await supabaseClient.from('profiles').select('is_disabled').eq('id', currentUser.id).single(); if (prof && prof.is_disabled) { alert('Dein Account wurde vom Administrator gesperrt.'); await supabaseClient.auth.signOut(); location.reload(); return; } document.getElementById('login-view').classList.add('hidden'); document.getElementById('dashboard-view').classList.remove('hidden'); if (isAdmin()) { document.getElementById('admin-users-btn').classList.remove('hidden'); document.getElementById('admin-divider').classList.remove('hidden'); document.getElementById('admin-close-announcement-btn').classList.remove('hidden'); document.getElementById('new-task-reward').classList.remove('hidden'); document.getElementById('new-task-assignee').classList.remove('hidden'); document.getElementById('modal-delete-room-btn').classList.remove('hidden'); } else { document.getElementById('admin-users-btn').classList.add('hidden'); document.getElementById('admin-divider').classList.add('hidden'); document.getElementById('admin-close-announcement-btn').classList.add('hidden'); document.getElementById('new-task-reward').classList.add('hidden'); document.getElementById('new-task-assignee').classList.add('hidden'); document.getElementById('modal-delete-room-btn').classList.add('hidden'); } await loadUserProfile(); await fetchAllProfiles(); startOnlineHeartbeat(); loadRooms(); checkGlobalAnnouncement(); fetchLatestChangelog(); } else { currentUser = null; if (pingInterval) clearInterval(pingInterval); document.getElementById('login-view').classList.remove('hidden'); document.getElementById('dashboard-view').classList.add('hidden'); } } function startOnlineHeartbeat() { updateHeartbeat(); if (pingInterval) clearInterval(pingInterval); pingInterval = setInterval(updateHeartbeat, 30000); } async function updateHeartbeat() { if (!currentUser || !navigator.onLine) return; await supabaseClient .from('profiles') .upsert({ id: currentUser.id, alias: currentUserAlias || currentUser.email.split('@')[0], last_seen: new Date().toISOString() }); } async function loadUserProfile() { if (!currentUser) return; document.getElementById('profile-email').value = currentUser.email; const { data } = await supabaseClient .from('profiles') .select('alias') .eq('id', currentUser.id) .maybeSingle(); const aliasBanner = document.getElementById('missing-alias-banner'); if (data && data.alias && data.alias.trim() !== '') { currentUserAlias = data.alias; if (aliasBanner) aliasBanner.classList.add('hidden'); } else { currentUserAlias = currentUser.email.split('@')[0]; if (aliasBanner) aliasBanner.classList.remove('hidden'); } document.getElementById('user-display-name').textContent = currentUserAlias; document.getElementById('profile-alias-input').value = currentUserAlias; } async function saveUserAlias() { if (!currentUser) return; const newAlias = document.getElementById('profile-alias-input').value.trim(); if (!newAlias) { alert('Bitte gib einen gültigen Namen ein.'); return; } const oldAlias = currentUserAlias; const { data: existingProfile } = await supabaseClient .from('profiles') .select('id') .eq('id', currentUser.id) .maybeSingle(); let saveError = null; const payload = { alias: newAlias, updated_at: new Date().toISOString() }; if (existingProfile) { const { error } = await supabaseClient .from('profiles') .update(payload) .eq('id', currentUser.id); saveError = error; } else { payload.id = currentUser.id; const { error } = await supabaseClient .from('profiles') .insert([payload]); saveError = error; } if (!saveError) { currentUserAlias = newAlias; document.getElementById('user-display-name').textContent = newAlias; const aliasBanner = document.getElementById('missing-alias-banner'); if (aliasBanner) aliasBanner.classList.add('hidden'); await fetchAllProfiles(); logActivity(null, `hat Namen von "${oldAlias}" zu "${newAlias}" geändert`); renderMyTasks(); checkNotifications(); closeProfileModal(); alert('Dein Name wurde erfolgreich gespeichert!'); } else { console.error('Supabase Fehler beim Speichern:', saveError); alert(`Fehler beim Speichern: ${saveError.message || 'Datenbank-Zugriff verweigert'}`); } } async function fetchAllProfiles() { const { data, error } = await supabaseClient .from('profiles') .select('*'); if (!error && data) { allProfiles = data; populateAssigneeDropdown(); } } function populateAssigneeDropdown() { const select = document.getElementById('new-task-assignee'); if (!select) return; select.innerHTML = ''; allProfiles.forEach(p => { if (p.alias) { const opt = document.createElement('option'); opt.value = p.id; opt.textContent = `👤 ${p.alias}`; select.appendChild(opt); } }); } function openProfileModal() { calculateUserEarnedAmounts(); renderMyTasks(); document.getElementById('profile-modal').classList.remove('hidden'); } function closeProfileModal() { document.getElementById('profile-modal').classList.add('hidden'); } function calculateUserEarnedAmounts() { if (!currentUser) return; let pendingTotal = 0; let unpaidTotal = 0; let paidTotal = 0; const paidHistoryList = []; allRoomsData.forEach(r => { if (r.tasks) { r.tasks.forEach(t => { if (t.is_completed && t.assigned_to === currentUser.id) { const fullReward = parseFloat(t.reward_amount || 0); const alreadyPaid = parseFloat(t.paid_amount || 0); if (!t.is_approved) { pendingTotal += fullReward; } else { paidTotal += alreadyPaid; unpaidTotal += (fullReward - alreadyPaid); if (alreadyPaid > 0) { paidHistoryList.push({ taskName: t.task_name, roomNumber: r.room_number, amount: alreadyPaid, date: t.paid_at ? new Date(t.paid_at).toLocaleDateString('de-DE') : 'Kürzlich' }); } } } }); } }); document.getElementById('header-user-earned-amount').textContent = `${unpaidTotal.toFixed(2)} €`; document.getElementById('profile-pending-amount').textContent = `${pendingTotal.toFixed(2)} €`; document.getElementById('profile-unpaid-amount').textContent = `${unpaidTotal.toFixed(2)} €`; document.getElementById('profile-paid-amount').textContent = `${paidTotal.toFixed(2)} €`; const historyContainer = document.getElementById('profile-paid-history-list'); if (historyContainer) { historyContainer.innerHTML = ''; if (paidHistoryList.length === 0) { historyContainer.innerHTML = '

Noch keine Auszahlungen erhalten.

'; } else { paidHistoryList.forEach(item => { const row = document.createElement('div'); row.className = "flex items-center justify-between text-xs bg-slate-900 border border-slate-700/60 p-2.5 rounded-lg"; row.innerHTML = `
${item.taskName} (${item.roomNumber})
${item.date}
+${item.amount.toFixed(2)} € `; historyContainer.appendChild(row); }); } } } function toggleSelectMode() { isSelectMode = !isSelectMode; const btn = document.getElementById('select-mode-btn'); const selBar = document.getElementById('selection-bar'); if (isSelectMode) { btn.classList.remove('bg-slate-800', 'text-slate-200'); btn.classList.add('bg-indigo-600', 'text-white'); selBar.classList.remove('hidden'); } else { btn.classList.remove('bg-indigo-600', 'text-white'); btn.classList.add('bg-slate-800', 'text-slate-200'); selBar.classList.add('hidden'); selectedRoomIds.clear(); } updateSelectionUI(); applyFilterAndSort(); } function toggleRoomSelection(roomId, event) { if (event) event.stopPropagation(); if (selectedRoomIds.has(roomId)) { selectedRoomIds.delete(roomId); } else { selectedRoomIds.add(roomId); } updateSelectionUI(); applyFilterAndSort(); } function selectAllVisibleRooms() { const visibleCards = document.querySelectorAll('#rooms-grid > div'); visibleCards.forEach(card => { const id = parseInt(card.dataset.id); if (id) selectedRoomIds.add(id); }); updateSelectionUI(); applyFilterAndSort(); } function clearSelection() { selectedRoomIds.clear(); updateSelectionUI(); applyFilterAndSort(); } function updateSelectionUI() { document.getElementById('selected-count-text').textContent = `${selectedRoomIds.size} Zimmer ausgewählt`; } function openShoppingListModal() { const container = document.getElementById('shopping-list-content'); container.innerHTML = ''; const materialRooms = allRoomsData.filter(r => r.material_status === 'Offen' || r.material_status === 'Bestellt'); if (materialRooms.length === 0) { container.innerHTML = '

Aktuell liegt kein offener Materialbedarf vor. Alle Materialien stehen bereit!

'; } else { materialRooms.forEach(r => { const statusBadge = r.material_status === 'Offen' ? 'Offen / Benötigt' : 'Bestellt'; const item = document.createElement('div'); item.className = "bg-slate-900 border border-slate-700/80 rounded-xl p-4 space-y-2"; item.innerHTML = `

🏢 ${r.room_number} (${r.floor})

${statusBadge}

${r.notes ? r.notes : 'Keine Notizen/Details eingetragen'}

`; container.appendChild(item); }); } document.getElementById('shopping-list-modal').classList.remove('hidden'); } function closeShoppingListModal() { document.getElementById('shopping-list-modal').classList.add('hidden'); } function checkNotifications() { const notifications = []; const userTag = `@${currentUserAlias.toLowerCase()}`; allRoomsData.forEach(room => { if (room.notes && room.notes.toLowerCase().includes(userTag)) { notifications.push({ type: 'mention', title: `Erwähnung in ${room.room_number}`, text: room.notes, room: room }); } if (room.tasks) { room.tasks.forEach(t => { if (t.assigned_to === currentUser.id && !t.is_completed) { notifications.push({ type: 'task', title: `Offene Aufgabe in ${room.room_number}`, text: t.task_name, room: room }); } }); } }); const badge = document.getElementById('notification-badge'); if (notifications.length > 0) { badge.textContent = notifications.length; badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } window.currentNotifications = notifications; } function toggleNotificationsModal() { const modal = document.getElementById('notifications-modal'); const list = document.getElementById('notifications-list'); if (modal.classList.contains('hidden')) { list.innerHTML = ''; const notifications = window.currentNotifications || []; if (notifications.length === 0) { list.innerHTML = '

Keine neuen Benachrichtigungen vorhanden.

'; } else { notifications.forEach(n => { const item = document.createElement('div'); item.className = "bg-slate-900 border border-slate-700/80 p-3 rounded-lg cursor-pointer hover:border-indigo-500 transition"; item.onclick = () => { toggleNotificationsModal(); openModal(n.room); }; item.innerHTML = `
${n.type === 'mention' ? '💬' : '📌'} ${n.title}

${n.text}

`; list.appendChild(item); }); } modal.classList.remove('hidden'); } else { modal.classList.add('hidden'); } } async function logActivity(room, actionText) { const roomName = room ? room.room_number : 'System'; if (room) { if (!room.history) room.history = []; const timestampStr = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); room.history.unshift(`[${timestampStr}] ${currentUserAlias}: ${actionText}`); renderRoomHistory(room); } if (!navigator.onLine) return; const { error } = await supabaseClient .from('activity_logs') .insert([{ user_alias: currentUserAlias, room_name: roomName, action_text: actionText }]); if (!error) { fetchGlobalActivityLog(); } } async function fetchGlobalActivityLog() { if (!navigator.onLine) return; const { data, error } = await supabaseClient .from('activity_logs') .select('*') .order('created_at', { ascending: false }) .limit(50); if (error) return; renderGlobalActivityLog(data || []); } function renderGlobalActivityLog(logs) { const feed = document.getElementById('global-activity-feed'); const modalList = document.getElementById('modal-global-log-list'); feed.innerHTML = ''; modalList.innerHTML = ''; const filteredLogs = logs.filter(item => item.user_alias !== 'ANNOUNCEMENT' && item.user_alias !== 'CHANGELOG_UPDATE'); if (filteredLogs.length === 0) { const emptyState = '

Noch keine Aktivitäten im gesamten System aufgezeichnet.

'; feed.innerHTML = emptyState; modalList.innerHTML = emptyState; return; } filteredLogs.forEach(item => { const logTime = new Date(item.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); const logDate = new Date(item.created_at).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); const isUndoable = isAdmin() && !item.action_text.includes('[ADMIN UNDO]'); const undoBtnHTML = isUndoable ? `` : ''; const row = document.createElement('div'); row.className = "bg-slate-900/80 border border-slate-800 p-2.5 rounded-lg flex items-center justify-between gap-3"; row.innerHTML = `
${logDate} ${logTime} ${item.user_alias}: ${item.action_text}
${undoBtnHTML} ${item.room_name}
`; feed.appendChild(row.cloneNode(true)); modalList.appendChild(row); }); } function toggleGlobalLogModal() { const modal = document.getElementById('global-log-modal'); if (modal.classList.contains('hidden')) { fetchGlobalActivityLog(); modal.classList.remove('hidden'); } else { modal.classList.add('hidden'); } } function renderRoomHistory(room) { const list = document.getElementById('room-history-list'); list.innerHTML = ''; if (!room.history || room.history.length === 0) { list.innerHTML = '

Noch keine Aktivitäten aufgezeichnet.

'; return; } room.history.forEach(h => { const item = document.createElement('div'); item.className = "text-slate-400 border-b border-slate-800 pb-1 last:border-0"; item.textContent = h; list.appendChild(item); }); } function renderMyTasks() { const openContainer = document.getElementById('my-open-tasks-list'); const completedContainer = document.getElementById('my-completed-tasks-list'); openContainer.innerHTML = ''; completedContainer.innerHTML = ''; let openCount = 0; let completedCount = 0; allRoomsData.forEach(room => { if (room.tasks) { room.tasks.forEach(task => { const isForMe = task.assigned_to === currentUser.id; const isForAll = !task.assigned_to; if (isForMe || isForAll) { const taskEl = document.createElement('div'); taskEl.className = "flex items-center justify-between bg-slate-900 border border-slate-700/80 p-3 rounded-lg text-xs"; const typeBadge = isForAll ? '👥 Alle' : `👤 ${currentUserAlias}`; const rewardBadge = parseFloat(task.reward_amount || 0) > 0 ? `+${parseFloat(task.reward_amount).toFixed(2)} €` : ''; let approvalStatusBadge = ''; if (task.is_completed && parseFloat(task.reward_amount || 0) > 0) { if (!task.is_approved) { approvalStatusBadge = '🔍 Wartet auf Admin-Prüfung'; } else if (task.is_paid) { approvalStatusBadge = '✅ Ausbezahlt'; } else { approvalStatusBadge = '💰 Bestätigt (Guthaben)'; } } if (task.is_completed) { completedCount++; taskEl.innerHTML = ` ${room.room_number} `; completedContainer.appendChild(taskEl); } else { openCount++; taskEl.innerHTML = ` ${room.room_number} (${room.floor}) `; openContainer.appendChild(taskEl); } } }); } }); if (openCount === 0) { openContainer.innerHTML = '

Keine offenen Aufgaben vorhanden.

'; } if (completedCount === 0) { completedContainer.innerHTML = '

Noch keine erledigten Aufgaben.

'; } } async function loadRooms() { if (navigator.onLine) { const { data, error } = await supabaseClient .from('rooms') .select('*, tasks(*)') .order('position', { ascending: true }); if (!error && data) { allRoomsData = data; localStorage.setItem('mainpark_rooms_cache', JSON.stringify(data)); } } else { const cached = localStorage.getItem('mainpark_rooms_cache'); if (cached) { allRoomsData = JSON.parse(cached); } } document.getElementById('room-count-badge').textContent = `${allRoomsData.length} Zimmer`; updateOverallProgress(); calculateUserEarnedAmounts(); checkNotifications(); fetchGlobalActivityLog(); applyFilterAndSort(); } function updateOverallProgress() { let totalTasks = 0; let completedTasks = 0; allRoomsData.forEach(room => { if (room.tasks) { totalTasks += room.tasks.length; completedTasks += room.tasks.filter(t => t.is_completed).length; } }); const overallPct = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; document.getElementById('overall-progress-text').textContent = `${overallPct}% (${completedTasks}/${totalTasks} Aufgaben)`; document.getElementById('overall-progress-bar').style.width = `${overallPct}%`; } function applyFilterAndSort() { let result = [...allRoomsData]; if (currentFilterStatus === 'my-rooms') { result = result.filter(r => r.tasks && r.tasks.some(t => (!t.assigned_to || t.assigned_to === currentUser.id) && !t.is_completed)); } else if (currentFilterStatus !== 'all') { result = result.filter(r => r.status === currentFilterStatus); } const sortBy = document.getElementById('sort-select').value; const sortFn = (a, b) => { if (sortBy === 'manual') return (a.position || 0) - (b.position || 0); if (sortBy === 'name-asc') return a.room_number.localeCompare(b.room_number, undefined, { numeric: true, sensitivity: 'base' }); if (sortBy === 'name-desc') return b.room_number.localeCompare(a.room_number, undefined, { numeric: true, sensitivity: 'base' }); if (sortBy === 'status') { const order = { 'Geplant': 1, 'In Arbeit': 2, 'Fertig': 3 }; return (order[a.status] || 0) - (order[b.status] || 0); } if (sortBy === 'progress-desc') return getRoomProgress(b) - getRoomProgress(a); if (sortBy === 'progress-asc') return getRoomProgress(a) - getRoomProgress(b); return 0; }; const pinned = result.filter(r => r.is_pinned).sort(sortFn); const unpinned = result.filter(r => !r.is_pinned).sort(sortFn); result = [...pinned, ...unpinned]; renderRooms(result); } function setFilter(status, btnElement) { currentFilterStatus = status; document.querySelectorAll('.filter-btn').forEach(btn => { btn.classList.remove('bg-indigo-600', 'text-white'); btn.classList.add('text-slate-300'); }); btnElement.classList.remove('text-slate-300'); btnElement.classList.add('bg-indigo-600', 'text-white'); applyFilterAndSort(); } async function togglePin(roomId, pinStatus) { if (navigator.onLine) { await supabaseClient.from('rooms').update({ is_pinned: pinStatus }).eq('id', roomId); } const room = allRoomsData.find(r => r.id === roomId); if (room) { room.is_pinned = pinStatus; logActivity(room, pinStatus ? 'hat Zimmer angepinnt' : 'hat Anpinnung aufgehoben'); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); applyFilterAndSort(); } async function saveNewOrder() { const grid = document.getElementById('rooms-grid'); const cards = Array.from(grid.children); const updates = cards.map((card, index) => { const roomId = parseInt(card.dataset.id); const room = allRoomsData.find(r => r.id === roomId); if (room) room.position = index + 1; if (navigator.onLine) { return supabaseClient .from('rooms') .update({ position: index + 1 }) .eq('id', roomId); } }); if (navigator.onLine) { await Promise.all(updates); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); logActivity(null, 'hat Reihung/Sortierung der Zimmer per Drag & Drop geändert'); } function openAddRoomModal() { document.getElementById('new-room-name').value = ''; document.getElementById('add-room-modal').classList.remove('hidden'); } function closeAddRoomModal() { document.getElementById('add-room-modal').classList.add('hidden'); } async function createNewRoom() { const nameInput = document.getElementById('new-room-name'); const floorSelect = document.getElementById('new-room-floor'); const roomName = nameInput.value.trim(); const floor = floorSelect.value; if (!roomName) return; const nextPos = allRoomsData.length + 1; if (navigator.onLine) { const { data, error } = await supabaseClient .from('rooms') .insert([{ room_number: roomName, floor: floor, status: 'Geplant', position: nextPos, is_pinned: false }]) .select(); if (!error && data && data.length > 0) { closeAddRoomModal(); const newRoom = data[0]; logActivity(newRoom, `hat das Zimmer neu angelegt`); loadRooms(); } } else { alert('Neuanlegen von Räumen erfordert eine aktive Internetverbindung.'); } } async function reassignTask(taskId, newAssigneeId) { const assigneeValue = (newAssigneeId && newAssigneeId.trim() !== '') ? newAssigneeId : null; if (navigator.onLine) { await supabaseClient.from('tasks').update({ assigned_to: assigneeValue }).eq('id', taskId); } let assignedPersonName = '👥 Alle (Team)'; if (assigneeValue) { const foundProf = allProfiles.find(p => p.id === assigneeValue); if (foundProf && foundProf.alias) { assignedPersonName = `👤 ${foundProf.alias}`; } } allRoomsData.forEach(r => { if (r.tasks) { const task = r.tasks.find(t => t.id === taskId); if (task) { task.assigned_to = assigneeValue; logActivity(r, `hat Aufgabe "${task.task_name}" neu zugewiesen an ${assignedPersonName}`); } } }); localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); calculateUserEarnedAmounts(); renderMyTasks(); checkNotifications(); } function closeModal() { document.getElementById('room-modal').classList.add('hidden'); loadRooms(); } async function deleteCurrentRoom() { if (!isAdmin()) { alert('Nur der Administrator darf Zimmer löschen.'); return; } const room = allRoomsData.find(r => r.id === currentRoomId); if (!room) return; const confirmDelete = confirm(`Möchten Sie "${room.room_number}" wirklich samt allen Aufgaben und Notizen löschen?`); if (!confirmDelete) return; if (navigator.onLine) { const { error } = await supabaseClient.from('rooms').delete().eq('id', currentRoomId); if (!error) { closeModal(); loadRooms(); } } else { alert('Löschen von Räumen ist nur im Online-Modus verfügbar.'); } } async function updateRoomDetails() { const newName = document.getElementById('modal-room-number').value.trim(); if (!newName) return; const room = allRoomsData.find(r => r.id === currentRoomId); if (!room) return; const oldName = room.room_number; if (navigator.onLine) { await supabaseClient.from('rooms').update({ room_number: newName }).eq('id', currentRoomId); } room.room_number = newName; document.getElementById('modal-title').textContent = `${newName} (${room.floor})`; logActivity(room, `hat das Zimmer von "${oldName}" in "${newName}" umbenannt`); localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); applyFilterAndSort(); } // Automatische Vergütung aus dem Katalog oder bestehenden Aufgaben beim Tippen ermitteln document.getElementById('new-task-input')?.addEventListener('input', function(e) { const inputVal = e.target.value.trim().toLowerCase(); const rewardInput = document.getElementById('new-task-reward'); if (!rewardInput || !inputVal) return; // 1. Suche im globalen Admin-Katalog if (window.taskCatalog && window.taskCatalog[inputVal] !== undefined) { rewardInput.value = window.taskCatalog[inputVal]; } else { // 2. Fallback: Suche in allen bereits existierenden Aufgaben im System let foundReward = null; allRoomsData.forEach(r => { if (r.tasks) { r.tasks.forEach(t => { if (t.task_name && t.task_name.trim().toLowerCase() === inputVal && parseFloat(t.reward_amount || 0) > 0) { foundReward = parseFloat(t.reward_amount); } }); } }); if (foundReward !== null) { rewardInput.value = foundReward; } } }); // NEUE AUFGABE ANLEGEN async function addTask() { const input = document.getElementById('new-task-input'); const assigneeSelect = document.getElementById('new-task-assignee'); const rewardInput = document.getElementById('new-task-reward'); if (!input || !input.value.trim()) { alert('Bitte gib einen Namen für die Aufgabe ein.'); if (input) input.focus(); return; } const taskName = input.value.trim(); const taskNameLower = taskName.toLowerCase(); const assignedTo = (assigneeSelect && assigneeSelect.value.trim() !== '') ? assigneeSelect.value : null; let rewardAmount = 0; // 1. Manuell eingegebenen Betrag bevorzugen if (rewardInput && rewardInput.value.trim() !== '') { const parsed = parseFloat(rewardInput.value.replace(',', '.')); rewardAmount = isNaN(parsed) ? 0 : parsed; } // 2. Falls leer: Automatisch aus dem Admin-Katalog ziehen else if (window.taskCatalog && window.taskCatalog[taskNameLower] !== undefined) { rewardAmount = window.taskCatalog[taskNameLower]; } // 3. Falls nicht im Katalog: Aus existierenden Aufgaben ermitteln else { allRoomsData.forEach(r => { if (r.tasks) { r.tasks.forEach(t => { if (t.task_name && t.task_name.trim().toLowerCase() === taskNameLower && parseFloat(t.reward_amount || 0) > 0) { rewardAmount = parseFloat(t.reward_amount); } }); } }); } const newTaskData = { room_id: currentRoomId, task_name: taskName, is_completed: false, assigned_to: assignedTo, reward_amount: rewardAmount }; if (navigator.onLine) { const { data, error } = await supabaseClient.from('tasks').insert([newTaskData]).select(); if (!error && data && data.length > 0) { input.value = ''; if (rewardInput) rewardInput.value = ''; if (assigneeSelect) assigneeSelect.selectedIndex = 0; const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { if (!room.tasks) room.tasks = []; room.tasks.push(data[0]); logActivity(room, `hat Aufgabe "${taskName}" hinzugefügt (${rewardAmount > 0 ? rewardAmount.toFixed(2) + ' €' : 'Keine Vergütung'})`); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); renderModalTasks(room ? room.tasks : []); renderMyTasks(); updateOverallProgress(); } else { console.error('Fehler beim Einfügen der Aufgabe:', error); alert(`Aufgabe konnte nicht gespeichert werden.\n\nFehlerursache: ${error ? error.message : 'Datenbankzugriff verweigert'}`); } } else { newTaskData.id = 'offline_' + Date.now(); newTaskData.is_offline_pending = true; queueOfflineAction({ type: 'addTask', taskData: newTaskData }); const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { if (!room.tasks) room.tasks = []; room.tasks.push(newTaskData); renderModalTasks(room.tasks); } input.value = ''; if (rewardInput) rewardInput.value = ''; renderMyTasks(); } } function renderModalTasks(tasks) { const container = document.getElementById('modal-tasks-list'); container.innerHTML = ''; if (!tasks || tasks.length === 0) { container.innerHTML = '

Keine Aufgaben vorhanden.

'; return; } tasks.forEach(t => { const div = document.createElement('div'); div.className = "flex items-center justify-between bg-slate-900 border border-slate-700/80 p-2.5 rounded-lg text-xs gap-2 flex-wrap"; const offlineBadge = t.is_offline_pending ? '📡 Offline' : ''; let priorityBadge = ''; if (t.priority === 'high') { priorityBadge = '⚠️ Dringend'; } else if (t.priority === 'low') { priorityBadge = 'Niedrig'; } const dueDateBadge = t.due_date ? `📅 ${new Date(t.due_date).toLocaleDateString('de-DE')}` : ''; const rewardAmount = parseFloat(t.reward_amount || 0); const rewardBadge = rewardAmount > 0 ? `+${rewardAmount.toFixed(2)} €` : ''; const paidAmount = parseFloat(t.paid_amount || 0); let splitBadge = ''; if (paidAmount > 0 && !t.is_paid) { splitBadge = `Teilbezahlt: ${paidAmount.toFixed(2)} / ${rewardAmount.toFixed(2)} €`; } else if (t.is_paid) { splitBadge = `Bezahlt (${paidAmount.toFixed(2)} €)`; } const isUnassigned = !t.assigned_to; let assigneeOptions = ``; allProfiles.forEach(p => { if (p.alias) { const selected = (t.assigned_to === p.id) ? 'selected' : ''; assigneeOptions += ``; } }); div.innerHTML = `
${isAdmin() ? `` : ''}
`; container.appendChild(div); }); } async function uploadRoomPhoto(type) { const input = document.getElementById(type === 'before' ? 'room-photo-before' : 'room-photo-after'); if (!input || !input.files || input.files.length === 0) return; const file = input.files[0]; const reader = new FileReader(); reader.onload = async function (e) { const base64Data = e.target.result; const room = allRoomsData.find(r => r.id === currentRoomId); if (!room) return; if (!room.photos) room.photos = { before: null, after: null }; room.photos[type] = base64Data; if (navigator.onLine) { await supabaseClient.from('rooms').update({ photos: room.photos }).eq('id', currentRoomId); } logActivity(room, `hat ein ${type === 'before' ? 'Vorher' : 'Nachher'}-Foto hochgeladen`); localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); renderRoomPhotoComparison(room); }; reader.readAsDataURL(file); } function renderRoomPhotoComparison(room) { const container = document.getElementById('modal-photo-comparison-container'); if (!container) return; const photos = room.photos || { before: null, after: null }; container.innerHTML = `
📷 Vorher-Zustand ${photos.before ? `` : '
Kein Bild vorhanden
'}
✨ Nachher-Zustand ${photos.after ? `` : '
Kein Bild vorhanden
'}
`; } async function deleteTask(taskId) { if (!isAdmin()) { alert('Nur der Administrator darf Aufgaben löschen.'); return; } if (navigator.onLine) { await supabaseClient.from('tasks').delete().eq('id', taskId); } let deletedName = ''; allRoomsData.forEach(r => { if (r.tasks) { const task = r.tasks.find(t => t.id === taskId); if (task) deletedName = task.task_name; r.tasks = r.tasks.filter(t => t.id !== taskId); } }); const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { logActivity(room, `hat Aufgabe "${deletedName}" gelöscht`); renderModalTasks(room.tasks); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); calculateUserEarnedAmounts(); renderMyTasks(); checkNotifications(); updateOverallProgress(); } async function toggleTask(taskId, isCompleted) { const payload = { is_completed: isCompleted }; if (navigator.onLine) { await supabaseClient.from('tasks').update(payload).eq('id', taskId); } else { queueOfflineAction({ type: 'toggleTask', taskId, isCompleted }); } allRoomsData.forEach(r => { if (r.tasks) { const task = r.tasks.find(t => t.id === taskId); if (task) { task.is_completed = isCompleted; logActivity(r, `hat Aufgabe "${task.task_name}" als ${isCompleted ? 'erledigt' : 'offen'} markiert`); } } }); localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { renderModalTasks(room.tasks); } calculateUserEarnedAmounts(); renderMyTasks(); checkNotifications(); updateOverallProgress(); } async function updateRoomStatus(newStatus) { if (navigator.onLine) { await supabaseClient.from('rooms').update({ status: newStatus }).eq('id', currentRoomId); } else { queueOfflineAction({ type: 'updateStatus', roomId: currentRoomId, status: newStatus }); } const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { room.status = newStatus; logActivity(room, `hat Gesamtstatus geändert auf "${newStatus}"`); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); } async function updateMaterialStatus(newMatStatus) { if (navigator.onLine) { await supabaseClient.from('rooms').update({ material_status: newMatStatus }).eq('id', currentRoomId); } const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { room.material_status = newMatStatus; logActivity(room, `hat Material-Status geändert auf "${newMatStatus}"`); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); } async function updateRoomNotes(notes) { if (navigator.onLine) { await supabaseClient.from('rooms').update({ notes: notes }).eq('id', currentRoomId); } else { queueOfflineAction({ type: 'updateNotes', roomId: currentRoomId, notes }); } const room = allRoomsData.find(r => r.id === currentRoomId); if (room) { room.notes = notes; logActivity(room, `hat Kommentare/Notizen aktualisiert`); checkNotifications(); } localStorage.setItem('mainpark_rooms_cache', JSON.stringify(allRoomsData)); } // System-Start checkAuth();