Anpassung des Menüs
This commit is contained in:
@@ -352,57 +352,6 @@ function app_primary_role_label(?array $auth): string
|
||||
return $labels[0] ?? 'Mitglied';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{href:string,label:string}>
|
||||
*/
|
||||
function app_tenant_nav_items(?array $auth, array $features = []): array
|
||||
{
|
||||
if (!is_array($auth)) {
|
||||
return [
|
||||
['href' => '/', 'label' => 'Start'],
|
||||
['href' => '/login/', 'label' => 'Anmeldung'],
|
||||
['href' => '/admin/login/', 'label' => 'Betreiber'],
|
||||
];
|
||||
}
|
||||
|
||||
$items = [
|
||||
['href' => '/dashboard/', 'label' => 'Dashboard'],
|
||||
['href' => '/content/', 'label' => 'Hinweise'],
|
||||
['href' => '/support/', 'label' => 'Support'],
|
||||
['href' => '/surveys/', 'label' => 'Umfragen'],
|
||||
];
|
||||
|
||||
if (app_can_manage_tenant($auth)) {
|
||||
$items[] = ['href' => '/members/', 'label' => 'Mitglieder'];
|
||||
}
|
||||
|
||||
if (app_can_manage_finance($auth)) {
|
||||
$items[] = ['href' => '/ledger/', 'label' => 'Buchungen'];
|
||||
$items[] = ['href' => '/payments/', 'label' => 'Einzahlungen'];
|
||||
}
|
||||
|
||||
if (app_can_manage_tenant($auth)) {
|
||||
$items[] = ['href' => '/tenants/roles/', 'label' => 'Rollen'];
|
||||
}
|
||||
|
||||
if (app_can_manage_tenant($auth) && !empty($features['tenant_settings'])) {
|
||||
$items[] = ['href' => '/settings/', 'label' => 'Einstellungen'];
|
||||
}
|
||||
|
||||
if (
|
||||
app_can_manage_tenant($auth)
|
||||
&& (
|
||||
!empty($features['pdf_export'])
|
||||
|| !empty($features['paper_strike_entry'])
|
||||
|| !empty($features['basic_exports'])
|
||||
)
|
||||
) {
|
||||
$items[] = ['href' => '/exports/', 'label' => 'Exporte'];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $roles
|
||||
*/
|
||||
@@ -442,7 +391,7 @@ function app_tenant_navigation_items(?array $auth, array $license = []): array
|
||||
|
||||
$features = is_array($license['features'] ?? null) ? $license['features'] : [];
|
||||
$canManage = app_can_manage_tenant($auth);
|
||||
$canFinance = $canManage || app_auth_has_any_role($auth, ['finance_admin']);
|
||||
$canFinance = app_can_manage_finance($auth);
|
||||
$hasExports = !empty($features['pdf_export']) || !empty($features['paper_strike_entry']) || !empty($features['basic_exports']);
|
||||
|
||||
$items = [
|
||||
@@ -2675,8 +2624,8 @@ function app_handle_tenant_action(PDO $pdo, array $auth): void
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app_can_manage_tenant($auth)) {
|
||||
app_flash('Für diese Aktion brauchst du Tenant-Admin-Rechte.', 'warning');
|
||||
if (!app_can_manage_finance($auth)) {
|
||||
app_flash('Für diese Aktion brauchst du Finanz- oder Tenant-Rechte.', 'warning');
|
||||
app_redirect('/dashboard/');
|
||||
}
|
||||
|
||||
|
||||
@@ -208,10 +208,16 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
$loginState = $loginFlow['state'] ?? [];
|
||||
$loginStep = (string) ($loginState['step'] ?? 'discover');
|
||||
$selectedMembership = $loginState['selected_membership'] ?? null;
|
||||
$restrictedPages = ['members', 'ledger', 'payments', 'settings', 'exports'];
|
||||
$restrictedPages = [
|
||||
'members' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
'ledger' => static fn(array $user): bool => app_can_manage_finance($user),
|
||||
'payments' => static fn(array $user): bool => app_can_manage_finance($user),
|
||||
'settings' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
'exports' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
];
|
||||
|
||||
if ($auth !== null && in_array($page, $restrictedPages, true) && !app_can_manage_tenant($auth)) {
|
||||
app_flash('Dieser Bereich ist nur für Tenant-Admins verfügbar.', 'warning');
|
||||
if ($auth !== null && isset($restrictedPages[$page]) && !$restrictedPages[$page]($auth)) {
|
||||
app_flash('Dieser Bereich ist für deine Rolle nicht freigegeben.', 'warning');
|
||||
app_redirect('/dashboard/');
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../app-support.php';
|
||||
require_once __DIR__ . '/../tenant-shell.php';
|
||||
require_once __DIR__ . '/../../app/Modules/Support/bootstrap.php';
|
||||
|
||||
if (!isset($_SESSION['support_csrf'])) {
|
||||
|
||||
@@ -10,7 +10,6 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/app-support.php';
|
||||
require_once dirname(__DIR__) . '/tenant-shell.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/Modules/Surveys/Domain/Survey.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/Modules/Surveys/Domain/SurveyQuestion.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/Modules/Surveys/Domain/SurveyPublication.php';
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/app-support.php';
|
||||
|
||||
function tenant_shell_h(string $value): string
|
||||
{
|
||||
return app_h($value);
|
||||
}
|
||||
|
||||
function tenant_shell_styles(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4efe4;
|
||||
--card:#fffdf8;
|
||||
--card-soft:#f7f1e5;
|
||||
--ink:#25180f;
|
||||
--muted:#63584a;
|
||||
--brand:#005e3f;
|
||||
--brand-strong:#00452f;
|
||||
--accent:#c18a00;
|
||||
--line:rgba(37,24,15,.14);
|
||||
--shadow:0 16px 36px rgba(37,24,15,.08);
|
||||
--radius:18px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0;
|
||||
color:var(--ink);
|
||||
font-family:"Aptos","Segoe UI",sans-serif;
|
||||
background:linear-gradient(180deg,#f9f6ef 0%,var(--bg) 100%);
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
.tenant-shell{width:min(1240px,calc(100vw - 32px));margin:20px auto 40px}
|
||||
.tenant-header,
|
||||
.tenant-nav,
|
||||
.tenant-context,
|
||||
.hero,
|
||||
.card,
|
||||
.panel,
|
||||
.table-card,
|
||||
.note,
|
||||
.alert{
|
||||
border:1px solid var(--line);
|
||||
border-radius:var(--radius);
|
||||
background:var(--card);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.tenant-header{
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:flex-start;
|
||||
gap:16px;
|
||||
padding:18px 22px;
|
||||
margin-bottom:14px;
|
||||
}
|
||||
.tenant-brand{display:grid;gap:4px}
|
||||
.tenant-brand__title,
|
||||
.hero__title,
|
||||
.card h2,
|
||||
.card h3,
|
||||
.panel h2,
|
||||
.panel h3,
|
||||
.table-card h2,
|
||||
.table-card h3{
|
||||
margin:0;
|
||||
font-family:Georgia,serif;
|
||||
letter-spacing:-.02em;
|
||||
}
|
||||
.tenant-brand__title{font-size:1.18rem}
|
||||
.tenant-brand__subtitle,
|
||||
.muted,
|
||||
p{color:var(--muted)}
|
||||
.tenant-toolbar,
|
||||
.tenant-nav__items,
|
||||
.hero__actions,
|
||||
.hero__meta,
|
||||
.actions,
|
||||
.context,
|
||||
.stack-inline{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
}
|
||||
.tenant-nav{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:14px;
|
||||
align-items:center;
|
||||
padding:14px 18px;
|
||||
margin-bottom:14px;
|
||||
background:var(--card-soft);
|
||||
}
|
||||
.tenant-nav__label{
|
||||
font-size:.86rem;
|
||||
font-weight:800;
|
||||
letter-spacing:.12em;
|
||||
text-transform:uppercase;
|
||||
color:var(--accent);
|
||||
}
|
||||
.tenant-nav__items{gap:8px}
|
||||
.tenant-nav__link,
|
||||
.button,
|
||||
button{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:10px 14px;
|
||||
border-radius:999px;
|
||||
border:1px solid transparent;
|
||||
font:inherit;
|
||||
font-weight:700;
|
||||
cursor:pointer;
|
||||
}
|
||||
.tenant-nav__link{
|
||||
background:#fff;
|
||||
color:var(--brand-strong);
|
||||
border-color:rgba(0,94,63,.12);
|
||||
}
|
||||
.tenant-nav__link.is-active,
|
||||
.button,
|
||||
button{
|
||||
background:var(--brand);
|
||||
color:#fff;
|
||||
}
|
||||
.button.secondary{
|
||||
background:#fff;
|
||||
color:var(--brand-strong);
|
||||
border-color:rgba(0,94,63,.18);
|
||||
}
|
||||
.tenant-context{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
padding:14px 18px;
|
||||
margin-bottom:18px;
|
||||
}
|
||||
.badge,
|
||||
.pill{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
padding:6px 11px;
|
||||
border-radius:999px;
|
||||
border:1px solid rgba(0,94,63,.12);
|
||||
background:#fff;
|
||||
color:var(--brand-strong);
|
||||
font-size:.84rem;
|
||||
font-weight:700;
|
||||
}
|
||||
.badge--solid,
|
||||
.badge--success{background:rgba(0,94,63,.1);color:var(--brand-strong)}
|
||||
.badge--warning{background:rgba(193,138,0,.14);color:#8c6500}
|
||||
.badge--danger{background:rgba(154,31,31,.12);color:#9a1f1f}
|
||||
.badge--neutral{background:rgba(0,94,63,.06);color:var(--brand-strong)}
|
||||
.hero{
|
||||
display:grid;
|
||||
gap:18px;
|
||||
padding:24px;
|
||||
margin-bottom:18px;
|
||||
background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%);
|
||||
}
|
||||
.hero--split{grid-template-columns:minmax(0,1.2fr) minmax(280px,.8fr)}
|
||||
.hero__content,
|
||||
.hero__aside,
|
||||
.stack{display:grid;gap:14px}
|
||||
.hero__kicker,
|
||||
.card__eyebrow,
|
||||
.eyebrow{
|
||||
margin:0 0 8px;
|
||||
font-size:.8rem;
|
||||
font-weight:800;
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.12em;
|
||||
color:var(--accent);
|
||||
}
|
||||
.hero__title{font-size:clamp(1.9rem,4vw,3rem);line-height:1.05}
|
||||
.hero__lead{margin:0;max-width:64ch;line-height:1.6}
|
||||
.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))}
|
||||
.split{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(360px,.9fr);gap:18px}
|
||||
.card,.panel,.table-card{padding:22px}
|
||||
.table-card__header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:16px;flex-wrap:wrap}
|
||||
.table{overflow-x:auto}
|
||||
table{width:100%;border-collapse:collapse;min-width:720px}
|
||||
th,td{padding:12px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
|
||||
th{font-size:.82rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
||||
input,select,textarea{
|
||||
width:100%;
|
||||
padding:12px 14px;
|
||||
border-radius:14px;
|
||||
border:1px solid rgba(37,24,15,.15);
|
||||
background:#fff;
|
||||
color:var(--ink);
|
||||
font:inherit;
|
||||
}
|
||||
label{display:flex;flex-direction:column;gap:8px;font-weight:700}
|
||||
textarea{min-height:120px}
|
||||
.metric{
|
||||
padding:18px;
|
||||
border-radius:16px;
|
||||
border:1px solid var(--line);
|
||||
background:#fff;
|
||||
display:grid;
|
||||
gap:8px;
|
||||
}
|
||||
.metric__label{color:var(--muted)}
|
||||
.metric__value{font-size:1.9rem;font-weight:800}
|
||||
.status{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
padding:6px 10px;
|
||||
border-radius:999px;
|
||||
background:rgba(0,94,63,.1);
|
||||
color:var(--brand-strong);
|
||||
font-weight:700;
|
||||
font-size:.84rem;
|
||||
}
|
||||
.status--warning{background:rgba(193,138,0,.14);color:#8c6500}
|
||||
.status--success{background:rgba(0,94,63,.12);color:var(--brand-strong)}
|
||||
.status--danger{background:rgba(154,31,31,.12);color:#9a1f1f}
|
||||
.note,
|
||||
.alert{
|
||||
padding:14px 16px;
|
||||
}
|
||||
.alert-success{background:rgba(0,94,63,.08)}
|
||||
.alert-warning{background:rgba(193,138,0,.1)}
|
||||
.alert-error{background:rgba(154,31,31,.1)}
|
||||
.timeline{display:grid;gap:12px}
|
||||
.timeline__item{
|
||||
padding:14px 16px;
|
||||
border-radius:16px;
|
||||
border:1px solid rgba(37,24,15,.08);
|
||||
background:#fff;
|
||||
}
|
||||
.timeline__title{margin:0 0 6px;font-weight:800;color:var(--ink)}
|
||||
.timeline__meta{margin:0;color:var(--muted);line-height:1.55}
|
||||
.tenant-footer{
|
||||
margin-top:18px;
|
||||
text-align:center;
|
||||
color:var(--muted);
|
||||
font-size:.92rem;
|
||||
}
|
||||
@media(max-width:980px){
|
||||
.tenant-header,
|
||||
.tenant-nav{flex-direction:column;align-items:flex-start}
|
||||
.hero--split,
|
||||
.grid--2,
|
||||
.grid--3,
|
||||
.grid--4,
|
||||
.split{grid-template-columns:1fr}
|
||||
table{min-width:0}
|
||||
}
|
||||
</style>
|
||||
HTML;
|
||||
}
|
||||
|
||||
function tenant_shell_path_is_active(string $currentPath, string $href): bool
|
||||
{
|
||||
$normalize = static function (string $path): string {
|
||||
$value = parse_url($path, PHP_URL_PATH);
|
||||
$value = is_string($value) ? rtrim($value, '/') : '';
|
||||
|
||||
return $value === '' ? '/' : $value;
|
||||
};
|
||||
|
||||
return $normalize($currentPath) === $normalize($href);
|
||||
}
|
||||
|
||||
function tenant_shell_render_header(array $auth, string $currentPath, array $tenantLicense = [], string $title = 'Kaffeeliste', string $subtitle = 'Mandantenbereich'): string
|
||||
{
|
||||
$navItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$tenantName = (string) ($auth['tenant_name'] ?? 'Tenant');
|
||||
$displayName = (string) ($auth['display_name'] ?? 'Benutzer');
|
||||
$roleLabel = app_primary_role_label($auth);
|
||||
$planName = (string) ($tenantLicense['plan_name'] ?? 'Free');
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<header class="tenant-header">
|
||||
<div class="tenant-brand">
|
||||
<h1 class="tenant-brand__title"><?= tenant_shell_h($title) ?></h1>
|
||||
<p class="tenant-brand__subtitle"><?= tenant_shell_h($subtitle) ?></p>
|
||||
</div>
|
||||
<div class="tenant-toolbar">
|
||||
<span class="badge"><?= tenant_shell_h($tenantName) ?></span>
|
||||
<span class="badge"><?= tenant_shell_h($roleLabel) ?></span>
|
||||
<span class="badge"><?= tenant_shell_h('Lizenz ' . $planName) ?></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="tenant-nav" aria-label="Tenant-Menue">
|
||||
<span class="tenant-nav__label">Menue</span>
|
||||
<div class="tenant-nav__items">
|
||||
<?php foreach ($navItems as $item): ?>
|
||||
<a href="<?= tenant_shell_h($item['href']) ?>" class="tenant-nav__link<?= tenant_shell_path_is_active($currentPath, (string) $item['href']) ? ' is-active' : '' ?>">
|
||||
<?= tenant_shell_h((string) $item['label']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<form method="post" action="/logout/" style="display:inline-flex;">
|
||||
<button type="submit" class="button secondary">Abmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="tenant-context">
|
||||
<span class="badge"><?= tenant_shell_h($displayName) ?></span>
|
||||
<?php foreach (app_auth_role_labels($auth) as $label): ?>
|
||||
<span class="badge badge--neutral"><?= tenant_shell_h($label) ?></span>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($auth['acting_as_platform_admin'])): ?>
|
||||
<span class="badge badge--warning">Global-Admin im Tenant</span>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
@@ -7,7 +7,6 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../app-support.php';
|
||||
require_once __DIR__ . '/../../tenant-shell.php';
|
||||
require_once dirname(__DIR__, 3) . '/app/Support/TenantResolver.php';
|
||||
require_once dirname(__DIR__, 3) . '/app/Modules/Tenants/Models/Tenant.php';
|
||||
require_once dirname(__DIR__, 3) . '/app/Modules/Tenants/Models/TenantUser.php';
|
||||
|
||||
Reference in New Issue
Block a user