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

This commit is contained in:
2026-06-25 00:17:31 +02:00
parent 46111d8988
commit ac2d95fc56
140 changed files with 1168 additions and 1984 deletions

View File

@@ -0,0 +1,509 @@
<?php $ownerQuery = $isAdmin ? '&owner_sub=' . urlencode((string) $ownerSub) : ''; ?>
<div class="bc-page">
<?php if ($error): ?>
<section class="section-box"><div class="bc-alert bc-alert--error"><?= e($error) ?></div></section>
<?php elseif ($notice): ?>
<section class="section-box"><div class="bc-alert bc-alert--success"><?= e($notice) ?></div></section>
<?php endif; ?>
<?php if ($isAdmin): ?>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Benutzer-Scope</h2>
<p>Depots anderer Benutzer sind nur fuer `appadmin` sichtbar und bearbeitbar.</p>
</div>
</div>
<form method="get" action="/apps/boersenchecker/index.php" style="margin-top:16px; display:flex; gap:10px; flex-wrap:wrap; align-items:end;">
<input type="hidden" name="view" value="depotverwaltung">
<label class="setup-field muted" style="margin:0; min-width:260px;">
<span>Depots von Benutzer</span>
<select name="owner_sub">
<?php foreach ($availableOwners as $owner): ?>
<option value="<?= e((string) $owner['sub']) ?>" <?= (string) $ownerSub === (string) $owner['sub'] ? 'selected' : '' ?>>
<?= e((string) $owner['label']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<button class="bc-button bc-button--primary" type="submit">Anzeigen</button>
</form>
</section>
<?php endif; ?>
<div class="bc-card-grid">
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title"><?= $editPortfolio ? 'Depot bearbeiten' : 'Neues Depot' ?></h2>
<p>Stammdaten und Berichtswahrung fuer ein Depot pflegen.</p>
</div>
</div>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_portfolio">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="portfolio_id" value="<?= e((string) ($editPortfolio['id'] ?? '0')) ?>">
<label class="setup-field muted">
<span>Depotname</span>
<input type="text" name="portfolio_name" value="<?= e((string) ($editPortfolio['name'] ?? '')) ?>" required>
</label>
<label class="setup-field muted">
<span>Berichtswahrung</span>
<input type="text" name="portfolio_base_currency" value="<?= e((string) ($editPortfolio['base_currency'] ?? $defaultReportCurrency)) ?>" required>
</label>
<label class="setup-field muted">
<span>Notizen</span>
<textarea name="portfolio_notes" rows="3"><?= e((string) ($editPortfolio['notes'] ?? '')) ?></textarea>
</label>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Depot speichern</button>
<?php if ($editPortfolio): ?>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung<?= e($ownerQuery) ?>">Abbrechen</a>
<?php endif; ?>
</div>
</form>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">API / FX</h2>
<p>Kurs- und Waehrungsdaten zentral aktualisieren.</p>
</div>
</div>
<p class="muted" style="margin-top:16px;">
Die Umrechnung liest gespeicherte FX-Daten zentral aus dem Modul fx-rates. Eine Aktualisierung wird nur manuell
angestossen und respektiert die dortige Max-Age- und Reuse-Logik.
</p>
<p class="muted" style="margin-top:12px;">
Aktienkurse werden ueber Alpha Vantage anhand des hinterlegten Symbols abgerufen. Die ISIN bleibt als Stammdatum erhalten.
</p>
<div class="bc-actions" style="margin-top:16px;">
<form method="post">
<input type="hidden" name="action" value="refresh_fx">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<button class="bc-button bc-button--primary" type="submit">FX-Daten aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="action" value="refresh_market_data_all">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<button class="bc-button bc-button--secondary" type="submit">Alle API-Kurse abrufen</button>
</form>
</div>
<div class="muted" style="margin-top:12px;">
Alpha Vantage Mindestabstand: <?= e((string) $marketDataMinIntervalMinutes) ?> Min.
</div>
<div class="muted" style="margin-top:6px;">
API-Key und Timeout fuer Aktienkurse werden ueber <a href="/apps/boersenchecker/index.php?view=setup">dieses Modul-Setup</a> gepflegt.
</div>
<div class="muted" style="margin-top:6px;">
FX-Provider, API-Key und Waehrungskatalog werden im Modul <a href="/apps/fx-rates">fx-rates</a> gepflegt.
</div>
<div class="muted" style="margin-top:12px;">
Standard-Berichtswahrung: <?= e($defaultReportCurrency) ?> · Max. Alter: <?= e((string) $fxMaxAgeHours) ?>h
</div>
</section>
</div>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title"><?= $editPosition ? 'Position bearbeiten' : 'Neue Position' ?></h2>
<p>Aktienpositionen fuer ein Depot mit Kaufdaten und Kurswaehrung verwalten.</p>
</div>
</div>
<?php if ($portfolios === []): ?>
<div class="muted" style="margin-top:16px;">Bitte zuerst ein Depot anlegen.</div>
<?php else: ?>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_position">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="position_id" value="<?= e((string) ($editPosition['id'] ?? '0')) ?>">
<input type="hidden" name="instrument_id" value="<?= e((string) ($editPosition['instrument_id'] ?? '0')) ?>">
<label class="setup-field muted">
<span>Depot</span>
<select name="portfolio_id" required>
<option value="">Bitte waehlen</option>
<?php foreach ($portfolios as $portfolio): ?>
<option value="<?= e((string) $portfolio['id']) ?>" <?= (string) ($editPosition['portfolio_id'] ?? '') === (string) $portfolio['id'] ? 'selected' : '' ?>>
<?= e((string) $portfolio['name']) ?> (<?= e((string) $portfolio['base_currency']) ?>)
</option>
<?php endforeach; ?>
</select>
</label>
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px;">
<label class="setup-field muted">
<span>Aktienname</span>
<input type="text" name="instrument_name" value="<?= e((string) ($editPosition['instrument_name'] ?? '')) ?>" required>
</label>
<label class="setup-field muted">
<span>API-Symbol / Ticker</span>
<input type="text" name="symbol" value="<?= e((string) ($editPosition['symbol'] ?? '')) ?>" placeholder="z.B. AAPL oder MBG.DE">
</label>
<label class="setup-field muted">
<span>ISIN</span>
<input type="text" name="isin" value="<?= e((string) ($editPosition['isin'] ?? '')) ?>">
</label>
<label class="setup-field muted">
<span>WKN</span>
<input type="text" name="wkn" value="<?= e((string) ($editPosition['wkn'] ?? '')) ?>">
</label>
<label class="setup-field muted">
<span>Boerse / Markt</span>
<input type="text" name="market" value="<?= e((string) ($editPosition['market'] ?? '')) ?>">
</label>
<label class="setup-field muted">
<span>Kurswaehrung</span>
<input type="text" name="quote_currency" value="<?= e((string) ($editPosition['quote_currency'] ?? $defaultReportCurrency)) ?>" required>
</label>
</div>
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px;">
<label class="setup-field muted">
<span>Stueckzahl</span>
<input type="number" name="quantity" min="0" step="0.000001" value="<?= e((string) ($editPosition['quantity'] ?? '')) ?>" required>
</label>
<label class="setup-field muted">
<span>Kaufpreis</span>
<input type="number" name="purchase_price" min="0" step="0.00000001" value="<?= e((string) ($editPosition['purchase_price'] ?? '')) ?>" required>
</label>
<label class="setup-field muted">
<span>Kaufwaehrung</span>
<input type="text" name="purchase_currency" value="<?= e((string) ($editPosition['purchase_currency'] ?? $defaultReportCurrency)) ?>" required>
</label>
<label class="setup-field muted">
<span>Kaufdatum</span>
<input type="date" name="purchase_date" value="<?= e((string) ($editPosition['purchase_date'] ?? date('Y-m-d'))) ?>" required>
</label>
<label class="setup-field muted">
<span>Gebuehren</span>
<input type="number" name="fees" min="0" step="0.00000001" value="<?= e((string) ($editPosition['fees'] ?? '')) ?>">
</label>
</div>
<label class="setup-field muted">
<span>Notizen</span>
<textarea name="position_notes" rows="3"><?= e((string) ($editPosition['notes'] ?? '')) ?></textarea>
</label>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Position speichern</button>
<?php if ($editPosition): ?>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung<?= e($ownerQuery) ?>">Abbrechen</a>
<?php endif; ?>
</div>
</form>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Wertpapiersuche</h2>
<p>Alpha-Vantage-Suchergebnisse pruefen und Daten direkt ins Positionsformular uebernehmen.</p>
</div>
</div>
<div class="bc-section-copy">
<form method="post" style="margin-top:16px; display:flex; gap:10px; flex-wrap:wrap; align-items:end;">
<input type="hidden" name="action" value="search_symbol">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<label class="setup-field muted" style="margin:0; min-width:260px; flex:1;">
<span>Suchbegriff</span>
<input type="text" name="search_keywords" value="<?= e($symbolSearchKeywords) ?>" placeholder="z.B. Mercedes, AAPL, Allianz" required>
</label>
<button class="bc-button bc-button--primary" type="submit">Suchen</button>
</form>
</div>
<?php if ($symbolSearchResults !== []): ?>
<table class="bc-table">
<thead>
<tr>
<th>Symbol</th>
<th>Name</th>
<th>Typ</th>
<th>Region</th>
<th>Waehrung</th>
<th>Match</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($symbolSearchResults as $result): ?>
<tr>
<td><strong><?= e((string) ($result['symbol'] ?? '')) ?></strong></td>
<td><?= e((string) ($result['name'] ?? '')) ?></td>
<td><?= e((string) ($result['type'] ?? '')) ?></td>
<td><?= e((string) ($result['region'] ?? '')) ?></td>
<td><?= e((string) ($result['currency'] ?? '')) ?></td>
<td><?= e((string) ($result['match_score'] ?? '')) ?></td>
<td>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung&owner_sub=<?= urlencode((string) $ownerSub) ?>&symbol_candidate=<?= urlencode((string) ($result['symbol'] ?? '')) ?>&instrument_name_candidate=<?= urlencode((string) ($result['name'] ?? '')) ?>&isin_candidate=<?= urlencode((string) ($result['isin'] ?? '')) ?>&market_candidate=<?= urlencode((string) ($result['region'] ?? '')) ?>&quote_currency_candidate=<?= urlencode((string) ($result['currency'] ?? '')) ?>">
In Formular uebernehmen
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="bc-section-copy">
<div class="muted">Noch keine Symbolsuche ausgefuehrt.</div>
</div>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Manuellen Kurs erfassen</h2>
<p>Kurse mit Uhrzeit und Quelle direkt in die Historie schreiben.</p>
</div>
</div>
<?php if ($instrumentList === []): ?>
<div class="muted" style="margin-top:16px;">Sobald Positionen vorhanden sind, koennen hier Kurse mit Uhrzeit gespeichert werden.</div>
<?php else: ?>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_quote">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px;">
<label class="setup-field muted">
<span>Aktie</span>
<select name="quote_instrument_id" required>
<option value="">Bitte waehlen</option>
<?php foreach ($instrumentList as $instrument): ?>
<option value="<?= e((string) $instrument['id']) ?>" <?= $selectedInstrumentForQuote === (int) $instrument['id'] ? 'selected' : '' ?>>
<?= e((string) $instrument['name']) ?><?= $instrument['symbol'] !== '' ? ' (' . e((string) $instrument['symbol']) . ')' : '' ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label class="setup-field muted">
<span>Kurs</span>
<input type="number" name="quote_price" min="0" step="0.00000001" required>
</label>
<label class="setup-field muted">
<span>Waehrung</span>
<input type="text" name="quote_currency" value="<?= e($selectedInstrumentQuoteCurrency) ?>" required>
</label>
<label class="setup-field muted">
<span>Zeitpunkt</span>
<input type="datetime-local" name="quoted_at" value="<?= e($localNowInputValue) ?>" required>
</label>
<label class="setup-field muted">
<span>Quelle</span>
<input type="text" name="quote_source" value="manual">
</label>
</div>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Kurs speichern</button>
</div>
</form>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Depots</h2>
<p>Uebersicht aller Depots mit Kennzahlen und Schnellaktionen.</p>
</div>
</div>
<?php if ($portfolios === []): ?>
<div class="muted" style="margin-top:16px;">Noch keine Depots vorhanden.</div>
<?php else: ?>
<div class="bc-card-grid" style="margin-top:16px;">
<?php foreach ($portfolios as $portfolio): ?>
<?php
$portfolioId = (int) $portfolio['id'];
$stats = $portfolioStats[$portfolioId] ?? ['positions' => 0, 'invested' => 0.0, 'current' => 0.0, 'gain' => null, 'has_invested' => false, 'has_current' => false];
?>
<section class="card-box">
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;">
<div>
<strong><?= e((string) $portfolio['name']) ?></strong>
<div class="muted"><?= e((string) $portfolio['base_currency']) ?> · <?= e((string) $stats['positions']) ?> Position(en)</div>
</div>
<div class="bc-actions">
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung&owner_sub=<?= urlencode((string) $ownerSub) ?>&edit_portfolio=<?= e((string) $portfolioId) ?>">Bearbeiten</a>
<form method="post" onsubmit="return confirm('Depot wirklich loeschen?')">
<input type="hidden" name="action" value="delete_portfolio">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="portfolio_id" value="<?= e((string) $portfolioId) ?>">
<button class="bc-button bc-button--secondary" type="submit">Loeschen</button>
</form>
</div>
</div>
<?php if (!empty($portfolio['notes'])): ?>
<div class="muted" style="margin-top:10px;"><?= e((string) $portfolio['notes']) ?></div>
<?php endif; ?>
<div class="grid" style="margin-top:16px; grid-template-columns:repeat(auto-fit, minmax(140px, 1fr)); gap:10px;">
<div class="bc-stat">
<div class="muted">Investiert</div>
<strong><?= $stats['has_invested'] ? e($fmtNumber((float) $stats['invested'])) . ' ' . e((string) $portfolio['base_currency']) : 'n/a' ?></strong>
</div>
<div class="bc-stat">
<div class="muted">Aktuell</div>
<strong><?= $stats['has_current'] ? e($fmtNumber((float) $stats['current'])) . ' ' . e((string) $portfolio['base_currency']) : 'n/a' ?></strong>
</div>
<div class="bc-stat">
<div class="muted">Gewinn / Verlust</div>
<strong><?= $stats['gain'] !== null ? e($fmtNumber((float) $stats['gain'])) . ' ' . e((string) $portfolio['base_currency']) : 'n/a' ?></strong>
</div>
</div>
</section>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Positionen</h2>
<p>Alle Positionen mit Kaufdaten, letztem Kurs und aktuellen Werten.</p>
</div>
</div>
<?php if ($positions === []): ?>
<div class="bc-section-copy">
<div class="muted">Noch keine Positionen vorhanden.</div>
</div>
<?php else: ?>
<table class="bc-table">
<thead>
<tr>
<th>Depot</th>
<th>Aktie</th>
<th>ISIN / WKN</th>
<th>Stueck</th>
<th>Kauf</th>
<th>Letzter Kurs</th>
<th>Wert</th>
<th>Delta</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($positions as $position): ?>
<tr>
<td><?= e((string) ($portfolioById[(int) $position['portfolio_id']]['name'] ?? '')) ?></td>
<td>
<strong><?= e((string) $position['instrument_name']) ?></strong>
<?php if (!empty($position['symbol'])): ?>
<div class="muted"><?= e((string) $position['symbol']) ?></div>
<?php endif; ?>
</td>
<td>
<?= e((string) ($position['isin'] ?: '-')) ?>
<div class="muted"><?= e((string) ($position['wkn'] ?: '-')) ?></div>
</td>
<td><?= e($fmtNumber((float) $position['quantity'], 6)) ?></td>
<td>
<?= e($fmtNumber((float) $position['purchase_price'], 4)) ?> <?= e((string) $position['purchase_currency']) ?>
<div class="muted"><?= e((string) $position['purchase_date']) ?></div>
</td>
<td>
<?php if ($position['latest_price'] !== null): ?>
<?= e($fmtNumber((float) $position['latest_price'], 4)) ?> <?= e((string) $position['latest_currency']) ?>
<div class="muted"><?= e($fmtDateTime((string) $position['latest_quoted_at'], (string) ($position['latest_source'] ?? ''))) ?></div>
<?php else: ?>
<span class="muted">kein Kurs</span>
<?php endif; ?>
</td>
<td>
<?php if ($position['current_total_base'] !== null): ?>
<?= e($fmtNumber((float) $position['current_total_base'])) ?> <?= e((string) $position['base_currency']) ?>
<?php else: ?>
<span class="muted">n/a</span>
<?php endif; ?>
</td>
<td>
<?php if ($position['gain_base'] !== null): ?>
<?= e($fmtNumber((float) $position['gain_base'])) ?> <?= e((string) $position['base_currency']) ?>
<?php else: ?>
<span class="muted">n/a</span>
<?php endif; ?>
</td>
<td>
<div class="bc-actions">
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung&owner_sub=<?= urlencode((string) $ownerSub) ?>&edit_position=<?= e((string) $position['id']) ?>">Bearbeiten</a>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung&owner_sub=<?= urlencode((string) $ownerSub) ?>&instrument_id=<?= e((string) $position['instrument_id']) ?>">Kurs erfassen</a>
<form method="post">
<input type="hidden" name="action" value="refresh_market_data_position">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="position_id" value="<?= e((string) $position['id']) ?>">
<button class="bc-button bc-button--secondary" type="submit">API-Kurs</button>
</form>
<form method="post" onsubmit="return confirm('Position wirklich loeschen?')">
<input type="hidden" name="action" value="delete_position">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="position_id" value="<?= e((string) $position['id']) ?>">
<button class="bc-button bc-button--secondary" type="submit">Loeschen</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Kursverlauf</h2>
<p>Historische Kurse pro Aktie mit Zeitstempel und Quelle.</p>
</div>
</div>
<?php if ($instrumentList === []): ?>
<div class="muted" style="margin-top:16px;">Noch keine Kursdaten vorhanden.</div>
<?php else: ?>
<div class="bc-card-grid" style="margin-top:16px;">
<?php foreach ($instrumentList as $instrumentId => $instrument): ?>
<?php $history = array_slice($quoteHistory[$instrumentId] ?? [], 0, 10); ?>
<section class="card-box">
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;">
<div>
<strong><?= e((string) $instrument['name']) ?></strong>
<div class="muted">
<?= e((string) ($instrument['symbol'] ?: '-')) ?> · <?= e((string) ($instrument['isin'] ?: '-')) ?>
</div>
</div>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=depotverwaltung&owner_sub=<?= urlencode((string) $ownerSub) ?>&instrument_id=<?= e((string) $instrumentId) ?>">Neuen Kurs erfassen</a>
</div>
<?php if ($history === []): ?>
<div class="muted" style="margin-top:12px;">Noch keine historischen Kurse vorhanden.</div>
<?php else: ?>
<div class="bc-table-shell" style="margin-top:12px;">
<table class="bc-table">
<thead>
<tr>
<th>Zeitpunkt</th>
<th>Kurs</th>
<th>Quelle</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($history as $quote): ?>
<tr>
<td><?= e($fmtDateTime((string) $quote['quoted_at'], (string) ($quote['source'] ?? ''))) ?></td>
<td><?= e($fmtNumber((float) $quote['price'], 4)) ?> <?= e((string) $quote['currency']) ?></td>
<td><?= e((string) $quote['source']) ?></td>
<td>
<form method="post" onsubmit="return confirm('Kurseintrag wirklich loeschen?')">
<input type="hidden" name="action" value="delete_quote">
<input type="hidden" name="owner_sub" value="<?= e((string) $ownerSub) ?>">
<input type="hidden" name="quote_id" value="<?= e((string) $quote['id']) ?>">
<button class="bc-button bc-button--secondary" type="submit">Loeschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
</div>

View File

@@ -0,0 +1,207 @@
<div class="bc-page" data-bc-home data-chart-endpoint="/api/boersenchecker/index.php?path=v1/chart-data">
<script type="application/json" data-bc-instruments-json><?= json_encode(array_map(static function (array $position): array {
return [
'instrument_id' => (int) ($position['instrument_id'] ?? 0),
'instrument_name' => (string) ($position['instrument_name'] ?? ''),
'symbol' => (string) ($position['symbol'] ?? ''),
'isin' => (string) ($position['isin'] ?? ''),
];
}, $positions), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></script>
<?php if ($error): ?>
<section class="section-box"><div class="bc-alert bc-alert--error"><?= e($error) ?></div></section>
<?php elseif ($notice): ?>
<section class="section-box"><div class="bc-alert bc-alert--success"><?= e($notice) ?></div></section>
<?php endif; ?>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Marktueberblick</h2>
<p>Depotauswahl, Aktienfokus und aktueller Kursabruf in einem Bereich.</p>
</div>
</div>
<div class="bc-toolbar" style="margin-top:16px;">
<form class="bc-panel" method="get" action="/apps/boersenchecker/index.php">
<input type="hidden" name="view" value="overview">
<div class="bc-field-label">Depotauswahl</div>
<?php if ($portfolios === []): ?>
<div class="bc-text" style="margin-top:12px;">Keine Depots vorhanden.</div>
<?php else: ?>
<label class="setup-field" style="margin-top:12px;">
<span class="bc-text">Depot</span>
<select name="portfolio_id" onchange="this.form.submit()">
<?php foreach ($portfolios as $portfolio): ?>
<option value="<?= e((string) $portfolio['id']) ?>" <?= (string) $selectedPortfolioId === (string) $portfolio['id'] ? 'selected' : '' ?>>
<?= e((string) $portfolio['name']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<?php endif; ?>
</form>
<form class="bc-panel" method="get" action="/apps/boersenchecker/index.php">
<input type="hidden" name="view" value="overview">
<input type="hidden" name="portfolio_id" value="<?= e((string) $selectedPortfolioId) ?>">
<div class="bc-field-label">Aktienauswahl</div>
<?php if ($positions === []): ?>
<div class="bc-text" style="margin-top:12px;">Keine Aktien im ausgewaehlten Depot.</div>
<?php else: ?>
<label class="setup-field" style="margin-top:12px;">
<span class="bc-text">Aktie</span>
<select name="instrument_id" data-bc-instrument onchange="this.form.submit()">
<?php foreach ($positions as $position): ?>
<option value="<?= e((string) $position['instrument_id']) ?>" <?= (string) $selectedInstrumentId === (string) $position['instrument_id'] ? 'selected' : '' ?>>
<?= e((string) $position['instrument_name']) ?><?= !empty($position['symbol']) ? ' (' . e((string) $position['symbol']) . ')' : '' ?>
</option>
<?php endforeach; ?>
</select>
</label>
<?php endif; ?>
</form>
<form class="bc-panel" method="post">
<input type="hidden" name="action" value="refresh_current_quotes_home">
<input type="hidden" name="portfolio_id" value="<?= e((string) $selectedPortfolioId) ?>">
<div class="bc-field-label">Marktdaten</div>
<p class="bc-text" style="margin-top:12px;">Aktuelle Kurse fuer das gewaehlte Depot ueber Alpha Vantage anhand des hinterlegten Symbols abrufen.</p>
<div class="bc-actions" style="margin-top:16px;">
<button class="bc-button bc-button--primary" type="submit" <?= $selectedPortfolioId > 0 ? '' : 'disabled' ?>>Aktuelle Kurse abrufen</button>
</div>
</form>
</div>
</section>
<div class="bc-overview-grid">
<section class="card-box bc-stat">
<div class="bc-field-label">Positionen</div>
<div class="bc-stat-value"><?= e((string) ($summary['positions'] ?? 0)) ?></div>
<div class="bc-text" style="margin-top:6px;">Aktien im aktuell gewaehlten Depot</div>
</section>
<section class="card-box bc-stat">
<div class="bc-field-label">Investiert</div>
<div class="bc-stat-value"><?= isset($summary['invested']) && $summary['invested'] !== null ? e(number_format((float) $summary['invested'], 2, ',', '.')) . ' ' . e($defaultReportCurrency) : 'n/a' ?></div>
<div class="bc-text" style="margin-top:6px;">In Berichtswahrung bewertet</div>
</section>
<section class="card-box bc-stat">
<div class="bc-field-label">Aktueller Wert</div>
<div class="bc-stat-value"><?= isset($summary['current']) && $summary['current'] !== null ? e(number_format((float) $summary['current'], 2, ',', '.')) . ' ' . e($defaultReportCurrency) : 'n/a' ?></div>
<div class="bc-text" style="margin-top:6px;">Basierend auf dem letzten gespeicherten Kurs</div>
</section>
<section class="card-box bc-stat">
<div class="bc-field-label">Performance</div>
<div class="bc-stat-value"><?= isset($summary['gain']) && $summary['gain'] !== null ? e(number_format((float) $summary['gain'], 2, ',', '.')) . ' ' . e($defaultReportCurrency) : 'n/a' ?></div>
<div class="bc-text" style="margin-top:6px;"><?= !empty($summary['best']['instrument_name']) ? 'Top-Wert: ' . e((string) $summary['best']['instrument_name']) : 'Noch keine Vergleichsdaten' ?></div>
</section>
</div>
<div class="bc-card-grid">
<section class="card-box">
<div class="bc-field-label">Bester Wert</div>
<?php if (!empty($summary['best'])): ?>
<div class="bc-stat-value"><?= e((string) $summary['best']['instrument_name']) ?></div>
<div class="bc-pill-soft" style="margin-top:12px;"><?= e(number_format((float) ($summary['best']['gain_percent'] ?? 0), 2, ',', '.')) ?>%</div>
<?php else: ?>
<div class="bc-text" style="margin-top:12px;">Noch keine Performance verfuegbar.</div>
<?php endif; ?>
</section>
<section class="card-box">
<div class="bc-field-label">Schwaechster Wert</div>
<?php if (!empty($summary['worst'])): ?>
<div class="bc-stat-value"><?= e((string) $summary['worst']['instrument_name']) ?></div>
<div class="bc-pill-soft" style="margin-top:12px;"><?= e(number_format((float) ($summary['worst']['gain_percent'] ?? 0), 2, ',', '.')) ?>%</div>
<?php else: ?>
<div class="bc-text" style="margin-top:12px;">Noch keine Performance verfuegbar.</div>
<?php endif; ?>
</section>
<?php foreach (array_slice($positions, 0, 2) as $position): ?>
<section class="card-box bc-stat">
<div class="bc-field-label"><?= e((string) $position['instrument_name']) ?></div>
<div class="bc-stat-value"><?= $position['latest_price'] !== null ? e(number_format((float) $position['latest_price'], 2, ',', '.')) . ' ' . e((string) $position['latest_currency']) : 'n/a' ?></div>
<div class="bc-text" style="margin-top:6px;"><?= e((string) (($position['latest_quoted_at'] ?? '') !== '' ? $fmtDateTime((string) $position['latest_quoted_at'], (string) ($position['latest_source'] ?? '')) : 'kein Kurs')) ?></div>
</section>
<?php endforeach; ?>
</div>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Kursverlauf</h2>
<p>Schlusskurse ueber mehrere Zeitfenster fuer das aktuell gewaehlte Instrument.</p>
</div>
</div>
<div class="bc-chart-card" style="margin-top:16px;">
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; align-items:center;">
<div>
<div class="bc-field-label">Aktie</div>
<div class="bc-stat-value" data-bc-instrument-name><?= e((string) ($selectedInstrument['instrument_name'] ?? 'Keine Aktie ausgewaehlt')) ?></div>
<?php if ($selectedInstrument): ?>
<div class="bc-text" data-bc-instrument-meta><?= e((string) ($selectedInstrument['symbol'] ?? '')) ?> · <?= e((string) ($selectedInstrument['isin'] ?? '-')) ?></div>
<?php endif; ?>
</div>
<div class="bc-range-list">
<button type="button" class="bc-range-button" data-range="1d" aria-pressed="false">Tag</button>
<button type="button" class="bc-range-button" data-range="5d" aria-pressed="false">5 Tage</button>
<button type="button" class="bc-range-button" data-range="1m" aria-pressed="true">Monat</button>
<button type="button" class="bc-range-button" data-range="3m" aria-pressed="false">3 Monate</button>
<button type="button" class="bc-range-button" data-range="6m" aria-pressed="false">6 Monate</button>
<button type="button" class="bc-range-button" data-range="1y" aria-pressed="false">Jahr</button>
<button type="button" class="bc-range-button" data-range="5y" aria-pressed="false">5 Jahre</button>
</div>
</div>
<div class="bc-text" data-bc-chart-status style="margin-top:12px;">Chartdaten werden geladen...</div>
<div class="bc-stat-value" data-bc-chart-summary style="margin-top:6px;">-</div>
<div class="bc-chart-shell" data-bc-chart style="margin-top:18px;"></div>
</div>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Aktien im Depot</h2>
<p>Stueckzahl, Kaufdaten, letzter Kurs und Performance auf einen Blick.</p>
</div>
</div>
<div class="bc-section-copy">
<?php if ($positions === []): ?>
<div class="bc-text" style="padding:0 0 18px;">Keine Aktien im ausgewaehlten Depot.</div>
<?php else: ?>
<div class="bc-position-list" style="padding:0 0 18px;">
<?php foreach ($positions as $position): ?>
<?php $gainClass = (($position['gain_report'] ?? 0) >= 0) ? 'is-positive' : 'is-negative'; ?>
<div class="bc-position-row">
<div>
<strong><?= e((string) $position['instrument_name']) ?></strong>
<div class="bc-text" style="margin-top:4px;"><?= e((string) ($position['symbol'] ?? '')) ?> · <?= e((string) ($position['isin'] ?? '-')) ?></div>
<?php if (!empty($position['market'])): ?>
<div class="bc-pill-soft" style="margin-top:10px;"><?= e((string) $position['market']) ?></div>
<?php endif; ?>
</div>
<div>
<div class="bc-field-label">Stueckzahl</div>
<div><?= e(number_format((float) $position['quantity'], 6, ',', '.')) ?></div>
</div>
<div>
<div class="bc-field-label">Kaufpreis</div>
<div><?= e(number_format((float) $position['purchase_price'], 2, ',', '.')) ?> <?= e((string) $position['purchase_currency']) ?></div>
</div>
<div>
<div class="bc-field-label">Letzter Kurs</div>
<div><?= $position['latest_price'] !== null ? e(number_format((float) $position['latest_price'], 2, ',', '.')) . ' ' . e((string) $position['latest_currency']) : 'n/a' ?></div>
</div>
<div>
<div class="bc-field-label">Performance</div>
<div class="bc-performance <?= e($gainClass) ?>">
<?= isset($position['gain_report']) && $position['gain_report'] !== null ? e(number_format((float) $position['gain_report'], 2, ',', '.')) . ' ' . e($defaultReportCurrency) : 'n/a' ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</section>
</div>

View File

@@ -0,0 +1,184 @@
<div class="bc-page">
<?php if ($error): ?>
<section class="section-box"><div class="bc-alert bc-alert--error"><?= e($error) ?></div></section>
<?php elseif ($notice): ?>
<section class="section-box"><div class="bc-alert bc-alert--success"><?= e($notice) ?></div></section>
<?php endif; ?>
<div class="bc-card-grid">
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Aktie waehlen</h2>
<p>Systemweit vorhandene Aktie aus allen Depots auswaehlen.</p>
</div>
</div>
<form method="get" action="/apps/boersenchecker/index.php" style="margin-top:16px;">
<input type="hidden" name="view" value="aktienverwaltung">
<label class="setup-field muted">
<span>Aktien aller Depots</span>
<select name="instrument_id" onchange="this.form.submit()">
<?php foreach ($instruments as $instrument): ?>
<option value="<?= e((string) $instrument['id']) ?>" <?= (string) $selectedInstrumentId === (string) $instrument['id'] ? 'selected' : '' ?>>
<?= e((string) $instrument['name']) ?><?= !empty($instrument['symbol']) ? ' (' . e((string) $instrument['symbol']) . ')' : '' ?>
</option>
<?php endforeach; ?>
</select>
</label>
</form>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Wertpapiersuche</h2>
<p>Alpha-Vantage-Suchergebnisse finden und direkt fuer die Aktie uebernehmen.</p>
</div>
</div>
<div class="bc-section-copy">
<form method="post" style="margin-top:16px; display:flex; gap:10px; flex-wrap:wrap; align-items:end;">
<input type="hidden" name="action" value="search_symbol">
<input type="hidden" name="instrument_id" value="<?= e((string) $selectedInstrumentId) ?>">
<label class="setup-field muted" style="margin:0; min-width:260px; flex:1;">
<span>Suchbegriff</span>
<input type="text" name="search_keywords" value="<?= e($searchKeywords) ?>" placeholder="z.B. Apple, AAPL, Allianz" required>
</label>
<button class="bc-button bc-button--primary" type="submit">Suchen</button>
</form>
</div>
<?php if ($searchResults !== []): ?>
<table class="bc-table">
<thead>
<tr>
<th>Symbol</th>
<th>Name</th>
<th>Region</th>
<th>Waehrung</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($searchResults as $result): ?>
<tr>
<td><strong><?= e((string) ($result['symbol'] ?? '')) ?></strong></td>
<td><?= e((string) ($result['name'] ?? '')) ?></td>
<td><?= e((string) ($result['region'] ?? '')) ?></td>
<td><?= e((string) ($result['currency'] ?? '')) ?></td>
<td>
<a class="bc-button bc-button--secondary" href="/apps/boersenchecker/index.php?view=aktienverwaltung&instrument_id=<?= e((string) $selectedInstrumentId) ?>&symbol_candidate=<?= urlencode((string) ($result['symbol'] ?? '')) ?>&instrument_name_candidate=<?= urlencode((string) ($result['name'] ?? '')) ?>&isin_candidate=<?= urlencode((string) ($result['isin'] ?? '')) ?>&market_candidate=<?= urlencode((string) ($result['region'] ?? '')) ?>&quote_currency_candidate=<?= urlencode((string) ($result['currency'] ?? '')) ?>">
Uebernehmen
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="bc-section-copy">
<div class="muted">Noch keine Symbolsuche ausgefuehrt.</div>
</div>
<?php endif; ?>
</section>
</div>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Aktie bearbeiten</h2>
<p>Stammdaten, Markt und Kurswaehrung zentral fuer die Aktie pflegen.</p>
</div>
</div>
<?php if (!$selectedInstrument || empty($selectedInstrument['id'])): ?>
<div class="muted" style="margin-top:16px;">Keine Aktie vorhanden.</div>
<?php else: ?>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_instrument">
<input type="hidden" name="instrument_id" value="<?= e((string) ($selectedInstrument['id'] ?? 0)) ?>">
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px;">
<label class="setup-field muted"><span>Name</span><input type="text" name="instrument_name" value="<?= e((string) (($selectedInstrument['name'] ?? '') ?: ($_GET['instrument_name_candidate'] ?? ''))) ?>" required></label>
<label class="setup-field muted"><span>Symbol</span><input type="text" name="symbol" value="<?= e((string) (($selectedInstrument['symbol'] ?? '') ?: ($_GET['symbol_candidate'] ?? ''))) ?>"></label>
<label class="setup-field muted"><span>ISIN</span><input type="text" name="isin" value="<?= e((string) $selectedInstrument['isin'] ?? '') ?>"></label>
<label class="setup-field muted"><span>WKN</span><input type="text" name="wkn" value="<?= e((string) $selectedInstrument['wkn'] ?? '') ?>"></label>
<label class="setup-field muted"><span>Markt</span><input type="text" name="market" value="<?= e((string) (($selectedInstrument['market'] ?? '') ?: ($_GET['market_candidate'] ?? ''))) ?>"></label>
<label class="setup-field muted"><span>Kurswaehrung</span><input type="text" name="quote_currency" value="<?= e((string) (($selectedInstrument['quote_currency'] ?? $defaultReportCurrency) ?: ($_GET['quote_currency_candidate'] ?? $defaultReportCurrency))) ?>"></label>
</div>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Aktie speichern</button>
</div>
</form>
<form method="post" style="margin-top:12px;">
<input type="hidden" name="action" value="refresh_market_data_instrument">
<input type="hidden" name="instrument_id" value="<?= e((string) ($selectedInstrument['id'] ?? 0)) ?>">
<button class="bc-button bc-button--secondary" type="submit">Aktuellen API-Kurs abrufen</button>
</form>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Manuellen Kurs eingeben</h2>
<p>Einzelne Kurse mit Zeitstempel und Quelle fuer die ausgewaehlte Aktie speichern.</p>
</div>
</div>
<?php if (!$selectedInstrument || empty($selectedInstrument['id'])): ?>
<div class="muted" style="margin-top:16px;">Keine Aktie vorhanden.</div>
<?php else: ?>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_quote">
<input type="hidden" name="instrument_id" value="<?= e((string) ($selectedInstrument['id'] ?? 0)) ?>">
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px;">
<label class="setup-field muted"><span>Kurs</span><input type="number" name="quote_price" min="0" step="0.00000001" required></label>
<label class="setup-field muted"><span>Waehrung</span><input type="text" name="quote_currency" value="<?= e((string) ($selectedInstrument['quote_currency'] ?? $defaultReportCurrency)) ?>" required></label>
<label class="setup-field muted"><span>Zeitpunkt</span><input type="datetime-local" name="quoted_at" value="<?= e($localNowInputValue) ?>" required></label>
<label class="setup-field muted"><span>Quelle</span><input type="text" name="quote_source" value="manual"></label>
</div>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Kurs speichern</button>
</div>
</form>
<?php endif; ?>
</section>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Kursverlauf</h2>
<p>Gespeicherte Kursdaten der ausgewaehlten Aktie mit Quelle und Loeschoption.</p>
</div>
</div>
<?php if ($quotes === []): ?>
<div class="bc-section-copy">
<div class="muted">Keine Kursdaten vorhanden.</div>
</div>
<?php else: ?>
<table class="bc-table">
<thead>
<tr>
<th>Zeitpunkt</th>
<th>Kurs</th>
<th>Quelle</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($quotes as $quote): ?>
<tr>
<td><?= e($fmtDateTime((string) $quote['quoted_at'], (string) ($quote['source'] ?? ''))) ?></td>
<td><?= e(number_format((float) $quote['price'], 4, ',', '.')) ?> <?= e((string) $quote['currency']) ?></td>
<td><?= e((string) $quote['source']) ?></td>
<td>
<form method="post" onsubmit="return confirm('Kurseintrag wirklich loeschen?')">
<input type="hidden" name="action" value="delete_quote">
<input type="hidden" name="instrument_id" value="<?= e((string) $selectedInstrumentId) ?>">
<input type="hidden" name="quote_id" value="<?= e((string) $quote['id']) ?>">
<button class="bc-button bc-button--secondary" type="submit">Loeschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</section>
</div>