Phase1 Bearbeitung

This commit is contained in:
2026-06-15 18:36:57 +02:00
parent e4fa714419
commit 06645f1e9c
29 changed files with 3367 additions and 300 deletions
+32 -15
View File
@@ -1,22 +1,39 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<?php
$resetRequestUrl = '/password/forgot?scope=platform';
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Plattform</span>
<span class="eyebrow">Plattformzugang</span>
<h1>Admin-Login</h1>
<p>Fuer Betrieb, Mandantenuebersicht, Support und spaetere Vermarktungssteuerung.</p>
<p>Fuer Pilotbetreuung, Mandantenuebersicht und Betriebsaufgaben. Mitglieder nutzen dafuer nicht diese Seite, sondern den jeweiligen Standort-Login.</p>
</section>
<form class="card form-card slim" method="post" action="/admin/login">
<?= csrf_field($csrf) ?>
<label>
<span>E-Mail</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" required>
</label>
<button class="button button-primary" type="submit">Anmelden</button>
</form>
<div class="two-column">
<form class="card form-card" method="post" action="/admin/login">
<?= csrf_field($csrf) ?>
<label>
<span>E-Mail</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" required>
</label>
<button class="button button-primary" type="submit">Plattform anmelden</button>
<p class="muted">Passwort vergessen? <a class="text-link" href="<?= e($resetRequestUrl) ?>">Reset anstossen</a></p>
</form>
<aside class="card" id="reset-help">
<span class="eyebrow">Passwort vergessen</span>
<h2>Reset im Pilotbetrieb</h2>
<p class="muted">Der Passwort-Reset ist fuer den Pilot bereits vorgesehen. Bis die automatische Zustellung freigeschaltet ist, sollte das Plattformteam die Anfrage pruefen und den Reset manuell begleiten.</p>
<ul class="feature-list">
<li>Nur fuer Plattform-Admins und Pilotbetreuung</li>
<li>Anfrage immer ueber die bekannte Login-E-Mail pruefen</li>
<li>Mandanten- und Member-Logins laufen getrennt vom Plattformzugang</li>
</ul>
</aside>
</div>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+119
View File
@@ -0,0 +1,119 @@
<?php
$scope = trim((string) ($scope ?? old('scope')));
if ($scope === '') {
$scope = trim((string) ($_GET['scope'] ?? ''));
}
$scope = $scope === 'tenant' ? 'tenant' : 'platform';
$tenantSlug = trim((string) ($tenantSlug ?? old('tenant_slug')));
if ($tenantSlug === '') {
$tenantSlug = trim((string) ($_GET['tenantSlug'] ?? $_GET['tenant'] ?? ''));
}
$tenantName = trim((string) ($tenantName ?? old('tenant_name')));
if ($tenantName === '') {
$tenantName = trim((string) ($_GET['tenantName'] ?? ''));
}
if ($tenantName === '' && $tenantSlug !== '') {
$tenantName = 'Standort ' . $tenantSlug;
}
if ($tenantName === '') {
$tenantName = 'Standortzugang';
}
$token = trim((string) ($token ?? old('token')));
$resetToken = isset($resetToken) && is_array($resetToken) ? $resetToken : null;
$hasValidResetToken = $resetToken !== null;
$loginUrl = $scope === 'tenant'
? ($tenantSlug !== '' ? tenant_url($tenantSlug, 'login') : '/')
: '/admin/login';
$requestUrl = '/password/forgot?scope=' . rawurlencode($scope);
if ($scope === 'tenant' && $tenantSlug !== '') {
$requestUrl .= '&tenantSlug=' . rawurlencode($tenantSlug);
}
if ($scope === 'tenant' && $tenantName !== '') {
$requestUrl .= '&tenantName=' . rawurlencode($tenantName);
}
$resetEmail = trim((string) ($resetToken['email'] ?? ''));
$expiresAt = trim((string) ($resetToken['expires_at'] ?? ''));
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Reset bestaetigen</span>
<h1>Neues Passwort setzen</h1>
<p>
<?= e($hasValidResetToken
? ($scope === 'platform'
? 'Der Plattformzugang bekommt hier sein neues Passwort.'
: 'Der Standortzugang fuer ' . $tenantName . ' bekommt hier sein neues Passwort.')
: 'Hier wird nur mit einem gueltigen Reset-Link ein neues Passwort gesetzt.') ?>
So bleibt der Login-Reset von PIN, RFID und sonstigen Standortdaten getrennt.
</p>
</section>
<div class="two-column">
<?php if ($hasValidResetToken): ?>
<form class="card form-card" method="post" action="<?= e(current_path()) ?>">
<?= csrf_field($csrf) ?>
<input type="hidden" name="scope" value="<?= e($scope) ?>">
<?php if ($tenantSlug !== ''): ?>
<input type="hidden" name="tenant_slug" value="<?= e($tenantSlug) ?>">
<?php endif; ?>
<?php if ($scope === 'tenant' && $tenantName !== ''): ?>
<input type="hidden" name="tenant_name" value="<?= e($tenantName) ?>">
<?php endif; ?>
<input type="hidden" name="token" value="<?= e($token) ?>">
<?php if ($resetEmail !== '' || $expiresAt !== ''): ?>
<p class="muted">
<?= e($resetEmail !== '' ? 'Link fuer ' . $resetEmail . '.' : 'Der Reset-Link wurde geprueft.') ?>
<?php if ($expiresAt !== ''): ?>
Gueltig bis <?= e($expiresAt) ?> UTC.
<?php endif; ?>
</p>
<?php endif; ?>
<label>
<span>Neues Passwort</span>
<input type="password" name="password" minlength="12" required>
</label>
<label>
<span>Passwort wiederholen</span>
<input type="password" name="password_confirm" minlength="12" required>
</label>
<button class="button button-primary" type="submit">Passwort uebernehmen</button>
<p class="muted">Nach erfolgreicher Bestaetigung soll dieser Flow wieder in den passenden Login zurueckfuehren.</p>
<p><a class="text-link" href="<?= e($loginUrl) ?>">Zurueck zum Login</a></p>
</form>
<?php else: ?>
<div class="card form-card">
<h2>Reset-Link neu anfordern</h2>
<p class="muted">
<?= e($token === ''
? 'Oeffne den Reset-Link aus der E-Mail, um hier ein neues Passwort zu setzen.'
: 'Dieser Reset-Link ist ungueltig oder abgelaufen. Bitte fordere einen neuen Link an.') ?>
</p>
<p><a class="text-link" href="<?= e($requestUrl) ?>">Neuen Reset-Link anfordern</a></p>
<p><a class="text-link" href="<?= e($loginUrl) ?>">Zurueck zum Login</a></p>
</div>
<?php endif; ?>
<aside class="card">
<span class="eyebrow">Worauf geachtet wird</span>
<ul class="feature-list">
<li>Mindestens 12 Zeichen fuer Plattform- und Standortzugang</li>
<li>Das neue Passwort ersetzt nur den Login, nicht PIN oder RFID-Zuweisungen</li>
<li>Bei Rueckfragen bleibt das Standortteam im Pilot die erste Anlaufstelle</li>
</ul>
</aside>
</div>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+92
View File
@@ -0,0 +1,92 @@
<?php
$scope = trim((string) ($scope ?? old('scope')));
if ($scope === '') {
$scope = trim((string) ($_GET['scope'] ?? ''));
}
$scope = $scope === 'tenant' ? 'tenant' : 'platform';
$tenantSlug = trim((string) ($tenantSlug ?? old('tenant_slug')));
if ($tenantSlug === '') {
$tenantSlug = trim((string) ($_GET['tenantSlug'] ?? $_GET['tenant'] ?? ''));
}
$tenantName = trim((string) ($tenantName ?? old('tenant_name')));
if ($tenantName === '') {
$tenantName = trim((string) ($_GET['tenantName'] ?? ''));
}
if ($tenantName === '' && $tenantSlug !== '') {
$tenantName = 'Standort ' . $tenantSlug;
}
if ($tenantName === '') {
$tenantName = 'Standortzugang';
}
$loginUrl = $scope === 'tenant'
? ($tenantSlug !== '' ? tenant_url($tenantSlug, 'login') : '/')
: '/admin/login';
$emailLabel = $scope === 'tenant' ? 'Login-E-Mail' : 'Admin-E-Mail';
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Passwort vergessen</span>
<h1>Reset-Anfrage vorbereiten</h1>
<p>
<?= e($scope === 'platform'
? 'Diese Seite sammelt die Anfrage fuer einen Plattform-Reset.'
: 'Diese Seite sammelt die Anfrage fuer den Standort-Reset von ' . $tenantName . '.') ?>
Im Pilotbetrieb kann die Bestaetigung noch manuell begleitet werden.
</p>
</section>
<div class="two-column">
<form class="card form-card" method="post" action="<?= e(current_path()) ?>">
<?= csrf_field($csrf) ?>
<input type="hidden" name="scope" value="<?= e($scope) ?>">
<?php if ($tenantSlug !== ''): ?>
<input type="hidden" name="tenant_slug" value="<?= e($tenantSlug) ?>">
<?php endif; ?>
<?php if ($scope === 'tenant' && $tenantName !== ''): ?>
<input type="hidden" name="tenant_name" value="<?= e($tenantName) ?>">
<?php endif; ?>
<label>
<span><?= e($emailLabel) ?></span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>" required>
</label>
<?php if ($scope !== 'platform' && $tenantSlug === ''): ?>
<label>
<span>Standort-Slug</span>
<input type="text" name="tenant_slug" value="<?= e((string) old('tenant_slug')) ?>" placeholder="mein-team" required>
</label>
<?php endif; ?>
<button class="button button-primary" type="submit">Reset-Link anfordern</button>
<p class="muted">Nach dem Versand soll der Link wieder auf diese Strecke zurueckfuehren und danach zum passenden Login.</p>
<p><a class="text-link" href="<?= e($loginUrl) ?>">Zurueck zum Login</a></p>
</form>
<aside class="card">
<span class="eyebrow">Pilotablauf</span>
<ul class="stack-list">
<li>
<strong>1. E-Mail pruefen</strong>
<small>Die Anfrage sollte immer ueber die bekannte Login-Adresse laufen.</small>
</li>
<li>
<strong>2. Reset bestaetigen</strong>
<small>Im Pilot kann diese Freigabe vorerst noch manuell durch Plattform- oder Standortteam begleitet werden.</small>
</li>
<li>
<strong>3. Neues Passwort setzen</strong>
<small>Danach geht es ueber die Confirm-Seite direkt zurueck in den passenden Login.</small>
</li>
</ul>
</aside>
</div>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+40 -39
View File
@@ -2,35 +2,35 @@
<section class="hero">
<div class="hero-copy">
<span class="eyebrow">Von der Papierliste zur produktiven SaaS</span>
<h1>Kaffeeliste, digitale Strichliste und RFID-Roadmap in einem System.</h1>
<p class="lead">Die Anwendung verbindet klassische Kaffeekasse, digitale Selbstbuchung und spaetere Geraeteintegration. Mandanten registrieren sich selbst, verwalten Mitglieder, Produkte, Einzahlungen und Papierlisten in einem PHP/MySQL-Stack, der auf Netcup-Webspace laeuft.</p>
<span class="eyebrow">Pilotbetrieb fuer Teams und Vereine</span>
<h1>Die digitale Kaffeeliste ist bereit fuer echte Pilotstandorte, nicht fuer leere Demo-Versprechen.</h1>
<p class="lead">Aktuell wird der Ablauf mit ausgewaehlten Teams verfeinert: Pilotmandanten koennen sich registrieren, Mitglieder fuer die Selbstbuchung anlegen und Backoffice-Prozesse fuer Papierlisten, Einzahlungen und spaetere RFID-Anbindung realistisch testen.</p>
<div class="button-row">
<?php if ($setupComplete): ?>
<a class="button button-primary" href="/register">Mandant starten</a>
<a class="button button-secondary" href="/admin/login">Plattform betreten</a>
<a class="button button-primary" href="/register">Pilotmandant anlegen</a>
<a class="button button-secondary" href="/admin/login">Plattform-Login</a>
<?php else: ?>
<a class="button button-primary" href="/install">Installation starten</a>
<?php endif; ?>
</div>
<ul class="metric-row">
<li><strong>1</strong><span>gemeinsames Ledger fuer digital, Papier und RFID</span></li>
<li><strong>3</strong><span>Pakete fuer Vermarktung und Rollout</span></li>
<li><strong>100%</strong><span>Webspace-kompatibler PHP/MySQL-Betrieb</span></li>
<li><strong>1</strong><span>gemeinsames Ledger fuer Selbstbuchung, Papier und spaetere RFID-Events</span></li>
<li><strong>Pilot</strong><span>klare Trennung zwischen Member-Self-Service und Standort-Backoffice</span></li>
<li><strong>100%</strong><span>PHP/MySQL-Betrieb fuer klassisches Webhosting</span></li>
</ul>
</div>
<aside class="hero-card">
<h2>Produktive Ausrichtung</h2>
<h2>Worauf der Pilot gerade abzielt</h2>
<ul class="feature-list">
<li>Mandanten-Self-Service mit Owner-Onboarding</li>
<li>Digitale Buchungen fuer Tablet, Browser und Kiosk</li>
<li>Papierlisten als Entwurf, Nacherfassung und Ledger-Posting</li>
<li>Einzahlungen, Salden und Audit-Trail pro Mandant</li>
<li>RFID-Devices, Karten und Event-Inbox fuer die spaetere Automatisierung</li>
<li>Owner-Onboarding mit sofort nutzbarem Testmandanten</li>
<li>Mitglieder-Logins fuer schlanke Selbstbuchung statt Volladmin-Anmutung</li>
<li>Papierlisten als Entwurf, Nacherfassung und sauberes Ledger-Posting</li>
<li>Einzahlungen, Salden und Audit-Trail fuer das Standortteam</li>
<li>RFID-Devices, Karten und Event-Inbox fuer spaetere Automatisierung</li>
</ul>
<?php if (is_array($stats)): ?>
<div class="stats-panel">
<div><strong><?= e((string) $stats['tenants']) ?></strong><span>Mandanten in der Datenbank</span></div>
<div><strong><?= e((string) $stats['tenants']) ?></strong><span>Pilotmandanten in der Datenbank</span></div>
<div><strong><?= e((string) $stats['members']) ?></strong><span>Mitglieder</span></div>
<div><strong><?= e((string) $stats['events']) ?></strong><span>Buchungsvorfaelle</span></div>
</div>
@@ -40,53 +40,53 @@
<section class="content-grid">
<article class="card">
<span class="eyebrow">Produkt</span>
<h2>Eine Buchungslogik, mehrere Erfassungswege</h2>
<p>Jede Kaffee- oder Tee-Buchung landet im selben Ledger. Ob jemand digital klickt, ob ein Admin eine Papierliste erfasst oder ob spaeter ein RFID-Leser Events einspeist: Salden, Historie und Auswertungen bleiben konsistent.</p>
<span class="eyebrow">Heute stabil</span>
<h2>Eine Buchungslogik fuer reale Nutzung</h2>
<p>Jede Kaffee- oder Tee-Buchung landet im selben Ledger. Ob jemand digital bucht, ob das Standortteam eine Papierliste erfasst oder spaeter ein RFID-Leser Events einspeist: Salden, Historie und Auswertungen bleiben konsistent.</p>
</article>
<article class="card">
<span class="eyebrow">Marketing</span>
<h2>Klare Positionierung fuer Teams, Vereine und Coworking</h2>
<p>Die Startseite spricht gezielt Organisationen an, die zwischen analoger Kaffeekasse und digitaler Selbstbedienung stehen. Das senkt Erklaerungsaufwand und macht den Upgrade-Pfad zu RFID nachvollziehbar.</p>
<span class="eyebrow">Pilotfokus</span>
<h2>Self-Service fuer Mitglieder, Backoffice fuers Standortteam</h2>
<p>Der Pilot trennt bewusst zwischen einfacher Selbstbuchung und administrativen Seiten. Dadurch wirkt der Zugang fuer Mitglieder alltagstauglich, waehrend Produkte, Einzahlungen und Korrekturen beim Standortteam bleiben.</p>
</article>
<article class="card">
<span class="eyebrow">Betrieb</span>
<h2>Netcup-tauglich statt hyperskalierter Overkill</h2>
<p>Pfadbasierte Mandanten, gemeinsamer MySQL-Kern, Cron statt Worker und ein sauberer Webroot auf `public/` sind genau auf klassisches Webhosting zugeschnitten.</p>
<span class="eyebrow">Rahmen</span>
<h2>Ehrlicher Rollout statt Schein-Automatisierung</h2>
<p>Reset-Flow, Exporte und Korrekturhilfen werden gerade an echten Pilotablaeufen geschliffen. Gleichzeitig bleibt der technische Kern schlank genug fuer klassisches Webhosting mit PHP, MySQL und Webroot auf `public/`.</p>
</article>
</section>
<section class="plans">
<div class="section-head">
<span class="eyebrow">Pakete</span>
<h2>Preismodelle fuer die Vermarktung</h2>
<span class="eyebrow">Pilotstufen</span>
<h2>Die Startkonfiguration im Pilot, nicht die finale Vermarktung</h2>
</div>
<div class="plan-grid">
<article class="plan-card">
<h3>Starter</h3>
<p>Fuer kleine Teams und Vereine mit digitaler Buchung und einfacher Kaffeekasse.</p>
<p>Fuer kleine Teams mit digitaler Buchung und moeglichst wenig Abstimmungsaufwand im Alltag.</p>
<ul>
<li>Mitglieder, Produkte, digitale Buchungen</li>
<li>Einzahlungen und Salden</li>
<li>Self-Service-Registrierung</li>
<li>Mitglieder, Produkte und Selbstbuchung</li>
<li>Schlanke Navigation fuer Member-Logins</li>
<li>Schneller Einstieg fuer den ersten Pilotstandort</li>
</ul>
</article>
<article class="plan-card highlighted">
<h3>Team</h3>
<p>Fuer Organisationen, die Papierlisten und digitale Prozesse parallel fahren.</p>
<p>Fuer Organisationen, die Papierlisten und digitale Prozesse im Pilot parallel fahren.</p>
<ul>
<li>Papierlisten-Workflow mit Nacherfassung</li>
<li>Plattform-Dashboard und Audit-Trail</li>
<li>Vorbereitung fuer Standortgeraete</li>
<li>Backoffice fuer Einzahlungen und Korrekturen</li>
<li>Vorbereitung fuer spaetere Export- und Supportpfade</li>
</ul>
</article>
<article class="plan-card">
<h3>Business</h3>
<p>Fuer Standorte mit RFID-Roadmap, Betriebsanforderungen und spaeterem White-Labeling.</p>
<p>Fuer Standorte mit RFID-Roadmap, hoeheren Betriebsanforderungen und fruehem Integrationsbedarf.</p>
<ul>
<li>RFID-Devices, Karten und Intake-API</li>
<li>Release-, Backup- und Deployment-Rahmen</li>
<li>Vorlage fuer spaetere Integrationen</li>
<li>Frueher Blick auf Betriebs- und Supportprozesse</li>
</ul>
</article>
</div>
@@ -94,14 +94,15 @@
<section class="timeline card">
<div class="section-head">
<span class="eyebrow">Roadmap</span>
<h2>Sauberer Pfad in die Produktion</h2>
<span class="eyebrow">Pilotablauf</span>
<h2>So startet ein neuer Standort realistisch</h2>
</div>
<ol class="step-list">
<li>Plattform installieren und ersten Admin anlegen.</li>
<li>Mandant registrieren, Owner-Onboarding abschliessen und Produkte vorbelegen.</li>
<li>Pilotmandant registrieren, Owner-Onboarding abschliessen und Produkte vorbelegen.</li>
<li>Mitglieder fuer Selbstbuchung anlegen und Standort-Backoffice abstimmen.</li>
<li>Digitale Buchungen und Papierlisten parallel im Team einsetzen.</li>
<li>Mit RFID-Geraeten spaeter ueber die Event-Inbox anbinden, ohne das Kernledger umzubauen.</li>
<li>RFID-Geraete spaeter ueber die Event-Inbox anbinden, ohne das Kernledger umzubauen.</li>
</ol>
</section>
+17 -17
View File
@@ -1,24 +1,24 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Self-Service</span>
<h1>Mandant registrieren</h1>
<p>Der Owner legt den ersten Standort an und bekommt automatisch die Grundbausteine fuer digitale Buchung, Papierliste und spaetere RFID-Anbindung.</p>
<span class="eyebrow">Pilot-Onboarding</span>
<h1>Pilotmandant registrieren</h1>
<p>Dieses Formular legt einen echten Teststandort an. Bitte nur verwenden, wenn Organisation, Ansprechpartner und Startpreis fuer den Pilotbetrieb abgestimmt sind.</p>
</section>
<div class="two-column">
<form class="card form-card" method="post" action="/register">
<?= csrf_field($csrf) ?>
<label>
<span>Organisationsname</span>
<span>Team- / Standortname</span>
<input type="text" name="tenant_name" value="<?= e((string) old('tenant_name')) ?>" required>
</label>
<label>
<span>Kurzname / URL-Slug</span>
<span>Kurzname fuer die Login-URL</span>
<input type="text" name="tenant_slug" value="<?= e((string) old('tenant_slug')) ?>" placeholder="mein-team">
</label>
<label>
<span>Paket</span>
<span>Pilotstufe</span>
<select name="plan">
<option value="starter">Starter</option>
<option value="team" selected>Team</option>
@@ -26,33 +26,33 @@
</select>
</label>
<label>
<span>Standardpreis pro Kaffee in EUR</span>
<span>Startpreis pro Kaffee in EUR</span>
<input type="number" name="default_price_eur" min="0.50" step="0.05" value="<?= e((string) old('default_price_eur', '1.20')) ?>">
</label>
<label>
<span>Owner-Name</span>
<span>Ansprechpartner / Owner</span>
<input type="text" name="owner_name" value="<?= e((string) old('owner_name')) ?>" required>
</label>
<label>
<span>Owner-E-Mail</span>
<span>Login-E-Mail fuer den Owner</span>
<input type="email" name="owner_email" value="<?= e((string) old('owner_email')) ?>" required>
</label>
<label>
<span>Owner-Passwort</span>
<span>Startpasswort fuer den Owner</span>
<input type="password" name="owner_password" minlength="12" required>
</label>
<button class="button button-primary" type="submit">Mandant anlegen</button>
<button class="button button-primary" type="submit">Pilotmandant anlegen</button>
</form>
<aside class="card">
<span class="eyebrow">Was automatisch entsteht</span>
<span class="eyebrow">Vor dem Absenden</span>
<ul class="feature-list">
<li>Tenant mit Owner-Rolle</li>
<li>Systemquellen fuer digital, Papier, Backoffice und RFID</li>
<li>Standardprodukte Kaffee, Espresso und Tee</li>
<li>Voreinstellung fuer Self-Service und Papierlisten</li>
<li>Die Registrierung erzeugt sofort einen nutzbaren Testmandanten</li>
<li>Owner-Zugang und erstes Mitglied werden automatisch angelegt</li>
<li>Systemquellen fuer digital, Papier, Backoffice und RFID stehen direkt bereit</li>
<li>Die Self-Service-Navigation fuer Mitglieder bleibt spaeter schlanker als das Admin-Backoffice</li>
</ul>
<p class="muted">Damit ist die Vermarktung sauber: Interessenten landen nicht in einem Technikgeruest, sondern in einem sofort nutzbaren Grundaufbau.</p>
<p class="muted">Wenn ihr erst unverbindlich pruefen wollt, startet lieber mit einem klar benannten Pilotteam statt mit Fantasiedaten. Das macht spaetere Login-, Reset- und Supportablaeufe deutlich sauberer.</p>
</aside>
</div>
+24 -11
View File
@@ -4,6 +4,12 @@ $flashError = flash($session, 'error');
$authState = $authState ?? $session->get('auth');
$tenant = $tenant ?? null;
$currentPath = current_path();
$tenantSlug = is_array($tenant) && isset($tenant['slug']) ? (string) $tenant['slug'] : '';
$tenantRole = is_array($authState) ? (string) ($authState['tenant_role'] ?? '') : '';
$hasTenantAccess = $tenantSlug !== ''
&& is_array($authState)
&& (($authState['tenant_slug'] ?? null) === $tenantSlug);
$isTenantAdmin = in_array($tenantRole, ['owner', 'admin'], true);
?>
<!DOCTYPE html>
<html lang="de">
@@ -25,18 +31,25 @@ $currentPath = current_path();
</span>
</a>
<nav class="top-nav">
<?php if (is_array($tenant) && isset($tenant['slug'])): ?>
<a href="<?= e(tenant_url($tenant['slug'])) ?>" class="<?= $currentPath === tenant_url($tenant['slug']) ? 'active' : '' ?>">Dashboard</a>
<a href="<?= e(tenant_url($tenant['slug'], 'members')) ?>" class="<?= str_contains($currentPath, '/members') ? 'active' : '' ?>">Mitglieder</a>
<a href="<?= e(tenant_url($tenant['slug'], 'products')) ?>" class="<?= str_contains($currentPath, '/products') ? 'active' : '' ?>">Produkte</a>
<a href="<?= e(tenant_url($tenant['slug'], 'bookings')) ?>" class="<?= str_contains($currentPath, '/bookings') ? 'active' : '' ?>">Digital</a>
<a href="<?= e(tenant_url($tenant['slug'], 'payments')) ?>" class="<?= str_contains($currentPath, '/payments') ? 'active' : '' ?>">Einzahlungen</a>
<a href="<?= e(tenant_url($tenant['slug'], 'paper-sheets')) ?>" class="<?= str_contains($currentPath, '/paper-sheets') ? 'active' : '' ?>">Papier</a>
<a href="<?= e(tenant_url($tenant['slug'], 'rfid')) ?>" class="<?= str_contains($currentPath, '/rfid') ? 'active' : '' ?>">RFID</a>
<?php if ($hasTenantAccess): ?>
<a href="<?= e(tenant_url($tenantSlug)) ?>" class="<?= $currentPath === tenant_url($tenantSlug) ? 'active' : '' ?>">Uebersicht</a>
<a href="<?= e(tenant_url($tenantSlug, 'bookings')) ?>" class="<?= str_contains($currentPath, '/bookings') ? 'active' : '' ?>">Buchen</a>
<?php if ($isTenantAdmin): ?>
<a href="<?= e(tenant_url($tenantSlug, 'members')) ?>" class="<?= str_contains($currentPath, '/members') ? 'active' : '' ?>">Mitglieder</a>
<a href="<?= e(tenant_url($tenantSlug, 'products')) ?>" class="<?= str_contains($currentPath, '/products') ? 'active' : '' ?>">Produkte</a>
<a href="<?= e(tenant_url($tenantSlug, 'payments')) ?>" class="<?= str_contains($currentPath, '/payments') ? 'active' : '' ?>">Einzahlungen</a>
<a href="<?= e(tenant_url($tenantSlug, 'paper-sheets')) ?>" class="<?= str_contains($currentPath, '/paper-sheets') ? 'active' : '' ?>">Papierlisten</a>
<a href="<?= e(tenant_url($tenantSlug, 'rfid')) ?>" class="<?= str_contains($currentPath, '/rfid') ? 'active' : '' ?>">RFID</a>
<?php endif; ?>
<?php elseif ($tenantSlug !== ''): ?>
<a href="/" class="<?= $currentPath === '/' ? 'active' : '' ?>">Start</a>
<a href="/register" class="<?= $currentPath === '/register' ? 'active' : '' ?>">Pilot registrieren</a>
<a href="<?= e(tenant_url($tenantSlug, 'login')) ?>" class="<?= $currentPath === tenant_url($tenantSlug, 'login') ? 'active' : '' ?>">Standort-Login</a>
<a href="/admin/login" class="<?= str_starts_with($currentPath, '/admin') ? 'active' : '' ?>">Plattform</a>
<?php else: ?>
<a href="/">Start</a>
<a href="/register">Registrieren</a>
<a href="/admin/login">Plattform</a>
<a href="/" class="<?= $currentPath === '/' ? 'active' : '' ?>">Start</a>
<a href="/register" class="<?= $currentPath === '/register' ? 'active' : '' ?>">Pilot registrieren</a>
<a href="/admin/login" class="<?= str_starts_with($currentPath, '/admin') ? 'active' : '' ?>">Plattform</a>
<?php endif; ?>
</nav>
</div>
+94 -37
View File
@@ -1,19 +1,44 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<?php
$tenantRole = (string) ($authState['tenant_role'] ?? 'member');
$isSelfService = $tenantRole === 'member';
$selectedMemberId = (string) old('member_id');
$selectedProductId = (string) old('product_id');
$selectedSourceCode = (string) old('source_code', 'digital_self');
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Selbstbedienung</span>
<h1>Digitale Buchungen</h1>
<p>Dies ist der produktive Kern fuer Browser, Tablet und spaetere Kiosk-Workflows.</p>
<span class="eyebrow"><?= e($isSelfService ? 'Selbstbuchung' : 'Digitale Erfassung') ?></span>
<h1><?= e($isSelfService ? 'Getraenke buchen' : 'Digitale Buchungen') ?></h1>
<?php if ($isSelfService): ?>
<p>Diese Seite ist auf taegliche Member-Self-Service-Buchungen zugeschnitten. Korrekturen, Sammelbuchungen und Exporte bleiben im Pilot vorerst beim Standortteam.</p>
<?php else: ?>
<p>Browser, Tablet und Kiosk landen im selben Ledger. Das Standortteam kann hier Selbstbuchung und Backoffice-Buchungen sichtbar trennen.</p>
<?php endif; ?>
</section>
<section class="card">
<span class="eyebrow">Export & Korrekturen</span>
<p class="muted">
<?= e($isSelfService
? 'Hier ist bewusst Platz fuer persoenliche Exporte und klare Korrekturhinweise reserviert, ohne heute schon neue Backend-Logik vorwegzunehmen.'
: 'Hier ist bewusst Platz fuer CSV-Exporte, Sammelkorrekturen und Rueckfragen reserviert, ohne heute schon neue Controllerpfade vorwegzunehmen.') ?>
</p>
<ul class="feature-list">
<li>Download-Link fuer Buchungs-Export: Platzhalter fuer spaetere CSV- oder Monatsansicht</li>
<li>Storno- und Korrekturhinweise: Platzhalter fuer kuenftige Bedienhilfe direkt am Workflow</li>
<li><?= e($isSelfService ? 'Bis dahin bitte im Pilot nur den eigenen Namen waehlen; persoenliche Vorauswahl folgt.' : 'Sammelkorrekturen und Nachbuchungen bleiben vorerst beim Standortteam.') ?></li>
</ul>
</section>
<div class="two-column">
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'bookings')) ?>">
<?= csrf_field($csrf) ?>
<label>
<span>Mitglied</span>
<span><?= e($isSelfService ? 'Dein Mitgliedseintrag' : 'Mitglied') ?></span>
<select name="member_id" required>
<?php foreach ($members as $member): ?>
<option value="<?= e((string) $member['id']) ?>"><?= e($member['display_name']) ?></option>
<option value="<?= e((string) $member['id']) ?>" <?= $selectedMemberId === (string) $member['id'] ? 'selected' : '' ?>><?= e($member['display_name']) ?></option>
<?php endforeach; ?>
</select>
</label>
@@ -21,17 +46,21 @@
<span>Produkt</span>
<select name="product_id" required>
<?php foreach ($products as $product): ?>
<option value="<?= e((string) $product['id']) ?>"><?= e($product['name']) ?> (<?= e(money_from_cents((int) $product['price_cents'])) ?>)</option>
<option value="<?= e((string) $product['id']) ?>" <?= $selectedProductId === (string) $product['id'] ? 'selected' : '' ?>><?= e($product['name']) ?> (<?= e(money_from_cents((int) $product['price_cents'])) ?>)</option>
<?php endforeach; ?>
</select>
</label>
<label>
<span>Quelle</span>
<select name="source_code">
<option value="digital_self">Digitale Selbstbuchung</option>
<option value="admin_backoffice">Backoffice-Buchung</option>
</select>
</label>
<?php if ($isSelfService): ?>
<input type="hidden" name="source_code" value="digital_self">
<?php else: ?>
<label>
<span>Quelle</span>
<select name="source_code">
<option value="digital_self" <?= $selectedSourceCode === 'digital_self' ? 'selected' : '' ?>>Digitale Selbstbuchung</option>
<option value="admin_backoffice" <?= $selectedSourceCode === 'admin_backoffice' ? 'selected' : '' ?>>Backoffice-Buchung</option>
</select>
</label>
<?php endif; ?>
<label>
<span>Menge</span>
<input type="number" name="quantity" min="0.25" step="0.25" value="<?= e((string) old('quantity', '1')) ?>" required>
@@ -44,33 +73,61 @@
<span>Notiz</span>
<input type="text" name="notes" value="<?= e((string) old('notes')) ?>">
</label>
<button class="button button-primary" type="submit">Buchung speichern</button>
<button class="button button-primary" type="submit"><?= e($isSelfService ? 'Jetzt buchen' : 'Buchung speichern') ?></button>
<?php if ($isSelfService): ?>
<p class="muted">Wenn du mehrere Namen siehst, nutze im Pilot bitte nur deinen eigenen Eintrag. Falsche Buchungen korrigiert das Standortteam.</p>
<?php endif; ?>
</form>
<section class="table-card">
<table>
<thead>
<tr>
<th>Zeit</th>
<th>Mitglied</th>
<th>Produkt</th>
<th>Quelle</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
<?php foreach ($bookings as $booking): ?>
<?php if ($isSelfService): ?>
<section class="card">
<span class="eyebrow">Pilot-Hinweis</span>
<ul class="stack-list">
<li>
<strong>Eigene Historie</strong>
<small>Ein persoenlicher Verlauf wird spaeter hier eingeblendet.</small>
</li>
<li>
<strong>Korrekturen</strong>
<small>Falsche Buchungen bitte direkt an das Standortteam melden.</small>
</li>
<li>
<strong>Exporte</strong>
<small>Downloads und Monatsauszuege folgen nach weiterem Pilotfeedback.</small>
</li>
</ul>
</section>
<?php else: ?>
<section class="table-card">
<table>
<thead>
<tr>
<td><?= e($booking['recorded_at']) ?></td>
<td><?= e($booking['member_name']) ?></td>
<td><?= e($booking['product_name']) ?></td>
<td><?= e($booking['source_name']) ?></td>
<td><?= e(money_from_cents((int) $booking['total_cents'])) ?></td>
<th>Zeit</th>
<th>Mitglied</th>
<th>Produkt</th>
<th>Quelle</th>
<th>Betrag</th>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
</thead>
<tbody>
<?php if ($bookings === []): ?>
<tr>
<td colspan="5">Noch keine Buchungen erfasst.</td>
</tr>
<?php endif; ?>
<?php foreach ($bookings as $booking): ?>
<tr>
<td><?= e($booking['recorded_at']) ?></td>
<td><?= e($booking['member_name']) ?></td>
<td><?= e($booking['product_name']) ?></td>
<td><?= e($booking['source_name']) ?></td>
<td><?= e(money_from_cents((int) $booking['total_cents'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<?php endif; ?>
</div>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+97 -46
View File
@@ -1,10 +1,18 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<?php
$tenantRole = (string) ($authState['tenant_role'] ?? 'member');
$isSelfService = $tenantRole === 'member';
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head inline-head">
<div>
<span class="eyebrow"><?= e(ucfirst($tenant['plan'])) ?> Paket</span>
<span class="eyebrow"><?= e($isSelfService ? 'Member-Self-Service' : ucfirst((string) $tenant['plan']) . ' Paket') ?></span>
<h1><?= e($tenant['name']) ?></h1>
<p>Rolle: <?= e((string) ($authState['tenant_role'] ?? 'member')) ?>. Hier laufen digitale Buchungen, Papierlisten und Betriebszahlen zusammen.</p>
<?php if ($isSelfService): ?>
<p>Rolle: <?= e($tenantRole) ?>. Dieser Zugang ist fuer die taegliche Selbstbuchung gedacht; Stammdaten, Saldenpflege und Korrekturen bleiben beim Standortteam.</p>
<?php else: ?>
<p>Rolle: <?= e($tenantRole) ?>. Hier laufen digitale Buchungen, Backoffice-Korrekturen und Standortzahlen zusammen.</p>
<?php endif; ?>
</div>
<form method="post" action="<?= e(tenant_url($tenant['slug'], 'logout')) ?>">
<?= csrf_field($csrf) ?>
@@ -12,49 +20,92 @@
</form>
</section>
<section class="metric-cards">
<article class="metric-card">
<span>Mitglieder</span>
<strong><?= e((string) $summary['member_count']) ?></strong>
</article>
<article class="metric-card">
<span>Produkte</span>
<strong><?= e((string) $summary['product_count']) ?></strong>
</article>
<article class="metric-card">
<span>Papierlisten</span>
<strong><?= e((string) $summary['paper_sheet_count']) ?></strong>
</article>
<article class="metric-card">
<span>Offene Salden</span>
<strong><?= e(money_from_cents((int) $summary['open_balance_cents'])) ?></strong>
</article>
</section>
<?php if ($isSelfService): ?>
<div class="two-column">
<section class="card">
<span class="eyebrow">Was du hier tun kannst</span>
<ul class="feature-list">
<li>Digitale Buchungen fuer deinen Standort erfassen</li>
<li>Produktpreise vor dem Buchen einsehen</li>
<li>Passwort-Reset oder Buchungskorrekturen ueber das Standortteam anstossen</li>
</ul>
<p class="muted">Die Navigation zeigt hier bewusst nur Self-Service-Seiten, damit dieser Zugang nicht wie Volladministration wirkt.</p>
</section>
<section class="card">
<span class="eyebrow">Pilotbetrieb</span>
<ul class="stack-list">
<li>
<strong>Stammdaten und Mitglieder</strong>
<small>Pflegt weiterhin das Standortteam im Backoffice.</small>
</li>
<li>
<strong>Einzahlungen und Papierlisten</strong>
<small>Werden separat verbucht und bei Bedarf dort korrigiert.</small>
</li>
<li>
<strong>Exporte und persoenliche Historie</strong>
<small>Werden im Pilot noch schrittweise nachgezogen.</small>
</li>
</ul>
</section>
</div>
<?php else: ?>
<section class="metric-cards">
<article class="metric-card">
<span>Mitglieder</span>
<strong><?= e((string) $summary['member_count']) ?></strong>
</article>
<article class="metric-card">
<span>Produkte</span>
<strong><?= e((string) $summary['product_count']) ?></strong>
</article>
<article class="metric-card">
<span>Papierlisten</span>
<strong><?= e((string) $summary['paper_sheet_count']) ?></strong>
</article>
<article class="metric-card">
<span>Offene Salden</span>
<strong><?= e(money_from_cents((int) $summary['open_balance_cents'])) ?></strong>
</article>
</section>
<div class="two-column">
<section class="card">
<span class="eyebrow">Letzte Buchungen</span>
<ul class="stack-list">
<?php foreach ($latestEvents as $event): ?>
<li>
<strong><?= e($event['member_name']) ?></strong>
<span><?= e($event['product_name']) ?> via <?= e($event['source_name']) ?></span>
<small><?= e(money_from_cents((int) $event['total_cents'])) ?> am <?= e($event['recorded_at']) ?></small>
</li>
<?php endforeach; ?>
</ul>
</section>
<section class="card">
<span class="eyebrow">Hohe Salden</span>
<ul class="stack-list">
<?php foreach ($memberBalances as $balance): ?>
<li>
<strong><?= e($balance['display_name']) ?></strong>
<small><?= e(money_from_cents((int) $balance['balance_cents'])) ?></small>
</li>
<?php endforeach; ?>
</ul>
</section>
</div>
<div class="two-column">
<section class="card">
<span class="eyebrow">Letzte Buchungen</span>
<ul class="stack-list">
<?php if ($latestEvents === []): ?>
<li>
<strong>Noch keine Buchungen</strong>
<small>Die letzten Ereignisse erscheinen hier, sobald der Pilotstandort aktiv bucht.</small>
</li>
<?php endif; ?>
<?php foreach ($latestEvents as $event): ?>
<li>
<strong><?= e($event['member_name']) ?></strong>
<span><?= e($event['product_name']) ?> via <?= e($event['source_name']) ?></span>
<small><?= e(money_from_cents((int) $event['total_cents'])) ?> am <?= e($event['recorded_at']) ?></small>
</li>
<?php endforeach; ?>
</ul>
</section>
<section class="card">
<span class="eyebrow">Hohe Salden</span>
<ul class="stack-list">
<?php if ($memberBalances === []): ?>
<li>
<strong>Noch keine Salden</strong>
<small>Sobald Buchungen oder Einzahlungen vorliegen, erscheint hier die Uebersicht.</small>
</li>
<?php endif; ?>
<?php foreach ($memberBalances as $balance): ?>
<li>
<strong><?= e($balance['display_name']) ?></strong>
<small><?= e(money_from_cents((int) $balance['balance_cents'])) ?></small>
</li>
<?php endforeach; ?>
</ul>
</section>
</div>
<?php endif; ?>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+33 -15
View File
@@ -1,22 +1,40 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<?php
$resetRequestUrl = '/password/forgot?scope=tenant&tenantSlug=' . rawurlencode((string) $tenant['slug'])
. '&tenantName=' . rawurlencode((string) $tenant['name']);
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Mandant</span>
<span class="eyebrow">Standortzugang</span>
<h1><?= e($tenant['name']) ?></h1>
<p>Digitale Strichliste, Papierlisten-Nacherfassung und spaetere RFID-Anbindung fuer diesen Standort.</p>
<p>Login fuer Owner, Standortteam und Mitglieder-Self-Service im Pilotbetrieb. Verwaltungsseiten bleiben bewusst dem Standortteam vorbehalten.</p>
</section>
<form class="card form-card slim" method="post" action="<?= e(tenant_url($tenant['slug'], 'login')) ?>">
<?= csrf_field($csrf) ?>
<label>
<span>E-Mail</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" required>
</label>
<button class="button button-primary" type="submit">Anmelden</button>
</form>
<div class="two-column">
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'login')) ?>">
<?= csrf_field($csrf) ?>
<label>
<span>Login-E-Mail</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" required>
</label>
<button class="button button-primary" type="submit">Standort anmelden</button>
<p class="muted">Passwort vergessen? <a class="text-link" href="<?= e($resetRequestUrl) ?>">Reset anfragen</a></p>
</form>
<aside class="card" id="reset-help">
<span class="eyebrow">Reset & Hilfe</span>
<h2>Member-Self-Service bleibt schlank</h2>
<p class="muted">Wenn ein Passwort vergessen wurde, prueft das Standortteam die Anfrage im Pilot zunaechst manuell. Die eigene Reset-Seite fuer dieses Login ist bereits vorgesehen.</p>
<ul class="feature-list">
<li>Mitglieder buchen ueber ihren Standortzugang, nicht ueber die Plattform</li>
<li>PIN und RFID sind getrennt vom Passwort-Reset</li>
<li>Korrekturen an Salden, Produkten und Papierlisten bleiben beim Standortteam</li>
</ul>
</aside>
</div>
<?php require __DIR__ . '/../partials/layout-bottom.php'; ?>
+36 -10
View File
@@ -1,9 +1,19 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Stammdaten</span>
<h1>Mitglieder verwalten</h1>
<p>Mitglieder koennen rein fachlich gefuehrt werden oder optional einen Login fuer die digitale Buchung erhalten.</p>
<span class="eyebrow">Standortteam</span>
<h1>Mitglieder und Zugaenge</h1>
<p>Mitglieder koennen rein fachlich gefuehrt werden oder optional einen Login fuer die Selbstbuchung erhalten. PINs werden nach dem Speichern nicht mehr im Klartext angezeigt.</p>
</section>
<section class="card">
<span class="eyebrow">Export & Korrekturen</span>
<p class="muted">Hier ist bewusst Platz fuer spaetere CSV-Exporte, Sammelimporte und Statuskorrekturen reserviert, ohne dafuer heute schon neue Controllerlogik zu verlangen.</p>
<ul class="feature-list">
<li>Mitgliederliste als Export: Platzhalter fuer kuenftigen Download-Link</li>
<li>Login-, Rollen- oder PIN-Korrekturen: Platzhalter fuer Hinweise direkt am Datensatz</li>
<li>Self-Service-Logins bleiben getrennt von den Backoffice-Feldern des Standortteams</li>
</ul>
</section>
<div class="two-column">
@@ -14,18 +24,19 @@
<input type="text" name="display_name" value="<?= e((string) old('display_name')) ?>" required>
</label>
<label>
<span>E-Mail fuer Login</span>
<span>Login-E-Mail fuer Self-Service</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>">
</label>
<label>
<span>Login-Passwort</span>
<span>Startpasswort fuer den ersten Login</span>
<input type="password" name="login_password" minlength="12">
</label>
<label>
<span>PIN fuer Kiosk / Tablet</span>
<span>Optionale PIN fuer Kiosk / Tablet</span>
<input type="text" name="access_pin" value="<?= e((string) old('access_pin')) ?>">
</label>
<button class="button button-primary" type="submit">Mitglied speichern</button>
<p class="muted">Wenn eine Login-E-Mail gesetzt ist, wird automatisch ein Member-Login fuer die digitale Selbstbuchung angelegt.</p>
</form>
<section class="table-card">
@@ -33,17 +44,32 @@
<thead>
<tr>
<th>Name</th>
<th>E-Mail</th>
<th>PIN</th>
<th>Login</th>
<th>PIN-Status</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($members as $member): ?>
<?php $loginEmail = (string) ($member['login_email'] ?: $member['email']); ?>
<?php $hasPin = trim((string) ($member['access_pin'] ?? '')) !== ''; ?>
<tr>
<td><?= e($member['display_name']) ?></td>
<td><?= e((string) ($member['login_email'] ?: $member['email'])) ?></td>
<td><?= e((string) ($member['access_pin'] ?? '')) ?></td>
<td>
<?php if ($loginEmail !== ''): ?>
<strong>aktiv</strong><br>
<small><?= e($loginEmail) ?></small>
<?php else: ?>
<span class="muted">nur Listenprofil</span>
<?php endif; ?>
</td>
<td>
<?php if ($hasPin): ?>
<code>****</code> hinterlegt
<?php else: ?>
<span class="muted">nicht gesetzt</span>
<?php endif; ?>
</td>
<td><?= e($member['status']) ?></td>
</tr>
<?php endforeach; ?>
+21 -2
View File
@@ -1,9 +1,18 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Klassische Liste</span>
<span class="eyebrow">Backoffice</span>
<h1>Papierlisten und Nacherfassung</h1>
<p>Die klassische Kaffeeliste bleibt erhalten, wird aber als geordneter Beleg mit sauberem Posting ins Ledger behandelt.</p>
<p>Die klassische Kaffeeliste bleibt erhalten, wird aber als geordneter Beleg mit sauberem Posting ins Ledger behandelt. Dieser Bereich ist bewusst fuer das Standortteam formuliert, nicht fuer Member-Self-Service.</p>
</section>
<section class="card">
<span class="eyebrow">Export & Korrekturen</span>
<ul class="feature-list">
<li>Druck- oder CSV-Export fuer Papierlisten: Platzhalter fuer spaetere Links</li>
<li>Rueckfragen zu einzelnen Positionen: Platzhalter fuer kuenftige Korrekturhinweise am Entwurf</li>
<li>Member-Self-Service bleibt von Entwurf, Posting und Nachbearbeitung getrennt</li>
</ul>
</section>
<div class="grid-three">
@@ -105,6 +114,11 @@
</tr>
</thead>
<tbody>
<?php if ($sheets === []): ?>
<tr>
<td colspan="4">Noch keine Papierlisten angelegt.</td>
</tr>
<?php endif; ?>
<?php foreach ($sheets as $sheet): ?>
<tr>
<td><?= e($sheet['title']) ?></td>
@@ -127,6 +141,11 @@
</tr>
</thead>
<tbody>
<?php if ($sheetLines === []): ?>
<tr>
<td colspan="4">Noch keine Positionen erfasst.</td>
</tr>
<?php endif; ?>
<?php foreach ($sheetLines as $line): ?>
<tr>
<td>#<?= e((string) $line['paper_sheet_id']) ?></td>
+16 -2
View File
@@ -1,9 +1,18 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Finanzen</span>
<span class="eyebrow">Backoffice</span>
<h1>Einzahlungen verbuchen</h1>
<p>Einzahlungen laufen ins selbe Ledger und reduzieren den offenen Saldo des Mitglieds nachvollziehbar.</p>
<p>Diese Seite bleibt bewusst beim Standortteam. Einzahlungen laufen ins selbe Ledger und reduzieren offene Salden nachvollziehbar, waehrend Mitglieder nur die Selbstbuchung sehen.</p>
</section>
<section class="card">
<span class="eyebrow">Export & Korrekturen</span>
<ul class="feature-list">
<li>Kassenexport oder Monatsabschluss: Platzhalter fuer kuenftige Download-Links</li>
<li>Rueckfragen zu Bareinzahlungen: Platzhalter fuer spaetere Korrekturhinweise direkt am Vorgang</li>
<li>Member-Self-Service blendet diesen Backoffice-Bereich bewusst aus</li>
</ul>
</section>
<div class="two-column">
@@ -43,6 +52,11 @@
</tr>
</thead>
<tbody>
<?php if ($payments === []): ?>
<tr>
<td colspan="4">Noch keine Einzahlungen verbucht.</td>
</tr>
<?php endif; ?>
<?php foreach ($payments as $payment): ?>
<tr>
<td><?= e($payment['occurred_at']) ?></td>
+26 -2
View File
@@ -1,9 +1,18 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Integration</span>
<span class="eyebrow">Pilot & Integration</span>
<h1>RFID-Roadmap und Event-Inbox</h1>
<p>Das MVP speichert Geraete, Karten und rohe Events bereits produktionsnah, ohne die spaetere Hardwareentscheidung vorwegzunehmen.</p>
<p>Das MVP speichert Geraete, Karten und rohe Events bereits produktionsnah, ohne die spaetere Hardwareentscheidung vorwegzunehmen. Dieser Bereich bleibt klar beim Standortteam und der Pilotbetreuung.</p>
</section>
<section class="card">
<span class="eyebrow">Export & Korrekturen</span>
<ul class="feature-list">
<li>Event-Export oder Inbox-Download: Platzhalter fuer spaetere Support-Links</li>
<li>Karten- und Geraetekorrekturen: Platzhalter fuer Hinweise direkt an den Tabellen</li>
<li>Member-Self-Service blendet RFID-Setup bewusst aus, bis der Pilot weiter ist</li>
</ul>
</section>
<div class="grid-three">
@@ -68,6 +77,11 @@
</tr>
</thead>
<tbody>
<?php if ($devices === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Geraete vorbereitet.</td>
</tr>
<?php endif; ?>
<?php foreach ($devices as $device): ?>
<tr>
<td><?= e($device['name']) ?></td>
@@ -90,6 +104,11 @@
</tr>
</thead>
<tbody>
<?php if ($tags === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Karten zugewiesen.</td>
</tr>
<?php endif; ?>
<?php foreach ($tags as $tag): ?>
<tr>
<td><?= e((string) ($tag['label'] ?: 'ohne Label')) ?></td>
@@ -114,6 +133,11 @@
</tr>
</thead>
<tbody>
<?php if ($events === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Events empfangen.</td>
</tr>
<?php endif; ?>
<?php foreach ($events as $event): ?>
<tr>
<td><?= e($event['received_at']) ?></td>