(() => { const root = document.getElementById('fx-rates-app'); if (!root) { return; } const page = JSON.parse(root.dataset.page || '{}'); const settings = page.settings || {}; const nodes = { ratesBody: root.querySelector('[data-bind="rates-body"]'), fetchesBody: root.querySelector('[data-bind="fetches-body"]'), convertResult: root.querySelector('[data-bind="convert-result"]'), convertFrom: root.querySelector('select[name="convert_from"]'), convertTo: root.querySelector('select[name="convert_to"]'), convertAmount: root.querySelector('input[name="convert_amount"]'), }; const apiBase = '/api/fx-rates/v1'; const preferredCurrencies = Array.isArray(page.preferred_currencies) ? page.preferred_currencies .map((item) => String(item || '').trim().toUpperCase()) .filter(Boolean) : []; const renderSnapshot = (snapshot) => { const rates = snapshot && snapshot.rates ? snapshot.rates : null; const entries = rates ? Object.entries(rates) : []; if (!nodes.ratesBody) { return; } if (!entries.length) { nodes.ratesBody.innerHTML = 'Noch keine Wechselkurse fuer die ausgewaehlten Waehrungen gespeichert.'; return; } nodes.ratesBody.innerHTML = entries.map(([code, rate]) => { const formatted = typeof rate === 'number' ? rate.toLocaleString('de-DE', { maximumFractionDigits: 8 }) : 'n/a'; return `${code}${formatted}`; }).join(''); }; const renderFetches = (fetches) => { if (!nodes.fetchesBody) { return; } const entries = Array.isArray(fetches) ? fetches : []; if (!entries.length) { nodes.fetchesBody.innerHTML = 'Noch keine Abrufe vorhanden.'; return; } nodes.fetchesBody.innerHTML = entries.map((entry) => ` ${entry?.fetched_at_display || entry?.fetched_at || ''} ${entry?.base_currency || ''} ${entry?.provider || ''} `).join(''); }; const request = async (path, options = {}) => { const response = await fetch(`${apiBase}${path}`, { credentials: 'same-origin', headers: { Accept: 'application/json', ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...(options.headers || {}), }, ...options, }); const payload = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(payload?.context?.message || payload?.error || `HTTP ${response.status}`); } return payload.data; }; const loadLatest = async () => { const base = String( settings.display_base_currency || settings.default_base_currency || 'EUR' ).trim().toUpperCase(); const query = new URLSearchParams(); query.set('base', base); if (preferredCurrencies.length) { query.set('symbols', preferredCurrencies.join(',')); } const data = await request(`/latest?${query.toString()}`); renderSnapshot(data); return data; }; const calculateConversion = async () => { if (!nodes.convertFrom || !nodes.convertTo || !nodes.convertAmount || !nodes.convertResult) { return; } const from = String(nodes.convertFrom.value || '').trim().toUpperCase(); const to = String(nodes.convertTo.value || '').trim().toUpperCase(); const amount = Number(nodes.convertAmount.value || '0'); if (!from || !to || !Number.isFinite(amount)) { nodes.convertResult.textContent = 'Bitte Quellwaehrung, Zielwaehrung und Betrag angeben.'; return; } if (from === to) { nodes.convertResult.textContent = `${amount.toLocaleString('de-DE', { maximumFractionDigits: 8 })} ${from} = ${amount.toLocaleString('de-DE', { maximumFractionDigits: 8 })} ${to}`; return; } try { const query = new URLSearchParams({ from, to }); const data = await request(`/rate?${query.toString()}`); const rate = Number(data?.rate || 0); if (!Number.isFinite(rate) || rate <= 0) { throw new Error('Kein Kurs verfuegbar.'); } const converted = amount * rate; nodes.convertResult.textContent = `${amount.toLocaleString('de-DE', { maximumFractionDigits: 8 })} ${from} = ${converted.toLocaleString('de-DE', { maximumFractionDigits: 8 })} ${to} | Kurs ${rate.toLocaleString('de-DE', { maximumFractionDigits: 8 })}`; } catch (error) { nodes.convertResult.textContent = error.message || 'Umrechnung konnte nicht berechnet werden.'; } }; [nodes.convertFrom, nodes.convertTo, nodes.convertAmount].forEach((node) => { node?.addEventListener('change', () => { calculateConversion().catch(() => {}); }); node?.addEventListener('input', () => { calculateConversion().catch(() => {}); }); }); renderFetches(page.recent_fetches || []); loadLatest().catch(() => {}); calculateConversion().catch(() => {}); })();