Weiterentwicklung Anwendung
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
ALTER TABLE survey_questions
|
||||
ADD COLUMN options_json TEXT NULL AFTER sort_order;
|
||||
SQL;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
ALTER TABLE members
|
||||
ADD COLUMN payment_reference VARCHAR(255) NULL AFTER email;
|
||||
SQL;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
ALTER TABLE survey_questions
|
||||
ADD COLUMN options_json LONGTEXT NULL AFTER question_type;
|
||||
SQL;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
CREATE TABLE survey_question_options (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
question_id CHAR(36) NOT NULL,
|
||||
option_label VARCHAR(255) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
FOREIGN KEY (question_id) REFERENCES survey_questions(id)
|
||||
);
|
||||
SQL;
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function app_reporting_rows(PDO $pdo, string $tenantId): array
|
||||
{
|
||||
return app_query_all(
|
||||
$pdo,
|
||||
<<<'SQL'
|
||||
SELECT
|
||||
m.id,
|
||||
m.display_name,
|
||||
m.email,
|
||||
COALESCE(SUM(le.amount), 0) AS balance,
|
||||
COALESCE((
|
||||
SELECT SUM(ABS(ce.strokes))
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.tenant_id = :tenant_id
|
||||
AND ce.member_id = m.id
|
||||
), 0) AS total_strokes,
|
||||
COALESCE((
|
||||
SELECT SUM(pe.amount)
|
||||
FROM payment_entries pe
|
||||
WHERE pe.tenant_id = :tenant_id
|
||||
AND pe.member_id = m.id
|
||||
), 0) AS total_payments,
|
||||
COALESCE((
|
||||
SELECT SUM(ABS(ce.total_cost))
|
||||
FROM coffee_entries ce
|
||||
WHERE ce.tenant_id = :tenant_id
|
||||
AND ce.member_id = m.id
|
||||
), 0) AS total_coffee_cost
|
||||
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, m.email
|
||||
ORDER BY m.display_name ASC
|
||||
SQL,
|
||||
['tenant_id' => $tenantId]
|
||||
);
|
||||
}
|
||||
|
||||
function app_bulk_stroke_entries(array $input): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ($input as $memberId => $value) {
|
||||
$strokes = max(0, (int) $value);
|
||||
if ($strokes > 0) {
|
||||
$entries[(string) $memberId] = $strokes;
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
function app_bulk_payment_entries(array $input): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ($input as $memberId => $value) {
|
||||
$amount = is_numeric($value) ? (float) $value : 0.0;
|
||||
if ($amount > 0) {
|
||||
$entries[(string) $memberId] = $amount;
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
if (!function_exists('app_create_coffee_booking')) {
|
||||
function app_create_coffee_booking(PDO $pdo, string $tenantId, string $memberId, int $strokes, float $unitPrice, string $bookedAt, string $source = 'manual'): void
|
||||
{
|
||||
$entryId = app_uuid();
|
||||
$ledgerId = app_uuid();
|
||||
$totalCost = round($strokes * $unitPrice, 2);
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
app_execute(
|
||||
$pdo,
|
||||
'INSERT INTO coffee_entries (id, tenant_id, member_id, strokes, unit_price, total_cost, booking_source, booked_at, created_at, updated_at) VALUES (:id, :tenant_id, :member_id, :strokes, :unit_price, :total_cost, :booking_source, :booked_at, :created_at, :updated_at)',
|
||||
[
|
||||
'id' => $entryId,
|
||||
'tenant_id' => $tenantId,
|
||||
'member_id' => $memberId,
|
||||
'strokes' => $strokes,
|
||||
'unit_price' => number_format($unitPrice, 2, '.', ''),
|
||||
'total_cost' => number_format($totalCost, 2, '.', ''),
|
||||
'booking_source' => $source,
|
||||
'booked_at' => $bookedAt,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
|
||||
app_execute(
|
||||
$pdo,
|
||||
'INSERT INTO ledger_entries (id, tenant_id, member_id, entry_type, amount, reference_type, reference_id, booked_at, created_at, updated_at) VALUES (:id, :tenant_id, :member_id, :entry_type, :amount, :reference_type, :reference_id, :booked_at, :created_at, :updated_at)',
|
||||
[
|
||||
'id' => $ledgerId,
|
||||
'tenant_id' => $tenantId,
|
||||
'member_id' => $memberId,
|
||||
'entry_type' => 'coffee_charge',
|
||||
'amount' => number_format($totalCost * -1, 2, '.', ''),
|
||||
'reference_type' => 'coffee_entry',
|
||||
'reference_id' => $entryId,
|
||||
'booked_at' => $bookedAt,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('app_create_payment_booking')) {
|
||||
function app_create_payment_booking(PDO $pdo, string $tenantId, string $memberId, float $amount, string $bookedAt, string $method = 'manual'): void
|
||||
{
|
||||
$entryId = app_uuid();
|
||||
$ledgerId = app_uuid();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
app_execute(
|
||||
$pdo,
|
||||
'INSERT INTO payment_entries (id, tenant_id, member_id, amount, payment_method, booked_at, created_at, updated_at) VALUES (:id, :tenant_id, :member_id, :amount, :payment_method, :booked_at, :created_at, :updated_at)',
|
||||
[
|
||||
'id' => $entryId,
|
||||
'tenant_id' => $tenantId,
|
||||
'member_id' => $memberId,
|
||||
'amount' => number_format($amount, 2, '.', ''),
|
||||
'payment_method' => $method,
|
||||
'booked_at' => $bookedAt,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
|
||||
app_execute(
|
||||
$pdo,
|
||||
'INSERT INTO ledger_entries (id, tenant_id, member_id, entry_type, amount, reference_type, reference_id, booked_at, created_at, updated_at) VALUES (:id, :tenant_id, :member_id, :entry_type, :amount, :reference_type, :reference_id, :booked_at, :created_at, :updated_at)',
|
||||
[
|
||||
'id' => $ledgerId,
|
||||
'tenant_id' => $tenantId,
|
||||
'member_id' => $memberId,
|
||||
'entry_type' => 'payment_credit',
|
||||
'amount' => number_format($amount, 2, '.', ''),
|
||||
'reference_type' => 'payment_entry',
|
||||
'reference_id' => $entryId,
|
||||
'booked_at' => $bookedAt,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function app_handle_bulk_finance_action(PDO $pdo, array $auth): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST' || !app_can_manage_finance($auth)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
$tenantId = (string) ($auth['tenant_id'] ?? '');
|
||||
$bookedAt = app_normalize_datetime_input((string) ($_POST['booked_at'] ?? ''));
|
||||
|
||||
if (!in_array($action, ['bulk-record-coffee', 'bulk-record-payments'], true) || $tenantId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($action === 'bulk-record-coffee') {
|
||||
$unitPrice = (float) ($_POST['unit_price'] ?? 0);
|
||||
$entries = app_bulk_stroke_entries((array) ($_POST['bulk_strokes'] ?? []));
|
||||
|
||||
if ($unitPrice <= 0 || $entries === []) {
|
||||
throw new RuntimeException('Bitte gib mindestens einen Sammel-Stricheintrag und einen gültigen Preis an.');
|
||||
}
|
||||
|
||||
foreach ($entries as $memberId => $strokes) {
|
||||
app_create_coffee_booking($pdo, $tenantId, $memberId, $strokes, $unitPrice, $bookedAt, 'bulk');
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
app_flash(count($entries) . ' Sammel-Stricheinträge wurden verbucht.', 'success');
|
||||
app_redirect('/ledger/');
|
||||
}
|
||||
|
||||
$entries = app_bulk_payment_entries((array) ($_POST['bulk_amounts'] ?? []));
|
||||
$method = trim((string) ($_POST['payment_method'] ?? 'manual'));
|
||||
$method = $method !== '' ? $method : 'manual';
|
||||
|
||||
if ($entries === []) {
|
||||
throw new RuntimeException('Bitte gib mindestens eine Sammel-Einzahlung an.');
|
||||
}
|
||||
|
||||
foreach ($entries as $memberId => $amount) {
|
||||
app_create_payment_booking($pdo, $tenantId, $memberId, $amount, $bookedAt, $method);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
app_flash(count($entries) . ' Sammel-Einzahlungen wurden verbucht.', 'success');
|
||||
app_redirect('/payments/');
|
||||
} catch (Throwable $exception) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
app_flash($exception->getMessage(), 'error');
|
||||
app_redirect(app_request_path());
|
||||
}
|
||||
}
|
||||
|
||||
function app_handle_csv_import(PDO $pdo, array $auth): array
|
||||
{
|
||||
$result = ['rows' => [], 'summary' => ['imported' => 0, 'duplicates' => 0, 'unmatched' => 0]];
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST' || (string) ($_POST['action'] ?? '') !== 'import-paypal-csv') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!app_can_manage_finance($auth)) {
|
||||
app_flash('Für den CSV-Import brauchst du Finanz- oder Tenant-Rechte.', 'warning');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
|
||||
if (!isset($_FILES['csv_file']) || (int) ($_FILES['csv_file']['error'] ?? 1) !== 0) {
|
||||
app_flash('Bitte lade eine CSV-Datei hoch.', 'error');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
|
||||
$tenantId = (string) ($auth['tenant_id'] ?? '');
|
||||
$handle = fopen((string) $_FILES['csv_file']['tmp_name'], 'rb');
|
||||
if ($handle === false) {
|
||||
app_flash('Die CSV-Datei konnte nicht gelesen werden.', 'error');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
|
||||
fgetcsv($handle, 0, ',');
|
||||
$members = app_members_for_tenant($pdo, $tenantId);
|
||||
$memberMap = [];
|
||||
foreach ($members as $member) {
|
||||
$display = strtolower(trim((string) ($member['display_name'] ?? '')));
|
||||
$email = strtolower(trim((string) ($member['email'] ?? '')));
|
||||
$reference = strtolower(trim((string) ($member['payment_reference'] ?? '')));
|
||||
|
||||
foreach ([$display, $email, $reference] as $key) {
|
||||
if ($key !== '') {
|
||||
$memberMap[$key] = $member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||
$bookedAt = app_normalize_datetime_input((string) ($row[0] ?? ''));
|
||||
$name = strtolower(trim((string) ($row[3] ?? '')));
|
||||
$amount = (float) str_replace(',', '.', (string) ($row[7] ?? '0'));
|
||||
|
||||
if ($name === '' || $amount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$member = $memberMap[$name] ?? null;
|
||||
if (!is_array($member)) {
|
||||
$result['rows'][] = ['date' => $bookedAt, 'name' => $name, 'amount' => $amount, 'status' => 'unmatched'];
|
||||
$result['summary']['unmatched']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$duplicate = app_query_one(
|
||||
$pdo,
|
||||
'SELECT id FROM payment_entries WHERE tenant_id = :tenant_id AND member_id = :member_id AND amount = :amount AND DATE(booked_at) = DATE(:booked_at) LIMIT 1',
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'member_id' => (string) ($member['id'] ?? ''),
|
||||
'amount' => number_format($amount, 2, '.', ''),
|
||||
'booked_at' => $bookedAt,
|
||||
]
|
||||
);
|
||||
|
||||
if ($duplicate !== null) {
|
||||
$result['rows'][] = ['date' => $bookedAt, 'name' => (string) ($member['display_name'] ?? $name), 'amount' => $amount, 'status' => 'duplicate'];
|
||||
$result['summary']['duplicates']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
app_create_payment_booking($pdo, $tenantId, (string) ($member['id'] ?? ''), $amount, $bookedAt, 'paypal_csv');
|
||||
$result['rows'][] = ['date' => $bookedAt, 'name' => (string) ($member['display_name'] ?? $name), 'amount' => $amount, 'status' => 'imported'];
|
||||
$result['summary']['imported']++;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
$pdo->commit();
|
||||
app_flash('Der CSV-Import wurde verarbeitet.', 'success');
|
||||
} catch (Throwable $exception) {
|
||||
fclose($handle);
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
app_flash($exception->getMessage(), 'error');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
+1434
-165
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$_GET['page'] = 'imports';
|
||||
|
||||
require dirname(__DIR__) . '/index.php';
|
||||
+212
-37
@@ -52,6 +52,8 @@ if ($requestedPage === null) {
|
||||
'/ledger' => 'ledger',
|
||||
'/payments' => 'payments',
|
||||
'/content' => 'content',
|
||||
'/imports' => 'imports',
|
||||
'/reports' => 'reports',
|
||||
'/support' => 'support',
|
||||
'/surveys' => 'surveys',
|
||||
'/settings' => 'settings',
|
||||
@@ -63,7 +65,7 @@ if ($requestedPage === null) {
|
||||
|
||||
$page = (string) $requestedPage;
|
||||
$requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
$tenantPages = ['dashboard', 'members', 'ledger', 'payments', 'content', 'support', 'surveys', 'settings', 'exports'];
|
||||
$tenantPages = ['dashboard', 'members', 'ledger', 'payments', 'content', 'imports', 'reports', 'support', 'surveys', 'settings', 'exports'];
|
||||
|
||||
if ($page === 'logout' && $requestMethod === 'POST') {
|
||||
app_logout();
|
||||
@@ -82,8 +84,20 @@ $ledger = [];
|
||||
$payments = [];
|
||||
$content = ['announcements' => [], 'faq' => []];
|
||||
$memberSummary = null;
|
||||
$memberReport = null;
|
||||
$ledgerScope = 'all';
|
||||
$paymentScope = 'all';
|
||||
$ledgerMembers = [];
|
||||
$paymentMembers = [];
|
||||
$reportRows = [];
|
||||
$importResult = ['rows' => [], 'summary' => ['imported' => 0, 'duplicates' => 0, 'unmatched' => 0]];
|
||||
$surveyBoard = ['all' => [], 'published' => []];
|
||||
$activeSurvey = null;
|
||||
$surveyResults = [];
|
||||
$loginFlow = ['state' => app_login_state(), 'message' => null, 'error' => null];
|
||||
$editingMember = null;
|
||||
$editingAnnouncement = null;
|
||||
$editingFaq = null;
|
||||
$memberForm = app_member_form_defaults();
|
||||
$tenantLicense = ['plan_key' => 'free', 'plan_name' => 'Free', 'member_limit' => 10, 'features' => app_feature_defaults()];
|
||||
$tenantSettings = app_tenant_settings_defaults();
|
||||
@@ -111,10 +125,6 @@ if ($page === 'support') {
|
||||
app_redirect('/support/');
|
||||
}
|
||||
|
||||
if ($page === 'surveys') {
|
||||
app_redirect('/surveys/');
|
||||
}
|
||||
|
||||
if ($page === 'login' && $pdo instanceof PDO) {
|
||||
try {
|
||||
$loginFlow = app_handle_login($pdo);
|
||||
@@ -137,12 +147,26 @@ 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);
|
||||
app_handle_profile_action($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'members') {
|
||||
app_handle_member_action($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'content') {
|
||||
app_handle_content_action($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'imports') {
|
||||
$importResult = app_handle_csv_import($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'surveys') {
|
||||
app_handle_survey_action($pdo, $auth);
|
||||
}
|
||||
|
||||
if ($page === 'settings') {
|
||||
app_handle_settings_action($pdo, $auth);
|
||||
}
|
||||
@@ -177,8 +201,40 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
$payments = app_payments_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
}
|
||||
|
||||
if ($page === 'ledger') {
|
||||
$ledgerScope = in_array((string) ($_GET['scope'] ?? 'all'), ['all', 'front', 'back'], true)
|
||||
? (string) ($_GET['scope'] ?? 'all')
|
||||
: 'all';
|
||||
$ledgerMembers = app_members_for_scope($pdo, (string) $auth['tenant_id'], $ledgerScope);
|
||||
}
|
||||
|
||||
if ($page === 'payments') {
|
||||
$paymentScope = in_array((string) ($_GET['scope'] ?? 'all'), ['all', 'front', 'back'], true)
|
||||
? (string) ($_GET['scope'] ?? 'all')
|
||||
: 'all';
|
||||
$paymentMembers = app_members_for_scope($pdo, (string) $auth['tenant_id'], $paymentScope);
|
||||
}
|
||||
|
||||
if ($page === 'content') {
|
||||
$content = app_content_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
|
||||
if (isset($_GET['edit_announcement']) && $_GET['edit_announcement'] !== '') {
|
||||
foreach (($content['announcements'] ?? []) as $announcement) {
|
||||
if ((string) ($announcement['id'] ?? '') === (string) $_GET['edit_announcement']) {
|
||||
$editingAnnouncement = $announcement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['edit_faq']) && $_GET['edit_faq'] !== '') {
|
||||
foreach (($content['faq'] ?? []) as $faq) {
|
||||
if ((string) ($faq['id'] ?? '') === (string) $_GET['edit_faq']) {
|
||||
$editingFaq = $faq;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($page === 'exports') {
|
||||
@@ -199,6 +255,27 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
$editingMember = app_tenant_user_by_id($pdo, (string) $_GET['edit'], (string) $auth['tenant_id']);
|
||||
$memberForm = app_member_form_defaults($editingMember);
|
||||
}
|
||||
|
||||
if ($page === 'members' && isset($_GET['member']) && $_GET['member'] !== '') {
|
||||
$memberReport = app_member_report($pdo, (string) $auth['tenant_id'], (string) $_GET['member']);
|
||||
}
|
||||
|
||||
if ($page === 'reports') {
|
||||
$reportRows = app_reporting_rows($pdo, (string) $auth['tenant_id']);
|
||||
if ($memberReport === null && isset($_GET['member']) && $_GET['member'] !== '') {
|
||||
$memberReport = app_member_report($pdo, (string) $auth['tenant_id'], (string) $_GET['member']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($page === 'surveys') {
|
||||
$surveyBoard = app_surveys_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
if (isset($_GET['survey']) && $_GET['survey'] !== '') {
|
||||
$activeSurvey = app_survey_by_id($pdo, (string) $auth['tenant_id'], (string) $_GET['survey']);
|
||||
if (is_array($activeSurvey) && app_can_manage_surveys($auth)) {
|
||||
$surveyResults = app_survey_results($pdo, (string) $activeSurvey['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$dbError = $exception->getMessage();
|
||||
}
|
||||
@@ -211,6 +288,8 @@ $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),
|
||||
'imports' => static fn(array $user): bool => app_can_manage_finance($user),
|
||||
'reports' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
'settings' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
'exports' => static fn(array $user): bool => app_can_manage_tenant($user),
|
||||
];
|
||||
@@ -266,6 +345,7 @@ $landingProof = [
|
||||
['title' => 'Im Browser und mobil', 'copy' => 'Die Oberfläche funktioniert ohne Installation auf Desktop und Handy.'],
|
||||
['title' => 'Für den Alltag gebaut', 'copy' => 'Kaffeeliste, Support, Buchungen und Verwaltung greifen sinnvoll ineinander.'],
|
||||
];
|
||||
$marketing = app_marketing_messages();
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="de">
|
||||
@@ -816,18 +896,52 @@ $landingProof = [
|
||||
</article>
|
||||
<?php else: ?>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Dein Bereich</div>
|
||||
<h2>Dein Bereich</h2>
|
||||
<ul class="list">
|
||||
<li>Hier siehst du deinen Kontostand, letzte Buchungen und aktuelle Hinweise.</li>
|
||||
<li>Weitere Bereiche findest du direkt im Menü.</li>
|
||||
<li>Wenn dir etwas fehlt, wende dich an den Betreiber deines Bereichs.</li>
|
||||
</ul>
|
||||
<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">Nächster Schritt</div>
|
||||
<h2>Direkt weiter</h2>
|
||||
<p>Nutze das Menü für den nächsten Bereich.</p>
|
||||
<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 endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -836,8 +950,8 @@ $landingProof = [
|
||||
<h2>Letzte Buchungen</h2>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Zeit</th><th>Mitglied</th><th>Typ</th><th>Referenz</th><th>Betrag</th></tr></thead>
|
||||
<tbody><?php foreach ($ledger as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><td><?= h((string) ($entry['entry_type'] ?? '')) ?></td><td><?= h((string) ($entry['reference_type'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td></tr><?php endforeach; ?></tbody>
|
||||
<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>
|
||||
</section>
|
||||
@@ -845,7 +959,7 @@ $landingProof = [
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Mitgliederverwaltung</div>
|
||||
<h1>Mitglieder</h1>
|
||||
<p>Hier legst du neue Personen an, gibst Zugänge frei und weist bei Bedarf die Rolle als Mandanten-Admin zu.</p>
|
||||
<p>Hier legst du neue Personen an, gibst Zugänge frei und weist Rollen für Verwaltung, Finanzen, Support und Umfragen zu.</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 zentralen Verwaltung</a>
|
||||
@@ -861,12 +975,13 @@ $landingProof = [
|
||||
<input type="hidden" name="tenant_user_id" value="<?= h((string) $memberForm['tenant_user_id']) ?>">
|
||||
<label>Name<input name="display_name" value="<?= h((string) $memberForm['display_name']) ?>" required></label>
|
||||
<label>E-Mail-Adresse<input type="email" name="email" value="<?= h((string) $memberForm['email']) ?>" required></label>
|
||||
<label>Zahlungsreferenz<input name="payment_reference" value="<?= h((string) ($memberForm['payment_reference'] ?? '')) ?>" placeholder="PayPal-Name oder Referenz"></label>
|
||||
<label>Status<select name="status"><option value="active"<?= $memberForm['status'] === 'active' ? ' selected' : '' ?>>Aktiv</option><option value="inactive"<?= $memberForm['status'] === 'inactive' ? ' selected' : '' ?>>Inaktiv</option></select></label>
|
||||
<label>Passwort<input type="password" name="password" placeholder="<?= $editingMember !== null ? 'Nur für Passwortwechsel ausfüllen' : 'Für die erste Anmeldung erforderlich' ?>"></label>
|
||||
<label class="checkbox" style="grid-column:1 / -1;display:flex;flex-direction:row;align-items:center;font-weight:600;">
|
||||
<input type="checkbox" name="is_tenant_admin"<?= !empty($memberForm['is_tenant_admin']) ? ' checked' : '' ?> style="width:auto">
|
||||
<span>Diese Person als Mandanten-Admin freischalten</span>
|
||||
</label>
|
||||
<label class="checkbox" style="display:flex;flex-direction:row;align-items:center;font-weight:600;"><input type="checkbox" name="tenant_admin"<?= !empty($memberForm['is_tenant_admin']) ? ' checked' : '' ?> style="width:auto"><span>Tenant-Admin</span></label>
|
||||
<label class="checkbox" style="display:flex;flex-direction:row;align-items:center;font-weight:600;"><input type="checkbox" name="finance_admin"<?= !empty($memberForm['is_finance_admin']) ? ' checked' : '' ?> style="width:auto"><span>Finanzen</span></label>
|
||||
<label class="checkbox" style="display:flex;flex-direction:row;align-items:center;font-weight:600;"><input type="checkbox" name="support_contact"<?= !empty($memberForm['is_support_contact']) ? ' checked' : '' ?> style="width:auto"><span>Support</span></label>
|
||||
<label class="checkbox" style="display:flex;flex-direction:row;align-items:center;font-weight:600;"><input type="checkbox" name="survey_manager"<?= !empty($memberForm['is_survey_manager']) ? ' checked' : '' ?> style="width:auto"><span>Umfragen</span></label>
|
||||
<div class="actions">
|
||||
<button type="submit">Speichern</button>
|
||||
<?php if ($editingMember !== null): ?>
|
||||
@@ -876,33 +991,55 @@ $landingProof = [
|
||||
</form>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h2>Was der Global-Admin hier sehen kann</h2>
|
||||
<ul class="list">
|
||||
<li>Alle Personen des Mandanten inklusive Rollen und Aktivstatus.</li>
|
||||
<li>Ob ein Zugang nur Mitglied ist oder als Mandanten-Admin arbeiten darf.</li>
|
||||
<li>Den Mandanten aus Sicht der operativen Verwaltung, ohne den Global-Admin als Mitglied anlegen zu müssen.</li>
|
||||
</ul>
|
||||
<?php if (is_array($memberReport)): ?>
|
||||
<h2>Mitgliedsdetail</h2>
|
||||
<div class="stack">
|
||||
<div class="metric"><h3><?= h((string) ($memberReport['display_name'] ?? '')) ?></h3><p><?= h((string) ($memberReport['email'] ?? '')) ?></p></div>
|
||||
<div class="metric"><h3>Saldo</h3><p><?= money($memberReport['summary']['balance'] ?? 0) ?></p></div>
|
||||
<div class="metric"><h3>Dieses Jahr</h3><p><?= num($memberReport['summary']['year_strokes'] ?? 0) ?> Striche, <?= money($memberReport['summary']['year_payments'] ?? 0) ?> Einzahlungen</p></div>
|
||||
<div class="actions"><a class="button secondary" href="/members/">Auswahl zurücksetzen</a></div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<h2>Mitglied auswählen</h2>
|
||||
<ul class="list">
|
||||
<li>Öffne eine Person aus der Tabelle, um Jahreswerte und letzte Buchungen zu sehen.</li>
|
||||
<li>Rollen können hier direkt differenziert nach Verwaltung, Finanzen, Support und Umfragen vergeben werden.</li>
|
||||
<li>So bildet die neue Webseite die Rollenmatrix der Roadmap sichtbar im Tenant ab.</li>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Name</th><th>E-Mail</th><th>Status</th><th>Benutzer</th><th>Rollen</th><th>Aktion</th></tr></thead>
|
||||
<thead><tr><th>Name</th><th>E-Mail</th><th>Referenz</th><th>Status</th><th>Saldo</th><th>Rollen</th><th>Aktion</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($members as $member): ?>
|
||||
<tr>
|
||||
<td><strong><?= h((string) $member['display_name']) ?></strong></td>
|
||||
<td><?= h((string) $member['email']) ?></td>
|
||||
<td><?= h((string) ($member['payment_reference'] ?? '-')) ?></td>
|
||||
<td><?= ((string) ($member['status'] ?? 'active')) === 'active' ? badge('Aktiv', 'success') : badge('Inaktiv', 'warning') ?></td>
|
||||
<td><?= h((string) ($member['user_display_name'] ?? '')) ?></td>
|
||||
<td><?= str_contains((string) ($member['role_keys'] ?? ''), 'tenant_admin') ? badge('Tenant-Admin', 'success') : badge('Mitglied') ?></td>
|
||||
<td><a class="button secondary" href="/members/?edit=<?= h((string) ($member['tenant_user_id'] ?? '')) ?>">Bearbeiten</a></td>
|
||||
<td><?= money($member['current_balance'] ?? 0) ?></td>
|
||||
<td><?= h((string) (($member['roles'] ?? '') !== '' ? $member['roles'] : 'Mitglied')) ?></td>
|
||||
<td><div class="actions"><a class="button secondary" href="/members/?edit=<?= h((string) ($member['tenant_user_id'] ?? '')) ?>">Bearbeiten</a><a class="button secondary" href="/members/?member=<?= h((string) ($member['id'] ?? '')) ?>">Details</a></div></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<?php if (is_array($memberReport) && ($memberReport['recent_entries'] ?? []) !== []): ?>
|
||||
<section class="card" style="margin-top:18px">
|
||||
<h2>Letzte Buchungen der ausgewählten Person</h2>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead><tr><th>Zeit</th><th>Typ</th><th>Referenz</th><th>Betrag</th></tr></thead>
|
||||
<tbody><?php foreach (($memberReport['recent_entries'] ?? []) as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><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>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($page === 'ledger'): ?>
|
||||
<section class="hero"><div class="eyebrow">Buchungen</div><h1>Striche und Buchungen</h1><p>Kaffeeeinträge und alle zugehörigen Buchungen bleiben tenantweise nachvollziehbar.</p></section>
|
||||
<section class="grid grid-2">
|
||||
@@ -919,7 +1056,7 @@ $landingProof = [
|
||||
</article>
|
||||
<article class="card"><h2>Was dieser Bereich abdeckt</h2><ul class="list"><li>Einzel- und Sammelbuchungen laufen in einem gemeinsamen Ablauf zusammen.</li><li>Jeder Verbrauch erzeugt automatisch den passenden Ledger-Eintrag.</li><li>Die letzten Buchungen bleiben je Tenant nachvollziehbar.</li></ul></article>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px"><div class="table"><table><thead><tr><th>Zeit</th><th>Mitglied</th><th>Typ</th><th>Referenz</th><th>Betrag</th></tr></thead><tbody><?php foreach ($ledger as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><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></section>
|
||||
<section class="card" style="margin-top:18px"><div class="table"><table><thead><tr><th>Zeit</th><th>Mitglied</th><th>Typ</th><th>Referenz</th><th>Betrag</th><th>Aktion</th></tr></thead><tbody><?php foreach ($ledger as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><td><?= h((string) ($entry['entry_type'] ?? '')) ?></td><td><?= h((string) ($entry['reference_type'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td><td><?php if ((string) ($entry['reference_type'] ?? '') === 'coffee_entry' && (string) ($entry['reference_id'] ?? '') !== ''): ?><form method="post" action="/ledger/"><input type="hidden" name="action" value="delete-coffee"><input type="hidden" name="reference_id" value="<?= h((string) ($entry['reference_id'] ?? '')) ?>"><button type="submit" class="button secondary">Löschen</button></form><?php else: ?><span class="muted">-</span><?php endif; ?></td></tr><?php endforeach; ?></tbody></table></div></section>
|
||||
<?php elseif ($page === 'payments'): ?>
|
||||
<section class="hero"><div class="eyebrow">Einzahlungen</div><h1>Zahlungen</h1><p>Einzahlungen werden direkt in Zahlungstabelle und Ledger geschrieben.</p></section>
|
||||
<section class="grid grid-2">
|
||||
@@ -934,14 +1071,40 @@ $landingProof = [
|
||||
<div class="actions"><button type="submit">Einzahlung speichern</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<article class="card"><h2>Wofür dieser Bereich da ist</h2><ul class="list"><li>Manuelle Einzahlungen sind direkt erfasst.</li><li>PayPal oder Bank können getrennt ausgewiesen werden.</li><li>Jede Zahlung erscheint sofort im Ledger.</li></ul></article>
|
||||
<article class="card"><h2>Wofür dieser Bereich da ist</h2><ul class="list"><li>Manuelle Einzahlungen sind direkt erfasst.</li><li>PayPal oder Bank können getrennt ausgewiesen werden.</li><li>Jede Zahlung erscheint sofort im Ledger.</li></ul><p style="margin-top:14px"><?= h((string) ($tenantSettings['payment_hint'] ?? '')) ?></p></article>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px"><div class="table"><table><thead><tr><th>Zeit</th><th>Mitglied</th><th>Methode</th><th>Betrag</th></tr></thead><tbody><?php foreach ($payments as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><td><?= h((string) ($entry['payment_method'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div></section>
|
||||
<section class="card" style="margin-top:18px"><div class="table"><table><thead><tr><th>Zeit</th><th>Mitglied</th><th>Methode</th><th>Betrag</th><th>Aktion</th></tr></thead><tbody><?php foreach ($payments as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><td><?= h((string) ($entry['payment_method'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td><td><form method="post" action="/payments/"><input type="hidden" name="action" value="delete-payment"><input type="hidden" name="payment_id" value="<?= h((string) ($entry['id'] ?? '')) ?>"><button type="submit" class="button secondary">Stornieren</button></form></td></tr><?php endforeach; ?></tbody></table></div></section>
|
||||
<?php elseif ($page === 'content'): ?>
|
||||
<section class="hero"><div class="eyebrow">Hinweise und FAQ</div><h1>Hinweise und FAQ</h1><p>Hinweise und häufige Fragen werden pro Tenant gepflegt und direkt an die Mitglieder ausgespielt.</p></section>
|
||||
<section class="grid grid-2">
|
||||
<article class="card"><h2>Hinweise</h2><div class="stack"><?php if (($content['announcements'] ?? []) === []): ?><div class="metric"><p>Aktuell sind keine Hinweise vorhanden.</p></div><?php endif; ?><?php foreach (($content['announcements'] ?? []) as $entry): ?><div class="metric"><h3><?= h((string) $entry['title']) ?></h3><p><?= nl2br(h((string) $entry['message'])) ?></p><p class="muted">Sichtbar bis <?= dt((string) ($entry['visible_until'] ?? '')) ?></p></div><?php endforeach; ?></div></article>
|
||||
<article class="card"><h2>FAQ</h2><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></div><?php endforeach; ?></div></article>
|
||||
<article class="card">
|
||||
<h2>Hinweise</h2>
|
||||
<?php if ($canManageTenant): ?>
|
||||
<form method="post" action="/content/" class="grid" style="margin-bottom:18px">
|
||||
<input type="hidden" name="action" value="save-announcement">
|
||||
<input type="hidden" name="announcement_id" value="<?= h((string) ($editingAnnouncement['id'] ?? '')) ?>">
|
||||
<label>Titel<input name="title" value="<?= h((string) ($editingAnnouncement['title'] ?? '')) ?>" required></label>
|
||||
<label>Sichtbar bis<input type="datetime-local" name="visible_until" value="<?= !empty($editingAnnouncement['visible_until']) ? h(date('Y-m-d\TH:i', strtotime((string) $editingAnnouncement['visible_until']))) : '' ?>"></label>
|
||||
<label style="grid-column:1 / -1;">Hinweis<textarea name="message" required><?= h((string) ($editingAnnouncement['message'] ?? '')) ?></textarea></label>
|
||||
<div class="actions"><button type="submit">Hinweis speichern</button><?php if ($editingAnnouncement !== null): ?><a class="button secondary" href="/content/">Neu anlegen</a><?php endif; ?></div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<div class="stack"><?php if (($content['announcements'] ?? []) === []): ?><div class="metric"><p>Aktuell sind keine Hinweise vorhanden.</p></div><?php endif; ?><?php foreach (($content['announcements'] ?? []) as $entry): ?><div class="metric"><h3><?= h((string) $entry['title']) ?></h3><p><?= nl2br(h((string) $entry['message'])) ?></p><p class="muted">Sichtbar bis <?= dt((string) ($entry['visible_until'] ?? '')) ?></p><?php if ($canManageTenant): ?><div class="actions" style="margin-top:12px"><a class="button secondary" href="/content/?edit_announcement=<?= h((string) ($entry['id'] ?? '')) ?>">Bearbeiten</a><form method="post" action="/content/"><input type="hidden" name="action" value="archive-announcement"><input type="hidden" name="announcement_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>
|
||||
<article class="card">
|
||||
<h2>FAQ</h2>
|
||||
<?php if ($canManageTenant): ?>
|
||||
<form method="post" action="/content/" class="grid" style="margin-bottom:18px">
|
||||
<input type="hidden" name="action" value="save-faq">
|
||||
<input type="hidden" name="faq_id" value="<?= h((string) ($editingFaq['id'] ?? '')) ?>">
|
||||
<label>Frage<input name="question" value="<?= h((string) ($editingFaq['question'] ?? '')) ?>" required></label>
|
||||
<label>Sortierung<input type="number" name="sort_order" min="0" step="1" value="<?= h((string) ($editingFaq['sort_order'] ?? '10')) ?>"></label>
|
||||
<label style="grid-column:1 / -1;">Antwort<textarea name="answer" required><?= h((string) ($editingFaq['answer'] ?? '')) ?></textarea></label>
|
||||
<div class="actions"><button type="submit">FAQ speichern</button><?php if ($editingFaq !== null): ?><a class="button secondary" href="/content/">Neu anlegen</a><?php endif; ?></div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<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 === '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>
|
||||
@@ -957,6 +1120,7 @@ $landingProof = [
|
||||
<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>PayPal-Link<input type="url" name="paypal_me_url" value="<?= h((string) ($tenantSettings['paypal_me_url'] ?? '')) ?>" placeholder="https://paypal.me/dein-tenant"></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>
|
||||
@@ -1044,6 +1208,17 @@ $landingProof = [
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php if ($hasBasicExportsFeature): ?>
|
||||
<article class="card">
|
||||
<h2>Mitgliederauswertung exportieren</h2>
|
||||
<p>Die Detailauswertung eines einzelnen Mitglieds lässt sich direkt als CSV herunterladen.</p>
|
||||
<form method="get" action="/exports/" class="grid" style="margin-top:18px">
|
||||
<input type="hidden" name="download" value="member-report-csv">
|
||||
<label>Mitglied<select name="member"><?php foreach ($members as $member): ?><option value="<?= h((string) ($member['id'] ?? '')) ?>"><?= h((string) ($member['display_name'] ?? '')) ?></option><?php endforeach; ?></select></label>
|
||||
<div class="actions"><button type="submit">Detail-CSV herunterladen</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php if ($hasPdfExportFeature || $hasPaperStrikeEntryFeature): ?>
|
||||
<section class="card" style="margin-top:18px">
|
||||
|
||||
@@ -1215,6 +1215,9 @@ function scripts_ensure_core_roles(PDO $pdo): array
|
||||
{
|
||||
return [
|
||||
'tenant_admin' => scripts_ensure_role($pdo, 'tenant_admin', 'Tenant Admin', 'tenant'),
|
||||
'finance_admin' => scripts_ensure_role($pdo, 'finance_admin', 'Finance Admin', 'tenant'),
|
||||
'support_contact' => scripts_ensure_role($pdo, 'support_contact', 'Support Contact', 'tenant'),
|
||||
'survey_manager' => scripts_ensure_role($pdo, 'survey_manager', 'Survey Manager', 'tenant'),
|
||||
'platform_admin' => scripts_ensure_role($pdo, 'platform_admin', 'Platform Admin', 'platform'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$_GET['page'] = 'reports';
|
||||
|
||||
require dirname(__DIR__) . '/index.php';
|
||||
+657
-454
File diff suppressed because it is too large
Load Diff
@@ -347,7 +347,7 @@
|
||||
<label>Betreff<input name="subject" maxlength="255" placeholder="Worum geht es?" required></label>
|
||||
<label>Kategorie
|
||||
<select name="category">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<option value="<?= support_h($category) ?>"><?= support_h(ucfirst(str_replace('_', ' ', $category))) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user