sfsdf
This commit is contained in:
14
modules/boersenchecker/design.json
Normal file
14
modules/boersenchecker/design.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"eyebrow": "Modul",
|
||||||
|
"title": "Boersenchecker",
|
||||||
|
"description": "Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung.",
|
||||||
|
"actions": [
|
||||||
|
{ "label": "Zur Startseite", "href": "/", "variant": "ghost" },
|
||||||
|
{ "label": "Setup", "href": "/modules/setup/boersenchecker", "variant": "secondary" }
|
||||||
|
],
|
||||||
|
"tabs": [
|
||||||
|
{ "label": "Ueberblick", "href": "/module/boersenchecker" },
|
||||||
|
{ "label": "Depotverwaltung", "href": "/module/boersenchecker/depotverwaltung" },
|
||||||
|
{ "label": "Aktienverwaltung", "href": "/module/boersenchecker/aktienverwaltung" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,26 +1,16 @@
|
|||||||
<?php $ownerQuery = $isAdmin ? '?owner_sub=' . urlencode((string) $ownerSub) : ''; ?>
|
<?php $ownerQuery = $isAdmin ? '?owner_sub=' . urlencode((string) $ownerSub) : ''; ?>
|
||||||
|
<?= module_shell_header('boersenchecker', [
|
||||||
|
'title' => 'Depotverwaltung',
|
||||||
|
'description' => 'Depots, Positionen und Kurs-Historien verwalten.',
|
||||||
|
'tabs' => [
|
||||||
|
['label' => 'Ueberblick', 'href' => '/module/boersenchecker'],
|
||||||
|
['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung', 'active' => true],
|
||||||
|
['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung'],
|
||||||
|
],
|
||||||
|
]) ?>
|
||||||
<div class="bc-app">
|
<div class="bc-app">
|
||||||
<div class="bc-grid-bg">
|
<div class="bc-grid-bg">
|
||||||
<div class="bc-shell bc-stack">
|
<div class="bc-shell bc-stack">
|
||||||
<header class="bc-hero">
|
|
||||||
<div class="bc-hero-top">
|
|
||||||
<div class="bc-hero-copy">
|
|
||||||
<div class="bc-eyebrow">Boersenchecker Modul</div>
|
|
||||||
<h1 class="bc-title">Depotverwaltung</h1>
|
|
||||||
<p class="bc-text">Depots, Positionen und Kurs-Historien verwalten. Die Waehrungsumrechnung nutzt weiterhin die bestehende FX-Logik des Mining-Checkers.</p>
|
|
||||||
</div>
|
|
||||||
<div class="bc-hero-controls">
|
|
||||||
<a class="bc-button bc-button--ghost" href="/">Zur Startseite</a>
|
|
||||||
<a class="bc-button bc-button--secondary" href="/modules/setup/boersenchecker">Setup</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bc-tabs">
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker">Ueberblick</a>
|
|
||||||
<a class="bc-button bc-button--tab-active" href="/module/boersenchecker/depotverwaltung">Depotverwaltung</a>
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker/aktienverwaltung">Aktienverwaltung</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
||||||
@@ -479,3 +469,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
<?= module_shell_header('boersenchecker', [
|
||||||
|
'title' => 'Depot-Ueberblick',
|
||||||
|
'description' => 'Depots, Aktien und Kursverlaeufe in einer Oberflaeche.',
|
||||||
|
'tabs' => [
|
||||||
|
['label' => 'Ueberblick', 'href' => '/module/boersenchecker', 'active' => true],
|
||||||
|
['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung'],
|
||||||
|
['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung'],
|
||||||
|
],
|
||||||
|
]) ?>
|
||||||
<div class="bc-app">
|
<div class="bc-app">
|
||||||
<div class="bc-grid-bg">
|
<div class="bc-grid-bg">
|
||||||
<div class="bc-shell bc-stack" data-bc-home data-chart-endpoint="<?= e($chartEndpoint) ?>">
|
<div class="bc-shell bc-stack" data-bc-home data-chart-endpoint="<?= e($chartEndpoint) ?>">
|
||||||
@@ -10,29 +19,6 @@
|
|||||||
];
|
];
|
||||||
}, $positions), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></script>
|
}, $positions), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></script>
|
||||||
|
|
||||||
<header class="bc-hero">
|
|
||||||
<div class="bc-hero-top">
|
|
||||||
<div class="bc-hero-copy">
|
|
||||||
<div class="bc-eyebrow">Boersenchecker Modul</div>
|
|
||||||
<h1 class="bc-title">Depot-Ueberblick</h1>
|
|
||||||
<p class="bc-text">Depots, Aktien und Kursverlaeufe in einer Oberflaeche. Die Navigation folgt jetzt dem gleichen sichtbaren Prinzip wie beim Mining-Checker.</p>
|
|
||||||
</div>
|
|
||||||
<div class="bc-hero-controls">
|
|
||||||
<a class="bc-button bc-button--ghost" href="/">Zur Startseite</a>
|
|
||||||
<div class="bc-form-card">
|
|
||||||
<div class="bc-field-label">Aktives Depot</div>
|
|
||||||
<div class="bc-text" style="margin-top:8px;"><?= $selectedPortfolioId > 0 && $portfolios !== [] ? e((string) (($portfolios[array_search($selectedPortfolioId, array_column($portfolios, 'id'), true)]['name'] ?? 'Auswahl aktiv'))) : 'Kein Depot ausgewaehlt' ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bc-tabs">
|
|
||||||
<a class="bc-button bc-button--tab-active" href="/module/boersenchecker">Ueberblick</a>
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker/depotverwaltung">Depotverwaltung</a>
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker/aktienverwaltung">Aktienverwaltung</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
||||||
<?php elseif ($notice): ?>
|
<?php elseif ($notice): ?>
|
||||||
@@ -207,3 +193,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
|
<?= module_shell_header('boersenchecker', [
|
||||||
|
'title' => 'Aktienverwaltung',
|
||||||
|
'description' => 'Stammdaten der Aktien pflegen, Symbole suchen und manuelle Kurse verwalten.',
|
||||||
|
'tabs' => [
|
||||||
|
['label' => 'Ueberblick', 'href' => '/module/boersenchecker'],
|
||||||
|
['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung'],
|
||||||
|
['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung', 'active' => true],
|
||||||
|
],
|
||||||
|
]) ?>
|
||||||
<div class="bc-app">
|
<div class="bc-app">
|
||||||
<div class="bc-grid-bg">
|
<div class="bc-grid-bg">
|
||||||
<div class="bc-shell bc-stack">
|
<div class="bc-shell bc-stack">
|
||||||
<header class="bc-hero">
|
|
||||||
<div class="bc-hero-top">
|
|
||||||
<div class="bc-hero-copy">
|
|
||||||
<div class="bc-eyebrow">Boersenchecker Modul</div>
|
|
||||||
<h1 class="bc-title">Aktienverwaltung</h1>
|
|
||||||
<p class="bc-text">Stammdaten der Aktien pflegen, Symbole suchen und manuelle Kurse verwalten.</p>
|
|
||||||
</div>
|
|
||||||
<div class="bc-hero-controls">
|
|
||||||
<a class="bc-button bc-button--ghost" href="/">Zur Startseite</a>
|
|
||||||
<a class="bc-button bc-button--secondary" href="/modules/setup/boersenchecker">Setup</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bc-tabs">
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker">Ueberblick</a>
|
|
||||||
<a class="bc-button bc-button--tab" href="/module/boersenchecker/depotverwaltung">Depotverwaltung</a>
|
|
||||||
<a class="bc-button bc-button--tab-active" href="/module/boersenchecker/aktienverwaltung">Aktienverwaltung</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
<div class="bc-alert bc-alert--error"><?= e($error) ?></div>
|
||||||
@@ -155,3 +145,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
9
modules/kea/design.json
Normal file
9
modules/kea/design.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"eyebrow": "Modul",
|
||||||
|
"title": "KEA DHCP",
|
||||||
|
"description": "Verwaltung von KEA DHCP Hosts und Reservierungen.",
|
||||||
|
"actions": [
|
||||||
|
{ "label": "Gruppen verwalten", "href": "/module/kea/groups", "variant": "secondary" },
|
||||||
|
{ "label": "Setup", "href": "/modules/setup/kea", "variant": "secondary" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
* @var array $stats Kennzahlen fuer die Uebersicht.
|
* @var array $stats Kennzahlen fuer die Uebersicht.
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
|
<?= module_shell_header('kea', [
|
||||||
|
'title' => 'KEA DHCP Hosts',
|
||||||
|
'description' => 'Reservierungen und aktuelle Leases aus der KEA-Datenbank.',
|
||||||
|
]) ?>
|
||||||
<section class="kea-page">
|
<section class="kea-page">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<div>
|
<div>
|
||||||
@@ -15,10 +19,6 @@
|
|||||||
Automatische Aktualisierung alle 5 Sekunden.
|
Automatische Aktualisierung alle 5 Sekunden.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="setup-actions">
|
|
||||||
<a class="cta-button" href="/module/kea/groups">Gruppen verwalten</a>
|
|
||||||
<a class="nav-link" href="/modules/setup/kea">Setup</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
@@ -226,3 +226,4 @@
|
|||||||
window.setInterval(refresh, 5000);
|
window.setInterval(refresh, 5000);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
15
modules/pihole/design.json
Normal file
15
modules/pihole/design.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"eyebrow": "Modul",
|
||||||
|
"title": "Pi-hole",
|
||||||
|
"description": "Pi-hole Monitoring, Listen und Steuerung fuer zwei Instanzen.",
|
||||||
|
"actions": [
|
||||||
|
{ "label": "Zur Startseite", "href": "/", "variant": "ghost" },
|
||||||
|
{ "label": "Instanzen", "href": "/module/pihole/instances", "variant": "secondary" }
|
||||||
|
],
|
||||||
|
"tabs": [
|
||||||
|
{ "label": "Dashboard", "href": "/module/pihole" },
|
||||||
|
{ "label": "Instanzen", "href": "/module/pihole/instances" },
|
||||||
|
{ "label": "Listen", "href": "/module/pihole/lists" },
|
||||||
|
{ "label": "Queries", "href": "/module/pihole/queries" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,10 +6,17 @@ $assets->addScript('/module/pihole/asset?file=pihole.js', 'footer', true);
|
|||||||
$instances = module_fn('pihole', 'instances');
|
$instances = module_fn('pihole', 'instances');
|
||||||
$hasConfig = !empty($instances);
|
$hasConfig = !empty($instances);
|
||||||
?>
|
?>
|
||||||
|
<?= module_shell_header('pihole', [
|
||||||
|
'title' => 'Pi-hole Dashboard',
|
||||||
|
'description' => 'Status, Blockings, Usage und Steuerung fuer beide Instanzen.',
|
||||||
|
'tabs' => [
|
||||||
|
['label' => 'Dashboard', 'href' => '/module/pihole', 'active' => true],
|
||||||
|
['label' => 'Instanzen', 'href' => '/module/pihole/instances'],
|
||||||
|
['label' => 'Listen', 'href' => '/module/pihole/lists'],
|
||||||
|
['label' => 'Queries', 'href' => '/module/pihole/queries'],
|
||||||
|
],
|
||||||
|
]) ?>
|
||||||
<div class="card pihole-page" data-pihole-page="dashboard">
|
<div class="card pihole-page" data-pihole-page="dashboard">
|
||||||
<div class="pill">Pi-hole</div>
|
|
||||||
<h1 style="margin-top:.75rem;">Pi-hole Dashboard</h1>
|
|
||||||
<p class="muted">Status, Blockings, Usage und Steuerung fuer beide Instanzen.</p>
|
|
||||||
|
|
||||||
<div class="card" style="margin-top:1rem;">
|
<div class="card" style="margin-top:1rem;">
|
||||||
<div class="pihole-section-header">
|
<div class="pihole-section-header">
|
||||||
@@ -134,3 +141,4 @@ $hasConfig = !empty($instances);
|
|||||||
<div class="pihole-error" data-instance-errors></div>
|
<div class="pihole-error" data-instance-errors></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
@@ -132,15 +132,23 @@ if ($primaryId === '') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
<?= module_shell_header('pihole', [
|
||||||
|
'title' => 'Pi-hole Instanzen',
|
||||||
|
'description' => 'Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.',
|
||||||
|
'tabs' => [
|
||||||
|
['label' => 'Dashboard', 'href' => '/module/pihole'],
|
||||||
|
['label' => 'Instanzen', 'href' => '/module/pihole/instances', 'active' => true],
|
||||||
|
['label' => 'Listen', 'href' => '/module/pihole/lists'],
|
||||||
|
['label' => 'Queries', 'href' => '/module/pihole/queries'],
|
||||||
|
],
|
||||||
|
]) ?>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="pill">Pi-hole</div>
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-top:.75rem;">
|
|
||||||
<h1 style="margin:0;">Instanzen</h1>
|
<h1 style="margin:0;">Instanzen</h1>
|
||||||
<div style="display:flex; gap:10px; flex-wrap:wrap;">
|
<div style="display:flex; gap:10px; flex-wrap:wrap;">
|
||||||
<button class="cta-button" type="button" data-instance-new>+ Neue Instanz</button>
|
<button class="cta-button" type="button" data-instance-new>+ Neue Instanz</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="muted">Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.</p>
|
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="card notice-card" style="margin-top:1rem; border-color:#ffb4a8; background:#fff5f3; color:#7a2114;">
|
<div class="card notice-card" style="margin-top:1rem; border-color:#ffb4a8; background:#fff5f3; color:#7a2114;">
|
||||||
@@ -222,3 +230,4 @@ if ($primaryId === '') {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?= module_shell_footer() ?>
|
||||||
|
|||||||
@@ -897,3 +897,192 @@ a {
|
|||||||
.ip-dot.is-used {
|
.ip-dot.is-used {
|
||||||
background: color-mix(in srgb, var(--muted) 55%, var(--surface));
|
background: color-mix(in srgb, var(--muted) 55%, var(--surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-shell {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-page-bg {
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-page-bg::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 12% 20%, color-mix(in srgb, var(--brand-accent-2) 12%, transparent), transparent 24%),
|
||||||
|
radial-gradient(circle at 90% 6%, color-mix(in srgb, var(--brand-accent-3) 12%, transparent), transparent 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-page-stack {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-hero {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
padding: 28px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 28px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(245, 252, 251, 0.88)),
|
||||||
|
linear-gradient(90deg, color-mix(in srgb, var(--brand-accent) 14%, transparent), color-mix(in srgb, var(--brand-accent-2) 14%, transparent));
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="night"] .module-hero {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(8, 18, 28, 0.94), rgba(15, 29, 42, 0.86)),
|
||||||
|
linear-gradient(90deg, color-mix(in srgb, var(--brand-accent) 18%, transparent), color-mix(in srgb, var(--brand-accent-2) 16%, transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-hero-top {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
grid-template-columns: minmax(0, 1.6fr) minmax(220px, 0.8fr);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-hero-copy,
|
||||||
|
.module-hero-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(1.75rem, 4vw, 2.9rem);
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-lead {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 160ms ease;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-button:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-button--tab-active,
|
||||||
|
.module-button--primary {
|
||||||
|
background: linear-gradient(135deg, var(--brand-accent), var(--brand-accent-3));
|
||||||
|
color: #fff7fb;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 14px 28px color-mix(in srgb, var(--brand-accent) 18%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-button--tab,
|
||||||
|
.module-button--secondary {
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
color: #09111f;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-button--ghost {
|
||||||
|
background: color-mix(in srgb, var(--brand-accent) 14%, transparent);
|
||||||
|
border-color: color-mix(in srgb, var(--brand-accent) 34%, transparent);
|
||||||
|
color: var(--brand-accent);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box,
|
||||||
|
.module-box-soft,
|
||||||
|
.module-box-table,
|
||||||
|
.module-box-empty {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 22px;
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: 0 12px 30px rgba(1, 22, 32, 0.08);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box,
|
||||||
|
.module-box-soft,
|
||||||
|
.module-box-table {
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-soft {
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(248,252,252,0.92));
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-empty {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-grid--stats {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-grid--panels {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-head p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-table {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-box-table > .module-box-head,
|
||||||
|
.module-box-table > .module-box-copy {
|
||||||
|
padding: 18px 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.module-hero-top {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -253,6 +253,96 @@ function module_tpl(string $module, string $name, array $data = []): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function module_design(string $module): array
|
||||||
|
{
|
||||||
|
if (preg_match('/[^a-zA-Z0-9_\-]/', $module)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static $cache = [];
|
||||||
|
if (array_key_exists($module, $cache)) {
|
||||||
|
return $cache[$module];
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../../modules/' . $module . '/design.json';
|
||||||
|
if (!is_file($path)) {
|
||||||
|
$cache[$module] = [];
|
||||||
|
return $cache[$module];
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = file_get_contents($path);
|
||||||
|
$decoded = is_string($raw) && $raw !== '' ? json_decode($raw, true) : null;
|
||||||
|
$cache[$module] = is_array($decoded) ? $decoded : [];
|
||||||
|
return $cache[$module];
|
||||||
|
}
|
||||||
|
|
||||||
|
function module_shell_header(string $module, array $options = []): string
|
||||||
|
{
|
||||||
|
$design = module_design($module);
|
||||||
|
$requestPath = app()->request()->path();
|
||||||
|
$title = trim((string) ($options['title'] ?? $design['title'] ?? ucfirst($module)));
|
||||||
|
$description = trim((string) ($options['description'] ?? $design['description'] ?? ''));
|
||||||
|
$eyebrow = trim((string) ($options['eyebrow'] ?? $design['eyebrow'] ?? 'Modul'));
|
||||||
|
$actions = is_array($options['actions'] ?? null) ? $options['actions'] : (is_array($design['actions'] ?? null) ? $design['actions'] : []);
|
||||||
|
$tabs = is_array($options['tabs'] ?? null) ? $options['tabs'] : (is_array($design['tabs'] ?? null) ? $design['tabs'] : []);
|
||||||
|
|
||||||
|
$html = '<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">';
|
||||||
|
$html .= '<header class="module-hero">';
|
||||||
|
$html .= '<div class="module-hero-top">';
|
||||||
|
$html .= '<div class="module-hero-copy">';
|
||||||
|
$html .= '<div class="eyebrow">' . e($eyebrow) . '</div>';
|
||||||
|
$html .= '<h1 class="module-title">' . e($title) . '</h1>';
|
||||||
|
if ($description !== '') {
|
||||||
|
$html .= '<p class="module-lead">' . e($description) . '</p>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
if ($actions !== []) {
|
||||||
|
$html .= '<div class="module-hero-actions">';
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
if (!is_array($action)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$label = trim((string) ($action['label'] ?? ''));
|
||||||
|
$href = trim((string) ($action['href'] ?? ''));
|
||||||
|
if ($label === '' || $href === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$variant = trim((string) ($action['variant'] ?? 'secondary'));
|
||||||
|
$class = $variant === 'ghost' ? 'module-button module-button--ghost' : 'module-button module-button--secondary';
|
||||||
|
$html .= '<a class="' . e($class) . '" href="' . e($href) . '">' . e($label) . '</a>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</div>';
|
||||||
|
if ($tabs !== []) {
|
||||||
|
$html .= '<nav class="module-tabs" aria-label="Modulnavigation">';
|
||||||
|
foreach ($tabs as $tab) {
|
||||||
|
if (!is_array($tab)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$label = trim((string) ($tab['label'] ?? ''));
|
||||||
|
$href = trim((string) ($tab['href'] ?? ''));
|
||||||
|
if ($label === '' || $href === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$isActive = !empty($tab['active']) || $href === $requestPath;
|
||||||
|
$class = $isActive ? 'module-button module-button--tab-active' : 'module-button module-button--tab';
|
||||||
|
$html .= '<a class="' . e($class) . '" href="' . e($href) . '">' . e($label) . '</a>';
|
||||||
|
}
|
||||||
|
$html .= '</nav>';
|
||||||
|
}
|
||||||
|
$html .= '</header>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function module_shell_footer(): string
|
||||||
|
{
|
||||||
|
return '</div></div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML Escaping Helper.
|
* HTML Escaping Helper.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user