Anpassung menü
This commit is contained in:
@@ -2297,9 +2297,76 @@ function app_tenant_settings_defaults(): array
|
||||
'location_label' => '',
|
||||
'allow_self_service_booking' => '1',
|
||||
'payment_hint' => 'Bitte Einzahlungen zeitnah verbuchen.',
|
||||
'brand_color' => '#005e3f',
|
||||
'brand_strong_color' => '#00452f',
|
||||
'accent_color' => '#c18a00',
|
||||
'background_color' => '#f4efe4',
|
||||
'card_color' => '#fffdf8',
|
||||
];
|
||||
}
|
||||
|
||||
function app_is_valid_hex_color(string $value): bool
|
||||
{
|
||||
return preg_match('/^#[0-9A-Fa-f]{6}$/', $value) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{brand_color:string,brand_strong_color:string,accent_color:string,background_color:string,card_color:string,text_color:string,muted_color:string,brand_rgb:string,accent_rgb:string}
|
||||
*/
|
||||
function app_tenant_theme(array $settings): array
|
||||
{
|
||||
$defaults = [
|
||||
'brand_color' => '#005e3f',
|
||||
'brand_strong_color' => '#00452f',
|
||||
'accent_color' => '#c18a00',
|
||||
'background_color' => '#f4efe4',
|
||||
'card_color' => '#fffdf8',
|
||||
'text_color' => '#25180f',
|
||||
'muted_color' => '#63584a',
|
||||
];
|
||||
|
||||
foreach ($defaults as $key => $default) {
|
||||
$candidate = strtoupper(trim((string) ($settings[$key] ?? $default)));
|
||||
$defaults[$key] = app_is_valid_hex_color($candidate) ? $candidate : $default;
|
||||
}
|
||||
|
||||
$brandRgb = sscanf($defaults['brand_color'], '#%02x%02x%02x') ?: [0, 94, 63];
|
||||
$accentRgb = sscanf($defaults['accent_color'], '#%02x%02x%02x') ?: [193, 138, 0];
|
||||
|
||||
return [
|
||||
'brand_color' => $defaults['brand_color'],
|
||||
'brand_strong_color' => $defaults['brand_strong_color'],
|
||||
'accent_color' => $defaults['accent_color'],
|
||||
'background_color' => $defaults['background_color'],
|
||||
'card_color' => $defaults['card_color'],
|
||||
'text_color' => $defaults['text_color'],
|
||||
'muted_color' => $defaults['muted_color'],
|
||||
'brand_rgb' => implode(', ', array_map('intval', $brandRgb)),
|
||||
'accent_rgb' => implode(', ', array_map('intval', $accentRgb)),
|
||||
];
|
||||
}
|
||||
|
||||
function app_tenant_theme_root_css(array $settings): string
|
||||
{
|
||||
$theme = app_tenant_theme($settings);
|
||||
|
||||
return implode('', [
|
||||
'--bg:', $theme['background_color'], ';',
|
||||
'--card:', $theme['card_color'], ';',
|
||||
'--bg-elevated:', $theme['card_color'], ';',
|
||||
'--bg-soft:', $theme['card_color'], ';',
|
||||
'--text:', $theme['text_color'], ';',
|
||||
'--ink:', $theme['text_color'], ';',
|
||||
'--muted:', $theme['muted_color'], ';',
|
||||
'--brand:', $theme['brand_color'], ';',
|
||||
'--brand-2:', $theme['brand_strong_color'], ';',
|
||||
'--brand-strong:', $theme['brand_strong_color'], ';',
|
||||
'--accent:', $theme['accent_color'], ';',
|
||||
'--brand-rgb:', $theme['brand_rgb'], ';',
|
||||
'--accent-rgb:', $theme['accent_rgb'], ';',
|
||||
]);
|
||||
}
|
||||
|
||||
function app_tenant_settings(PDO $pdo, string $tenantId): array
|
||||
{
|
||||
$settings = app_tenant_settings_defaults();
|
||||
@@ -2331,6 +2398,11 @@ function app_save_tenant_settings(PDO $pdo, string $tenantId, array $data): void
|
||||
'location_label' => trim((string) ($data['location_label'] ?? '')),
|
||||
'allow_self_service_booking' => !empty($data['allow_self_service_booking']) ? '1' : '0',
|
||||
'payment_hint' => trim((string) ($data['payment_hint'] ?? '')),
|
||||
'brand_color' => strtoupper(trim((string) ($data['brand_color'] ?? '#005e3f'))),
|
||||
'brand_strong_color' => strtoupper(trim((string) ($data['brand_strong_color'] ?? '#00452f'))),
|
||||
'accent_color' => strtoupper(trim((string) ($data['accent_color'] ?? '#c18a00'))),
|
||||
'background_color' => strtoupper(trim((string) ($data['background_color'] ?? '#f4efe4'))),
|
||||
'card_color' => strtoupper(trim((string) ($data['card_color'] ?? '#fffdf8'))),
|
||||
];
|
||||
|
||||
if (!is_numeric($values['default_unit_price']) || (float) $values['default_unit_price'] <= 0) {
|
||||
@@ -2345,6 +2417,12 @@ function app_save_tenant_settings(PDO $pdo, string $tenantId, array $data): void
|
||||
throw new RuntimeException('Bitte gib eine gültige Support-E-Mail-Adresse an.');
|
||||
}
|
||||
|
||||
foreach (['brand_color', 'brand_strong_color', 'accent_color', 'background_color', 'card_color'] as $colorKey) {
|
||||
if (!app_is_valid_hex_color($values[$colorKey])) {
|
||||
throw new RuntimeException('Bitte gib für die Farbfelder gültige HEX-Werte an.');
|
||||
}
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
|
||||
@@ -223,6 +223,7 @@ if ($auth !== null && isset($restrictedPages[$page]) && !$restrictedPages[$page]
|
||||
|
||||
$canManageTenant = app_can_manage_tenant($auth);
|
||||
$tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="de">
|
||||
@@ -231,8 +232,8 @@ $tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kaffeeliste SaaS</title>
|
||||
<style>
|
||||
:root{--bg:#f4efe4;--card:#fffdf8;--ink:#25180f;--muted:#63584a;--brand:#005e3f;--accent:#c18a00;--line:rgba(37,24,15,.14);--radius:18px;--shadow:0 16px 36px rgba(37,24,15,.08)}
|
||||
*{box-sizing:border-box}body{margin:0;font-family:"Aptos","Segoe UI",sans-serif;color:var(--ink);background:linear-gradient(180deg,#f9f6ef 0%,var(--bg) 100%)}a{color:inherit}.shell{width:min(1180px,calc(100vw - 32px));margin:20px auto 40px}.bar,.hero,.card,.alert{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);box-shadow:var(--shadow)}.bar,.links,.actions,.context{display:flex;flex-wrap:wrap;gap:10px}.bar{justify-content:space-between;align-items:center;padding:18px 22px;margin-bottom:14px}.brand strong,h1,h2,h3{font-family:Georgia,serif}.brand strong{font-size:1.18rem}.brand span,p,.muted{color:var(--muted)}.hero,.card{padding:24px}.hero{margin-bottom:18px;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}.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))}.links a,.button,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;text-decoration:none;font-weight:700;border:1px solid transparent;cursor:pointer}.links a{background:#fff;color:var(--brand);border-color:rgba(0,94,63,.12)}.links a.active{background:var(--brand);color:#fff}.button,button{background:var(--brand);color:#fff}.button.secondary{background:#fff;color:var(--brand);border-color:rgba(0,94,63,.18)}.eyebrow{display:inline-block;margin-bottom:12px;color:var(--accent);font-size:.82rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2rem,4vw,3.2rem);margin:0 0 12px}h2{font-size:1.35rem;margin:0 0 12px}h3{font-size:1.05rem;margin:0 0 10px}p{margin:0;line-height:1.6}.stack{display:grid;gap:12px}.metric{padding:18px;border:1px solid var(--line);border-radius:16px;background:#fff}.metric strong{display:block;font-size:1.8rem;margin-bottom:8px}.list{margin:14px 0 0;padding-left:18px;color:var(--muted);line-height:1.7}.alert{padding:16px 18px;margin-bottom:18px}.alert-success{background:rgba(0,94,63,.08)}.alert-warning{background:rgba(193,138,0,.1)}.alert-error{background:rgba(154,31,31,.1)}.alert-info{background:rgba(0,94,63,.08)}.badge{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;font-size:.86rem;font-weight:700}.badge-neutral{background:rgba(0,94,63,.08);color:var(--brand)}.badge-success{background:rgba(0,94,63,.12);color:var(--brand)}.badge-warning{background:rgba(193,138,0,.14);color:#8c6500}.table{overflow-x:auto}.table table{width:100%;border-collapse:collapse;min-width:720px}.table th,.table td{padding:13px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.table th{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}form.grid{grid-template-columns:repeat(2,minmax(0,1fr))}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(37,24,15,.15);font:inherit;background:#fff;color:var(--ink)}textarea{min-height:120px}.footer{margin-top:18px;text-align:center;color:var(--muted);font-size:.92rem}@media(max-width:960px){.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.bar{align-items:flex-start;flex-direction:column}.table table{min-width:0}}
|
||||
:root{<?= $themeCss ?>--line:rgba(37,24,15,.14);--radius:18px;--shadow:0 16px 36px rgba(37,24,15,.08)}
|
||||
*{box-sizing:border-box}body{margin:0;font-family:"Aptos","Segoe UI",sans-serif;color:var(--ink);background:linear-gradient(180deg,#f9f6ef 0%,var(--bg) 100%)}a{color:inherit}.shell{width:min(1180px,calc(100vw - 32px));margin:20px auto 40px}.bar,.hero,.card,.alert{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);box-shadow:var(--shadow)}.bar,.links,.actions,.context{display:flex;flex-wrap:wrap;gap:10px}.bar{justify-content:space-between;align-items:center;padding:18px 22px;margin-bottom:14px}.brand strong,h1,h2,h3{font-family:Georgia,serif}.brand strong{font-size:1.18rem}.brand span,p,.muted{color:var(--muted)}.hero,.card{padding:24px}.hero{margin-bottom:18px;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}.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))}.links a,.button,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;text-decoration:none;font-weight:700;border:1px solid transparent;cursor:pointer}.links a{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.12)}.links a.active{background:var(--brand);color:#fff}.button,button{background:var(--brand);color:#fff}.button.secondary{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}.eyebrow{display:inline-block;margin-bottom:12px;color:var(--accent);font-size:.82rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2rem,4vw,3.2rem);margin:0 0 12px}h2{font-size:1.35rem;margin:0 0 12px}h3{font-size:1.05rem;margin:0 0 10px}p{margin:0;line-height:1.6}.stack{display:grid;gap:12px}.metric{padding:18px;border:1px solid var(--line);border-radius:16px;background:#fff}.metric strong{display:block;font-size:1.8rem;margin-bottom:8px}.list{margin:14px 0 0;padding-left:18px;color:var(--muted);line-height:1.7}.alert{padding:16px 18px;margin-bottom:18px}.alert-success{background:rgba(var(--brand-rgb),.08)}.alert-warning{background:rgba(var(--accent-rgb),.1)}.alert-error{background:rgba(154,31,31,.1)}.alert-info{background:rgba(var(--brand-rgb),.08)}.badge{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;font-size:.86rem;font-weight:700}.badge-neutral{background:rgba(var(--brand-rgb),.08);color:var(--brand)}.badge-success{background:rgba(var(--brand-rgb),.12);color:var(--brand)}.badge-warning{background:rgba(var(--accent-rgb),.14);color:#8c6500}.table{overflow-x:auto}.table table{width:100%;border-collapse:collapse;min-width:720px}.table th,.table td{padding:13px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.table th{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}form.grid{grid-template-columns:repeat(2,minmax(0,1fr))}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(37,24,15,.15);font:inherit;background:#fff;color:var(--ink)}textarea{min-height:120px}.footer{margin-top:18px;text-align:center;color:var(--muted);font-size:.92rem}@media(max-width:960px){.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.bar{align-items:flex-start;flex-direction:column}.table table{min-width:0}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -640,6 +641,11 @@ $tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
<label>Zeilenhöhe PDF-Liste in mm<input type="number" name="pdf_row_height_mm" min="3.2" max="12" step="0.1" value="<?= h((string) ($tenantSettings['pdf_row_height_mm'] ?? '4.00')) ?>"></label>
|
||||
<label>Standortbezeichnung<input name="location_label" value="<?= h((string) ($tenantSettings['location_label'] ?? '')) ?>"></label>
|
||||
<label>Support-E-Mail<input type="email" name="support_email" value="<?= h((string) ($tenantSettings['support_email'] ?? '')) ?>"></label>
|
||||
<label>Primärfarbe<input type="color" name="brand_color" value="<?= h((string) ($tenantSettings['brand_color'] ?? '#005e3f')) ?>"></label>
|
||||
<label>Dunkle Primärfarbe<input type="color" name="brand_strong_color" value="<?= h((string) ($tenantSettings['brand_strong_color'] ?? '#00452f')) ?>"></label>
|
||||
<label>Akzentfarbe<input type="color" name="accent_color" value="<?= h((string) ($tenantSettings['accent_color'] ?? '#c18a00')) ?>"></label>
|
||||
<label>Hintergrundfarbe<input type="color" name="background_color" value="<?= h((string) ($tenantSettings['background_color'] ?? '#f4efe4')) ?>"></label>
|
||||
<label>Kartenfarbe<input type="color" name="card_color" value="<?= h((string) ($tenantSettings['card_color'] ?? '#fffdf8')) ?>"></label>
|
||||
<label class="checkbox" style="grid-column:1 / -1;display:flex;flex-direction:row;align-items:center;font-weight:600;">
|
||||
<input type="checkbox" name="allow_self_service_booking"<?= (($tenantSettings['allow_self_service_booking'] ?? '1') === '1') ? ' checked' : '' ?> style="width:auto">
|
||||
<span>Selbstbedienungs-Buchungen im Mandanten aktiv lassen</span>
|
||||
@@ -657,6 +663,23 @@ $tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<h2>Farben im Tenant</h2>
|
||||
<div class="grid grid-4">
|
||||
<?php foreach ([
|
||||
'Primärfarbe' => $tenantSettings['brand_color'] ?? '#005e3f',
|
||||
'Dunkle Primärfarbe' => $tenantSettings['brand_strong_color'] ?? '#00452f',
|
||||
'Akzentfarbe' => $tenantSettings['accent_color'] ?? '#c18a00',
|
||||
'Hintergrund' => $tenantSettings['background_color'] ?? '#f4efe4',
|
||||
] as $label => $color): ?>
|
||||
<div class="metric">
|
||||
<div style="width:100%;height:42px;border-radius:12px;border:1px solid var(--line);background:<?= h((string) $color) ?>"></div>
|
||||
<h3 style="margin-top:12px"><?= h($label) ?></h3>
|
||||
<p class="muted"><?= h((string) $color) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php elseif ($page === 'exports'): ?>
|
||||
<?php if (isset($_GET['view']) && $_GET['view'] === 'print' && is_array($activePrintList)): ?>
|
||||
<section class="hero"><div class="eyebrow">PDF-Vorschau</div><h1><?= h((string) $activePrintList['title']) ?></h1><p><?= (($printListGroups['mode'] ?? 'single') === 'single') ? 'Diese Liste wird als einzelne PDF-Seite erzeugt.' : 'Diese Liste wird als Vorder- und Rückseite erzeugt.' ?> Die Zeilenhöhe liegt aktuell bei <?= h((string) ($printListGroups['row_height_mm'] ?? ($tenantSettings['pdf_row_height_mm'] ?? '4.00'))) ?> mm.</p><div class="actions" style="margin-top:18px"><a class="button secondary" href="/exports/?list=<?= h((string) $activePrintList['id']) ?>">Zurück</a><a class="button" href="/exports/?download=print-list-pdf&list=<?= h((string) $activePrintList['id']) ?>">PDF herunterladen</a></div></section>
|
||||
|
||||
@@ -40,6 +40,7 @@ $pdo = null;
|
||||
$dbError = null;
|
||||
$supportTablesReady = false;
|
||||
$tenantLicense = ['plan_name' => 'Free', 'features' => app_feature_defaults()];
|
||||
$tenantSettings = app_tenant_settings_defaults();
|
||||
$controller = new \App\Modules\Support\Controllers\SupportController(new \App\Modules\Support\Application\SupportService());
|
||||
$pageData = [
|
||||
'title' => 'Support',
|
||||
@@ -65,6 +66,7 @@ $pageData = [
|
||||
try {
|
||||
$pdo = app_pdo();
|
||||
$tenantLicense = app_tenant_license($pdo, (string) $auth['tenant_id']);
|
||||
$tenantSettings = app_tenant_settings($pdo, (string) $auth['tenant_id']);
|
||||
$supportTablesReady = scripts_table_exists($pdo, 'support_requests')
|
||||
&& scripts_table_exists($pdo, 'support_request_messages');
|
||||
} catch (\Throwable $exception) {
|
||||
@@ -97,7 +99,6 @@ $priorities = $pageData['priorities'] ?? [];
|
||||
$routeTargets = $pageData['route_targets'] ?? [];
|
||||
$selectedRequestId = (string) ($_GET['request'] ?? '');
|
||||
$tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$tenantNavItems = app_tenant_navigation_items($auth);
|
||||
|
||||
$template = dirname(__DIR__, 2) . '/resources/views/support/index.blade.php';
|
||||
if (!is_file($template)) {
|
||||
|
||||
@@ -37,10 +37,12 @@ $tenantId = (string) ($auth['tenant_id'] ?? 'tenant-demo');
|
||||
$tenantName = (string) ($auth['tenant_name'] ?? 'Demo Tenant');
|
||||
$tenantUser = (string) ($auth['display_name'] ?? 'Survey-Verantwortliche');
|
||||
$tenantLicense = ['plan_name' => 'Free', 'features' => app_feature_defaults()];
|
||||
$tenantSettings = app_tenant_settings_defaults();
|
||||
|
||||
try {
|
||||
$pdo = app_pdo();
|
||||
$tenantLicense = app_tenant_license($pdo, $tenantId);
|
||||
$tenantSettings = app_tenant_settings($pdo, $tenantId);
|
||||
} catch (\Throwable $ignored) {
|
||||
}
|
||||
$tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
@@ -50,6 +52,7 @@ $payload = $controller->index($tenantId);
|
||||
$data = $payload['data'];
|
||||
$board = $data['board'];
|
||||
$memberBoard = $data['memberBoard'];
|
||||
$themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="de">
|
||||
@@ -58,7 +61,7 @@ $memberBoard = $data['memberBoard'];
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kaffeeliste - Umfragen</title>
|
||||
<style>
|
||||
:root{--bg:#f4efe4;--card:#fffdf8;--ink:#25180f;--muted:#63584a;--brand:#005e3f;--accent:#c18a00;--line:rgba(37,24,15,.14);--shadow:0 16px 36px rgba(37,24,15,.08);--radius:18px;--radius-lg:16px;--content-width:1200px}
|
||||
:root{<?= $themeCss ?>--line:rgba(37,24,15,.14);--shadow:0 16px 36px rgba(37,24,15,.08);--radius:18px;--radius-lg:16px;--content-width:1200px}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;min-height:100vh;font-family:"Aptos","Segoe UI",sans-serif;color:var(--ink);background:linear-gradient(180deg,#f9f6ef 0%,var(--bg) 100%)}
|
||||
a{color:inherit;text-decoration:none}
|
||||
@@ -70,15 +73,19 @@ $memberBoard = $data['memberBoard'];
|
||||
.brand__title{font-size:1.18rem;font-weight:700}
|
||||
.brand__subtitle{color:var(--muted)}
|
||||
.toolbar,.meta,.stack{display:flex;flex-wrap:wrap;gap:10px}
|
||||
.badge,.pill{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;background:#fff;color:var(--brand);font-weight:700;font-size:.86rem;border:1px solid rgba(0,94,63,.12)}
|
||||
.links,.meta,.stack{display:flex;flex-wrap:wrap;gap:10px}
|
||||
.links a,.button,.pill,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;font-weight:700;border:1px solid transparent}
|
||||
.links a{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.12)}
|
||||
.links a.active{background:var(--brand);color:#fff}
|
||||
.badge,.pill{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;background:#fff;color:var(--brand);font-weight:700;font-size:.86rem;border:1px solid rgba(var(--brand-rgb),.12)}
|
||||
.badge--solid{background:var(--brand);color:#fff}
|
||||
.hero{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(260px,.65fr);gap:20px;padding:24px;margin-bottom:18px;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}
|
||||
.hero__kicker{margin:0 0 10px;color:var(--accent);text-transform:uppercase;letter-spacing:.15em;font-size:.8rem;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:var(--muted);max-width:70ch;line-height:1.7}
|
||||
.hero__actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:16px}
|
||||
.button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;border:1px solid transparent;background:var(--brand);color:#fff;font-weight:700}
|
||||
.button--ghost{background:#fff;color:var(--brand);border:1px solid rgba(0,94,63,.18)}
|
||||
.button{background:var(--brand);color:#fff}
|
||||
.button--ghost{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.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))}
|
||||
@@ -90,17 +97,17 @@ $memberBoard = $data['memberBoard'];
|
||||
table{width:100%;border-collapse:collapse}
|
||||
th,td{padding:12px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
|
||||
th{font-size:.82rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
|
||||
.status{display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(0,94,63,.1);color:var(--brand);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)}
|
||||
.status{display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(var(--brand-rgb),.1);color:var(--brand);font-weight:700;font-size:.84rem}
|
||||
.status--warning{background:rgba(var(--accent-rgb),.14);color:#8c6500}
|
||||
.status--success{background:rgba(var(--brand-rgb),.12);color:var(--brand)}
|
||||
.feature-list__item{display:flex;gap:12px;padding:14px 16px;border-radius:16px;background:rgba(255,255,255,.78);border:1px solid rgba(31,41,51,.08)}
|
||||
.feature-list__badge{flex:0 0 auto;width:34px;height:34px;border-radius:12px;display:grid;place-items:center;background:rgba(0,94,63,.1);color:var(--brand);font-weight:800}
|
||||
.feature-list__badge{flex:0 0 auto;width:34px;height:34px;border-radius:12px;display:grid;place-items:center;background:rgba(var(--brand-rgb),.1);color:var(--brand);font-weight:800}
|
||||
.feature-list__title{margin:0 0 4px;font-weight:700}
|
||||
.timeline{display:grid;gap:12px}
|
||||
.timeline__item{padding:14px 16px;border-radius:16px;background:#fff;border:1px solid rgba(31,41,51,.08)}
|
||||
.timeline__title{margin:0 0 4px;font-weight:700}
|
||||
.timeline__meta{margin:0;color:var(--muted)}
|
||||
.note{padding:16px 18px;margin-top:16px;background:rgba(0,94,63,.06)}
|
||||
.note{padding:16px 18px;margin-top:16px;background:rgba(var(--brand-rgb),.06)}
|
||||
.list-reset{margin:0;padding-left:18px}
|
||||
.list-reset li+li{margin-top:10px}
|
||||
@media(max-width:960px){.hero,.grid--2,.grid--3{grid-template-columns:1fr}.bar{align-items:flex-start;flex-direction:column}}
|
||||
@@ -113,12 +120,12 @@ $memberBoard = $data['memberBoard'];
|
||||
<strong class="brand__title">Kaffeeliste SaaS</strong>
|
||||
<span class="brand__subtitle">Umfragen und Freigaben im Tenant.</span>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<nav class="links">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a class="badge <?= (($item['key'] ?? '') === 'surveys') ? 'badge--solid' : '' ?>" href="<?= h((string) ($item['href'] ?? '/')) ?>"><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<a class="<?= (($item['key'] ?? '') === 'surveys') ? 'active' : '' ?>" href="<?= h((string) ($item['href'] ?? '/')) ?>"><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
|
||||
@@ -34,6 +34,7 @@ if (!function_exists('tenant_roles_implode')) {
|
||||
|
||||
$auth = app_require_auth();
|
||||
$tenantLicense = ['plan_name' => 'Free', 'features' => app_feature_defaults()];
|
||||
$tenantSettings = app_tenant_settings_defaults();
|
||||
|
||||
if (!app_can_manage_tenant($auth)) {
|
||||
app_flash('Dieser Bereich ist nur für Tenant-Admins oder Global-Admins verfügbar.', 'warning');
|
||||
@@ -50,6 +51,7 @@ $controller = new \App\Modules\Tenants\Controllers\TenantConsoleController($tena
|
||||
try {
|
||||
$pdo = app_pdo();
|
||||
$tenantLicense = app_tenant_license($pdo, (string) ($auth['tenant_id'] ?? ''));
|
||||
$tenantSettings = app_tenant_settings($pdo, (string) ($auth['tenant_id'] ?? ''));
|
||||
} catch (\Throwable $ignored) {
|
||||
}
|
||||
|
||||
@@ -65,7 +67,6 @@ $tenantSnapshots = $overview['tenant_snapshots'] ?? [];
|
||||
$permissionGroups = $overview['permission_groups'] ?? [];
|
||||
$notes = $overview['notes'] ?? [];
|
||||
$tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$tenantNavItems = app_tenant_navigation_items($auth);
|
||||
|
||||
$template = dirname(__DIR__, 3) . '/resources/views/tenants/roles.blade.php';
|
||||
if (!is_file($template)) {
|
||||
|
||||
@@ -8,22 +8,25 @@
|
||||
@php
|
||||
$layoutAuth = $auth ?? (function_exists('app_auth_user') ? app_auth_user() : null);
|
||||
$layoutLicense = $tenantLicense ?? ['features' => []];
|
||||
$layoutThemeSettings = $tenantSettings ?? (function_exists('app_tenant_settings_defaults') ? app_tenant_settings_defaults() : []);
|
||||
if (!isset($tenantSettings) && is_array($layoutAuth) && function_exists('app_pdo') && function_exists('app_tenant_settings')) {
|
||||
try {
|
||||
$layoutThemeSettings = app_tenant_settings(app_pdo(), (string) ($layoutAuth['tenant_id'] ?? ''));
|
||||
} catch (Throwable $ignored) {
|
||||
}
|
||||
}
|
||||
$layoutPath = parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH);
|
||||
$layoutPath = is_string($layoutPath) ? rtrim($layoutPath, '/') : '/';
|
||||
$layoutNavItems = function_exists('app_tenant_navigation_items')
|
||||
? app_tenant_navigation_items($layoutAuth, $layoutLicense)
|
||||
: [];
|
||||
$layoutThemeCss = function_exists('app_tenant_theme_root_css')
|
||||
? app_tenant_theme_root_css($layoutThemeSettings)
|
||||
: '';
|
||||
@endphp
|
||||
<style>
|
||||
:root {
|
||||
--bg: #efe6d6;
|
||||
--bg-elevated: rgba(255, 251, 244, 0.96);
|
||||
--bg-soft: rgba(247, 239, 227, 0.96);
|
||||
--text: #2c2017;
|
||||
--muted: #6d5d4f;
|
||||
--brand: #2d6a4f;
|
||||
--brand-strong: #234f3c;
|
||||
--accent: #9f5a1d;
|
||||
{!! $layoutThemeCss !!}
|
||||
--line: rgba(44, 32, 23, 0.14);
|
||||
--shadow: 0 18px 42px rgba(68, 48, 34, 0.08);
|
||||
--radius-xl: 24px;
|
||||
@@ -127,12 +130,12 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
.app-nav a.is-primary {
|
||||
background: linear-gradient(135deg, var(--brand) 0%, #356f56 100%);
|
||||
background: linear-gradient(135deg, var(--brand) 0%, var(--brand-strong) 100%);
|
||||
color: #fff;
|
||||
}
|
||||
.app-nav a:hover {
|
||||
text-decoration: none;
|
||||
border-color: rgba(45, 106, 79, 0.2);
|
||||
border-color: rgba(var(--brand-rgb), 0.2);
|
||||
background: #fffdf8;
|
||||
}
|
||||
.app-main { padding: 34px 0 56px; }
|
||||
@@ -189,17 +192,17 @@
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 0.8rem 1.15rem;
|
||||
background: linear-gradient(135deg, var(--brand) 0%, #115e59 100%);
|
||||
background: linear-gradient(135deg, var(--brand) 0%, var(--brand-strong) 100%);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 12px 24px rgba(15, 118, 110, 0.18);
|
||||
box-shadow: 0 12px 24px rgba(var(--brand-rgb), 0.18);
|
||||
}
|
||||
.button--ghost {
|
||||
background: transparent;
|
||||
color: var(--brand-strong);
|
||||
border: 1px solid rgba(15, 118, 110, 0.18);
|
||||
border: 1px solid rgba(var(--brand-rgb), 0.18);
|
||||
box-shadow: none;
|
||||
}
|
||||
.grid { display: grid; gap: 18px; }
|
||||
|
||||
@@ -4,18 +4,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<?php $themeCss = app_tenant_theme_root_css($tenantSettings ?? app_tenant_settings_defaults()); ?>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kaffeeliste - Support</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4efe4;
|
||||
--card:#fffdf8;
|
||||
--ink:#25180f;
|
||||
--muted:#63584a;
|
||||
--brand:#005e3f;
|
||||
--brand-2:#00452f;
|
||||
--accent:#c18a00;
|
||||
<?= $themeCss ?>
|
||||
--line:rgba(37,24,15,.14);
|
||||
--shadow:0 16px 36px rgba(37,24,15,.08);
|
||||
--radius:18px;
|
||||
@@ -36,10 +31,10 @@
|
||||
.brand span,p,.muted{color:var(--muted)}
|
||||
.links,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.links a,.button,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;text-decoration:none;font-weight:700;border:1px solid transparent;cursor:pointer}
|
||||
.links a{background:#fff;color:var(--brand);border-color:rgba(0,94,63,.12)}
|
||||
.links a{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.12)}
|
||||
.links a.active{background:var(--brand);color:#fff}
|
||||
.button,button{background:var(--brand);color:#fff}
|
||||
.button.secondary{background:#fff;color:var(--brand);border-color:rgba(0,94,63,.18)}
|
||||
.button.secondary{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.hero{padding:24px;margin-bottom:18px;display:grid;gap:18px;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}
|
||||
.hero__kicker{text-transform:uppercase;letter-spacing:.16em;color:var(--accent);font-size:.8rem;font-weight:800;margin:0}
|
||||
.hero__title{margin:0;font-size:clamp(1.9rem,4vw,3rem);line-height:1.05}
|
||||
@@ -54,7 +49,7 @@
|
||||
.metric{padding:18px;border-radius:16px;border:1px solid var(--line);background:#fff;display:grid;gap:8px}
|
||||
.metric strong{font-size:1.8rem}
|
||||
.badge{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;font-size:.86rem;font-weight:700}
|
||||
.badge--neutral{background:rgba(12,107,102,.1);color:var(--brand)}
|
||||
.badge--neutral{background:rgba(var(--brand-rgb),.1);color:var(--brand)}
|
||||
.badge--success{background:rgba(17,98,61,.12);color:#11623d}
|
||||
.badge--warning{background:rgba(163,75,18,.12);color:#98510c}
|
||||
.badge--danger{background:rgba(154,31,31,.12);color:#9a1f1f}
|
||||
@@ -75,7 +70,7 @@
|
||||
.timeline__item{padding:14px 16px;border-radius:16px;background:#fff;border:1px solid rgba(31,41,51,.08)}
|
||||
.timeline__meta{margin:0;color:var(--muted);font-size:.94rem;line-height:1.6}
|
||||
.timeline__title{margin:0 0 6px;font-weight:800}
|
||||
.pill{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;border:1px solid rgba(12,107,102,.15);background:rgba(12,107,102,.07);color:var(--brand);font-size:.84rem;font-weight:700}
|
||||
.pill{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;border:1px solid rgba(var(--brand-rgb),.15);background:rgba(var(--brand-rgb),.07);color:var(--brand);font-size:.84rem;font-weight:700}
|
||||
.status-grid{display:grid;gap:8px}
|
||||
.status{display:inline-flex;align-items:center;gap:8px}
|
||||
.status::before{content:"";width:8px;height:8px;border-radius:999px;background:var(--brand)}
|
||||
|
||||
@@ -4,18 +4,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<?php $themeCss = app_tenant_theme_root_css($tenantSettings ?? app_tenant_settings_defaults()); ?>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kaffeeliste - Rollen</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4efe4;
|
||||
--card:#fffdf8;
|
||||
--ink:#25180f;
|
||||
--muted:#63584a;
|
||||
--brand:#005e3f;
|
||||
--brand-2:#00452f;
|
||||
--accent:#c18a00;
|
||||
<?= $themeCss ?>
|
||||
--line:rgba(37,24,15,.14);
|
||||
--shadow:0 16px 36px rgba(37,24,15,.08);
|
||||
--radius:18px;
|
||||
@@ -36,8 +31,10 @@
|
||||
.brand{display:grid;gap:4px}
|
||||
.brand strong{font-size:1.18rem}
|
||||
.brand span,.muted{color:var(--muted)}
|
||||
.toolbar,.actions,.context,.meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.badge,.pill{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;background:#fff;color:var(--brand);font-weight:700;font-size:.86rem;border:1px solid rgba(0,94,63,.12)}
|
||||
.links,.actions,.context,.meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.links a,.badge,.pill{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;background:#fff;color:var(--brand);font-weight:700;border:1px solid rgba(var(--brand-rgb),.12)}
|
||||
.links a.active{background:var(--brand);color:#fff}
|
||||
.badge,.pill{padding:7px 12px;font-size:.86rem}
|
||||
.badge--solid{background:var(--brand);color:#fff}
|
||||
.hero{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(260px,.6fr);gap:20px;padding:24px;margin-bottom:18px;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}
|
||||
.hero__kicker{margin:0 0 10px;color:var(--accent);text-transform:uppercase;letter-spacing:.15em;font-size:.8rem;font-weight:800}
|
||||
@@ -45,7 +42,7 @@
|
||||
.hero__lead{margin:0;color:var(--muted);max-width:70ch;line-height:1.7}
|
||||
.hero__actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:16px}
|
||||
.button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;border:1px solid transparent;background:var(--brand);color:#fff;font-weight:700}
|
||||
.button--ghost{background:#fff;color:var(--brand);border:1px solid rgba(0,94,63,.18)}
|
||||
.button--ghost{background:#fff;color:var(--brand);border:1px solid rgba(var(--brand-rgb),.18)}
|
||||
.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))}
|
||||
@@ -58,15 +55,15 @@
|
||||
table{width:100%;border-collapse:collapse}
|
||||
th,td{padding:12px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
|
||||
th{font-size:.82rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
|
||||
.status{display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(0,94,63,.1);color:var(--brand);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)}
|
||||
.status{display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(var(--brand-rgb),.1);color:var(--brand);font-weight:700;font-size:.84rem}
|
||||
.status--warning{background:rgba(var(--accent-rgb),.14);color:#8c6500}
|
||||
.status--success{background:rgba(var(--brand-rgb),.12);color:var(--brand)}
|
||||
.list-reset{margin:0;padding-left:18px}
|
||||
.list-reset li+li{margin-top:10px}
|
||||
.note{padding:16px 18px;background:rgba(0,94,63,.06)}
|
||||
.note{padding:16px 18px;background:rgba(var(--brand-rgb),.06)}
|
||||
.stack{display:grid;gap:12px}
|
||||
.chip-list{display:flex;flex-wrap:wrap;gap:8px}
|
||||
.chip{display:inline-flex;align-items:center;padding:5px 10px;border-radius:999px;background:rgba(12,107,102,.08);color:var(--brand);font-size:.84rem;font-weight:700}
|
||||
.chip{display:inline-flex;align-items:center;padding:5px 10px;border-radius:999px;background:rgba(var(--brand-rgb),.08);color:var(--brand);font-size:.84rem;font-weight:700}
|
||||
@media(max-width:960px){.hero,.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}.bar{align-items:flex-start;flex-direction:column}}
|
||||
</style>
|
||||
</head>
|
||||
@@ -77,12 +74,12 @@
|
||||
<strong>Kaffeeliste</strong>
|
||||
<span>Rollen und Rechte im Tenant.</span>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<nav class="links">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a class="badge <?= (($item['key'] ?? '') === 'roles') ? 'badge--solid' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>"><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<a class="<?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>"><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
|
||||
Reference in New Issue
Block a user