design anpassung

This commit is contained in:
2026-06-23 00:06:31 +02:00
parent 1891ec0a51
commit cbea23083d
30 changed files with 785 additions and 547 deletions
+40 -44
View File
@@ -4,44 +4,46 @@ $isSelfService = $tenantRole === 'member';
$selectedMemberId = (string) old('member_id');
$selectedProductId = (string) old('product_id');
$selectedSourceCode = (string) old('source_code', 'digital_self');
$sourceLabels = [
'digital_self' => 'Selbst gebucht',
'paper_sheet' => 'Papierliste',
'admin_backoffice' => 'Nachgetragen durch Verwaltung',
'rfid_reader' => 'Kartenleser',
];
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow"><?= e($isSelfService ? 'Selbstbuchung' : 'Digitale Erfassung') ?></span>
<h1><?= e($isSelfService ? 'Getraenke buchen' : 'Digitale Buchungen') ?></h1>
<span class="eyebrow"><?= e($isSelfService ? 'Mein Kaffee' : 'Kaffeekasse') ?></span>
<h1>Kaffee buchen</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>
<p>Wählen Sie, was Sie genommen haben. Die Buchung landet direkt auf Ihrem Kaffeekonto.</p>
<?php else: ?>
<p>Browser, Tablet und Kiosk landen im selben Ledger. Das Standortteam kann hier Selbstbuchung und Backoffice-Buchungen sichtbar trennen.</p>
<p>Wählen Sie Mitglied, Produkt und Menge. Die Kaffeekasse berechnet den Betrag sofort mit dem hinterlegten Preis.</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 class="section-actions">
<a class="button button-secondary" href="<?= e(tenant_url($tenant['slug'], 'bookings/export')) ?>">Buchungen als CSV exportieren</a>
</section>
<div class="two-column">
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'bookings')) ?>">
<?= csrf_field($csrf) ?>
<label>
<span><?= e($isSelfService ? 'Dein Mitgliedseintrag' : 'Mitglied') ?></span>
<select name="member_id" required>
<?php foreach ($members as $member): ?>
<option value="<?= e((string) $member['id']) ?>" <?= $selectedMemberId === (string) $member['id'] ? 'selected' : '' ?>><?= e($member['display_name']) ?></option>
<?php endforeach; ?>
</select>
</label>
<?php if ($isSelfService): ?>
<?php if (is_array($currentMember)): ?>
<p class="form-note">Buchung für <?= e($currentMember['display_name']) ?></p>
<?php endif; ?>
<?php else: ?>
<label>
<span>Mitglied</span>
<select name="member_id" required>
<?php foreach ($members as $member): ?>
<option value="<?= e((string) $member['id']) ?>" <?= $selectedMemberId === (string) $member['id'] ? 'selected' : '' ?>><?= e($member['display_name']) ?></option>
<?php endforeach; ?>
</select>
</label>
<?php endif; ?>
<label>
<span>Produkt</span>
<select name="product_id" required>
@@ -54,10 +56,10 @@ require __DIR__ . '/../partials/layout-top.php';
<input type="hidden" name="source_code" value="digital_self">
<?php else: ?>
<label>
<span>Quelle</span>
<span>Buchungsart</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>
<option value="digital_self" <?= $selectedSourceCode === 'digital_self' ? 'selected' : '' ?>>Selbst gebucht</option>
<option value="admin_backoffice" <?= $selectedSourceCode === 'admin_backoffice' ? 'selected' : '' ?>>Nachgetragen durch Verwaltung</option>
</select>
</label>
<?php endif; ?>
@@ -66,34 +68,27 @@ require __DIR__ . '/../partials/layout-top.php';
<input type="number" name="quantity" min="0.25" step="0.25" value="<?= e((string) old('quantity', '1')) ?>" required>
</label>
<label>
<span>Verbrauchszeitpunkt</span>
<span>Wann genommen?</span>
<input type="datetime-local" name="effective_at" value="<?= e((string) old('effective_at', gmdate('Y-m-d\TH:i'))) ?>" required>
</label>
<label>
<span>Notiz</span>
<span>Notiz, falls nötig</span>
<input type="text" name="notes" value="<?= e((string) old('notes')) ?>">
</label>
<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; ?>
<button class="button button-primary" type="submit">Auf Kaffeekonto buchen</button>
</form>
<?php if ($isSelfService): ?>
<section class="card">
<span class="eyebrow">Pilot-Hinweis</span>
<span class="eyebrow">Hinweise</span>
<ul class="stack-list">
<li>
<strong>Eigene Historie</strong>
<small>Ein persoenlicher Verlauf wird spaeter hier eingeblendet.</small>
<strong>Ihr Verlauf</strong>
<small>Ihre Buchungen können Sie als Tabelle exportieren.</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>
<small>Falsche Buchungen bitte direkt an die verantwortliche Person melden.</small>
</li>
</ul>
</section>
@@ -105,22 +100,23 @@ require __DIR__ . '/../partials/layout-top.php';
<th>Zeit</th>
<th>Mitglied</th>
<th>Produkt</th>
<th>Quelle</th>
<th>Buchungsart</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
<?php if ($bookings === []): ?>
<tr>
<td colspan="5">Noch keine Buchungen erfasst.</td>
<td class="table-empty" colspan="5">Noch keine Kaffee-Buchungen. Die erste Buchung können Sie links erfassen.</td>
</tr>
<?php endif; ?>
<?php foreach ($bookings as $booking): ?>
<?php $sourceLabel = $sourceLabels[(string) ($booking['source_code'] ?? '')] ?? (string) $booking['source_name']; ?>
<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($sourceLabel) ?></td>
<td><?= e(money_from_cents((int) $booking['total_cents'])) ?></td>
</tr>
<?php endforeach; ?>
+62 -19
View File
@@ -1,17 +1,23 @@
<?php
$tenantRole = (string) ($authState['tenant_role'] ?? 'member');
$isSelfService = $tenantRole === 'member';
$sourceLabels = [
'digital_self' => 'Selbst gebucht',
'paper_sheet' => 'Papierliste',
'admin_backoffice' => 'Nachgetragen durch Verwaltung',
'rfid_reader' => 'Kartenleser',
];
require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head inline-head">
<div>
<span class="eyebrow"><?= e($isSelfService ? 'Member-Self-Service' : ucfirst((string) $tenant['plan']) . ' Paket') ?></span>
<span class="eyebrow"><?= e($isSelfService ? 'Mein Kaffeekonto' : 'Kaffeekasse') ?></span>
<h1><?= e($tenant['name']) ?></h1>
<?php if ($isSelfService): ?>
<p>Rolle: <?= e($tenantRole) ?>. Dieser Zugang ist fuer die taegliche Selbstbuchung gedacht; Stammdaten, Saldenpflege und Korrekturen bleiben beim Standortteam.</p>
<p>Buchen Sie hier Ihren Kaffee oder andere Produkte und sehen Sie Ihren aktuellen offenen Betrag. Korrekturen übernimmt die verantwortliche Person.</p>
<?php else: ?>
<p>Rolle: <?= e($tenantRole) ?>. Hier laufen digitale Buchungen, Backoffice-Korrekturen und Standortzahlen zusammen.</p>
<p>Hier sehen Sie, wer Kaffee gebucht hat, welche Beträge offen sind und was als Nächstes in der Kaffeekasse gepflegt werden muss.</p>
<?php endif; ?>
</div>
<form method="post" action="<?= e(tenant_url($tenant['slug'], 'logout')) ?>">
@@ -20,31 +26,67 @@ require __DIR__ . '/../partials/layout-top.php';
</form>
</section>
<section class="action-strip">
<a class="quick-action primary" href="<?= e(tenant_url($tenant['slug'], 'bookings')) ?>">
<strong><?= e($isSelfService ? 'Kaffee buchen' : 'Kaffee oder Produkt buchen') ?></strong>
<span><?= e($isSelfService ? 'Kaffee, Getränke oder Snacks auf Ihren Namen buchen' : 'Verbrauch direkt einem Mitglied zuordnen') ?></span>
</a>
<?php if ($isSelfService): ?>
<a class="quick-action" href="<?= e(tenant_url($tenant['slug'], 'bookings/export')) ?>">
<strong>Verlauf exportieren</strong>
<span>Eigene Buchungen als Tabelle herunterladen</span>
</a>
<?php else: ?>
<a class="quick-action" href="<?= e(tenant_url($tenant['slug'], 'members')) ?>">
<strong>Mitglied anlegen</strong>
<span>Person für Buchung, Papierliste oder Login hinzufügen</span>
</a>
<a class="quick-action" href="<?= e(tenant_url($tenant['slug'], 'payments')) ?>">
<strong>Geld einzahlen</strong>
<span>Barzahlung, Überweisung oder Sammelkasse zuordnen</span>
</a>
<a class="quick-action" href="<?= e(tenant_url($tenant['slug'], 'products')) ?>">
<strong>Produkte verwalten</strong>
<span>Kaffee, Getränke und Preise pflegen</span>
</a>
<?php endif; ?>
</section>
<?php if ($isSelfService): ?>
<section class="metric-cards">
<article class="metric-card">
<span>Produkte</span>
<strong><?= e((string) $summary['product_count']) ?></strong>
</article>
<article class="metric-card">
<span>Offener Betrag</span>
<strong><?= e(money_from_cents((int) $summary['open_balance_cents'])) ?></strong>
</article>
</section>
<div class="two-column">
<section class="card">
<span class="eyebrow">Was du hier tun kannst</span>
<span class="eyebrow">Ihr Kaffeekonto</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>
<li>Kaffee, Getränke oder Snacks auf Ihren Namen buchen</li>
<li>Preise vor dem Buchen einsehen</li>
<li>Bei Fragen die verantwortliche Person informieren</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>
<span class="eyebrow">Kaffeekasse</span>
<ul class="stack-list">
<li>
<strong>Stammdaten und Mitglieder</strong>
<small>Pflegt weiterhin das Standortteam im Backoffice.</small>
<small>Pflegen die verantwortlichen Personen der Kaffeeliste.</small>
</li>
<li>
<strong>Einzahlungen und Papierlisten</strong>
<small>Werden separat verbucht und bei Bedarf dort korrigiert.</small>
<small>Werden von den verantwortlichen Personen verbucht und bei Bedarf korrigiert.</small>
</li>
<li>
<strong>Exporte und persoenliche Historie</strong>
<small>Werden im Pilot noch schrittweise nachgezogen.</small>
<strong>Eigener Verlauf</strong>
<small>Kann über den Export heruntergeladen werden.</small>
</li>
</ul>
</section>
@@ -64,7 +106,7 @@ require __DIR__ . '/../partials/layout-top.php';
<strong><?= e((string) $summary['paper_sheet_count']) ?></strong>
</article>
<article class="metric-card">
<span>Offene Salden</span>
<span>Offene Beträge</span>
<strong><?= e(money_from_cents((int) $summary['open_balance_cents'])) ?></strong>
</article>
</section>
@@ -76,25 +118,26 @@ require __DIR__ . '/../partials/layout-top.php';
<?php if ($latestEvents === []): ?>
<li>
<strong>Noch keine Buchungen</strong>
<small>Die letzten Ereignisse erscheinen hier, sobald der Pilotstandort aktiv bucht.</small>
<small>Die letzten Buchungen erscheinen hier, sobald die erste Kaffeebuchung erfasst wurde.</small>
</li>
<?php endif; ?>
<?php foreach ($latestEvents as $event): ?>
<?php $sourceLabel = $sourceLabels[(string) ($event['source_code'] ?? '')] ?? (string) $event['source_name']; ?>
<li>
<strong><?= e($event['member_name']) ?></strong>
<span><?= e($event['product_name']) ?> via <?= e($event['source_name']) ?></span>
<span><?= e($event['product_name']) ?>, gebucht über <?= e($sourceLabel) ?></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>
<span class="eyebrow">Offene Beträge</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>
<strong>Noch keine offenen Beträge</strong>
<small>Sobald Buchungen oder Einzahlungen vorliegen, erscheint hier die Übersicht.</small>
</li>
<?php endif; ?>
<?php foreach ($memberBalances as $balance): ?>
+11 -11
View File
@@ -5,34 +5,34 @@ require __DIR__ . '/../partials/layout-top.php';
?>
<section class="page-head">
<span class="eyebrow">Standortzugang</span>
<span class="eyebrow">Kaffeeliste</span>
<h1><?= e($tenant['name']) ?></h1>
<p>Login fuer Owner, Standortteam und Mitglieder-Self-Service im Pilotbetrieb. Verwaltungsseiten bleiben bewusst dem Standortteam vorbehalten.</p>
<p>Melden Sie sich an, um Kaffee zu buchen, den eigenen Verlauf zu sehen oder die Kaffeeliste zu verwalten.</p>
</section>
<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>
<span>E-Mail-Adresse</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>
<button class="button button-primary" type="submit">Anmelden</button>
<p class="muted">Passwort vergessen? <a class="text-link" href="<?= e($resetRequestUrl) ?>">Passwort zurücksetzen</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>
<span class="eyebrow">Zugang</span>
<h2>Je nach Rolle buchen oder verwalten</h2>
<p class="muted">Mitglieder erfassen ihren Verbrauch. Verantwortliche Personen sehen zusätzlich Mitglieder, Einzahlungen, Papierlisten und Korrekturen.</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>
<li>Der Zugang zur Kaffeeliste ist von der Plattformverwaltung getrennt</li>
<li>PINs und Karten bleiben vom Passwortwechsel unberührt</li>
<li>Bei Fragen hilft die verantwortliche Person der Kaffeeliste</li>
</ul>
</aside>
</div>
+17 -22
View File
@@ -1,42 +1,32 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<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>
<span class="eyebrow">Kaffeeliste</span>
<h1>Mitglieder der Kaffeeliste</h1>
<p>Legen Sie alle Personen an, die Kaffee buchen oder auf Papierlisten stehen. Ein Login ist nur nötig, wenn Mitglieder selbst buchen sollen. PINs werden nach dem Speichern nicht mehr im Klartext angezeigt.</p>
</section>
<div class="two-column">
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'members')) ?>">
<?= csrf_field($csrf) ?>
<label>
<span>Anzeigename</span>
<span>Name auf der Kaffeeliste</span>
<input type="text" name="display_name" value="<?= e((string) old('display_name')) ?>" required>
</label>
<label>
<span>Login-E-Mail fuer Self-Service</span>
<span>E-Mail für eigenen Login</span>
<input type="email" name="email" value="<?= e((string) old('email')) ?>">
</label>
<label>
<span>Startpasswort fuer den ersten Login</span>
<span>Startpasswort für Selbstbuchung</span>
<input type="password" name="login_password" minlength="12">
</label>
<label>
<span>Optionale PIN fuer Kiosk / Tablet</span>
<span>PIN für Kiosk oder 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>
<button class="button button-primary" type="submit">Mitglied anlegen</button>
<p class="muted">Ohne E-Mail bleibt das Mitglied ein Listenprofil für Nachträge und Papierlisten.</p>
</form>
<section class="table-card">
@@ -44,12 +34,17 @@
<thead>
<tr>
<th>Name</th>
<th>Login</th>
<th>PIN-Status</th>
<th>Selbstbuchung</th>
<th>Kiosk-PIN</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if ($members === []): ?>
<tr>
<td class="table-empty" colspan="4">Noch keine Mitglieder. Legen Sie das erste Mitglied links im Formular an.</td>
</tr>
<?php endif; ?>
<?php foreach ($members as $member): ?>
<?php $loginEmail = (string) ($member['login_email'] ?: $member['email']); ?>
<?php $hasPin = trim((string) ($member['access_pin'] ?? '')) !== ''; ?>
@@ -60,7 +55,7 @@
<strong>aktiv</strong><br>
<small><?= e($loginEmail) ?></small>
<?php else: ?>
<span class="muted">nur Listenprofil</span>
<span class="muted">nur Verwaltung oder Papierliste</span>
<?php endif; ?>
</td>
<td>
+17 -24
View File
@@ -1,30 +1,22 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<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. 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>
<span class="eyebrow">Papierlisten</span>
<h1>Papierliste nachtragen</h1>
<p>Übertragen Sie Strichlisten oder Thekenzettel in die Kaffeekasse. Erst beim Abschließen werden die Positionen als Buchungen gezählt.</p>
</section>
<div class="grid-three">
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'paper-sheets')) ?>">
<?= csrf_field($csrf) ?>
<input type="hidden" name="action" value="create_sheet">
<h2>Neue Papierliste</h2>
<label>
<span>Titel</span>
<input type="text" name="title" value="<?= e((string) old('title', 'Wochenliste Kaffee')) ?>" required>
<span>Name der Liste</span>
<input type="text" name="title" value="<?= e((string) old('title', 'Wochenliste')) ?>" required>
</label>
<label>
<span>Periodenlabel</span>
<span>Zeitraum anzeigen als</span>
<input type="text" name="period_label" value="<?= e((string) old('period_label', 'KW ' . gmdate('W'))) ?>">
</label>
<label>
@@ -45,6 +37,7 @@
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'paper-sheets')) ?>">
<?= csrf_field($csrf) ?>
<input type="hidden" name="action" value="add_line">
<h2>Position von der Liste übernehmen</h2>
<label>
<span>Papierliste</span>
<select name="paper_sheet_id" required>
@@ -76,26 +69,26 @@
<input type="number" name="quantity" min="0.25" step="0.25" value="<?= e((string) old('quantity', '1')) ?>" required>
</label>
<label>
<span>Verbrauchszeitpunkt</span>
<span>Wann genommen?</span>
<input type="datetime-local" name="effective_at" value="<?= e((string) old('effective_at', gmdate('Y-m-d\TH:i'))) ?>">
</label>
<label>
<span>Notiz</span>
<input type="text" name="note" value="<?= e((string) old('note')) ?>">
</label>
<button class="button button-primary" type="submit">Position erfassen</button>
<button class="button button-primary" type="submit">Position zur Papierliste hinzufügen</button>
</form>
<section class="card">
<span class="eyebrow">Posting</span>
<p class="muted">Wenn ein Entwurf vollstaendig ist, wird er mit einem Klick in dieselbe Ledger-Logik uebernommen wie digitale Buchungen.</p>
<span class="eyebrow">In Buchungen übernehmen</span>
<p class="muted">Prüfen Sie die Positionen. Nach dem Abschließen erscheinen sie als normale Buchungen in der Kaffeekasse.</p>
<?php foreach ($sheets as $sheet): ?>
<?php if ($sheet['status'] === 'draft'): ?>
<form class="inline-form" method="post" action="<?= e(tenant_url($tenant['slug'], 'paper-sheets')) ?>">
<?= csrf_field($csrf) ?>
<input type="hidden" name="action" value="post_sheet">
<input type="hidden" name="paper_sheet_id" value="<?= e((string) $sheet['id']) ?>">
<button class="button button-secondary" type="submit"><?= e($sheet['title']) ?> verbuchen</button>
<button class="button button-secondary" type="submit"><?= e($sheet['title']) ?> als Buchungen übernehmen</button>
</form>
<?php endif; ?>
<?php endforeach; ?>
@@ -109,14 +102,14 @@
<tr>
<th>Titel</th>
<th>Status</th>
<th>Zeilen</th>
<th>Positionen</th>
<th>Zeitraum</th>
</tr>
</thead>
<tbody>
<?php if ($sheets === []): ?>
<tr>
<td colspan="4">Noch keine Papierlisten angelegt.</td>
<td class="table-empty" colspan="4">Noch keine Papierlisten. Legen Sie zuerst die erste Liste an.</td>
</tr>
<?php endif; ?>
<?php foreach ($sheets as $sheet): ?>
@@ -134,7 +127,7 @@
<table>
<thead>
<tr>
<th>Papierliste</th>
<th>Liste</th>
<th>Mitglied</th>
<th>Produkt</th>
<th>Menge</th>
@@ -143,7 +136,7 @@
<tbody>
<?php if ($sheetLines === []): ?>
<tr>
<td colspan="4">Noch keine Positionen erfasst.</td>
<td class="table-empty" colspan="4">Noch keine Striche oder Positionen übertragen.</td>
</tr>
<?php endif; ?>
<?php foreach ($sheetLines as $line): ?>
+13 -22
View File
@@ -1,18 +1,9 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Backoffice</span>
<h1>Einzahlungen verbuchen</h1>
<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>
<span class="eyebrow">Kaffeekasse</span>
<h1>Geld einzahlen</h1>
<p>Tragen Sie Bargeld, Überweisungen oder Sammelzahlungen ein. Der offene Betrag des Mitglieds sinkt um diese Einzahlung.</p>
</section>
<div class="two-column">
@@ -27,18 +18,18 @@
</select>
</label>
<label>
<span>Betrag in EUR</span>
<span>Eingezahlter Betrag</span>
<input type="number" name="amount_eur" min="0.50" step="0.50" value="<?= e((string) old('amount_eur', '5.00')) ?>" required>
</label>
<label>
<span>Zeitpunkt</span>
<span>Wann bezahlt?</span>
<input type="datetime-local" name="occurred_at" value="<?= e((string) old('occurred_at', gmdate('Y-m-d\TH:i'))) ?>" required>
</label>
<label>
<span>Notiz</span>
<input type="text" name="notes" value="<?= e((string) old('notes')) ?>" placeholder="Barzahlung, Monatsbeitrag, ...">
<input type="text" name="notes" value="<?= e((string) old('notes')) ?>" placeholder="Bar, Überweisung, PayPal, Sammelkasse">
</label>
<button class="button button-primary" type="submit">Einzahlung speichern</button>
<button class="button button-primary" type="submit">Einzahlung dem Mitglied gutschreiben</button>
</form>
<section class="table-card">
@@ -47,16 +38,16 @@
<tr>
<th>Zeit</th>
<th>Mitglied</th>
<th>Beschreibung</th>
<th>Zahlungsnotiz</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
<?php if ($payments === []): ?>
<tr>
<td colspan="4">Noch keine Einzahlungen verbucht.</td>
</tr>
<?php endif; ?>
<?php if ($payments === []): ?>
<tr>
<td class="table-empty" colspan="4">Noch keine Einzahlungen erfasst. Tragen Sie links die erste Zahlung in die Kaffeekasse ein.</td>
</tr>
<?php endif; ?>
<?php foreach ($payments as $payment): ?>
<tr>
<td><?= e($payment['occurred_at']) ?></td>
+8 -3
View File
@@ -1,9 +1,9 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<span class="eyebrow">Sortiment</span>
<span class="eyebrow">Kaffee und Produkte</span>
<h1>Produkte und Preise</h1>
<p>Die Preislogik bleibt versionierbar. Jede Buchung friert den zum Zeitpunkt gueltigen Preis ein.</p>
<p>Legen Sie fest, was gebucht werden kann: Kaffee, Getränke, Snacks oder Pauschalen. Preisänderungen gelten nur für neue Buchungen.</p>
</section>
<div class="two-column">
@@ -25,12 +25,17 @@
<thead>
<tr>
<th>Produkt</th>
<th>SKU</th>
<th>Kurzkennung</th>
<th>Preis</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if ($products === []): ?>
<tr>
<td class="table-empty" colspan="4">Noch keine Produkte. Legen Sie zuerst z. B. Kaffee, Espresso oder Tee an.</td>
</tr>
<?php endif; ?>
<?php foreach ($products as $product): ?>
<tr>
<td><?= e($product['name']) ?></td>
+19 -28
View File
@@ -1,18 +1,9 @@
<?php require __DIR__ . '/../partials/layout-top.php'; ?>
<section class="page-head">
<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. 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>
<span class="eyebrow">RFID-Kaffeekasse</span>
<h1>Kartenleser einrichten</h1>
<p>Richten Sie Kartenleser ein und verbinden Sie Karten mit Mitgliedern. Eingelesene Karten erscheinen unten als Ereignisse.</p>
</section>
<div class="grid-three">
@@ -20,14 +11,14 @@
<?= csrf_field($csrf) ?>
<input type="hidden" name="action" value="create_device">
<label>
<span>Geraetename</span>
<span>Name für diesen Leser</span>
<input type="text" name="name" value="<?= e((string) old('name')) ?>" required>
</label>
<label>
<span>Standort</span>
<span>Ort am Standort</span>
<input type="text" name="location_label" value="<?= e((string) old('location_label')) ?>">
</label>
<button class="button button-primary" type="submit">RFID-Geraet vorbereiten</button>
<button class="button button-primary" type="submit">Kartenleser anlegen</button>
</form>
<form class="card form-card" method="post" action="<?= e(tenant_url($tenant['slug'], 'rfid')) ?>">
@@ -42,26 +33,26 @@
</select>
</label>
<label>
<span>Karten-UID</span>
<span>Kartennummer</span>
<input type="text" name="uid" value="<?= e((string) old('uid')) ?>" required>
</label>
<label>
<span>Label</span>
<span>Kartenlabel</span>
<input type="text" name="label" value="<?= e((string) old('label')) ?>">
</label>
<button class="button button-primary" type="submit">Karte zuweisen</button>
<button class="button button-primary" type="submit">Karte mit Mitglied verbinden</button>
</form>
<section class="card">
<span class="eyebrow">API</span>
<p class="muted">POST `/api/rfid/intake` mit JSON-Body:</p>
<span class="eyebrow">Schnittstelle für den Leser</span>
<p class="muted">Der Leser sendet Ereignisse in diesem Format:</p>
<pre class="code-block">{
"device_token": "...",
"uid": "04AABBCCDD",
"event_id": "reader-0001",
"ts": "2026-06-15T09:30:00Z"
}</pre>
<p class="muted">Events werden tenant-scharf gespeichert und spaeter in Buchungsregeln ueberfuehrt.</p>
<p class="muted">Ein Ereignis ist noch keine Kaffeebuchung. Es zeigt zuerst, welche Karte an welchem Leser erkannt wurde.</p>
</section>
</div>
@@ -70,16 +61,16 @@
<table>
<thead>
<tr>
<th>Geraet</th>
<th>Kartenleser</th>
<th>Standort</th>
<th>Status</th>
<th>Token</th>
<th>Geräte-Token</th>
</tr>
</thead>
<tbody>
<?php if ($devices === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Geraete vorbereitet.</td>
<td class="table-empty" colspan="4">Noch kein Kartenleser angelegt. Legen Sie oben den ersten Leser an.</td>
</tr>
<?php endif; ?>
<?php foreach ($devices as $device): ?>
@@ -106,7 +97,7 @@
<tbody>
<?php if ($tags === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Karten zugewiesen.</td>
<td class="table-empty" colspan="4">Noch keine Karten mit Mitgliedern verbunden.</td>
</tr>
<?php endif; ?>
<?php foreach ($tags as $tag): ?>
@@ -127,15 +118,15 @@
<thead>
<tr>
<th>Zeit</th>
<th>Geraet</th>
<th>Kartenleser</th>
<th>Status</th>
<th>Event-ID</th>
<th>Ereignis-ID</th>
</tr>
</thead>
<tbody>
<?php if ($events === []): ?>
<tr>
<td colspan="4">Noch keine RFID-Events empfangen.</td>
<td class="table-empty" colspan="4">Noch keine Karte wurde von einem Leser gemeldet.</td>
</tr>
<?php endif; ?>
<?php foreach ($events as $event): ?>