asdsad
All checks were successful
Deploy / deploy-production (push) Has been skipped
Deploy / deploy-staging (push) Successful in 5s

This commit is contained in:
2026-04-29 02:38:09 +02:00
parent 79872f3337
commit ef87dc6cd3
2 changed files with 111 additions and 2 deletions

View File

@@ -8,6 +8,8 @@
const settings = page.settings || {}; const settings = page.settings || {};
const nodes = { const nodes = {
ratesBody: root.querySelector('[data-bind="rates-body"]'), ratesBody: root.querySelector('[data-bind="rates-body"]'),
historyHead: root.querySelector('[data-bind="history-head"]'),
historyBody: root.querySelector('[data-bind="history-body"]'),
fetchesBody: root.querySelector('[data-bind="fetches-body"]'), fetchesBody: root.querySelector('[data-bind="fetches-body"]'),
convertResult: root.querySelector('[data-bind="convert-result"]'), convertResult: root.querySelector('[data-bind="convert-result"]'),
convertFrom: root.querySelector('select[name="convert_from"]'), convertFrom: root.querySelector('select[name="convert_from"]'),
@@ -59,6 +61,45 @@
`).join(''); `).join('');
}; };
const renderHistory = (rows, currencies) => {
if (!nodes.historyHead || !nodes.historyBody) {
return;
}
const series = Array.isArray(currencies) ? currencies : [];
if (!series.length) {
nodes.historyHead.innerHTML = '<tr><th>Datum</th><th>Kurse</th></tr>';
nodes.historyBody.innerHTML = '<tr><td colspan="2">Keine bevorzugten Waehrungen fuer den Verlauf vorhanden.</td></tr>';
return;
}
nodes.historyHead.innerHTML = `
<tr>
<th>Datum</th>
${series.map((currency) => `<th>${currency}</th>`).join('')}
</tr>
`;
const entries = Array.isArray(rows) ? rows : [];
if (!entries.length) {
nodes.historyBody.innerHTML = `<tr><td colspan="${series.length + 1}">Noch keine Verlaufsdaten vorhanden.</td></tr>`;
return;
}
nodes.historyBody.innerHTML = entries.map((entry) => `
<tr>
<td>${entry.label}</td>
${series.map((currency) => {
const value = entry.rates?.[currency];
if (typeof value !== 'number' || !Number.isFinite(value)) {
return '<td></td>';
}
return `<td>${value.toLocaleString('de-DE', { maximumFractionDigits: 8 })}</td>`;
}).join('')}
</tr>
`).join('');
};
const request = async (path, options = {}) => { const request = async (path, options = {}) => {
const response = await fetch(`${apiBase}${path}`, { const response = await fetch(`${apiBase}${path}`, {
credentials: 'same-origin', credentials: 'same-origin',
@@ -91,6 +132,50 @@
return data; return data;
}; };
const loadHistory = async () => {
const base = String(
settings.display_base_currency || settings.default_base_currency || 'EUR'
).trim().toUpperCase();
const historyCurrencies = preferredCurrencies.filter((currency) => currency !== base);
if (!historyCurrencies.length) {
renderHistory([], []);
return;
}
const histories = await Promise.all(historyCurrencies.map(async (currency) => {
const query = new URLSearchParams({ from: base, to: currency, limit: '20' });
const rows = await request(`/history?${query.toString()}`);
return { currency, rows: Array.isArray(rows) ? rows : [] };
}));
const byDate = new Map();
histories.forEach(({ currency, rows }) => {
rows.forEach((row) => {
const key = String(row?.fetched_at || row?.rate_date || '').trim();
if (!key) {
return;
}
if (!byDate.has(key)) {
byDate.set(key, {
sortKey: key,
label: row?.fetched_at_display || row?.fetched_at || row?.rate_date || key,
rates: {},
});
}
const entry = byDate.get(key);
if (entry && typeof row?.rate === 'number' && Number.isFinite(row.rate)) {
entry.rates[currency] = row.rate;
}
});
});
const mergedRows = Array.from(byDate.values())
.sort((left, right) => String(left.sortKey).localeCompare(String(right.sortKey)));
renderHistory(mergedRows, historyCurrencies);
};
const calculateConversion = async () => { const calculateConversion = async () => {
if (!nodes.convertFrom || !nodes.convertTo || !nodes.convertAmount || !nodes.convertResult) { if (!nodes.convertFrom || !nodes.convertTo || !nodes.convertAmount || !nodes.convertResult) {
return; return;
@@ -136,5 +221,10 @@
renderFetches(page.recent_fetches || []); renderFetches(page.recent_fetches || []);
loadLatest().catch(() => {}); loadLatest().catch(() => {});
loadHistory().catch(() => {
renderHistory([], preferredCurrencies.filter((currency) => currency !== String(
settings.display_base_currency || settings.default_base_currency || 'EUR'
).trim().toUpperCase()));
});
calculateConversion().catch(() => {}); calculateConversion().catch(() => {});
})(); })();

View File

@@ -87,11 +87,12 @@ $pageData = json_encode([
<div class="fx-card-head"> <div class="fx-card-head">
<div> <div>
<h2>Letzte Kurse</h2> <h2>Letzte Kurse</h2>
<p>Letzter gespeicherter Snapshot fuer <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')) ?>.</p> <p>Letzter gespeicherter Snapshot, umgerechnet auf <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')) ?>.</p>
</div> </div>
<div class="fx-card-meta"> <div class="fx-card-meta">
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div> <div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
<div><strong>Basis:</strong> <?= e((string) ($latest['base_currency'] ?? $settings['default_base_currency'] ?? '')) ?></div> <div><strong>Anzeige-Basis:</strong> <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? '')) ?></div>
<div><strong>Snapshot-Basis:</strong> <?= e((string) ($latest['base_currency'] ?? '')) ?></div>
</div> </div>
</div> </div>
<div class="fx-table-wrap"> <div class="fx-table-wrap">
@@ -109,6 +110,24 @@ $pageData = json_encode([
</div> </div>
</div> </div>
<div class="fx-card">
<h2>Kursverlauf</h2>
<p>Chronologischer Verlauf der bevorzugten Waehrungen relativ zur Anzeige-Basiswaehrung.</p>
<div class="fx-table-wrap">
<table class="fx-table">
<thead data-bind="history-head">
<tr>
<th>Datum</th>
<th>Kurse</th>
</tr>
</thead>
<tbody data-bind="history-body">
<tr><td colspan="2">Noch keine Verlaufsdaten geladen.</td></tr>
</tbody>
</table>
</div>
</div>
<div class="fx-card"> <div class="fx-card">
<h2>Letzte Abrufe</h2> <h2>Letzte Abrufe</h2>
<div class="fx-table-wrap"> <div class="fx-table-wrap">