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

This commit is contained in:
2026-05-04 03:03:30 +02:00
parent c81e89dc3f
commit c3ba24e939
4 changed files with 318 additions and 298 deletions

View File

@@ -117,155 +117,171 @@ $tabs = [
['label' => 'Waehrungen', 'href' => '/module/fx-rates/currencies', 'active' => true],
];
?>
<?= module_shell_header('fx-rates', [
'title' => 'Waehrungskurse',
'tabs' => $tabs,
'actions' => [
['label' => 'Setup', 'href' => '/modules/setup/fx-rates', 'variant' => 'secondary', 'size' => 'sm'],
],
]) ?>
<div id="fx-rates-currencies" data-page='<?= e(is_string($currencyPageData) ? $currencyPageData : '{}') ?>'>
<div class="fx-stack">
<div class="fx-card">
<?php if ($notice !== ''): ?>
<div class="fx-message is-success"><?= e($notice) ?></div>
<?php elseif ($error !== ''): ?>
<div class="fx-message is-error"><?= e($error) ?></div>
<?php endif; ?>
<h2>Waehrungs-Update</h2>
<p>Auswahl wird in den FX-Rates-Einstellungen gespeichert und steht damit auf Handy und Desktop gleich zur Verfuegung.</p>
<div class="fx-action-row">
<form method="post">
<input type="hidden" name="fx_action" value="refresh_rates">
<button type="submit" class="fx-button fx-button--accent">Alle Wechselkurse aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="fx_action" value="sync_catalog">
<button type="submit" class="fx-button fx-button--ghost">Waehrungskatalog sync</button>
</form>
</div>
<div class="fx-mini-grid">
<div class="fx-mini-card">
<div class="fx-mini-label">Fiat</div>
<div><?= e((string) $fiatCount) ?> Waehrungen</div>
</div>
<div class="fx-mini-card">
<div class="fx-mini-label">Krypto</div>
<div><?= e((string) $cryptoCount) ?> Waehrungen</div>
</div>
</div>
<div class="fx-field-label">Bevorzugte Waehrungen fuer Anzeige</div>
<div class="fx-currency-selection-row">
<div class="fx-token-list fx-token-list--inline" data-fx-token-list></div>
<div class="fx-currency-search">
<input type="text" class="fx-input" value="" placeholder="Waehrung hinzufuegen: EUR, USD, DOGE oder Euro" autocomplete="off" data-fx-search-input>
</div>
</div>
<div class="fx-suggestion-list" data-fx-suggestions></div>
<div class="fx-field fx-currency-search">
<label class="fx-field-label" for="fx-display-base-select">Darstellung auf Basis von</label>
<select id="fx-display-base-select" class="fx-select" data-fx-display-base-select></select>
</div>
<div class="fx-save-row">
<form method="post">
<input type="hidden" name="fx_action" value="save_selection">
<input type="hidden" name="display_base_currency" value="<?= e($displayBaseCurrency) ?>" data-fx-display-base-hidden>
<div data-fx-hidden-preferred></div>
<button type="submit" class="fx-button fx-button--ghost">Auswahl speichern</button>
</form>
</div>
</div>
<div class="fx-card">
<div class="fx-card-head">
<div>
<h2>Letzte 15 Kurs-Uploads</h2>
<p>Zeigt die zuletzt gespeicherten Wechselkurse aus der Datenbank.</p>
</div>
<div class="fx-card-meta">
<div><strong>Anzeige-Basis:</strong> <?= e($displayBaseCurrency) ?></div>
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
</div>
</div>
<div class="fx-table-wrap">
<table class="fx-table">
<thead>
<tr>
<th>Zeit</th>
<?php foreach ($tableCurrencies as $currency): ?>
<th><?= e((string) $currency) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if ($recentFetches === []): ?>
<tr><td colspan="<?= 1 + count($tableCurrencies) ?>">Noch keine Abrufe vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($recentFetches as $fetch): ?>
<?php
$fetchBaseCurrency = strtoupper(trim((string) ($fetch['base_currency'] ?? '')));
$snapshot = $service->snapshotByFetchId((int) ($fetch['id'] ?? 0), $fetchBaseCurrency, $tableCurrencies);
$originalRates = is_array($snapshot['rates'] ?? null) ? $snapshot['rates'] : [];
$displayBaseRate = $displayBaseCurrency === $fetchBaseCurrency
? 1.0
: (is_numeric($originalRates[$displayBaseCurrency] ?? null) ? (float) $originalRates[$displayBaseCurrency] : null);
$tableRates = [];
foreach ($tableCurrencies as $currency) {
$currency = strtoupper(trim((string) $currency));
if ($currency === '') {
continue;
}
if ($currency === $displayBaseCurrency) {
$tableRates[$currency] = 1.0;
continue;
}
if ($displayBaseRate === null || $displayBaseRate <= 0) {
$tableRates[$currency] = null;
continue;
}
if ($currency === $fetchBaseCurrency) {
$tableRates[$currency] = 1 / $displayBaseRate;
continue;
}
$rawRate = $originalRates[$currency] ?? null;
$tableRates[$currency] = is_numeric($rawRate) ? ((float) $rawRate / $displayBaseRate) : null;
}
$infoTitle = sprintf(
'Basis: %s | Provider: %s | Ausloeser: %s',
(string) ($fetch['base_currency'] ?? '-'),
(string) ($fetch['provider'] ?? '-'),
(string) ($fetch['trigger_source_label'] ?? $fetch['trigger_source'] ?? '-')
);
?>
<tr>
<td>
<div class="fx-history-date">
<span><?= e((string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?></span>
<button
type="button"
class="fx-info-button"
title="<?= e($infoTitle) ?>"
aria-label="<?= e('Abrufinfo fuer ' . (string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?>"
>i</button>
</div>
</td>
<?php foreach ($tableCurrencies as $currency): ?>
<?php $value = $tableRates[(string) $currency] ?? null; ?>
<td><?= is_numeric($value) ? e(number_format((float) $value, 8, ',', '')) : '' ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<div class="submenu-box">
<div class="fx-submenu-row">
<nav class="module-tabs" aria-label="Unterseiten von Waehrungskurse">
<?php foreach ($tabs as $tab): ?>
<a
class="<?= !empty($tab['active']) ? 'module-button module-button--tab-active' : 'module-button module-button--tab' ?>"
href="<?= e((string) $tab['href']) ?>"
><?= e((string) $tab['label']) ?></a>
<?php endforeach; ?>
</nav>
<div class="module-submenu-actions">
<a class="module-button module-button--secondary module-button--small" href="/modules/setup/fx-rates">Setup</a>
</div>
</div>
</div>
<?php if ($notice !== ''): ?>
<section class="section-box">
<div class="fx-message is-success"><?= e($notice) ?></div>
</section>
<?php elseif ($error !== ''): ?>
<section class="section-box">
<div class="fx-message is-error"><?= e($error) ?></div>
</section>
<?php endif; ?>
<section class="section-box">
<div class="fx-section-head">
<div>
<h2>Waehrungs-Update</h2>
<p>Auswahl wird in den Waehrungskurs-Einstellungen gespeichert und steht damit auf Handy und Desktop gleich zur Verfuegung.</p>
</div>
</div>
<div class="fx-action-row">
<form method="post">
<input type="hidden" name="fx_action" value="refresh_rates">
<button type="submit" class="module-button module-button--primary">Alle Wechselkurse aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="fx_action" value="sync_catalog">
<button type="submit" class="module-button module-button--ghost">Waehrungskatalog sync</button>
</form>
</div>
</section>
<div class="fx-card-grid">
<section class="card-box">
<div class="fx-mini-label">Fiat</div>
<div class="fx-card-value"><?= e((string) $fiatCount) ?> Waehrungen</div>
</section>
<section class="card-box">
<div class="fx-mini-label">Krypto</div>
<div class="fx-card-value"><?= e((string) $cryptoCount) ?> Waehrungen</div>
</section>
</div>
<section class="section-box">
<div class="fx-field-label">Bevorzugte Waehrungen fuer Anzeige</div>
<div class="fx-currency-selection-row">
<div class="fx-token-list fx-token-list--inline" data-fx-token-list></div>
<div class="fx-currency-search">
<input type="text" class="fx-input" value="" placeholder="Waehrung hinzufuegen: EUR, USD, DOGE oder Euro" autocomplete="off" data-fx-search-input>
</div>
</div>
<div class="fx-suggestion-list" data-fx-suggestions></div>
<div class="fx-field fx-currency-search">
<label class="fx-field-label" for="fx-display-base-select">Darstellung auf Basis von</label>
<select id="fx-display-base-select" class="fx-select" data-fx-display-base-select></select>
</div>
<div class="fx-save-row">
<form method="post">
<input type="hidden" name="fx_action" value="save_selection">
<input type="hidden" name="display_base_currency" value="<?= e($displayBaseCurrency) ?>" data-fx-display-base-hidden>
<div data-fx-hidden-preferred></div>
<button type="submit" class="module-button module-button--ghost">Auswahl speichern</button>
</form>
</div>
</section>
<section class="section-box">
<div class="fx-card-head">
<div>
<h2>Letzte 15 Kurs-Uploads</h2>
<p>Zeigt die zuletzt gespeicherten Wechselkurse aus der Datenbank.</p>
</div>
<div class="fx-card-meta">
<div><strong>Anzeige-Basis:</strong> <?= e($displayBaseCurrency) ?></div>
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
</div>
</div>
<div class="fx-table-wrap">
<table class="fx-table">
<thead>
<tr>
<th>Zeit</th>
<?php foreach ($tableCurrencies as $currency): ?>
<th><?= e((string) $currency) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if ($recentFetches === []): ?>
<tr><td colspan="<?= 1 + count($tableCurrencies) ?>">Noch keine Abrufe vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($recentFetches as $fetch): ?>
<?php
$fetchBaseCurrency = strtoupper(trim((string) ($fetch['base_currency'] ?? '')));
$snapshot = $service->snapshotByFetchId((int) ($fetch['id'] ?? 0), $fetchBaseCurrency, $tableCurrencies);
$originalRates = is_array($snapshot['rates'] ?? null) ? $snapshot['rates'] : [];
$displayBaseRate = $displayBaseCurrency === $fetchBaseCurrency
? 1.0
: (is_numeric($originalRates[$displayBaseCurrency] ?? null) ? (float) $originalRates[$displayBaseCurrency] : null);
$tableRates = [];
foreach ($tableCurrencies as $currency) {
$currency = strtoupper(trim((string) $currency));
if ($currency === '') {
continue;
}
if ($currency === $displayBaseCurrency) {
$tableRates[$currency] = 1.0;
continue;
}
if ($displayBaseRate === null || $displayBaseRate <= 0) {
$tableRates[$currency] = null;
continue;
}
if ($currency === $fetchBaseCurrency) {
$tableRates[$currency] = 1 / $displayBaseRate;
continue;
}
$rawRate = $originalRates[$currency] ?? null;
$tableRates[$currency] = is_numeric($rawRate) ? ((float) $rawRate / $displayBaseRate) : null;
}
$infoTitle = sprintf(
'Basis: %s | Provider: %s | Ausloeser: %s',
(string) ($fetch['base_currency'] ?? '-'),
(string) ($fetch['provider'] ?? '-'),
(string) ($fetch['trigger_source_label'] ?? $fetch['trigger_source'] ?? '-')
);
?>
<tr>
<td>
<div class="fx-history-date">
<span><?= e((string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?></span>
<button
type="button"
class="fx-info-button"
title="<?= e($infoTitle) ?>"
aria-label="<?= e('Abrufinfo fuer ' . (string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?>"
>i</button>
</div>
</td>
<?php foreach ($tableCurrencies as $currency): ?>
<?php $value = $tableRates[(string) $currency] ?? null; ?>
<td><?= is_numeric($value) ? e(number_format((float) $value, 8, ',', '')) : '' ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
</div>
<?= module_shell_footer() ?>

View File

@@ -58,80 +58,99 @@ $pageData = json_encode([
'preferred_currencies' => $preferredCurrencies,
'recent_fetches' => $recentFetches,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$tabs = [
['label' => 'Ueberblick', 'href' => '/module/fx-rates', 'active' => true],
['label' => 'Waehrungen', 'href' => '/module/fx-rates/currencies'],
];
?>
<?= module_shell_header('fx-rates', [
'title' => 'Waehrungskurse',
'actions' => [
['label' => 'Setup', 'href' => '/modules/setup/fx-rates', 'variant' => 'secondary', 'size' => 'sm'],
['label' => 'Aktuelle Kurse abrufen', 'href' => '/module/fx-rates?refresh=1', 'variant' => 'secondary', 'size' => 'sm'],
],
]) ?>
<div id="fx-rates-app" data-page='<?= e(is_string($pageData) ? $pageData : '{}') ?>'>
<div class="fx-stack">
<div class="fx-card">
<?php if ($notice !== ''): ?>
<div class="fx-message is-success"><?= e($notice) ?></div>
<?php elseif ($error !== ''): ?>
<div class="fx-message is-error"><?= e($error) ?></div>
<?php endif; ?>
<p class="fx-api-note">
API-Self-Describe-Endpoint:
<a href="<?= e($apiDescribeUrl) ?>" target="_blank" rel="noopener noreferrer"><?= e($apiDescribeUrl) ?></a>
</p>
<h2>Umrechnung</h2>
<p>Umrechnung auf Basis des letzten verfuegbaren Kurses zwischen den bevorzugten Waehrungen.</p>
<div class="fx-form-grid">
<label>
<span>Quellwaehrung</span>
<select name="convert_from">
<?php foreach ($preferredCurrencies as $currency): ?>
<option value="<?= e((string) $currency) ?>"><?= e((string) $currency) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
<span>Zielwaehrung</span>
<select name="convert_to">
<?php foreach ($preferredCurrencies as $currency): ?>
<option value="<?= e((string) $currency) ?>" <?= (string) $currency === (string) ($preferredCurrencies[1] ?? $preferredCurrencies[0] ?? '') ? 'selected' : '' ?>><?= e((string) $currency) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
<span>Betrag</span>
<input type="number" name="convert_amount" min="0" step="0.00000001" value="1">
</label>
</div>
<div class="fx-convert-result" data-bind="convert-result">Noch keine Umrechnung berechnet.</div>
</div>
<div class="fx-card">
<div class="fx-card-head">
<div>
<h2>Kursverlauf</h2>
<p>Neueste Abrufe zuerst. Verlauf der bevorzugten Waehrungen relativ zur Anzeige-Basiswaehrung.</p>
</div>
<div class="fx-card-meta">
<div><strong>Anzeige-Basis:</strong> <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? '')) ?></div>
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
</div>
</div>
<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 class="submenu-box">
<div class="fx-submenu-row">
<nav class="module-tabs" aria-label="Unterseiten von Waehrungskurse">
<?php foreach ($tabs as $tab): ?>
<a
class="<?= !empty($tab['active']) ? 'module-button module-button--tab-active' : 'module-button module-button--tab' ?>"
href="<?= e((string) $tab['href']) ?>"
><?= e((string) $tab['label']) ?></a>
<?php endforeach; ?>
</nav>
<div class="module-submenu-actions">
<a class="module-button module-button--secondary module-button--small" href="/modules/setup/fx-rates">Setup</a>
<a class="module-button module-button--secondary module-button--small" href="/module/fx-rates?refresh=1">Aktuelle Kurse abrufen</a>
</div>
</div>
</div>
</div>
<?= module_shell_footer() ?>
<?php if ($notice !== ''): ?>
<section class="section-box">
<div class="fx-message is-success"><?= e($notice) ?></div>
</section>
<?php elseif ($error !== ''): ?>
<section class="section-box">
<div class="fx-message is-error"><?= e($error) ?></div>
</section>
<?php endif; ?>
<section class="section-box">
<div class="fx-section-head">
<div>
<h2>Umrechnung</h2>
<p>Umrechnung auf Basis des letzten verfuegbaren Kurses zwischen den bevorzugten Waehrungen.</p>
</div>
</div>
<p class="fx-api-note">
API-Self-Describe-Endpoint:
<a href="<?= e($apiDescribeUrl) ?>" target="_blank" rel="noopener noreferrer"><?= e($apiDescribeUrl) ?></a>
</p>
<div class="fx-form-grid">
<label>
<span>Quellwaehrung</span>
<select name="convert_from">
<?php foreach ($preferredCurrencies as $currency): ?>
<option value="<?= e((string) $currency) ?>"><?= e((string) $currency) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
<span>Zielwaehrung</span>
<select name="convert_to">
<?php foreach ($preferredCurrencies as $currency): ?>
<option value="<?= e((string) $currency) ?>" <?= (string) $currency === (string) ($preferredCurrencies[1] ?? $preferredCurrencies[0] ?? '') ? 'selected' : '' ?>><?= e((string) $currency) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
<span>Betrag</span>
<input type="number" name="convert_amount" min="0" step="0.00000001" value="1">
</label>
</div>
<div class="fx-convert-result" data-bind="convert-result">Noch keine Umrechnung berechnet.</div>
</section>
<section class="section-box">
<div class="fx-card-head">
<div>
<h2>Kursverlauf</h2>
<p>Neueste Abrufe zuerst. Verlauf der bevorzugten Waehrungen relativ zur Anzeige-Basiswaehrung.</p>
</div>
<div class="fx-card-meta">
<div><strong>Anzeige-Basis:</strong> <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? '')) ?></div>
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
</div>
</div>
<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>
</section>
</div>