const API_URL = 'https://fb.srv.medeba-media.de/api'; let currentProfile = 1; let currentTab = 'pending'; let posts = []; let profilePollTimer = null; const MAX_PROFILES = 5; const PROFILE_NAMES = { 1: 'Profil 1', 2: 'Profil 2', 3: 'Profil 3', 4: 'Profil 4', 5: 'Profil 5' }; const screenshotModal = document.getElementById('screenshotModal'); const screenshotModalContent = document.getElementById('screenshotModalContent'); const screenshotModalImage = document.getElementById('screenshotModalImage'); const screenshotModalClose = document.getElementById('screenshotModalClose'); const screenshotModalBackdrop = document.getElementById('screenshotModalBackdrop'); let screenshotModalLastFocus = null; let screenshotModalPreviousOverflow = ''; let screenshotModalZoomed = false; function getProfileName(profileNumber) { if (!profileNumber) { return ''; } return PROFILE_NAMES[profileNumber] || `Profil ${profileNumber}`; } function formatDateTime(value) { if (!value) { return ''; } try { const date = value instanceof Date ? value : new Date(value); if (Number.isNaN(date.getTime())) { return ''; } return date.toLocaleString('de-DE'); } catch (error) { console.warn('Ungültiges Datum:', error); return ''; } } function formatUrlForDisplay(url) { if (!url) { return ''; } try { const parsed = new URL(url); const pathname = parsed.pathname === '/' ? '' : parsed.pathname; const search = parsed.search || ''; return `${parsed.hostname}${pathname}${search}`; } catch (error) { return url; } } function normalizeRequiredProfiles(post) { if (Array.isArray(post.required_profiles) && post.required_profiles.length) { return post.required_profiles .map((value) => { const parsed = parseInt(value, 10); if (Number.isNaN(parsed)) { return null; } return Math.min(MAX_PROFILES, Math.max(1, parsed)); }) .filter(Boolean); } const parsedTarget = parseInt(post.target_count, 10); const count = Number.isNaN(parsedTarget) ? 1 : Math.min(MAX_PROFILES, Math.max(1, parsedTarget)); return Array.from({ length: count }, (_, index) => index + 1); } function normalizeChecks(checks) { if (!Array.isArray(checks)) { return []; } return checks .map((check) => { if (!check) { return null; } const parsed = parseInt(check.profile_number, 10); if (Number.isNaN(parsed)) { return null; } const profileNumber = Math.min(MAX_PROFILES, Math.max(1, parsed)); return { ...check, profile_number: profileNumber, profile_name: check.profile_name || getProfileName(profileNumber), checked_at: check.checked_at || null }; }) .filter(Boolean) .sort((a, b) => { const aTime = a.checked_at ? new Date(a.checked_at).getTime() : 0; const bTime = b.checked_at ? new Date(b.checked_at).getTime() : 0; if (aTime === bTime) { return a.profile_number - b.profile_number; } return aTime - bTime; }); } function computePostStatus(post, profileNumber = currentProfile) { const requiredProfiles = normalizeRequiredProfiles(post); const checks = normalizeChecks(post.checks); const backendStatuses = Array.isArray(post.profile_statuses) ? post.profile_statuses : []; let profileStatuses = backendStatuses .map((status) => { if (!status) { return null; } const parsed = parseInt(status.profile_number, 10); if (Number.isNaN(parsed)) { return null; } const profileNumberValue = Math.min(MAX_PROFILES, Math.max(1, parsed)); let normalizedStatus = status.status; if (normalizedStatus !== 'done' && normalizedStatus !== 'available') { normalizedStatus = 'locked'; } const check = checks.find((item) => item.profile_number === profileNumberValue) || null; return { profile_number: profileNumberValue, profile_name: status.profile_name || getProfileName(profileNumberValue), status: normalizedStatus, checked_at: status.checked_at || (check ? check.checked_at : null) || null }; }) .filter(Boolean); if (profileStatuses.length !== requiredProfiles.length) { const checksByProfile = new Map(checks.map((check) => [check.profile_number, check])); const completedSet = new Set(checks.map((check) => check.profile_number)); profileStatuses = requiredProfiles.map((value, index) => { const prerequisites = requiredProfiles.slice(0, index); const prerequisitesMet = prerequisites.every((profile) => completedSet.has(profile)); const check = checksByProfile.get(value) || null; return { profile_number: value, profile_name: getProfileName(value), status: check ? 'done' : (prerequisitesMet ? 'available' : 'locked'), checked_at: check ? check.checked_at : null }; }); } else { const checksByProfile = new Map(checks.map((check) => [check.profile_number, check])); profileStatuses = requiredProfiles.map((value) => { const status = profileStatuses.find((item) => item.profile_number === value); if (!status) { const check = checksByProfile.get(value) || null; return { profile_number: value, profile_name: getProfileName(value), status: check ? 'done' : 'locked', checked_at: check ? check.checked_at : null }; } if (status.status === 'done') { const check = checksByProfile.get(value) || null; return { ...status, checked_at: status.checked_at || (check ? check.checked_at : null) || null }; } if (status.status === 'available') { return { ...status, checked_at: status.checked_at || null }; } return { ...status, status: 'locked', checked_at: status.checked_at || null }; }); } const completedProfilesSet = new Set( profileStatuses .filter((status) => status.status === 'done') .map((status) => status.profile_number) ); const checkedCount = profileStatuses.filter((status) => status.status === 'done').length; const targetCount = profileStatuses.length; const isComplete = profileStatuses.every((status) => status.status === 'done'); const nextRequiredProfile = profileStatuses.find((status) => status.status === 'available') || null; const isCurrentProfileRequired = requiredProfiles.includes(profileNumber); const isCurrentProfileDone = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'done'); const canCurrentProfileCheck = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'available'); const waitingForStatuses = profileStatuses.filter((status) => status.profile_number < profileNumber && status.status !== 'done'); const waitingForProfiles = waitingForStatuses.map((status) => status.profile_number); const waitingForNames = waitingForStatuses.map((status) => status.profile_name); return { requiredProfiles, profileStatuses, checks, completedProfilesSet, checkedCount, targetCount, isComplete, isCurrentProfileRequired, isCurrentProfileDone, canCurrentProfileCheck, waitingForProfiles, waitingForNames, nextRequiredProfile, profileNumber }; } function applyScreenshotModalSize() { if (!screenshotModalContent || !screenshotModalImage) { return; } if (screenshotModalZoomed) { return; } if (!screenshotModalImage.src) { return; } requestAnimationFrame(() => { const padding = 48; const viewportWidth = Math.max(320, window.innerWidth * 0.95); const viewportHeight = Math.max(280, window.innerHeight * 0.92); const naturalWidth = screenshotModalImage.naturalWidth || screenshotModalImage.width || 0; const naturalHeight = screenshotModalImage.naturalHeight || screenshotModalImage.height || 0; const targetWidth = Math.min(Math.max(320, naturalWidth + padding), viewportWidth); const targetHeight = Math.min(Math.max(260, naturalHeight + padding), viewportHeight); screenshotModalContent.style.width = `${targetWidth}px`; screenshotModalContent.style.height = `${targetHeight}px`; }); } async function fetchProfileState() { try { const response = await fetch(`${API_URL}/profile-state`); if (!response.ok) { return null; } const data = await response.json(); if (data && typeof data.profile_number !== 'undefined') { const parsed = parseInt(data.profile_number, 10); if (!Number.isNaN(parsed)) { return parsed; } } return null; } catch (error) { console.warn('Profilstatus konnte nicht geladen werden:', error); return null; } } async function pushProfileState(profileNumber) { try { await fetch(`${API_URL}/profile-state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profile_number: profileNumber }) }); } catch (error) { console.error('Profilstatus konnte nicht gespeichert werden:', error); } } function applyProfileNumber(profileNumber, { fromBackend = false } = {}) { if (!profileNumber) { return; } document.getElementById('profileSelect').value = String(profileNumber); if (currentProfile === profileNumber) { if (!fromBackend) { pushProfileState(profileNumber); } return; } currentProfile = profileNumber; localStorage.setItem('profileNumber', currentProfile); if (!fromBackend) { pushProfileState(currentProfile); } renderPosts(); } // Load profile from localStorage function loadProfile() { fetchProfileState().then((backendProfile) => { if (backendProfile) { applyProfileNumber(backendProfile, { fromBackend: true }); } else { const saved = localStorage.getItem('profileNumber'); if (saved) { applyProfileNumber(parseInt(saved, 10) || 1, { fromBackend: true }); } else { applyProfileNumber(1, { fromBackend: true }); } } }); } // Save profile to localStorage function saveProfile(profileNumber) { applyProfileNumber(profileNumber); } function startProfilePolling() { if (profilePollTimer) { clearInterval(profilePollTimer); } profilePollTimer = setInterval(async () => { const backendProfile = await fetchProfileState(); if (backendProfile && backendProfile !== currentProfile) { applyProfileNumber(backendProfile, { fromBackend: true }); } }, 5000); } // Profile selector change handler document.getElementById('profileSelect').addEventListener('change', (e) => { saveProfile(parseInt(e.target.value, 10)); }); // Tab switching document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentTab = btn.dataset.tab; renderPosts(); }); }); // Fetch all posts async function fetchPosts() { try { showLoading(); const response = await fetch(`${API_URL}/posts`); if (!response.ok) { throw new Error('Failed to fetch posts'); } posts = await response.json(); renderPosts(); } catch (error) { showError('Fehler beim Laden der Beiträge. Stelle sicher, dass das Backend läuft.'); console.error('Error fetching posts:', error); } } // Render posts function renderPosts() { hideLoading(); hideError(); const container = document.getElementById('postsContainer'); const postItems = posts.map((post) => ({ post, status: computePostStatus(post) })); let filteredItems = postItems; if (currentTab === 'pending') { filteredItems = postItems.filter((item) => item.status.canCurrentProfileCheck && !item.status.isComplete); } if (filteredItems.length === 0) { container.innerHTML = `