asdasd
This commit is contained in:
358
custom/apps/boersenchecker/pages/index.php
Normal file
358
custom/apps/boersenchecker/pages/index.php
Normal file
@@ -0,0 +1,358 @@
|
||||
<?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&path=assets/boersenchecker.css&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.css'), ENT_QUOTES) ?>">
|
||||
</head>
|
||||
<body>
|
||||
<?= $pageContent ?>
|
||||
|
||||
<script src="/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.js&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.js'), ENT_QUOTES) ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user