diff --git a/modules/fx-rates/assets/fx-rates.js b/modules/fx-rates/assets/fx-rates.js
index ad61718..84e8213 100644
--- a/modules/fx-rates/assets/fx-rates.js
+++ b/modules/fx-rates/assets/fx-rates.js
@@ -8,6 +8,8 @@
const settings = page.settings || {};
const nodes = {
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"]'),
convertResult: root.querySelector('[data-bind="convert-result"]'),
convertFrom: root.querySelector('select[name="convert_from"]'),
@@ -59,6 +61,45 @@
`).join('');
};
+ const renderHistory = (rows, currencies) => {
+ if (!nodes.historyHead || !nodes.historyBody) {
+ return;
+ }
+
+ const series = Array.isArray(currencies) ? currencies : [];
+ if (!series.length) {
+ nodes.historyHead.innerHTML = '
| Datum | Kurse |
';
+ nodes.historyBody.innerHTML = '| Keine bevorzugten Waehrungen fuer den Verlauf vorhanden. |
';
+ return;
+ }
+
+ nodes.historyHead.innerHTML = `
+
+ | Datum |
+ ${series.map((currency) => `${currency} | `).join('')}
+
+ `;
+
+ const entries = Array.isArray(rows) ? rows : [];
+ if (!entries.length) {
+ nodes.historyBody.innerHTML = `| Noch keine Verlaufsdaten vorhanden. |
`;
+ return;
+ }
+
+ nodes.historyBody.innerHTML = entries.map((entry) => `
+
+ | ${entry.label} |
+ ${series.map((currency) => {
+ const value = entry.rates?.[currency];
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
+ return '– | ';
+ }
+ return `${value.toLocaleString('de-DE', { maximumFractionDigits: 8 })} | `;
+ }).join('')}
+
+ `).join('');
+ };
+
const request = async (path, options = {}) => {
const response = await fetch(`${apiBase}${path}`, {
credentials: 'same-origin',
@@ -91,6 +132,50 @@
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 () => {
if (!nodes.convertFrom || !nodes.convertTo || !nodes.convertAmount || !nodes.convertResult) {
return;
@@ -136,5 +221,10 @@
renderFetches(page.recent_fetches || []);
loadLatest().catch(() => {});
+ loadHistory().catch(() => {
+ renderHistory([], preferredCurrencies.filter((currency) => currency !== String(
+ settings.display_base_currency || settings.default_base_currency || 'EUR'
+ ).trim().toUpperCase()));
+ });
calculateConversion().catch(() => {});
})();
diff --git a/modules/fx-rates/pages/index.php b/modules/fx-rates/pages/index.php
index fbf7138..043dbc7 100644
--- a/modules/fx-rates/pages/index.php
+++ b/modules/fx-rates/pages/index.php
@@ -87,11 +87,12 @@ $pageData = json_encode([
Letzte Kurse
-
Letzter gespeicherter Snapshot fuer = e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')) ?>.
+
Letzter gespeicherter Snapshot, umgerechnet auf = e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')) ?>.
@@ -109,6 +110,24 @@ $pageData = json_encode([
+
+
Kursverlauf
+
Chronologischer Verlauf der bevorzugten Waehrungen relativ zur Anzeige-Basiswaehrung.
+
+
+
+
+ | Datum |
+ Kurse |
+
+
+
+ | Noch keine Verlaufsdaten geladen. |
+
+
+
+
+