/* ============================================================ BAN YARO — Hunde-Filme Seiten-Modul: Film-Datenbank, Promi-Hunde, Hund des Monats. ============================================================ */ window.Page_movies = (() => { // ---------------------------------------------------------- // MODUL-STATE // ---------------------------------------------------------- let _container = null; let _appState = null; let _filme = []; let _activeTab = 'filme'; let _filter = 'alle'; // ---------------------------------------------------------- // INIT // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; await _render(); } // ---------------------------------------------------------- // REFRESH // ---------------------------------------------------------- async function refresh() { _filme = []; await _render(); } // ---------------------------------------------------------- // RENDER — Haupt-Layout mit Tabs // ---------------------------------------------------------- async function _render() { _container.innerHTML = `
`; _container.querySelectorAll('.movies-tab').forEach(btn => { btn.addEventListener('click', () => { _activeTab = btn.dataset.tab; _container.querySelectorAll('.movies-tab').forEach(b => b.classList.remove('movies-tab--active')); btn.classList.add('movies-tab--active'); _renderTab(); }); }); await _renderTab(); } async function _renderTab() { const content = _container.querySelector('#movies-tab-content'); if (!content) return; content.innerHTML = UI.skeleton(3); if (_activeTab === 'filme') await _renderFilme(content); if (_activeTab === 'promis') _renderPromis(content); if (_activeTab === 'hdm') await _renderHundDesMonats(content); } // ---------------------------------------------------------- // TAB 1: FILME // ---------------------------------------------------------- async function _renderFilme(content) { try { _filme = await API.get('/movies/filme'); } catch { content.innerHTML = UI.emptyState({ icon: 'film-slate', title: 'Filme konnten nicht geladen werden', text: 'Bitte versuche es erneut.' }); return; } content.innerHTML = `
`; content.querySelectorAll('.movies-filter-btn').forEach(btn => { btn.addEventListener('click', () => { _filter = btn.dataset.filter; content.querySelectorAll('.movies-filter-btn').forEach(b => b.classList.remove('movies-filter-btn--active')); btn.classList.add('movies-filter-btn--active'); _renderMovieGrid(content.querySelector('#movie-grid')); }); }); _renderMovieGrid(content.querySelector('#movie-grid')); } function _renderMovieGrid(grid) { if (!grid) return; let list = [..._filme]; if (_filter === 'stirbt') list = list.filter(f => f.stirbt_der_hund); if (_filter === 'ueberlebt') list = list.filter(f => !f.stirbt_der_hund); if (_filter === 'top') list = list.filter(f => f.bewertung_avg >= 4.0); if (list.length === 0) { grid.innerHTML = `
Keine Filme für diesen Filter.
`; return; } grid.innerHTML = list.map(f => _movieCard(f)).join(''); grid.querySelectorAll('.movie-card').forEach(card => { card.addEventListener('click', (e) => { if (e.target.closest('.movie-star-rating')) return; const id = card.dataset.filmId; const film = _filme.find(f => f.id === id); if (film) _openMovieModal(film); }); }); _bindStarRatings(grid); } function _movieCard(film) { const stirbt = film.stirbt_der_hund; const tag = stirbt ? `
ACHTUNG: Der Hund stirbt
` : `
Der Hund überlebt
`; const stars = _starsHtml(film.bewertung_avg, film.id, film.user_rating, false); return `
${film.bild_emoji}
${_esc(film.titel)} (${film.jahr})
${_esc(film.genre)}
${_esc(film.hund_rasse)}
${tag}
${stars}
`; } function _openMovieModal(film) { const stirbt = film.stirbt_der_hund; const bannerClass = stirbt ? 'movie-tag-stirbt' : 'movie-tag-ueberlebt'; const bannerText = stirbt ? ' ACHTUNG: Der Hund stirbt!' : ' Der Hund überlebt'; const stars = _starsHtml(film.bewertung_avg, film.id, film.user_rating, true); const loginHint = !_appState.user ? `

Anmelden um zu bewerten

` : ''; const body = `
${film.bild_emoji}
${_esc(film.genre)} ${_esc(film.hund_rasse)} ${film.jahr}
${bannerText}

${_esc(film.beschreibung)}

Community-Bewertung:
${loginHint} `; UI.modal.open({ title: film.titel, body }); const starsEl = document.getElementById(`modal-stars-${film.id}`); if (starsEl && _appState.user) { _bindStarRatingsEl(starsEl, film.id, true); } } function _starsHtml(avg, filmId, userRating, interactive) { const filled = Math.round(avg); const stars = [1,2,3,4,5].map(i => { const active = i <= (userRating || filled) ? ' movie-star--active' : ''; return ``; }).join(''); return `
${stars} ${avg}
`; } function _bindStarRatings(container) { container.querySelectorAll('.movie-star-rating').forEach(el => { _bindStarRatingsEl(el, el.dataset.filmId, false); }); } function _bindStarRatingsEl(el, filmId, inModal) { if (!_appState.user) return; const stars = el.querySelectorAll('.movie-star'); stars.forEach(star => { star.addEventListener('mouseenter', () => { const val = parseInt(star.dataset.val); stars.forEach((s, i) => s.classList.toggle('movie-star--active', i < val)); }); star.addEventListener('mouseleave', () => { const film = _filme.find(f => f.id === filmId); const cur = film?.user_rating || 0; stars.forEach((s, i) => s.classList.toggle('movie-star--active', i < cur)); }); star.addEventListener('click', async () => { const val = parseInt(star.dataset.val); try { const res = await API.post(`/movies/filme/${filmId}/vote`, { bewertung: val }); // Update in _filme array const idx = _filme.findIndex(f => f.id === filmId); if (idx !== -1) { _filme[idx].user_rating = val; _filme[idx].bewertung_avg = res.bewertung_avg; _filme[idx].bewertung_cnt = res.bewertung_cnt; } // Update star display stars.forEach((s, i) => s.classList.toggle('movie-star--active', i < val)); const avgEl = el.querySelector('.movie-star-avg') || (inModal ? document.getElementById(`modal-avg-${filmId}`) : null); if (el.querySelector('.movie-star-avg')) { el.querySelector('.movie-star-avg').textContent = res.bewertung_avg; } if (inModal) { const avgInfo = document.getElementById(`modal-avg-${filmId}`); if (avgInfo) avgInfo.textContent = `Ø ${res.bewertung_avg} von ${res.bewertung_cnt} Bewertungen`; } UI.toast.success('Bewertung gespeichert!'); } catch { UI.toast.error('Bewertung konnte nicht gespeichert werden.'); } }); }); } // ---------------------------------------------------------- // TAB 2: BERÜHMTHEITEN (hardcoded, kein Backend) // ---------------------------------------------------------- const PROMIS = [ { name: "Hachikō", rasse: "Akita Inu", bekannt_fuer: "9 Jahre lang täglich auf seinen verstorbenen Herrchen am Bahnhof Shibuya gewartet. Statue in Tokio.", emoji: "🗿" }, { name: "Rin Tin Tin", rasse: "Deutscher Schäferhund", bekannt_fuer: "Filmhund der 1920er-Jahre. Rettete Warner Bros. vor dem Bankrott. Erster Hundestar Hollywoods.", emoji: "🎬" }, { name: "Laika", rasse: "Mischling", bekannt_fuer: "Erstes Lebewesen im Weltall (Sputnik 2, 1957). Wurde zur sowjetischen Weltraumpionierin.", emoji: "🚀" }, { name: "Endal", rasse: "Labrador", bekannt_fuer: "Assistenzhund in England. Erster Hund der eine EC-Karte am Geldautomaten benutzte.", emoji: "💳" }, { name: "Barry", rasse: "Bernhardiner", bekannt_fuer: "Legendärer Rettungshund der Alpen (1800–1812). Soll 40 Menschen das Leben gerettet haben.", emoji: "🏔️" }, { name: "Greyfriars Bobby", rasse: "Skye Terrier", bekannt_fuer: "14 Jahre lang das Grab seines Herrchens in Edinburgh bewacht. Statue und Pub benannt nach ihm.", emoji: "⛪" }, ]; function _renderPromis(content) { content.innerHTML = `
${PROMIS.map(p => `
${p.emoji}
${_esc(p.name)}
${_esc(p.rasse)}
${_esc(p.bekannt_fuer)}
`).join('')}
`; } // ---------------------------------------------------------- // TAB 3: HUND DES MONATS // ---------------------------------------------------------- async function _renderHundDesMonats(content) { let data; try { data = await API.get('/movies/hund-des-monats'); } catch { content.innerHTML = UI.emptyState({ icon: '🏆', title: 'Fehler beim Laden', text: 'Bitte versuche es erneut.' }); return; } const [year, month] = data.monat.split('-'); const monthName = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }) .format(new Date(+year, +month - 1, 1)); let voteSection = ''; if (_appState.user && _appState.dogs?.length > 0) { const voteCards = _appState.dogs.map(dog => { const isVoted = data.user_vote === dog.id; const av = dog.foto_url ? `${_esc(dog.name)}` : `${_esc(dog.name.charAt(0).toUpperCase())}`; return `
${av}
${_esc(dog.name)}
${dog.rasse ? `
${_esc(dog.rasse)}
` : ''}
`; }).join(''); voteSection = `

Für welchen deiner Hunde möchtest du abstimmen?

${voteCards}
`; } else if (!_appState.user) { voteSection = `

Anmelden um für deinen Hund abzustimmen.

`; } const topList = data.top.length > 0 ? data.top.slice(0, 5).map((dog, i) => { const medal = ['🥇','🥈','🥉','4️⃣','5️⃣'][i] || `${i+1}.`; const av = dog.foto_url ? `${_esc(dog.name)}` : `${_esc(dog.name.charAt(0).toUpperCase())}`; const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : ''; return `
${medal}
${av}
${_esc(dog.name)}
${dog.rasse ? `
${_esc(dog.rasse)}
` : ''} ${vorname ? `
von ${vorname}
` : ''}
${dog.stimmen}
`; }).join('') : `

Noch keine Stimmen diesen Monat. Sei der Erste!

`; content.innerHTML = `
🏆

Hund des Monats

${_esc(monthName)}
${voteSection}

Top 5 diesen Monat

${topList}
`; // Login-Link content.querySelector('#hdm-login-link')?.addEventListener('click', e => { e.preventDefault(); App.navigate('settings'); }); // Vote-Buttons content.querySelectorAll('.hdm-vote-btn').forEach(btn => { btn.addEventListener('click', async () => { const dogId = parseInt(btn.dataset.dogId); await UI.asyncButton(btn, async () => { try { await API.post('/movies/hund-des-monats/vote', { dog_id: dogId }); UI.toast.success('Stimme abgegeben!'); // Refresh the tab await _renderHundDesMonats(content); } catch (err) { UI.toast.error(err.message || 'Fehler beim Abstimmen.'); } }); }); }); } // ---------------------------------------------------------- // HELPER // ---------------------------------------------------------- function _esc(str) { if (!str && str !== 0) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // ---------------------------------------------------------- // PUBLIC // ---------------------------------------------------------- return { init, refresh }; })();