Anpassung Menü
This commit is contained in:
+126
-17
@@ -424,12 +424,13 @@ function app_tenant_navigation_items(?array $auth, array $license = []): array
|
||||
if ($canManage) {
|
||||
$items[] = ['key' => 'content', 'label' => 'Hinweise & FAQ', 'href' => '/content/'];
|
||||
$items[] = ['key' => 'reports', 'label' => 'Reporting', 'href' => '/reports/'];
|
||||
$items[] = ['key' => 'roles', 'label' => 'Rollen', 'href' => '/tenants/roles/'];
|
||||
|
||||
if (!empty($features['tenant_settings'])) {
|
||||
$items[] = ['key' => 'settings', 'label' => 'Einstellungen', 'href' => '/settings/'];
|
||||
$items[] = ['key' => 'settings', 'label' => 'Mandanten-Einstellungen', 'href' => '/settings/'];
|
||||
}
|
||||
|
||||
$items[] = ['key' => 'roles', 'label' => 'Rollen', 'href' => '/tenants/roles/'];
|
||||
|
||||
if ($hasExports) {
|
||||
$items[] = ['key' => 'exports', 'label' => 'Exporte', 'href' => '/exports/'];
|
||||
}
|
||||
@@ -454,7 +455,7 @@ function app_tenant_navigation_groups(array $items): array
|
||||
$groupOrder = ['data', 'content', 'management'];
|
||||
$groupLabels = [
|
||||
'data' => 'Daten',
|
||||
'content' => 'Hilfe & Inhalte',
|
||||
'content' => 'Inhalte & Support',
|
||||
'management' => 'Verwaltung',
|
||||
];
|
||||
$groupMap = [
|
||||
@@ -464,6 +465,7 @@ function app_tenant_navigation_groups(array $items): array
|
||||
'content' => 'content',
|
||||
'support' => 'content',
|
||||
'surveys' => 'content',
|
||||
'members' => 'management',
|
||||
'roles' => 'management',
|
||||
'settings' => 'management',
|
||||
];
|
||||
@@ -496,6 +498,20 @@ function app_tenant_navigation_groups(array $items): array
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{key:string,label:string,href:string}>
|
||||
*/
|
||||
function app_tenant_account_items(?array $auth): array
|
||||
{
|
||||
if (!is_array($auth)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
['key' => 'profile', 'label' => 'Persönliche Einstellungen', 'href' => '/profile/'],
|
||||
];
|
||||
}
|
||||
|
||||
function app_users_support_theme_mode(PDO $pdo): bool
|
||||
{
|
||||
static $cache = null;
|
||||
@@ -1234,19 +1250,19 @@ function app_handle_profile_action(PDO $pdo, array $auth): void
|
||||
|
||||
if ($tenantId === '' || $tenantUserId === '') {
|
||||
app_flash('Das Profil konnte nicht aktualisiert werden.', 'error');
|
||||
app_redirect('/dashboard/');
|
||||
app_redirect(app_request_path());
|
||||
}
|
||||
|
||||
if ($displayName === '') {
|
||||
app_flash('Bitte gib einen Anzeigenamen an.', 'error');
|
||||
app_redirect('/dashboard/');
|
||||
app_redirect(app_request_path());
|
||||
}
|
||||
|
||||
$membership = app_tenant_user_by_id($pdo, $tenantUserId, $tenantId);
|
||||
|
||||
if ($membership === null) {
|
||||
app_flash('Die zugehörige Person konnte nicht gefunden werden.', 'error');
|
||||
app_redirect('/dashboard/');
|
||||
app_redirect(app_request_path());
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
@@ -1278,7 +1294,7 @@ function app_handle_profile_action(PDO $pdo, array $auth): void
|
||||
|
||||
app_set_auth_user(array_merge($auth, ['display_name' => $displayName]));
|
||||
app_flash('Dein Profil wurde aktualisiert.', 'success');
|
||||
app_redirect('/dashboard/');
|
||||
app_redirect(app_request_path());
|
||||
}
|
||||
|
||||
function app_handle_theme_action(PDO $pdo, array $auth): void
|
||||
@@ -2368,6 +2384,38 @@ SQL;
|
||||
|
||||
$summary = app_query_one($pdo, $summarySql, ['tenant_id' => $tenantId]) ?? [];
|
||||
|
||||
$yearSummary = app_query_one(
|
||||
$pdo,
|
||||
<<<'SQL'
|
||||
SELECT
|
||||
COALESCE((
|
||||
SELECT SUM(ce.strokes)
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.tenant_id = :tenant_id
|
||||
AND YEAR(ce.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_strokes,
|
||||
COALESCE((
|
||||
SELECT SUM(ce.total_cost)
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.tenant_id = :tenant_id
|
||||
AND YEAR(ce.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_spend,
|
||||
COALESCE((
|
||||
SELECT SUM(pe.amount)
|
||||
FROM payment_entries pe
|
||||
WHERE pe.tenant_id = :tenant_id
|
||||
AND YEAR(pe.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_payments,
|
||||
COALESCE((
|
||||
SELECT COUNT(DISTINCT ce.member_id)
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.tenant_id = :tenant_id
|
||||
AND YEAR(ce.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_active_members
|
||||
SQL,
|
||||
['tenant_id' => $tenantId]
|
||||
) ?? [];
|
||||
|
||||
$recentSql = <<<'SQL'
|
||||
SELECT
|
||||
le.booked_at,
|
||||
@@ -2382,11 +2430,68 @@ ORDER BY le.booked_at DESC
|
||||
LIMIT 10
|
||||
SQL;
|
||||
|
||||
$memberOverviewSql = <<<'SQL'
|
||||
SELECT
|
||||
m.id,
|
||||
m.display_name,
|
||||
COALESCE(SUM(le.amount), 0) AS balance,
|
||||
COALESCE((
|
||||
SELECT SUM(ce.strokes)
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.member_id = m.id
|
||||
AND ce.tenant_id = m.tenant_id
|
||||
AND YEAR(ce.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_strokes,
|
||||
COALESCE((
|
||||
SELECT SUM(pe.amount)
|
||||
FROM payment_entries pe
|
||||
WHERE pe.member_id = m.id
|
||||
AND pe.tenant_id = m.tenant_id
|
||||
AND YEAR(pe.booked_at) = YEAR(CURRENT_DATE())
|
||||
), 0) AS year_payments,
|
||||
MAX(le.booked_at) AS last_booking_at
|
||||
FROM members m
|
||||
LEFT JOIN ledger_entries le ON le.member_id = m.id AND le.tenant_id = m.tenant_id
|
||||
WHERE m.tenant_id = :tenant_id
|
||||
AND m.status = 'active'
|
||||
GROUP BY m.id, m.display_name
|
||||
ORDER BY balance ASC, m.display_name ASC
|
||||
LIMIT 12
|
||||
SQL;
|
||||
|
||||
$memberRows = app_query_all($pdo, $memberOverviewSql, ['tenant_id' => $tenantId]);
|
||||
$largestDebtors = array_values(array_filter(
|
||||
$memberRows,
|
||||
static fn(array $row): bool => (float) ($row['balance'] ?? 0) < 0
|
||||
));
|
||||
usort(
|
||||
$largestDebtors,
|
||||
static fn(array $left, array $right): int => ((float) ($left['balance'] ?? 0) <=> (float) ($right['balance'] ?? 0))
|
||||
);
|
||||
$largestDebtors = array_slice($largestDebtors, 0, 3);
|
||||
|
||||
$largestCredits = array_values(array_filter(
|
||||
$memberRows,
|
||||
static fn(array $row): bool => (float) ($row['balance'] ?? 0) > 0
|
||||
));
|
||||
usort(
|
||||
$largestCredits,
|
||||
static fn(array $left, array $right): int => ((float) ($right['balance'] ?? 0) <=> (float) ($left['balance'] ?? 0))
|
||||
);
|
||||
$largestCredits = array_slice($largestCredits, 0, 3);
|
||||
|
||||
return [
|
||||
'active_members' => (string) ($summary['active_members'] ?? '0'),
|
||||
'coffee_volume' => (string) ($summary['coffee_volume'] ?? '0.00'),
|
||||
'payment_volume' => (string) ($summary['payment_volume'] ?? '0.00'),
|
||||
'open_balance' => (string) ($summary['open_balance'] ?? '0.00'),
|
||||
'year_strokes' => (string) ($yearSummary['year_strokes'] ?? '0'),
|
||||
'year_spend' => (string) ($yearSummary['year_spend'] ?? '0.00'),
|
||||
'year_payments' => (string) ($yearSummary['year_payments'] ?? '0.00'),
|
||||
'year_active_members' => (string) ($yearSummary['year_active_members'] ?? '0'),
|
||||
'member_rows' => $memberRows,
|
||||
'largest_debtors' => $largestDebtors,
|
||||
'largest_credits' => $largestCredits,
|
||||
'recent_entries' => app_query_all($pdo, $recentSql, ['tenant_id' => $tenantId]),
|
||||
];
|
||||
}
|
||||
@@ -2834,17 +2939,21 @@ function app_delete_payment_entry(PDO $pdo, string $tenantId, string $paymentId)
|
||||
|
||||
function app_content_for_tenant(PDO $pdo, string $tenantId): array
|
||||
{
|
||||
$announcements = app_query_all(
|
||||
$pdo,
|
||||
'SELECT id, title, message, visible_until, is_active FROM announcements WHERE tenant_id = :tenant_id ORDER BY is_active DESC, created_at DESC LIMIT 12',
|
||||
['tenant_id' => $tenantId]
|
||||
);
|
||||
$announcements = scripts_table_exists($pdo, 'announcements')
|
||||
? app_query_all(
|
||||
$pdo,
|
||||
'SELECT id, title, message, visible_until, is_active FROM announcements WHERE tenant_id = :tenant_id ORDER BY is_active DESC, created_at DESC LIMIT 12',
|
||||
['tenant_id' => $tenantId]
|
||||
)
|
||||
: [];
|
||||
|
||||
$faq = app_query_all(
|
||||
$pdo,
|
||||
'SELECT id, question, answer, sort_order, is_active FROM faq_items WHERE tenant_id = :tenant_id ORDER BY is_active DESC, sort_order ASC, created_at DESC LIMIT 12',
|
||||
['tenant_id' => $tenantId]
|
||||
);
|
||||
$faq = scripts_table_exists($pdo, 'faq_items')
|
||||
? app_query_all(
|
||||
$pdo,
|
||||
'SELECT id, question, answer, sort_order, is_active FROM faq_items WHERE tenant_id = :tenant_id ORDER BY is_active DESC, sort_order ASC, created_at DESC LIMIT 12',
|
||||
['tenant_id' => $tenantId]
|
||||
)
|
||||
: [];
|
||||
|
||||
return [
|
||||
'announcements' => $announcements,
|
||||
|
||||
+272
-114
@@ -56,6 +56,7 @@ if ($requestedPage === null) {
|
||||
'/reports' => 'reports',
|
||||
'/support' => 'support',
|
||||
'/surveys' => 'surveys',
|
||||
'/profil', '/profile' => 'profile',
|
||||
'/settings' => 'settings',
|
||||
'/exports' => 'exports',
|
||||
'/logout' => 'logout',
|
||||
@@ -65,7 +66,7 @@ if ($requestedPage === null) {
|
||||
|
||||
$page = (string) $requestedPage;
|
||||
$requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
$tenantPages = ['dashboard', 'members', 'ledger', 'payments', 'content', 'imports', 'reports', 'support', 'surveys', 'settings', 'exports'];
|
||||
$tenantPages = ['dashboard', 'members', 'ledger', 'payments', 'content', 'imports', 'reports', 'support', 'surveys', 'profile', 'settings', 'exports'];
|
||||
|
||||
if ($page === 'logout' && $requestMethod === 'POST') {
|
||||
app_logout();
|
||||
@@ -83,6 +84,7 @@ $members = [];
|
||||
$ledger = [];
|
||||
$payments = [];
|
||||
$content = ['announcements' => [], 'faq' => []];
|
||||
$dashboardAnnouncements = [];
|
||||
$memberSummary = null;
|
||||
$memberReport = null;
|
||||
$ledgerScope = 'all';
|
||||
@@ -149,6 +151,9 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
if (in_array($page, ['dashboard', 'ledger', 'payments'], true)) {
|
||||
app_handle_tenant_action($pdo, $auth);
|
||||
app_handle_bulk_finance_action($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'profile') {
|
||||
app_handle_profile_action($pdo, $auth);
|
||||
}
|
||||
|
||||
@@ -216,8 +221,21 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
$paymentMembers = app_members_for_scope($pdo, (string) $auth['tenant_id'], $paymentScope);
|
||||
}
|
||||
|
||||
if ($page === 'content') {
|
||||
if ($page === 'content' || $page === 'dashboard') {
|
||||
$content = app_content_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
$dashboardAnnouncements = array_slice(
|
||||
array_values(
|
||||
array_filter(
|
||||
$content['announcements'] ?? [],
|
||||
static fn(array $announcement): bool => (int) ($announcement['is_active'] ?? 0) === 1
|
||||
)
|
||||
),
|
||||
0,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
if ($page === 'content') {
|
||||
|
||||
if (isset($_GET['edit_announcement']) && $_GET['edit_announcement'] !== '') {
|
||||
foreach (($content['announcements'] ?? []) as $announcement) {
|
||||
@@ -309,10 +327,11 @@ $guestNavItems = [
|
||||
];
|
||||
$primaryNavItems = $auth === null ? $guestNavItems : $tenantNavItems;
|
||||
$navGroups = $auth === null ? [] : app_tenant_navigation_groups($primaryNavItems);
|
||||
$accountNavItems = $auth === null ? [] : app_tenant_account_items($auth);
|
||||
$headerNavItems = $primaryNavItems;
|
||||
if ($auth !== null) {
|
||||
$headerNavItems = [];
|
||||
$primaryKeys = ['dashboard', 'members', 'ledger', 'payments'];
|
||||
$primaryKeys = ['dashboard', 'ledger', 'payments'];
|
||||
foreach ($primaryNavItems as $item) {
|
||||
if (in_array((string) ($item['key'] ?? ''), $primaryKeys, true)) {
|
||||
$headerNavItems[] = $item;
|
||||
@@ -324,8 +343,9 @@ $ledgerViewMode = in_array((string) ($_GET['mode'] ?? 'capture'), ['capture', 'j
|
||||
? (string) ($_GET['mode'] ?? 'capture')
|
||||
: 'capture';
|
||||
$mobileBottomNavItems = [];
|
||||
$mobileDrawerPrimaryItems = [];
|
||||
if ($auth !== null) {
|
||||
foreach (['dashboard', 'members', 'ledger'] as $mobileKey) {
|
||||
foreach (['dashboard', 'ledger', 'payments'] as $mobileKey) {
|
||||
foreach ($primaryNavItems as $item) {
|
||||
if ((string) ($item['key'] ?? '') === $mobileKey) {
|
||||
$mobileBottomNavItems[] = $item;
|
||||
@@ -341,6 +361,9 @@ foreach ($primaryNavItems as $item) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($page === 'profile') {
|
||||
$currentNavLabel = 'Persönliche Einstellungen';
|
||||
}
|
||||
$themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
$isMarketingHome = $page === 'home' && $auth === null;
|
||||
$heroEyebrow = 'Für Teams, Büros und Standorte';
|
||||
@@ -409,7 +432,7 @@ $marketing = app_marketing_messages();
|
||||
.site-more__group{display:grid;gap:8px;padding:8px;border-radius:12px;background:var(--surface-soft)}
|
||||
.site-more__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint)}
|
||||
.site-mobile{display:none}
|
||||
.site-mobile__toggle{display:inline-flex;align-items:center;gap:10px;cursor:pointer;min-height:38px;padding:.45rem .72rem;border-radius:10px;border:1px solid var(--line-strong);background:rgba(255,255,255,.04);color:var(--text-strong);font-weight:600}
|
||||
.site-mobile__toggle{display:none;align-items:center;gap:10px;cursor:pointer;min-height:38px;padding:.45rem .72rem;border-radius:10px;border:1px solid var(--line-strong);background:rgba(255,255,255,.04);color:var(--text-strong);font-weight:600}
|
||||
.site-mobile__toggle::before{content:"";display:block;width:18px;height:2px;border-radius:999px;background:currentColor;box-shadow:0 -6px 0 currentColor,0 6px 0 currentColor}
|
||||
.mobile-drawer{position:fixed;inset:0;display:none;z-index:55}
|
||||
.mobile-drawer.is-open{display:block}
|
||||
@@ -538,7 +561,7 @@ $marketing = app_marketing_messages();
|
||||
.marketing-footer{position:fixed;left:0;right:0;bottom:0;z-index:25;margin-top:0;padding:10px 24px;border-top:1px solid rgba(137,154,188,.14);background:rgba(8,10,18,.88);backdrop-filter:blur(18px);color:rgba(209,217,235,.68);font-size:.92rem}
|
||||
.marketing-footer__inner{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;width:min(1240px,calc(100% - 48px));margin:0 auto}
|
||||
@media(max-width:960px){.marketing-main,.landing-hero,.landing-section,.landing-callout{width:calc(100vw - 24px)}.marketing-bar{height:72px;min-height:72px;margin-bottom:16px;padding:0 12px}.marketing-nav,.marketing-actions{display:none}.marketing-mobile{display:block}.landing-hero,.landing-feature-grid,.landing-step-grid,.landing-use-grid,.landing-proof-grid{grid-template-columns:1fr}.landing-hero{gap:24px;padding-top:0}.landing-section,.landing-callout{padding:20px}.marketing-shell{padding:0 0 88px}.marketing-footer{padding:10px 12px}.marketing-footer__inner{width:100%}}
|
||||
@media(max-width:1180px){body.mobile-drawer-open{overflow:hidden}.page-shell{padding-bottom:94px}.site-header{margin-bottom:14px}.site-header__inner{min-height:68px;width:100%;align-items:center;padding:10px 16px}.site-brand__subtitle{font-size:.8rem}.site-nav{display:none}.site-mobile{display:block}.site-actions{gap:8px}.content{width:min(100vw - 20px,1460px);gap:14px}.hero{padding:16px}.hero .eyebrow,.hero p{display:none}.hero h1{font-size:1.5rem;margin:0}.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.table table{min-width:0}.desktop-ledger-shell{display:none}.mobile-ledger-shell{display:grid;gap:14px}.mobile-bottom-nav{position:fixed;left:10px;right:10px;bottom:max(10px,env(safe-area-inset-bottom));z-index:45;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;padding:6px;border:1px solid var(--line);border-radius:18px;background:color-mix(in srgb,var(--surface-3) 92%, rgba(var(--brand-rgb),.12) 8%);box-shadow:var(--shadow)}}
|
||||
@media(max-width:1180px){body.mobile-drawer-open{overflow:hidden}.page-shell{padding-bottom:94px}.site-header{margin-bottom:14px}.site-header__inner{min-height:68px;width:100%;align-items:center;padding:10px 16px}.site-brand__subtitle{font-size:.8rem}.site-nav{display:none}.site-mobile{display:block}.site-actions{gap:8px}.site-actions .site-more{display:none}.site-mobile__toggle{display:inline-flex}.content{width:min(100vw - 20px,1460px);gap:14px}.hero{padding:16px}.hero .eyebrow,.hero p{display:none}.hero h1{font-size:1.5rem;margin:0}.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.table table{min-width:0}.desktop-ledger-shell{display:none}.mobile-ledger-shell{display:grid;gap:14px}.mobile-bottom-nav{position:fixed;left:10px;right:10px;bottom:max(10px,env(safe-area-inset-bottom));z-index:45;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;padding:6px;border:1px solid var(--line);border-radius:18px;background:color-mix(in srgb,var(--surface-3) 92%, rgba(var(--brand-rgb),.12) 8%);box-shadow:var(--shadow)}}
|
||||
@media(min-width:1181px){.site-mobile{display:none}}
|
||||
</style>
|
||||
</head>
|
||||
@@ -737,6 +760,18 @@ $marketing = app_marketing_messages();
|
||||
<?php if ($auth === null): ?>
|
||||
<a class="button secondary" href="/login/">Anmelden</a>
|
||||
<?php else: ?>
|
||||
<details class="site-more">
|
||||
<summary class="site-more__toggle">Konto</summary>
|
||||
<div class="site-more__panel">
|
||||
<div class="site-more__group">
|
||||
<div class="site-more__label">Konto</div>
|
||||
<?php foreach ($accountNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="site-nav__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<button type="button" class="site-mobile__toggle" data-mobile-drawer-open>Mehr</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -754,14 +789,16 @@ $marketing = app_marketing_messages();
|
||||
</div>
|
||||
<button type="button" class="mobile-drawer__close" aria-label="Schließen" data-mobile-drawer-close>×</button>
|
||||
</div>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Direkt</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileBottomNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="mobile-drawer__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>"><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($mobileDrawerPrimaryItems !== []): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Weitere Kernfunktionen</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileDrawerPrimaryItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="mobile-drawer__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($navGroups as $group): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label"><?= h((string) ($group['label'] ?? 'Mehr')) ?></div>
|
||||
@@ -773,11 +810,14 @@ $marketing = app_marketing_messages();
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="mobile-drawer__footer">
|
||||
<div class="mobile-drawer__meta">
|
||||
<strong><?= h((string) ($auth['tenant_name'] ?? 'Bereich')) ?></strong>
|
||||
<span class="muted"><?= h((string) ($auth['display_name'] ?? 'Angemeldet')) ?></span>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Konto</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($accountNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="mobile-drawer__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/settings/" class="mobile-drawer__link <?= $page === 'settings' ? 'active' : '' ?>">Einstellungen</a>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -943,9 +983,9 @@ $marketing = app_marketing_messages();
|
||||
<?php endif; ?>
|
||||
<?php elseif ($page === 'dashboard'): ?>
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Übersicht</div>
|
||||
<div class="eyebrow">Kaffeeliste aktuell</div>
|
||||
<h1><?= h((string) $auth['tenant_name']) ?></h1>
|
||||
<p>Alles Wichtige auf einen Blick.</p>
|
||||
<p>Aktueller Stand der Kaffeeliste mit Salden, Jahreswerten, letzter Aktivität und den wichtigsten Arbeitswegen.</p>
|
||||
<?php if (app_is_platform_admin($auth) && app_admin_user() !== null): ?>
|
||||
<div class="actions" style="margin-top:18px">
|
||||
<a class="button secondary" href="/admin/">Zur Verwaltung</a>
|
||||
@@ -966,108 +1006,182 @@ $marketing = app_marketing_messages();
|
||||
|
||||
<section class="grid grid-2" style="margin-top:18px">
|
||||
<article class="card">
|
||||
<div class="eyebrow">Heute</div>
|
||||
<h2>Schnell weiter</h2>
|
||||
<div class="section-mini">
|
||||
<p class="section-mini__copy">Mitglieder, Buchungen und weitere Bereiche öffnest du direkt über das Menü.</p>
|
||||
<div class="eyebrow">Jahresübersicht</div>
|
||||
<h2>Stand im laufenden Jahr</h2>
|
||||
<div class="grid grid-3">
|
||||
<article class="metric">
|
||||
<strong><?= num($tenantDashboard['year_strokes'] ?? 0) ?></strong>
|
||||
<h3>Striche</h3>
|
||||
<p>Gebuchte Striche im aktuellen Jahr.</p>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<strong><?= money($tenantDashboard['year_spend'] ?? 0) ?></strong>
|
||||
<h3>Ausgaben</h3>
|
||||
<p>Gesamtkosten aus Kaffeeeinträgen.</p>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<strong><?= money($tenantDashboard['year_payments'] ?? 0) ?></strong>
|
||||
<h3>Einzahlungen</h3>
|
||||
<p>Alle verbuchten Einzahlungen im Jahr.</p>
|
||||
</article>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Status</div>
|
||||
<h2>Alles im Blick</h2>
|
||||
<div class="section-mini">
|
||||
<p class="section-mini__copy">Die wichtigsten Zahlen und die letzten Buchungen stehen hier zuerst.</p>
|
||||
<h2>Kaffeeliste im Überblick</h2>
|
||||
<div class="grid grid-2">
|
||||
<article class="metric">
|
||||
<strong><?= money($tenantDashboard['open_balance'] ?? 0) ?></strong>
|
||||
<h3>Offener Gesamtsaldo</h3>
|
||||
<p>Aktueller Saldo über alle Ledger-Einträge.</p>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<strong><?= num($tenantDashboard['year_active_members'] ?? 0) ?></strong>
|
||||
<h3>Aktive im Jahr</h3>
|
||||
<p>Mitglieder mit mindestens einem Kaffeeeintrag.</p>
|
||||
</article>
|
||||
</div>
|
||||
<div class="actions" style="margin-top:18px">
|
||||
<a class="button secondary" href="/ledger/">Buchungen öffnen</a>
|
||||
<a class="button secondary" href="/payments/">Zahlungen öffnen</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-2" style="margin-top:18px">
|
||||
<?php if ($canManageTenant): ?>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Kernfunktion</div>
|
||||
<h2>Buchungen für Kaffee erfassen</h2>
|
||||
<article class="card">
|
||||
<div class="eyebrow"><?= $canManageTenant ? 'Schnellaktionen' : 'Self-Service' ?></div>
|
||||
<h2><?= $canManageTenant ? 'Direkt arbeiten' : 'Mein Bereich' ?></h2>
|
||||
<?php if ($canManageTenant): ?>
|
||||
<div class="grid grid-2">
|
||||
<article class="metric">
|
||||
<strong>Buchungen</strong>
|
||||
<h3>Sammelerfassung öffnen</h3>
|
||||
<p>Striche für alle, Vorderseite oder Rückseite direkt buchen.</p>
|
||||
<div class="actions" style="margin-top:14px"><a class="button secondary" href="/ledger/">Zu den Buchungen</a></div>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<strong>Zahlungen</strong>
|
||||
<h3>Einzahlungen und Korrekturen</h3>
|
||||
<p>Einzahlungen buchen, stornieren und Zahlarten sauber nachführen.</p>
|
||||
<div class="actions" style="margin-top:14px"><a class="button secondary" href="/payments/">Zu den Zahlungen</a></div>
|
||||
</article>
|
||||
</div>
|
||||
<?php elseif (($tenantSettings['allow_self_service_booking'] ?? '1') === '1' && !empty($auth['member_id'])): ?>
|
||||
<form method="post" action="/dashboard/" class="grid">
|
||||
<input type="hidden" name="action" value="record-coffee">
|
||||
<label>Mitglied<select name="member_id"><?php foreach ($members as $member): ?><option value="<?= h((string) $member['id']) ?>" <?= ((string) ($auth['member_id'] ?? '')) === (string) $member['id'] ? 'selected' : '' ?>><?= h((string) $member['display_name']) ?></option><?php endforeach; ?></select></label>
|
||||
<label>Anzahl Striche<input type="number" name="strokes" min="1" step="1" value="1"></label>
|
||||
<label>Preis pro Strich<input type="number" name="unit_price" min="0.01" step="0.01" value="0.50"></label>
|
||||
<input type="hidden" name="member_id" value="<?= h((string) ($auth['member_id'] ?? '')) ?>">
|
||||
<label>Anzahl Striche<input type="number" name="strokes" min="1" max="2" step="1" value="1"></label>
|
||||
<label>Preis pro Strich<input type="number" name="unit_price" min="0.01" step="0.01" value="<?= h((string) ($tenantSettings['default_unit_price'] ?? '0.50')) ?>"></label>
|
||||
<label>Buchungszeit<input type="datetime-local" name="booked_at" value="<?= date('Y-m-d\TH:i') ?>"></label>
|
||||
<label>Quelle<input type="text" name="booking_source" value="self-service"></label>
|
||||
<div class="actions"><button type="submit">Striche buchen</button></div>
|
||||
<input type="hidden" name="booking_source" value="self-service">
|
||||
<div class="actions"><button type="submit">Eigene Striche buchen</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Kernfunktion</div>
|
||||
<h2>Einzahlung erfassen</h2>
|
||||
<form method="post" action="/dashboard/" class="grid">
|
||||
<input type="hidden" name="action" value="record-payment">
|
||||
<label>Mitglied<select name="member_id"><?php foreach ($members as $member): ?><option value="<?= h((string) $member['id']) ?>" <?= ((string) ($auth['member_id'] ?? '')) === (string) $member['id'] ? 'selected' : '' ?>><?= h((string) $member['display_name']) ?></option><?php endforeach; ?></select></label>
|
||||
<label>Betrag<input type="number" name="amount" min="0.01" step="0.01" value="5.00"></label>
|
||||
<label>Zahlungsart<select name="payment_method"><option value="manual">Manuell</option><option value="paypal">PayPal</option><option value="bank">Bank</option></select></label>
|
||||
<label>Buchungszeit<input type="datetime-local" name="booked_at" value="<?= date('Y-m-d\TH:i') ?>"></label>
|
||||
<div class="actions"><button type="submit">Einzahlung buchen</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<?php else: ?>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Self-Service</div>
|
||||
<h2>Mein Bereich</h2>
|
||||
<?php if (($tenantSettings['allow_self_service_booking'] ?? '1') === '1' && !empty($auth['member_id'])): ?>
|
||||
<form method="post" action="/dashboard/" class="grid">
|
||||
<input type="hidden" name="action" value="record-coffee">
|
||||
<input type="hidden" name="member_id" value="<?= h((string) ($auth['member_id'] ?? '')) ?>">
|
||||
<label>Anzahl Striche<input type="number" name="strokes" min="1" max="2" step="1" value="1"></label>
|
||||
<label>Preis pro Strich<input type="number" name="unit_price" min="0.01" step="0.01" value="<?= h((string) ($tenantSettings['default_unit_price'] ?? '0.50')) ?>"></label>
|
||||
<label>Buchungszeit<input type="datetime-local" name="booked_at" value="<?= date('Y-m-d\TH:i') ?>"></label>
|
||||
<input type="hidden" name="booking_source" value="self-service">
|
||||
<div class="actions"><button type="submit">Eigene Striche buchen</button></div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<ul class="list">
|
||||
<li>Hier siehst du deinen Kontostand, letzte Buchungen und aktuelle Hinweise.</li>
|
||||
<li>Self-Service-Striche sind in diesem Mandanten aktuell deaktiviert.</li>
|
||||
<li>Wenn dir etwas fehlt, wende dich an den Betreiber deines Bereichs.</li>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Profil</div>
|
||||
<h2>Name und Passwort pflegen</h2>
|
||||
<form method="post" action="/dashboard/" class="grid">
|
||||
<input type="hidden" name="action" value="save-profile">
|
||||
<label>Anzeigename<input name="display_name" value="<?= h((string) ($auth['display_name'] ?? '')) ?>" required></label>
|
||||
<label>Neues Passwort<input type="password" name="password" placeholder="Nur bei Änderung ausfüllen"></label>
|
||||
<label style="grid-column:1 / -1;">Hinweis zu Einzahlungen<textarea readonly><?= h((string) ($tenantSettings['payment_hint'] ?? '')) ?></textarea></label>
|
||||
<div class="actions"><button type="submit">Profil speichern</button></div>
|
||||
</form>
|
||||
<?php $paypalBaseUrl = trim((string) ($tenantSettings['paypal_me_url'] ?? '')); ?>
|
||||
<?php if ($paypalBaseUrl !== ''): ?>
|
||||
<div class="section-mini" style="margin-top:18px">
|
||||
<h3 class="section-mini__title">PayPal-Einzahlungen</h3>
|
||||
<p class="section-mini__copy">Feste Beträge und der offene Saldo lassen sich direkt öffnen.</p>
|
||||
<div class="actions">
|
||||
<?php $balance = (float) ($memberSummary['balance'] ?? 0); ?>
|
||||
<?php if ($balance < 0): ?>
|
||||
<a class="button secondary" target="_blank" href="<?= h(rtrim($paypalBaseUrl, '/') . '/' . number_format(abs($balance), 2, '.', '')) ?>"><?= h(number_format(abs($balance), 2, ',', '.')) ?> EUR ausgleichen</a>
|
||||
<?php endif; ?>
|
||||
<?php foreach ([5, 10, 15] as $quickAmount): ?>
|
||||
<a class="button secondary" target="_blank" href="<?= h(rtrim($paypalBaseUrl, '/') . '/' . number_format((float) $quickAmount, 2, '.', '')) ?>"><?= h(number_format((float) $quickAmount, 2, ',', '.')) ?> EUR</a>
|
||||
<?php else: ?>
|
||||
<ul class="list">
|
||||
<li>Hier siehst du deinen Kontostand, letzte Buchungen und aktuelle Hinweise.</li>
|
||||
<li>Self-Service-Striche sind in diesem Mandanten aktuell deaktiviert.</li>
|
||||
<li>Wenn dir etwas fehlt, wende dich an den Betreiber deines Bereichs.</li>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="eyebrow"><?= $canManageTenant ? 'Saldo-Lage' : 'Konto' ?></div>
|
||||
<h2><?= $canManageTenant ? 'Auffällige Kontostände' : 'Persönliche Einstellungen' ?></h2>
|
||||
<?php if ($canManageTenant): ?>
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<h3 style="margin-bottom:10px">Höchste Rückstände</h3>
|
||||
<ul class="list">
|
||||
<?php foreach (($tenantDashboard['largest_debtors'] ?? []) as $row): ?>
|
||||
<li><?= h((string) ($row['display_name'] ?? 'Mitglied')) ?> · <?= money($row['balance'] ?? 0) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (($tenantDashboard['largest_debtors'] ?? []) === []): ?><li>Aktuell keine negativen Salden.</li><?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
<div>
|
||||
<h3 style="margin-bottom:10px">Größte Guthaben</h3>
|
||||
<ul class="list">
|
||||
<?php foreach (($tenantDashboard['largest_credits'] ?? []) as $row): ?>
|
||||
<li><?= h((string) ($row['display_name'] ?? 'Mitglied')) ?> · <?= money($row['balance'] ?? 0) ?></li>
|
||||
<?php endforeach; ?>
|
||||
<?php if (($tenantDashboard['largest_credits'] ?? []) === []): ?><li>Aktuell keine Guthaben über 0 EUR.</li><?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Profil, Passwort und Anzeigemodus liegen in deiner persönlichen Einstellungsseite.</p>
|
||||
<div class="actions" style="margin-top:18px">
|
||||
<a class="button secondary" href="/profile/">Persönliche Einstellungen öffnen</a>
|
||||
</div>
|
||||
<p class="muted" style="margin-top:12px">Aktuell aktiv: <?= h($themeMode === 'dark' ? 'Dunkelmodus' : 'Hellmodus') ?></p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top:18px">
|
||||
<h2>Letzte Buchungen</h2>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Zeit</th><?php if ($canManageTenant): ?><th>Mitglied</th><?php endif; ?><th>Typ</th><th>Referenz</th><th>Betrag</th></tr></thead>
|
||||
<tbody><?php foreach (($canManageTenant ? $ledger : ($memberSummary['recent_entries'] ?? [])) as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><?php if ($canManageTenant): ?><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><?php endif; ?><td><?= h((string) ($entry['entry_type'] ?? '')) ?></td><td><?= h((string) ($entry['reference_type'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td></tr><?php endforeach; ?></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if ($canManageTenant): ?>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<div class="actions" style="justify-content:space-between;margin-bottom:14px">
|
||||
<div>
|
||||
<div class="eyebrow">Kaffeelisten-Stand</div>
|
||||
<h2 style="margin:0">Aktive Mitglieder im Überblick</h2>
|
||||
</div>
|
||||
<a class="button secondary" href="/members/">Mitgliederverwaltung öffnen</a>
|
||||
</div>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Mitglied</th><th>Saldo</th><th>Striche Jahr</th><th>Einzahlungen Jahr</th><th>Letzte Aktivität</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach (($tenantDashboard['member_rows'] ?? []) as $row): ?>
|
||||
<tr>
|
||||
<td><?= h((string) ($row['display_name'] ?? '')) ?></td>
|
||||
<td><?= money($row['balance'] ?? 0) ?></td>
|
||||
<td><?= num($row['year_strokes'] ?? 0) ?></td>
|
||||
<td><?= money($row['year_payments'] ?? 0) ?></td>
|
||||
<td><?= dt((string) ($row['last_booking_at'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="grid grid-2" style="margin-top:18px">
|
||||
<article class="card">
|
||||
<div class="eyebrow">Verlauf</div>
|
||||
<h2>Letzte Buchungen</h2>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Zeit</th><?php if ($canManageTenant): ?><th>Mitglied</th><?php endif; ?><th>Typ</th><th>Referenz</th><th>Betrag</th></tr></thead>
|
||||
<tbody><?php foreach (($canManageTenant ? $ledger : ($memberSummary['recent_entries'] ?? [])) as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><?php if ($canManageTenant): ?><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><?php endif; ?><td><?= h((string) ($entry['entry_type'] ?? '')) ?></td><td><?= h((string) ($entry['reference_type'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td></tr><?php endforeach; ?></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Hinweise</div>
|
||||
<h2>Wichtige Informationen</h2>
|
||||
<?php if ($dashboardAnnouncements !== []): ?>
|
||||
<ul class="list">
|
||||
<?php foreach ($dashboardAnnouncements as $announcement): ?>
|
||||
<li>
|
||||
<strong><?= h((string) ($announcement['title'] ?? 'Hinweis')) ?></strong><br>
|
||||
<?= h((string) ($announcement['message'] ?? '')) ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="muted">Aktuell sind keine aktiven Hinweise veröffentlicht.</p>
|
||||
<?php endif; ?>
|
||||
<div class="actions" style="margin-top:18px">
|
||||
<?php if ($canManageTenant): ?>
|
||||
<a class="button secondary" href="/content/">Hinweise verwalten</a>
|
||||
<?php else: ?>
|
||||
<a class="button secondary" href="/support/">Hilfe öffnen</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<?php elseif ($page === 'members'): ?>
|
||||
<section class="hero">
|
||||
@@ -1376,8 +1490,57 @@ $marketing = app_marketing_messages();
|
||||
<div class="stack"><?php if (($content['faq'] ?? []) === []): ?><div class="metric"><p>Aktuell keine FAQ vorhanden.</p></div><?php endif; ?><?php foreach (($content['faq'] ?? []) as $entry): ?><div class="metric"><h3><?= h((string) $entry['question']) ?></h3><p><?= nl2br(h((string) $entry['answer'])) ?></p><?php if ($canManageTenant): ?><div class="actions" style="margin-top:12px"><a class="button secondary" href="/content/?edit_faq=<?= h((string) ($entry['id'] ?? '')) ?>">Bearbeiten</a><form method="post" action="/content/"><input type="hidden" name="action" value="archive-faq"><input type="hidden" name="faq_id" value="<?= h((string) ($entry['id'] ?? '')) ?>"><button type="submit" class="button secondary"><?= !empty($entry['is_active']) ? 'Archivieren' : 'Ausblenden' ?></button></form></div><?php endif; ?></div><?php endforeach; ?></div>
|
||||
</article>
|
||||
</section>
|
||||
<?php elseif ($page === 'profile'): ?>
|
||||
<section class="hero"><div class="eyebrow">Konto</div><h1>Persönliche Einstellungen</h1><p>Hier pflegst du deinen Anzeigenamen, dein Passwort und deinen persönlichen Anzeigemodus. Diese Einstellungen gelten nur für dich.</p></section>
|
||||
<section class="grid grid-2">
|
||||
<article class="card">
|
||||
<h2>Profil</h2>
|
||||
<form method="post" action="/profile/" class="grid">
|
||||
<input type="hidden" name="action" value="save-profile">
|
||||
<label>Anzeigename<input name="display_name" value="<?= h((string) ($auth['display_name'] ?? '')) ?>" required></label>
|
||||
<label>Neues Passwort<input type="password" name="password" placeholder="Nur bei Änderung ausfüllen"></label>
|
||||
<label style="grid-column:1 / -1;">Hinweis zu Einzahlungen<textarea readonly><?= h((string) ($tenantSettings['payment_hint'] ?? '')) ?></textarea></label>
|
||||
<div class="actions"><button type="submit">Profil speichern</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h2>Anzeige</h2>
|
||||
<div class="stack">
|
||||
<div class="metric"><h3>Anzeigemodus</h3><p>Der Hell-/Dunkelmodus wird pro Benutzer gespeichert und ist bewusst vom Mandanten getrennt.</p></div>
|
||||
<form method="post" action="/profile/" class="actions">
|
||||
<input type="hidden" name="action" value="save-theme-mode">
|
||||
<input type="hidden" name="theme_mode" value="<?= h($themeMode === 'dark' ? 'light' : 'dark') ?>">
|
||||
<button type="submit"><?= h($themeMode === 'dark' ? 'Auf Hell umstellen' : 'Auf Dunkel umstellen') ?></button>
|
||||
</form>
|
||||
<p class="muted">Aktuell aktiv: <?= h($themeMode === 'dark' ? 'Dunkelmodus' : 'Hellmodus') ?></p>
|
||||
</div>
|
||||
<?php $paypalBaseUrl = trim((string) ($tenantSettings['paypal_me_url'] ?? '')); ?>
|
||||
<?php if ($paypalBaseUrl !== ''): ?>
|
||||
<div class="section-mini" style="margin-top:18px">
|
||||
<h3 class="section-mini__title">PayPal-Einzahlungen</h3>
|
||||
<p class="section-mini__copy">Direkte Schnelllinks für feste Beträge oder den aktuellen Ausgleich.</p>
|
||||
<div class="actions">
|
||||
<?php $balance = (float) ($memberSummary['balance'] ?? 0); ?>
|
||||
<?php if ($balance < 0): ?>
|
||||
<a class="button secondary" target="_blank" href="<?= h(rtrim($paypalBaseUrl, '/') . '/' . number_format(abs($balance), 2, '.', '')) ?>"><?= h(number_format(abs($balance), 2, ',', '.')) ?> EUR ausgleichen</a>
|
||||
<?php endif; ?>
|
||||
<?php foreach ([5, 10, 15] as $quickAmount): ?>
|
||||
<a class="button secondary" target="_blank" href="<?= h(rtrim($paypalBaseUrl, '/') . '/' . number_format((float) $quickAmount, 2, '.', '')) ?>"><?= h(number_format((float) $quickAmount, 2, ',', '.')) ?> EUR</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
<?php if ($canManageTenant && $hasTenantSettingsFeature): ?>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<h2>Mandant</h2>
|
||||
<p class="muted">Mandantenweite Einstellungen bleiben getrennt von deinem persönlichen Konto.</p>
|
||||
<div class="actions" style="margin-top:14px"><a class="button secondary" href="/settings/">Zu den Mandanten-Einstellungen</a></div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($page === 'settings'): ?>
|
||||
<section class="hero"><div class="eyebrow">Mandanten-Einstellungen</div><h1>Einstellungen</h1><p>Diese Einstellungen gelten nur für den aktuell geöffneten Mandanten und bleiben unabhängig von anderen Bereichen.</p></section>
|
||||
<section class="hero"><div class="eyebrow">Mandanten-Einstellungen</div><h1>Mandanten-Einstellungen</h1><p>Diese Einstellungen gelten nur für den aktuell geöffneten Mandanten und bleiben unabhängig von anderen Bereichen.</p></section>
|
||||
<section class="grid grid-2">
|
||||
<article class="card">
|
||||
<h2>Einstellungen speichern</h2>
|
||||
@@ -1405,15 +1568,10 @@ $marketing = app_marketing_messages();
|
||||
</form>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h2>Anzeige</h2>
|
||||
<h2>Abgrenzung</h2>
|
||||
<div class="stack">
|
||||
<div class="metric"><h3>Anzeigemodus</h3><p>Der Hell/Dunkelmodus wird pro Benutzer gespeichert und gehört bewusst in die Einstellungen statt in die Hauptnavigation.</p></div>
|
||||
<form method="post" action="/settings/" class="actions">
|
||||
<input type="hidden" name="action" value="save-theme-mode">
|
||||
<input type="hidden" name="theme_mode" value="<?= h($themeMode === 'dark' ? 'light' : 'dark') ?>">
|
||||
<button type="submit"><?= h($themeMode === 'dark' ? 'Auf Hell umstellen' : 'Auf Dunkel umstellen') ?></button>
|
||||
</form>
|
||||
<p class="muted">Aktuell aktiv: <?= h($themeMode === 'dark' ? 'Dunkelmodus' : 'Hellmodus') ?></p>
|
||||
<div class="metric"><h3>Mandantenweite Werte</h3><p>Hier werden nur Einstellungen gepflegt, die den gesamten Mandanten betreffen.</p></div>
|
||||
<div class="metric"><h3>Persönliche Einstellungen</h3><p>Profil, Passwort und Anzeigemodus liegen bewusst im eigenen Bereich unter <a href="/profile/" style="text-decoration:underline;">Persönliche Einstellungen</a>.</p></div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -1576,8 +1734,8 @@ $marketing = app_marketing_messages();
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="mobile-bottom-nav__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>>
|
||||
<span class="mobile-bottom-nav__icon"><?= match ((string) ($item['key'] ?? '')) {
|
||||
'dashboard' => 'U',
|
||||
'members' => 'M',
|
||||
'ledger' => 'B',
|
||||
'payments' => 'Z',
|
||||
default => '•',
|
||||
} ?></span>
|
||||
<span class="mobile-bottom-nav__label"><?= h((string) ($item['label'] ?? 'Link')) ?></span>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$_GET['page'] = 'profile';
|
||||
|
||||
require dirname(__DIR__) . '/index.php';
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$_GET['page'] = 'profile';
|
||||
|
||||
require dirname(__DIR__) . '/index.php';
|
||||
@@ -102,8 +102,9 @@ $tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$tenantNavGroups = app_tenant_navigation_groups($tenantNavItems);
|
||||
$tenantHeaderNavItems = [];
|
||||
$mobileBottomNavItems = [];
|
||||
$mobileDrawerPrimaryItems = [];
|
||||
foreach ($tenantNavItems as $item) {
|
||||
if (in_array((string) ($item['key'] ?? ''), ['dashboard', 'members', 'ledger', 'payments'], true)) {
|
||||
if (in_array((string) ($item['key'] ?? ''), ['dashboard', 'ledger', 'payments'], true)) {
|
||||
$tenantHeaderNavItems[] = $item;
|
||||
$mobileBottomNavItems[] = $item;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -70,8 +70,9 @@ $tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$tenantNavGroups = app_tenant_navigation_groups($tenantNavItems);
|
||||
$tenantHeaderNavItems = [];
|
||||
$mobileBottomNavItems = [];
|
||||
$mobileDrawerPrimaryItems = [];
|
||||
foreach ($tenantNavItems as $item) {
|
||||
if (in_array((string) ($item['key'] ?? ''), ['dashboard', 'members', 'ledger', 'payments'], true)) {
|
||||
if (in_array((string) ($item['key'] ?? ''), ['dashboard', 'ledger', 'payments'], true)) {
|
||||
$tenantHeaderNavItems[] = $item;
|
||||
$mobileBottomNavItems[] = $item;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
];
|
||||
$layoutPrimaryNavItems = $layoutNavItems !== [] ? $layoutNavItems : $layoutGuestItems;
|
||||
$layoutHeaderNavItems = $layoutPrimaryNavItems;
|
||||
if (is_array($layoutAuth)) {
|
||||
$layoutHeaderNavItems = [];
|
||||
foreach ($layoutPrimaryNavItems as $item) {
|
||||
if (in_array((string) ($item['key'] ?? ''), ['dashboard', 'ledger', 'payments'], true)) {
|
||||
$layoutHeaderNavItems[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
$layoutCurrentLabel = 'Start';
|
||||
foreach ($layoutPrimaryNavItems as $item) {
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
@@ -39,6 +47,12 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($layoutPath === '/profile') {
|
||||
$layoutCurrentLabel = 'Persönliche Einstellungen';
|
||||
}
|
||||
$layoutAccountItems = function_exists('app_tenant_account_items')
|
||||
? app_tenant_account_items($layoutAuth)
|
||||
: [];
|
||||
$layoutThemeCss = function_exists('app_tenant_theme_root_css')
|
||||
? app_tenant_theme_root_css($layoutThemeSettings)
|
||||
: '';
|
||||
@@ -47,7 +61,20 @@
|
||||
} else {
|
||||
$layoutMobilePrimaryNavItems = array_slice($layoutPrimaryNavItems, 0, 2);
|
||||
}
|
||||
$layoutMobileDrawerPrimaryItems = array_slice($layoutPrimaryNavItems, count($layoutMobilePrimaryNavItems));
|
||||
$layoutMobileDrawerPrimaryItems = [];
|
||||
foreach ($layoutHeaderNavItems as $item) {
|
||||
$itemKey = (string) ($item['key'] ?? '');
|
||||
$isAlreadyPrimary = false;
|
||||
foreach ($layoutMobilePrimaryNavItems as $primaryItem) {
|
||||
if ((string) ($primaryItem['key'] ?? '') === $itemKey) {
|
||||
$isAlreadyPrimary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$isAlreadyPrimary) {
|
||||
$layoutMobileDrawerPrimaryItems[] = $item;
|
||||
}
|
||||
}
|
||||
$layoutMobileHasDrawer = $layoutMobileDrawerPrimaryItems !== [] || $layoutNavGroups !== [] || is_array($layoutAuth);
|
||||
$layoutMobileNavColumns = count($layoutMobilePrimaryNavItems) + ($layoutMobileHasDrawer ? 1 : 0);
|
||||
if ($layoutMobileNavColumns < 3) {
|
||||
@@ -1209,11 +1236,43 @@
|
||||
@endphp
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="top-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@endforeach
|
||||
@foreach ($layoutNavGroups as $group)
|
||||
<details class="top-nav__group">
|
||||
<summary class="top-nav__toggle">{{ $group['label'] ?? 'Mehr' }}</summary>
|
||||
<div class="top-nav__panel">
|
||||
<div class="top-nav__label">{{ $group['label'] ?? 'Mehr' }}</div>
|
||||
@foreach (($group['items'] ?? []) as $item)
|
||||
@php
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
$isActive = $layoutPath === $itemHref;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="top-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</details>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="site-actions">
|
||||
@if (is_array($layoutAuth))
|
||||
<button type="button" class="site-actions__toggle" data-mobile-drawer-open>Mehr</button>
|
||||
<details class="top-nav__group">
|
||||
<summary class="top-nav__toggle">Konto</summary>
|
||||
<div class="top-nav__panel">
|
||||
<div class="top-nav__label">Konto</div>
|
||||
@foreach ($layoutAccountItems as $item)
|
||||
@php
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
$isActive = $layoutPath === $itemHref;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="top-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@endforeach
|
||||
<form method="post" action="/logout/">
|
||||
<button type="submit" class="button button--ghost" style="width: 100%;">Abmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
@else
|
||||
<a class="button button--ghost" href="/login/">Anmelden</a>
|
||||
@endif
|
||||
@@ -1275,7 +1334,7 @@
|
||||
<div class="mobile-drawer__sections">
|
||||
@if ($layoutMobileDrawerPrimaryItems !== [])
|
||||
<section class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Weitere Seiten</div>
|
||||
<div class="mobile-drawer__label">Weitere Kernfunktionen</div>
|
||||
<div class="mobile-drawer__links">
|
||||
@foreach ($layoutMobileDrawerPrimaryItems as $item)
|
||||
@php
|
||||
@@ -1309,7 +1368,7 @@
|
||||
<div class="mobile-drawer__label">Konto</div>
|
||||
@if (is_array($layoutAuth))
|
||||
<div class="mobile-drawer__links">
|
||||
<div class="mobile-drawer__empty">{{ $layoutAuth['tenant_name'] ?? 'Tenant' }}</div>
|
||||
<a class="mobile-drawer__link {{ $layoutPath === '/profile' ? 'is-active' : '' }}" href="/profile/" @if ($layoutPath === '/profile') aria-current="page" @endif>Persönliche Einstellungen</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="mobile-drawer__links">
|
||||
|
||||
@@ -11,20 +11,27 @@
|
||||
<style>
|
||||
:root{
|
||||
<?= $themeCss ?>
|
||||
--line:rgba(37,24,15,.14);
|
||||
--shadow:0 18px 40px rgba(37,24,15,.08);
|
||||
--line:rgba(143,158,191,.16);
|
||||
--line-strong:rgba(174,189,223,.26);
|
||||
--shadow:0 24px 64px rgba(1,6,18,.30);
|
||||
--radius:18px;
|
||||
--surface-0:#0f1720;
|
||||
--surface-1:rgba(18,26,36,.84);
|
||||
--surface-2:rgba(23,32,45,.92);
|
||||
--surface-3:rgba(29,40,55,.96);
|
||||
--text-strong:#f4f7ff;
|
||||
--text-soft:rgba(219,228,248,.74);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0;
|
||||
min-height:100vh;
|
||||
color:#edf2ff;
|
||||
color:var(--text-strong);
|
||||
font-family:"Aptos","Segoe UI",sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(88,122,255,.18), transparent 26%),
|
||||
radial-gradient(circle at 18% 12%, rgba(var(--brand-rgb),.16), transparent 22%),
|
||||
linear-gradient(180deg,#05070d 0%,#0b1220 48%,#0d1524 100%);
|
||||
linear-gradient(180deg,var(--surface-0) 0%,color-mix(in srgb,var(--surface-0) 82%, black 18%) 100%);
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
.button,button{
|
||||
@@ -42,14 +49,14 @@
|
||||
}
|
||||
.button--ghost{
|
||||
background:transparent;
|
||||
color:#f4f7ff;
|
||||
border-color:rgba(201,214,255,.16);
|
||||
color:var(--text-strong);
|
||||
border-color:var(--line-strong);
|
||||
}
|
||||
.page-shell{
|
||||
width:min(1460px,calc(100vw - 32px));
|
||||
margin:0 auto 40px;
|
||||
width:100%;
|
||||
margin:0 0 34px;
|
||||
display:grid;
|
||||
gap:18px;
|
||||
gap:16px;
|
||||
}
|
||||
.site-header{
|
||||
position:sticky;
|
||||
@@ -68,11 +75,11 @@
|
||||
min-height:72px;
|
||||
padding:12px 24px;
|
||||
border:0;
|
||||
border-bottom:1px solid rgba(137,154,188,.18);
|
||||
border-bottom:1px solid var(--line);
|
||||
border-radius:0;
|
||||
background:rgba(8,10,18,.86);
|
||||
box-shadow:none;
|
||||
backdrop-filter:blur(16px);
|
||||
background:color-mix(in srgb,var(--surface-1) 88%, rgba(var(--brand-rgb),.12) 12%);
|
||||
box-shadow:var(--shadow);
|
||||
backdrop-filter:blur(18px);
|
||||
}
|
||||
.site-brand{display:flex;align-items:center;gap:12px;min-width:0}
|
||||
.site-brand__mark{
|
||||
@@ -86,8 +93,8 @@
|
||||
font-weight:800;
|
||||
box-shadow:none;
|
||||
}
|
||||
.site-brand__title{margin:0;font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;font-size:1.08rem;letter-spacing:-.04em;color:#f4f7ff}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:rgba(219,228,248,.72);font-size:.9rem}
|
||||
.site-brand__title{margin:0;font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;font-size:1.08rem;letter-spacing:-.04em;color:var(--text-strong)}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:var(--text-soft);font-size:.9rem}
|
||||
.site-nav,.site-actions,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.site-nav__link{
|
||||
display:inline-flex;
|
||||
@@ -95,35 +102,35 @@
|
||||
justify-content:center;
|
||||
min-height:34px;
|
||||
padding:0 .3rem;
|
||||
border-radius:8px;
|
||||
border-radius:6px;
|
||||
border:1px solid transparent;
|
||||
color:rgba(231,238,255,.8);
|
||||
font-weight:700;
|
||||
color:var(--text-soft);
|
||||
font-weight:600;
|
||||
font-size:.96rem;
|
||||
}
|
||||
.site-nav__link:hover{text-decoration:none;color:#f4f7ff;background:rgba(255,255,255,.06);border-color:rgba(201,214,255,.12)}
|
||||
.site-nav__link.active{background:rgba(255,255,255,.08);color:#f4f7ff;border-color:rgba(201,214,255,.12)}
|
||||
.site-nav__link:hover{text-decoration:none;color:var(--text-strong);background:rgba(var(--brand-rgb),.09);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.site-nav__link.active{background:rgba(var(--brand-rgb),.16);color:var(--text-strong);border-color:rgba(var(--brand-rgb),.28)}
|
||||
.site-more{position:relative}
|
||||
.site-more[open]{z-index:25}
|
||||
.site-more__toggle{display:inline-flex;align-items:center;justify-content:center;min-height:34px;padding:0 .6rem;border-radius:8px;border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.03);color:rgba(231,238,255,.8);font-size:.96rem;font-weight:700;cursor:pointer;list-style:none}
|
||||
.site-more__toggle{display:inline-flex;align-items:center;justify-content:center;min-height:34px;padding:0 .6rem;border-radius:8px;border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text-soft);font-size:.96rem;font-weight:600;cursor:pointer;list-style:none}
|
||||
.site-more__toggle::-webkit-details-marker{display:none}
|
||||
.site-more__panel{position:absolute;right:0;top:calc(100% + 10px);min-width:240px;padding:12px;border-radius:14px;border:1px solid rgba(163,183,255,.12);background:rgba(14,20,34,.96);box-shadow:0 16px 40px rgba(2,5,12,.34);display:grid;gap:8px}
|
||||
.site-more__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:rgba(219,228,248,.52)}
|
||||
.site-more__panel{position:absolute;right:0;top:calc(100% + 10px);min-width:240px;padding:12px;border-radius:14px;border:1px solid var(--line);background:var(--surface-3);box-shadow:var(--shadow);display:grid;gap:8px}
|
||||
.site-more__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft)}
|
||||
.hero,.card,.alert{
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
border-radius:16px;
|
||||
background:rgba(14,20,34,.86);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-2);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.hero__title,.card h2,.card h3{
|
||||
font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;
|
||||
letter-spacing:-.04em;
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
}
|
||||
.muted,p{color:rgba(219,228,246,.74)}
|
||||
.muted,p{color:var(--text-soft)}
|
||||
.site-mobile{display:none}
|
||||
.site-mobile__toggle{
|
||||
display:inline-flex;
|
||||
display:none;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
gap:10px;
|
||||
@@ -131,9 +138,9 @@
|
||||
min-height:38px;
|
||||
padding:.45rem .72rem;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(201,214,255,.14);
|
||||
border:1px solid var(--line-strong);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-weight:700;
|
||||
}
|
||||
.site-mobile__toggle::before{
|
||||
@@ -156,17 +163,17 @@
|
||||
width:min(100vw,420px);
|
||||
max-width:100%;
|
||||
padding:18px 16px calc(28px + env(safe-area-inset-bottom));
|
||||
background:rgba(14,20,34,.96);
|
||||
border-left:1px solid rgba(163,183,255,.12);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-3);
|
||||
border-left:1px solid var(--line);
|
||||
box-shadow:var(--shadow);
|
||||
display:grid;
|
||||
grid-template-rows:auto 1fr auto;
|
||||
gap:18px;
|
||||
overflow:auto;
|
||||
}
|
||||
.mobile-drawer__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
|
||||
.mobile-drawer__title{margin:0;font-size:1.08rem;color:#f4f7ff}
|
||||
.mobile-drawer__subtitle{margin:4px 0 0;color:rgba(219,228,246,.74);font-size:.9rem}
|
||||
.mobile-drawer__title{margin:0;font-size:1.08rem;color:var(--text-strong)}
|
||||
.mobile-drawer__subtitle{margin:4px 0 0;color:var(--text-soft);font-size:.9rem}
|
||||
.mobile-drawer__close{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
@@ -174,15 +181,15 @@
|
||||
width:40px;
|
||||
height:40px;
|
||||
border-radius:999px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-size:1.15rem;
|
||||
font-weight:700;
|
||||
cursor:pointer;
|
||||
}
|
||||
.mobile-drawer__section{display:grid;gap:10px}
|
||||
.mobile-drawer__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:rgba(219,228,248,.52)}
|
||||
.mobile-drawer__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft)}
|
||||
.mobile-drawer__links{display:grid;gap:8px}
|
||||
.mobile-drawer__link{
|
||||
display:flex;
|
||||
@@ -191,13 +198,13 @@
|
||||
gap:12px;
|
||||
padding:.9rem 1rem;
|
||||
border-radius:14px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-weight:700;
|
||||
}
|
||||
.mobile-drawer__link.active{background:rgba(88,122,255,.16);border-color:rgba(88,122,255,.28)}
|
||||
.mobile-drawer__footer{display:grid;gap:12px;padding-top:8px;border-top:1px solid rgba(163,183,255,.12)}
|
||||
.mobile-drawer__footer{display:grid;gap:12px;padding-top:8px;border-top:1px solid var(--line)}
|
||||
.mobile-drawer__meta{display:grid;gap:4px}
|
||||
.mobile-bottom-nav{display:none}
|
||||
.mobile-bottom-nav__link,.mobile-bottom-nav__toggle{
|
||||
@@ -209,14 +216,14 @@
|
||||
padding:0 8px;
|
||||
border:0;
|
||||
background:transparent;
|
||||
color:rgba(231,238,255,.8);
|
||||
color:var(--text-soft);
|
||||
font:inherit;
|
||||
font-weight:700;
|
||||
}
|
||||
.mobile-bottom-nav__link.active,.mobile-bottom-nav__toggle.active{color:#f4f7ff}
|
||||
.mobile-bottom-nav__link.active,.mobile-bottom-nav__toggle.active{color:var(--text-strong)}
|
||||
.mobile-bottom-nav__icon{font-size:1rem;line-height:1}
|
||||
.mobile-bottom-nav__label{font-size:.74rem}
|
||||
.content{min-width:0;display:grid;gap:18px}
|
||||
.content{width:min(1260px,calc(100% - 28px));margin:0 auto;min-width:0;display:grid;gap:18px}
|
||||
.hero{
|
||||
padding:24px;
|
||||
display:grid;
|
||||
@@ -255,7 +262,7 @@
|
||||
.metric{
|
||||
padding:18px;
|
||||
border-radius:16px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.04);
|
||||
display:grid;
|
||||
gap:8px;
|
||||
@@ -288,13 +295,13 @@
|
||||
th,td{padding:13px 10px;border-bottom:1px solid rgba(163,183,255,.1);text-align:left;vertical-align:top}
|
||||
th{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:rgba(193,205,232,.7)}
|
||||
label{display:flex;flex-direction:column;gap:8px;font-weight:700}
|
||||
input,select,textarea{width:100%;padding:12px 14px;border-radius:14px;border:1px solid rgba(163,183,255,.14);font:inherit;background:rgba(9,13,24,.82);color:#f4f7ff}
|
||||
input,select,textarea{width:100%;padding:12px 14px;border-radius:14px;border:1px solid var(--line);font:inherit;background:rgba(9,13,24,.82);color:var(--text-strong)}
|
||||
select{color-scheme:dark;background-color:rgba(9,13,24,.94)}
|
||||
select option,select optgroup{background:rgba(9,13,24,.98);color:#f4f7ff}
|
||||
textarea{min-height:120px}
|
||||
.timeline{display:grid;gap:12px}
|
||||
.timeline__item{padding:14px 16px;border-radius:16px;background:rgba(255,255,255,.04);border:1px solid rgba(163,183,255,.12)}
|
||||
.timeline__meta{margin:0;color:rgba(219,228,246,.74);font-size:.94rem;line-height:1.6}
|
||||
.timeline__item{padding:14px 16px;border-radius:16px;background:rgba(255,255,255,.04);border:1px solid var(--line)}
|
||||
.timeline__meta{margin:0;color:var(--text-soft);font-size:.94rem;line-height:1.6}
|
||||
.timeline__title{margin:0 0 6px;font-weight:800}
|
||||
.pill{
|
||||
display:inline-flex;
|
||||
@@ -317,12 +324,13 @@
|
||||
.footer{margin-top:18px;text-align:center;color:rgba(209,217,235,.68);font-size:.92rem}
|
||||
@media(max-width:1180px){
|
||||
body.mobile-drawer-open{overflow:hidden}
|
||||
.page-shell{width:min(100vw - 20px,1460px)}
|
||||
.content{width:min(100vw - 20px,1260px)}
|
||||
.page-shell{padding-bottom:94px}
|
||||
.site-header__inner{align-items:center;padding:12px 16px;min-height:68px;width:100%}
|
||||
.site-nav{display:none}
|
||||
.site-actions{gap:8px}
|
||||
.site-actions .badge,.site-actions > form{display:none}
|
||||
.site-actions .badge,.site-actions .site-more{display:none}
|
||||
.site-mobile__toggle{display:inline-flex}
|
||||
.site-mobile{display:block}
|
||||
.split,.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}
|
||||
table{min-width:0}
|
||||
@@ -336,10 +344,10 @@
|
||||
grid-template-columns:repeat(4,minmax(0,1fr));
|
||||
gap:4px;
|
||||
padding:6px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
border-radius:18px;
|
||||
background:rgba(14,20,34,.96);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-3);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
}
|
||||
@media(min-width:1181px){
|
||||
@@ -378,6 +386,14 @@
|
||||
|
||||
<div class="site-actions">
|
||||
<?= support_badge($isManager ? 'Verantwortlichen-Sicht' : 'Mitgliedersicht', 'success') ?>
|
||||
<details class="site-more">
|
||||
<summary class="site-more__toggle">Konto</summary>
|
||||
<div class="site-more__panel">
|
||||
<div class="site-more__label">Konto</div>
|
||||
<a href="/profile/" class="site-nav__link">Persönliche Einstellungen</a>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
</details>
|
||||
<button type="button" class="site-mobile__toggle" data-mobile-drawer-open>Mehr</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -394,14 +410,16 @@
|
||||
</div>
|
||||
<button type="button" class="mobile-drawer__close" aria-label="Schließen" data-mobile-drawer-close>×</button>
|
||||
</div>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Direkt</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileBottomNavItems as $item): ?>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="mobile-drawer__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($mobileDrawerPrimaryItems !== []): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Weitere Kernfunktionen</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileDrawerPrimaryItems as $item): ?>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="mobile-drawer__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($tenantNavGroups as $group): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label"><?= support_h((string) ($group['label'] ?? 'Mehr')) ?></div>
|
||||
@@ -413,9 +431,11 @@
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="mobile-drawer__footer">
|
||||
<div class="mobile-drawer__meta">
|
||||
<strong><?= support_h((string) ($auth['tenant_name'] ?? 'Bereich')) ?></strong>
|
||||
<span class="muted"><?= support_h((string) ($auth['display_name'] ?? 'Angemeldet')) ?></span>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Konto</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<a href="/profile/" class="mobile-drawer__link">Persönliche Einstellungen</a>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
|
||||
@@ -11,20 +11,27 @@
|
||||
<style>
|
||||
:root{
|
||||
<?= $themeCss ?>
|
||||
--line:rgba(37,24,15,.14);
|
||||
--shadow:0 18px 40px rgba(37,24,15,.08);
|
||||
--line:rgba(143,158,191,.16);
|
||||
--line-strong:rgba(174,189,223,.26);
|
||||
--shadow:0 24px 64px rgba(1,6,18,.30);
|
||||
--radius:18px;
|
||||
--surface-0:#0f1720;
|
||||
--surface-1:rgba(18,26,36,.84);
|
||||
--surface-2:rgba(23,32,45,.92);
|
||||
--surface-3:rgba(29,40,55,.96);
|
||||
--text-strong:#f4f7ff;
|
||||
--text-soft:rgba(219,228,248,.74);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0;
|
||||
min-height:100vh;
|
||||
font-family:"Aptos","Segoe UI",sans-serif;
|
||||
color:#edf2ff;
|
||||
color:var(--text-strong);
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(88,122,255,.18), transparent 26%),
|
||||
radial-gradient(circle at 18% 12%, rgba(var(--brand-rgb),.16), transparent 22%),
|
||||
linear-gradient(180deg,#05070d 0%,#0b1220 48%,#0d1524 100%);
|
||||
linear-gradient(180deg,var(--surface-0) 0%,color-mix(in srgb,var(--surface-0) 82%, black 18%) 100%);
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
h1,h2,h3{font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;letter-spacing:-.04em}
|
||||
@@ -43,14 +50,14 @@
|
||||
}
|
||||
.button--ghost{
|
||||
background:transparent;
|
||||
color:#f4f7ff;
|
||||
border-color:rgba(201,214,255,.16);
|
||||
color:var(--text-strong);
|
||||
border-color:var(--line-strong);
|
||||
}
|
||||
.page-shell{
|
||||
width:min(1460px,calc(100vw - 32px));
|
||||
margin:0 auto 40px;
|
||||
width:100%;
|
||||
margin:0 0 34px;
|
||||
display:grid;
|
||||
gap:18px;
|
||||
gap:16px;
|
||||
}
|
||||
.site-header{
|
||||
position:sticky;
|
||||
@@ -69,11 +76,11 @@
|
||||
min-height:72px;
|
||||
padding:12px 24px;
|
||||
border:0;
|
||||
border-bottom:1px solid rgba(137,154,188,.18);
|
||||
border-bottom:1px solid var(--line);
|
||||
border-radius:0;
|
||||
background:rgba(8,10,18,.86);
|
||||
box-shadow:none;
|
||||
backdrop-filter:blur(16px);
|
||||
background:color-mix(in srgb,var(--surface-1) 88%, rgba(var(--brand-rgb),.12) 12%);
|
||||
box-shadow:var(--shadow);
|
||||
backdrop-filter:blur(18px);
|
||||
}
|
||||
.site-brand{display:flex;align-items:center;gap:12px;min-width:0}
|
||||
.site-brand__mark{
|
||||
@@ -87,8 +94,8 @@
|
||||
font-weight:800;
|
||||
box-shadow:none;
|
||||
}
|
||||
.site-brand__title{margin:0;font-size:1.08rem;color:#f4f7ff}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:rgba(219,228,248,.72);font-size:.9rem}
|
||||
.site-brand__title{margin:0;font-size:1.08rem;color:var(--text-strong)}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:var(--text-soft);font-size:.9rem}
|
||||
.site-nav,.site-actions,.actions,.context,.meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.site-nav__link{
|
||||
display:inline-flex;
|
||||
@@ -96,23 +103,23 @@
|
||||
justify-content:center;
|
||||
min-height:34px;
|
||||
padding:0 .3rem;
|
||||
border-radius:8px;
|
||||
border-radius:6px;
|
||||
border:1px solid transparent;
|
||||
color:rgba(231,238,255,.8);
|
||||
font-weight:700;
|
||||
color:var(--text-soft);
|
||||
font-weight:600;
|
||||
font-size:.96rem;
|
||||
}
|
||||
.site-nav__link:hover{text-decoration:none;color:#f4f7ff;background:rgba(255,255,255,.06);border-color:rgba(201,214,255,.12)}
|
||||
.site-nav__link.active{background:rgba(255,255,255,.08);color:#f4f7ff;border-color:rgba(201,214,255,.12)}
|
||||
.site-nav__link:hover{text-decoration:none;color:var(--text-strong);background:rgba(var(--brand-rgb),.09);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.site-nav__link.active{background:rgba(var(--brand-rgb),.16);color:var(--text-strong);border-color:rgba(var(--brand-rgb),.28)}
|
||||
.site-more{position:relative}
|
||||
.site-more[open]{z-index:25}
|
||||
.site-more__toggle{display:inline-flex;align-items:center;justify-content:center;min-height:34px;padding:0 .6rem;border-radius:8px;border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.03);color:rgba(231,238,255,.8);font-size:.96rem;font-weight:700;cursor:pointer;list-style:none}
|
||||
.site-more__toggle{display:inline-flex;align-items:center;justify-content:center;min-height:34px;padding:0 .6rem;border-radius:8px;border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text-soft);font-size:.96rem;font-weight:600;cursor:pointer;list-style:none}
|
||||
.site-more__toggle::-webkit-details-marker{display:none}
|
||||
.site-more__panel{position:absolute;right:0;top:calc(100% + 10px);min-width:240px;padding:12px;border-radius:14px;border:1px solid rgba(163,183,255,.12);background:rgba(14,20,34,.96);box-shadow:0 16px 40px rgba(2,5,12,.34);display:grid;gap:8px}
|
||||
.site-more__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:rgba(219,228,248,.52)}
|
||||
.site-more__panel{position:absolute;right:0;top:calc(100% + 10px);min-width:240px;padding:12px;border-radius:14px;border:1px solid var(--line);background:var(--surface-3);box-shadow:var(--shadow);display:grid;gap:8px}
|
||||
.site-more__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft)}
|
||||
.site-mobile{display:none}
|
||||
.site-mobile__toggle{
|
||||
display:inline-flex;
|
||||
display:none;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
gap:10px;
|
||||
@@ -120,9 +127,9 @@
|
||||
min-height:38px;
|
||||
padding:.45rem .72rem;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(201,214,255,.14);
|
||||
border:1px solid var(--line-strong);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-weight:700;
|
||||
}
|
||||
.site-mobile__toggle::before{
|
||||
@@ -145,17 +152,17 @@
|
||||
width:min(100vw,420px);
|
||||
max-width:100%;
|
||||
padding:18px 16px calc(28px + env(safe-area-inset-bottom));
|
||||
background:rgba(14,20,34,.96);
|
||||
border-left:1px solid rgba(163,183,255,.12);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-3);
|
||||
border-left:1px solid var(--line);
|
||||
box-shadow:var(--shadow);
|
||||
display:grid;
|
||||
grid-template-rows:auto 1fr auto;
|
||||
gap:18px;
|
||||
overflow:auto;
|
||||
}
|
||||
.mobile-drawer__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
|
||||
.mobile-drawer__title{margin:0;font-size:1.08rem;color:#f4f7ff}
|
||||
.mobile-drawer__subtitle{margin:4px 0 0;color:rgba(219,228,246,.74);font-size:.9rem}
|
||||
.mobile-drawer__title{margin:0;font-size:1.08rem;color:var(--text-strong)}
|
||||
.mobile-drawer__subtitle{margin:4px 0 0;color:var(--text-soft);font-size:.9rem}
|
||||
.mobile-drawer__close{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
@@ -163,15 +170,15 @@
|
||||
width:40px;
|
||||
height:40px;
|
||||
border-radius:999px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-size:1.15rem;
|
||||
font-weight:700;
|
||||
cursor:pointer;
|
||||
}
|
||||
.mobile-drawer__section{display:grid;gap:10px}
|
||||
.mobile-drawer__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:rgba(219,228,248,.52)}
|
||||
.mobile-drawer__label{font-size:.76rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft)}
|
||||
.mobile-drawer__links{display:grid;gap:8px}
|
||||
.mobile-drawer__link{
|
||||
display:flex;
|
||||
@@ -180,13 +187,13 @@
|
||||
gap:12px;
|
||||
padding:.9rem 1rem;
|
||||
border-radius:14px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.04);
|
||||
color:#f4f7ff;
|
||||
color:var(--text-strong);
|
||||
font-weight:700;
|
||||
}
|
||||
.mobile-drawer__link.active{background:rgba(88,122,255,.16);border-color:rgba(88,122,255,.28)}
|
||||
.mobile-drawer__footer{display:grid;gap:12px;padding-top:8px;border-top:1px solid rgba(163,183,255,.12)}
|
||||
.mobile-drawer__footer{display:grid;gap:12px;padding-top:8px;border-top:1px solid var(--line)}
|
||||
.mobile-drawer__meta{display:grid;gap:4px}
|
||||
.mobile-bottom-nav{display:none}
|
||||
.mobile-bottom-nav__link,.mobile-bottom-nav__toggle{
|
||||
@@ -198,22 +205,22 @@
|
||||
padding:0 8px;
|
||||
border:0;
|
||||
background:transparent;
|
||||
color:rgba(231,238,255,.8);
|
||||
color:var(--text-soft);
|
||||
font:inherit;
|
||||
font-weight:700;
|
||||
}
|
||||
.mobile-bottom-nav__link.active,.mobile-bottom-nav__toggle.active{color:#f4f7ff}
|
||||
.mobile-bottom-nav__link.active,.mobile-bottom-nav__toggle.active{color:var(--text-strong)}
|
||||
.mobile-bottom-nav__icon{font-size:1rem;line-height:1}
|
||||
.mobile-bottom-nav__label{font-size:.74rem}
|
||||
.hero,.card,.table-card,.note{
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
border-radius:16px;
|
||||
background:rgba(14,20,34,.86);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-2);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.hero__title,.card h2,.card h3{font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;letter-spacing:-.04em;color:#f4f7ff}
|
||||
.sidebar__subtitle,.muted{color:rgba(219,228,246,.74)}
|
||||
.content{min-width:0;display:grid;gap:18px}
|
||||
.hero__title,.card h2,.card h3{font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;letter-spacing:-.04em;color:var(--text-strong)}
|
||||
.sidebar__subtitle,.muted{color:var(--text-soft)}
|
||||
.content{width:min(1260px,calc(100% - 28px));margin:0 auto;min-width:0;display:grid;gap:18px}
|
||||
.hero{
|
||||
display:grid;
|
||||
grid-template-columns:minmax(0,1.4fr) minmax(260px,.6fr);
|
||||
@@ -230,14 +237,14 @@
|
||||
font-weight:800;
|
||||
}
|
||||
.hero__title{margin:0 0 12px;font-size:clamp(1.9rem,4vw,3rem);line-height:1.05}
|
||||
.hero__lead{margin:0;color:rgba(219,228,246,.74);max-width:70ch;line-height:1.7}
|
||||
.hero__lead{margin:0;color:var(--text-soft);max-width:70ch;line-height:1.7}
|
||||
.hero__actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:16px}
|
||||
.grid{display:grid;gap:18px}
|
||||
.grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}
|
||||
.metric{padding:18px;border:1px solid rgba(163,183,255,.12);border-radius:16px;background:rgba(255,255,255,.04)}
|
||||
.metric__label{color:rgba(219,228,246,.74)}
|
||||
.metric{padding:18px;border:1px solid var(--line);border-radius:16px;background:rgba(255,255,255,.04)}
|
||||
.metric__label{color:var(--text-soft)}
|
||||
.metric__value{font-size:1.9rem;font-weight:800;margin:6px 0 8px}
|
||||
.table-card,.card{padding:22px}
|
||||
.table-card__header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:16px;flex-wrap:wrap}
|
||||
@@ -280,12 +287,13 @@
|
||||
}
|
||||
@media(max-width:1180px){
|
||||
body.mobile-drawer-open{overflow:hidden}
|
||||
.page-shell{width:min(100vw - 20px,1460px)}
|
||||
.content{width:min(100vw - 20px,1260px)}
|
||||
.page-shell{padding-bottom:94px}
|
||||
.site-header__inner{align-items:center;padding:12px 16px;min-height:68px;width:100%}
|
||||
.site-nav{display:none}
|
||||
.site-actions{gap:8px}
|
||||
.site-actions .pill,.site-actions > form{display:none}
|
||||
.site-actions .pill,.site-actions .site-more{display:none}
|
||||
.site-mobile__toggle{display:inline-flex}
|
||||
.site-mobile{display:block}
|
||||
.hero,.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}
|
||||
.mobile-bottom-nav{
|
||||
@@ -298,10 +306,10 @@
|
||||
grid-template-columns:repeat(4,minmax(0,1fr));
|
||||
gap:4px;
|
||||
padding:6px;
|
||||
border:1px solid rgba(163,183,255,.12);
|
||||
border:1px solid var(--line);
|
||||
border-radius:18px;
|
||||
background:rgba(14,20,34,.96);
|
||||
box-shadow:0 16px 40px rgba(2,5,12,.34);
|
||||
background:var(--surface-3);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
}
|
||||
@media(min-width:1181px){
|
||||
@@ -340,6 +348,14 @@
|
||||
|
||||
<div class="site-actions">
|
||||
<span class="pill">Rollenmatrix</span>
|
||||
<details class="site-more">
|
||||
<summary class="site-more__toggle">Konto</summary>
|
||||
<div class="site-more__panel">
|
||||
<div class="site-more__label">Konto</div>
|
||||
<a class="site-nav__link" href="/profile/">Persönliche Einstellungen</a>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
</details>
|
||||
<button type="button" class="site-mobile__toggle" data-mobile-drawer-open>Mehr</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -356,14 +372,16 @@
|
||||
</div>
|
||||
<button type="button" class="mobile-drawer__close" aria-label="Schließen" data-mobile-drawer-close>×</button>
|
||||
</div>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Direkt</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileBottomNavItems as $item): ?>
|
||||
<a class="mobile-drawer__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($mobileDrawerPrimaryItems !== []): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Weitere Kernfunktionen</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<?php foreach ($mobileDrawerPrimaryItems as $item): ?>
|
||||
<a class="mobile-drawer__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($tenantNavGroups as $group): ?>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label"><?= tenant_roles_h((string) ($group['label'] ?? 'Mehr')) ?></div>
|
||||
@@ -375,9 +393,11 @@
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="mobile-drawer__footer">
|
||||
<div class="mobile-drawer__meta">
|
||||
<strong><?= tenant_roles_h((string) ($auth['tenant_name'] ?? 'Bereich')) ?></strong>
|
||||
<span class="muted"><?= tenant_roles_h((string) ($auth['display_name'] ?? 'Angemeldet')) ?></span>
|
||||
<div class="mobile-drawer__section">
|
||||
<div class="mobile-drawer__label">Konto</div>
|
||||
<div class="mobile-drawer__links">
|
||||
<a class="mobile-drawer__link" href="/profile/">Persönliche Einstellungen</a>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost" style="width:100%">Abmelden</button></form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user