Files
desktop/modules/boersenchecker/pages/index.php
Lars Gebhardt-Kusche baa57bb913
All checks were successful
Deploy / deploy-staging (push) Successful in 28s
Deploy / deploy-production (push) Has been skipped
adasd
2026-06-22 23:49:50 +02:00

359 lines
15 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php';
require_once dirname(__DIR__) . '/bootstrap.php';
use ModulesCore\ModuleHttp;
session_start();
$projectRoot = dirname(__DIR__, 3);
ModuleHttp::requireDesktopAccess($projectRoot);
$isPartial = !empty($_GET['partial']);
$view = trim((string) ($_GET['view'] ?? 'overview'));
$allowedViews = ['overview', 'depotverwaltung', 'aktienverwaltung', 'setup'];
if (!in_array($view, $allowedViews, true)) {
$view = 'overview';
}
$viewMeta = [
'overview' => [
'label' => 'Ueberblick',
'title' => 'Depot-Ueberblick',
'description' => 'Kennzahlen, Kursverlauf und Performance fuer das ausgewaehlte Depot.',
'nav_description' => 'Marktueberblick, Positionen und gespeicherte Kurse.',
],
'depotverwaltung' => [
'label' => 'Depotverwaltung',
'title' => 'Depotverwaltung',
'description' => 'Depots, Positionen und manuelle Kursdaten zentral verwalten.',
'nav_description' => 'Depots pflegen, FX abrufen und Positionen bearbeiten.',
],
'aktienverwaltung' => [
'label' => 'Aktienverwaltung',
'title' => 'Aktienverwaltung',
'description' => 'Wertpapier-Stammdaten, Suche und Kursverlauf pro Aktie pflegen.',
'nav_description' => 'Ticker, ISIN, Markt und Kursverlauf pro Instrument.',
],
'setup' => [
'label' => 'Setup',
'title' => 'Setup',
'description' => 'API-Zugaenge, Datenbankoptionen und Modulvorgaben verwalten.',
'nav_description' => 'Schluessel, Timeouts und Modulkonfiguration speichern.',
],
];
$assetVersion = static function (string $relativePath): string {
$fullPath = dirname(__DIR__) . '/' . ltrim($relativePath, '/');
$mtime = is_file($fullPath) ? filemtime($fullPath) : false;
return $mtime === false ? '0' : (string) $mtime;
};
$renderSetup = static function (): void {
$moduleName = 'boersenchecker';
$manifestPath = dirname(__DIR__) . '/module.json';
$manifestRaw = is_file($manifestPath) ? file_get_contents($manifestPath) : false;
$manifest = is_string($manifestRaw) && trim($manifestRaw) !== '' ? json_decode($manifestRaw, true) : [];
$fields = is_array($manifest['setup']['fields'] ?? null) ? $manifest['setup']['fields'] : [];
$settings = modules()->settings($moduleName);
$notice = null;
$error = null;
$getValue = static function (array $source, string $path): mixed {
$value = $source;
foreach (explode('.', $path) as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
return null;
}
$value = $value[$segment];
}
return $value;
};
$setValue = static function (array &$target, string $path, mixed $value): void {
$segments = explode('.', $path);
$cursor = &$target;
foreach ($segments as $index => $segment) {
if ($index === count($segments) - 1) {
$cursor[$segment] = $value;
return;
}
if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) {
$cursor[$segment] = [];
}
$cursor = &$cursor[$segment];
}
};
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string) ($_POST['action'] ?? '') === 'save_setup') {
try {
$nextSettings = $settings;
foreach ($fields as $field) {
if (!is_array($field)) {
continue;
}
$name = trim((string) ($field['name'] ?? ''));
$type = trim((string) ($field['type'] ?? 'text'));
if ($name === '') {
continue;
}
$raw = $_POST['settings'][$name] ?? null;
$value = match ($type) {
'checkbox' => $raw !== null,
'number' => ($raw === null || $raw === '') ? null : (is_numeric((string) $raw) ? 0 + $raw : null),
default => is_string($raw) ? trim($raw) : '',
};
$setValue($nextSettings, $name, $value);
}
modules()->saveSettings($moduleName, $nextSettings);
$settings = modules()->settings($moduleName);
$notice = 'Setup gespeichert.';
} catch (\Throwable $e) {
$error = $e->getMessage();
}
}
?>
<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; ?>
<section class="section-box">
<div class="bc-section-head">
<div>
<h2 class="bc-section-title">Modul-Setup</h2>
<p>Die Felder entsprechen dem alten Nexus-Modul und werden lokal in `data/module-settings/boersenchecker.json` gespeichert.</p>
</div>
</div>
<form method="post" style="margin-top:16px; display:grid; gap:12px;">
<input type="hidden" name="action" value="save_setup">
<div class="grid" style="grid-template-columns:repeat(auto-fit, minmax(260px, 1fr)); gap:12px;">
<?php foreach ($fields as $field): ?>
<?php
if (!is_array($field)) {
continue;
}
$name = trim((string) ($field['name'] ?? ''));
$label = trim((string) ($field['label'] ?? $name));
$type = trim((string) ($field['type'] ?? 'text'));
$help = trim((string) ($field['help'] ?? ''));
$value = $getValue($settings, $name);
?>
<label class="setup-field muted">
<span><?= e($label) ?></span>
<?php if ($type === 'checkbox'): ?>
<input type="checkbox" name="settings[<?= e($name) ?>]" value="1" <?= !empty($value) ? 'checked' : '' ?>>
<?php elseif ($type === 'password'): ?>
<input type="password" name="settings[<?= e($name) ?>]" value="<?= e(is_scalar($value) ? (string) $value : '') ?>">
<?php elseif ($type === 'number'): ?>
<input type="number" step="any" name="settings[<?= e($name) ?>]" value="<?= e(is_scalar($value) ? (string) $value : '') ?>">
<?php else: ?>
<input type="text" name="settings[<?= e($name) ?>]" value="<?= e(is_scalar($value) ? (string) $value : '') ?>">
<?php endif; ?>
<?php if ($help !== ''): ?>
<small class="bc-text"><?= e($help) ?></small>
<?php endif; ?>
</label>
<?php endforeach; ?>
</div>
<div class="bc-actions">
<button class="bc-button bc-button--primary" type="submit">Setup speichern</button>
</div>
</form>
</section>
</div>
<?php
};
$renderContent = static function () use ($view, $renderSetup, $viewMeta): void {
$currentViewMeta = $viewMeta[$view] ?? $viewMeta['overview'];
$navItems = [
'overview' => '/apps/boersenchecker/index.php?view=overview',
'depotverwaltung' => '/apps/boersenchecker/index.php?view=depotverwaltung',
'aktienverwaltung' => '/apps/boersenchecker/index.php?view=aktienverwaltung',
'setup' => '/apps/boersenchecker/index.php?view=setup',
];
ob_start();
if ($view === 'setup') {
$renderSetup();
} elseif ($view === 'depotverwaltung') {
$page = new \Modules\Boersenchecker\Support\DashboardPage();
module_tpl('boersenchecker', 'dashboard', $page->handle());
} elseif ($view === 'aktienverwaltung') {
$page = new \Modules\Boersenchecker\Support\InstrumentPage();
module_tpl('boersenchecker', 'instruments', $page->handle());
} else {
$page = new \Modules\Boersenchecker\Support\HomePage();
module_tpl('boersenchecker', 'home', $page->handle());
}
$innerContent = (string) ob_get_clean();
?>
<div class="boersenchecker-module-shell window-app-shell">
<div class="window-app-frame bc-frame">
<aside class="window-app-sidebar bc-sidebar">
<div class="window-app-brand bc-brand">
<p class="window-app-kicker bc-kicker">Modul</p>
<h1>Boersenchecker</h1>
<p class="window-app-copy">Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung im Desktop-Standardlayout.</p>
</div>
<div class="window-app-nav-list bc-nav-list">
<?php foreach ($navItems as $navView => $href): ?>
<?php $meta = $viewMeta[$navView] ?? null; ?>
<?php if (!$meta): continue; endif; ?>
<a
class="window-app-nav-button bc-nav-button<?= $view === $navView ? ' is-active' : '' ?>"
href="<?= e($href) ?>"
>
<strong><?= e((string) $meta['label']) ?></strong>
<span class="window-app-meta"><?= e((string) $meta['nav_description']) ?></span>
</a>
<?php endforeach; ?>
</div>
</aside>
<main class="window-app-main bc-main">
<div class="window-app-panel bc-panel-shell">
<section class="window-app-hero bc-hero">
<div>
<p class="window-app-kicker bc-kicker">Boersenchecker</p>
<h2 class="window-app-title bc-title"><?= e((string) $currentViewMeta['title']) ?></h2>
<p class="window-app-copy"><?= e((string) $currentViewMeta['description']) ?></p>
</div>
<div class="window-app-pill-row bc-pill-row">
<span class="window-app-pill bc-pill">Desktop-App</span>
<span class="window-app-pill bc-pill">Legacy-Migration</span>
<span class="window-app-pill bc-pill">View: <?= e((string) $currentViewMeta['label']) ?></span>
</div>
</section>
<?= $innerContent ?>
</div>
</main>
</div>
</div>
<?php
};
ob_start();
$renderContent();
$pageContent = (string) ob_get_clean();
if ($isPartial) {
echo $pageContent;
return;
}
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Börsenchecker</title>
<style>
:root {
color-scheme: light;
--surface: rgba(255, 255, 255, 0.92);
--line: rgba(148, 163, 184, 0.24);
--text: #0f172a;
--muted: #475569;
--brand-accent: #14b8a6;
--brand-accent-2: #0f766e;
--brand-accent-3: #0f766e;
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
html, body { min-height: 100%; }
body {
margin: 0;
background:
radial-gradient(circle at top left, rgba(45, 212, 191, 0.12), transparent 24%),
linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
color: var(--text);
}
.boersenchecker-module-shell {
min-height: 100vh;
padding: 18px;
}
.section-box,
.submenu-box,
.card-box {
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 28px;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
padding: 22px 24px;
}
.module-page-stack {
display: grid;
gap: 18px;
}
.module-hero-copy { margin-bottom: 16px; }
.module-title { margin: 0; font-size: 2.4rem; line-height: 1.05; }
.module-lead { margin: 12px 0 0; color: var(--muted); line-height: 1.6; }
.eyebrow {
margin: 0 0 12px;
color: #4338ca;
text-transform: uppercase;
letter-spacing: 0.18em;
font-size: 12px;
font-weight: 700;
}
.module-hero-top--compact,
.module-tabs,
.module-hero-actions { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
.module-hero-top--compact { justify-content:space-between; gap:16px; }
.module-button {
display:inline-flex;
align-items:center;
justify-content:center;
min-height:44px;
padding:10px 16px;
border-radius:999px;
text-decoration:none;
border:1px solid rgba(148, 163, 184, 0.28);
color:#0f172a;
background:#ffffff;
font-weight:700;
}
.module-button--tab-active,
.module-button--primary {
color:#ffffff;
border-color:transparent;
background:linear-gradient(135deg, var(--brand-accent), var(--brand-accent-3));
}
.module-button--secondary,
.module-button--tab {
background: rgba(255, 255, 255, 0.94);
}
.module-button--small { min-height:40px; padding:8px 14px; }
.setup-field { display:grid; gap:8px; }
.setup-field input, .setup-field select, .setup-field textarea {
width: 100%;
min-height: 46px;
padding: 10px 12px;
border: 1px solid rgba(148, 163, 184, 0.4);
border-radius: 14px;
font: inherit;
background: #ffffff;
color: #0f172a;
}
.setup-field textarea { min-height: 100px; }
.grid { display:grid; }
</style>
<link rel="stylesheet" href="/assets/desktop/desktop.css">
<link rel="stylesheet" href="/module-assets/index.php?module=boersenchecker&amp;path=assets/boersenchecker.css&amp;v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.css'), ENT_QUOTES) ?>">
</head>
<body>
<?= $pageContent ?>
<script src="/module-assets/index.php?module=boersenchecker&amp;path=assets/boersenchecker.js&amp;v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.js'), ENT_QUOTES) ?>"></script>
</body>
</html>