This commit is contained in:
2026-03-31 14:48:36 +02:00
commit 6eb940c37c
50 changed files with 4433 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
<script>
import '../app.css';
</script>
<slot />

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { onMount } from 'svelte';
import { api, type Company } from '$lib/api/client';
let companies: Company[] = [];
let loading = true;
onMount(async () => {
companies = await api.getCompanies();
loading = false;
});
</script>
<svelte:head>
<title>Strichliste</title>
</svelte:head>
<div class="page-header">
<h1>Strichliste</h1>
<p style="color: var(--color-text-muted); margin-top: 4px;">Firma auswählen</p>
</div>
{#if loading}
<div style="text-align: center; padding: 48px;">
<p>Laden...</p>
</div>
{:else}
<div class="card-grid" style="padding: 24px;">
{#each companies as company}
<a href="/company/{company.id}" class="card">
<div style="font-size: 2.5rem;">🏢</div>
<h3>{company.name}</h3>
</a>
{/each}
</div>
{/if}

View File

@@ -0,0 +1,228 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { api, type Employee, type MonthlyReport, type EmployeeReportLine } from '$lib/api/client';
let token = '';
let employees: Employee[] = [];
let report: MonthlyReport | null = null;
let selectedEmployee: EmployeeReportLine | null = null;
let loading = true;
let selectedMonth = new Date().toISOString().slice(0, 7);
// Modal state
let showModal = false;
let editId: number | null = null;
let formFirstName = '';
let formLastName = '';
$: token = $page.url.searchParams.get('token') ?? '';
onMount(async () => {
if (!token) return;
await loadData();
});
async function loadData() {
loading = true;
[employees, report] = await Promise.all([
api.companyAdmin.getEmployees(token),
api.companyAdmin.getReport(token, selectedMonth)
]);
loading = false;
}
async function changeMonth() {
report = await api.companyAdmin.getReport(token, selectedMonth);
selectedEmployee = null;
}
function openCreate() {
editId = null;
formFirstName = '';
formLastName = '';
showModal = true;
}
function openEdit(emp: Employee) {
editId = emp.id;
formFirstName = emp.firstName;
formLastName = emp.lastName;
showModal = true;
}
async function saveEmployee() {
if (editId) {
await api.companyAdmin.updateEmployee(token, editId, formFirstName, formLastName);
} else {
await api.companyAdmin.createEmployee(token, formFirstName, formLastName);
}
showModal = false;
await loadData();
}
async function toggleEmployee(id: number) {
await api.companyAdmin.toggleEmployee(token, id);
await loadData();
}
async function showEmployeeDetail(employeeId: number) {
selectedEmployee = await api.companyAdmin.getEmployeeReport(token, employeeId, selectedMonth);
}
function formatPrice(cents: number): string {
return (cents / 100).toFixed(2).replace('.', ',') + ' €';
}
</script>
<svelte:head>
<title>Firmen-Admin - Strichliste</title>
</svelte:head>
<div class="admin-container">
<div class="admin-header">
<h1>Firmen-Administration</h1>
</div>
{#if !token}
<p>Kein Zugangstoken angegeben.</p>
{:else if loading}
<p>Laden...</p>
{:else}
<!-- Mitarbeiterverwaltung -->
<section style="margin-bottom: 40px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Mitarbeiter</h2>
<button class="btn btn-primary" on:click={openCreate}>+ Mitarbeiter anlegen</button>
</div>
<table class="admin-table">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{#each employees as emp}
<tr>
<td>{emp.firstName} {emp.lastName}</td>
<td>
<span class="badge" class:badge-active={emp.active} class:badge-inactive={!emp.active}>
{emp.active ? 'Aktiv' : 'Inaktiv'}
</span>
</td>
<td>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => openEdit(emp)}>Bearbeiten</button>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; margin-left: 8px;" on:click={() => toggleEmployee(emp.id)}>
{emp.active ? 'Deaktivieren' : 'Aktivieren'}
</button>
</td>
</tr>
{/each}
</tbody>
</table>
</section>
<!-- Monatsauswertung -->
<section>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Monatsauswertung</h2>
<div class="month-selector">
<input type="month" bind:value={selectedMonth} on:change={changeMonth} />
</div>
</div>
{#if report}
<table class="admin-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th style="text-align: right;">Anzahl</th>
<th style="text-align: right;">Summe</th>
<th>Detail</th>
</tr>
</thead>
<tbody>
{#each report.employees as line}
<tr>
<td>{line.firstName} {line.lastName}</td>
<td style="text-align: right;">{line.totalCount}×</td>
<td style="text-align: right;" class="price">{formatPrice(line.totalCents)}</td>
<td>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => showEmployeeDetail(line.employeeId)}>Details</button>
</td>
</tr>
{/each}
{#if report.employees.length > 0}
<tr style="font-weight: 700;">
<td>Gesamt</td>
<td></td>
<td style="text-align: right;" class="price">{formatPrice(report.totalCents)}</td>
<td></td>
</tr>
{:else}
<tr><td colspan="4" style="text-align: center; color: var(--color-text-muted);">Keine Einträge in diesem Monat</td></tr>
{/if}
</tbody>
</table>
{/if}
{#if selectedEmployee}
<div style="margin-top: 24px; background: var(--color-bg-secondary); padding: 20px; border-radius: var(--radius);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h3>Detail: {selectedEmployee.firstName} {selectedEmployee.lastName}</h3>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => selectedEmployee = null}>Schließen</button>
</div>
<table class="admin-table" style="margin-top: 12px;">
<thead>
<tr>
<th>Produkt</th>
<th style="text-align: right;">Einzelpreis</th>
<th style="text-align: right;">Anzahl</th>
<th style="text-align: right;">Summe</th>
</tr>
</thead>
<tbody>
{#each selectedEmployee.products as prod}
<tr>
<td>{prod.productName}</td>
<td style="text-align: right;" class="price">{formatPrice(prod.priceCents)}</td>
<td style="text-align: right;">{prod.count}×</td>
<td style="text-align: right;" class="price">{formatPrice(prod.totalCents)}</td>
</tr>
{/each}
<tr style="font-weight: 700;">
<td>Gesamt</td>
<td></td>
<td style="text-align: right;">{selectedEmployee.totalCount}×</td>
<td style="text-align: right;" class="price">{formatPrice(selectedEmployee.totalCents)}</td>
</tr>
</tbody>
</table>
</div>
{/if}
</section>
{/if}
</div>
{#if showModal}
<div class="modal-overlay" on:click|self={() => showModal = false}>
<div class="modal">
<h2>{editId ? 'Mitarbeiter bearbeiten' : 'Neuer Mitarbeiter'}</h2>
<div class="form-group">
<label for="firstName">Vorname</label>
<input id="firstName" type="text" bind:value={formFirstName} />
</div>
<div class="form-group">
<label for="lastName">Nachname</label>
<input id="lastName" type="text" bind:value={formLastName} />
</div>
<div class="modal-actions">
<button class="btn btn-secondary" on:click={() => showModal = false}>Abbrechen</button>
<button class="btn btn-primary" on:click={saveEmployee}>Speichern</button>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,402 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { api, type Company, type Product, type ProviderReport, type AccessLink } from '$lib/api/client';
let token = '';
let activeTab: 'companies' | 'products' | 'report' | 'links' = 'companies';
// Data
let companies: Company[] = [];
let products: Product[] = [];
let report: ProviderReport | null = null;
let accessLinks: AccessLink[] = [];
let loading = true;
let selectedMonth = new Date().toISOString().slice(0, 7);
// Company Modal
let showCompanyModal = false;
let editCompanyId: number | null = null;
let formCompanyName = '';
// Product Modal
let showProductModal = false;
let editProductId: number | null = null;
let formProductName = '';
let formProductPrice = 0;
let formProductIcon = 'coffee';
// Link Modal
let showLinkModal = false;
let formLinkRole = 'COMPANY_ADMIN';
let formLinkCompanyId: number | null = null;
let formLinkDescription = '';
$: token = $page.url.searchParams.get('token') ?? '';
onMount(async () => {
if (!token) return;
await loadAll();
});
async function loadAll() {
loading = true;
[companies, products, accessLinks] = await Promise.all([
api.providerAdmin.getCompanies(token),
api.providerAdmin.getProducts(token),
api.providerAdmin.getAccessLinks(token)
]);
loading = false;
}
async function loadReport() {
report = await api.providerAdmin.getReport(token, selectedMonth);
}
async function switchTab(tab: typeof activeTab) {
activeTab = tab;
if (tab === 'report' && !report) {
await loadReport();
}
}
// --- Company CRUD ---
function openCreateCompany() {
editCompanyId = null;
formCompanyName = '';
showCompanyModal = true;
}
function openEditCompany(c: Company) {
editCompanyId = c.id;
formCompanyName = c.name;
showCompanyModal = true;
}
async function saveCompany() {
if (editCompanyId) {
await api.providerAdmin.updateCompany(token, editCompanyId, formCompanyName);
} else {
await api.providerAdmin.createCompany(token, formCompanyName);
}
showCompanyModal = false;
companies = await api.providerAdmin.getCompanies(token);
}
async function toggleCompany(id: number) {
await api.providerAdmin.toggleCompany(token, id);
companies = await api.providerAdmin.getCompanies(token);
}
// --- Product CRUD ---
function openCreateProduct() {
editProductId = null;
formProductName = '';
formProductPrice = 0;
formProductIcon = 'coffee';
showProductModal = true;
}
function openEditProduct(p: Product) {
editProductId = p.id;
formProductName = p.name;
formProductPrice = p.priceCents;
formProductIcon = p.iconPlaceholder;
showProductModal = true;
}
async function saveProduct() {
if (editProductId) {
await api.providerAdmin.updateProduct(token, editProductId, formProductName, formProductPrice, formProductIcon);
} else {
await api.providerAdmin.createProduct(token, formProductName, formProductPrice, formProductIcon);
}
showProductModal = false;
products = await api.providerAdmin.getProducts(token);
}
async function toggleProduct(id: number) {
await api.providerAdmin.toggleProduct(token, id);
products = await api.providerAdmin.getProducts(token);
}
// --- Access Links ---
function openCreateLink() {
formLinkRole = 'COMPANY_ADMIN';
formLinkCompanyId = companies.length > 0 ? companies[0].id : null;
formLinkDescription = '';
showLinkModal = true;
}
async function saveLink() {
await api.providerAdmin.createAccessLink(
token,
formLinkRole,
formLinkRole === 'COMPANY_ADMIN' ? formLinkCompanyId ?? undefined : undefined,
formLinkDescription
);
showLinkModal = false;
accessLinks = await api.providerAdmin.getAccessLinks(token);
}
function formatPrice(cents: number): string {
return (cents / 100).toFixed(2).replace('.', ',') + ' €';
}
function buildLink(link: AccessLink): string {
const base = window.location.origin;
if (link.role === 'COMPANY_ADMIN') {
return `${base}/admin/company?token=${link.token}`;
}
return `${base}/admin/provider?token=${link.token}`;
}
</script>
<svelte:head>
<title>Anbieter-Admin - Strichliste</title>
</svelte:head>
<div class="admin-container">
<div class="admin-header">
<h1>Anbieter-Administration</h1>
</div>
{#if !token}
<p>Kein Zugangstoken angegeben.</p>
{:else if loading}
<p>Laden...</p>
{:else}
<!-- Tab Navigation -->
<div style="display: flex; gap: 8px; margin-bottom: 24px; border-bottom: 1px solid var(--color-border); padding-bottom: 12px;">
{#each [
{ key: 'companies', label: 'Firmen' },
{ key: 'products', label: 'Produkte' },
{ key: 'report', label: 'Auswertung' },
{ key: 'links', label: 'Zugangslinks' }
] as tab}
<button
class="btn btn-secondary"
style="padding: 8px 20px; {activeTab === tab.key ? 'background: var(--color-primary); color: white; border-color: var(--color-primary);' : ''}"
on:click={() => switchTab(tab.key as typeof activeTab)}
>
{tab.label}
</button>
{/each}
</div>
<!-- Firmen -->
{#if activeTab === 'companies'}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Firmen</h2>
<button class="btn btn-primary" on:click={openCreateCompany}>+ Firma anlegen</button>
</div>
<table class="admin-table">
<thead>
<tr><th>Name</th><th>Status</th><th>Aktionen</th></tr>
</thead>
<tbody>
{#each companies as c}
<tr>
<td>{c.name}</td>
<td>
<span class="badge" class:badge-active={c.active} class:badge-inactive={!c.active}>
{c.active ? 'Aktiv' : 'Inaktiv'}
</span>
</td>
<td>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => openEditCompany(c)}>Bearbeiten</button>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; margin-left: 8px;" on:click={() => toggleCompany(c.id)}>
{c.active ? 'Deaktivieren' : 'Aktivieren'}
</button>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
<!-- Produkte -->
{#if activeTab === 'products'}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Produkte</h2>
<button class="btn btn-primary" on:click={openCreateProduct}>+ Produkt anlegen</button>
</div>
<table class="admin-table">
<thead>
<tr><th>Name</th><th style="text-align: right;">Preis</th><th>Icon</th><th>Status</th><th>Aktionen</th></tr>
</thead>
<tbody>
{#each products as p}
<tr>
<td>{p.name}</td>
<td style="text-align: right;" class="price">{formatPrice(p.priceCents)}</td>
<td>{p.iconPlaceholder}</td>
<td>
<span class="badge" class:badge-active={p.active} class:badge-inactive={!p.active}>
{p.active ? 'Aktiv' : 'Inaktiv'}
</span>
</td>
<td>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => openEditProduct(p)}>Bearbeiten</button>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; margin-left: 8px;" on:click={() => toggleProduct(p.id)}>
{p.active ? 'Deaktivieren' : 'Aktivieren'}
</button>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
<!-- Auswertung -->
{#if activeTab === 'report'}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Gesamtauswertung</h2>
<div class="month-selector">
<input type="month" bind:value={selectedMonth} on:change={loadReport} />
</div>
</div>
{#if report}
{#each report.companies as companyReport}
<div style="margin-bottom: 24px; background: var(--color-bg-secondary); padding: 20px; border-radius: var(--radius);">
<h3 style="margin-bottom: 12px;">{companyReport.companyName ?? 'Firma'}</h3>
<table class="admin-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th style="text-align: right;">Anzahl</th>
<th style="text-align: right;">Summe</th>
</tr>
</thead>
<tbody>
{#each companyReport.employees as line}
<tr>
<td>{line.firstName} {line.lastName}</td>
<td style="text-align: right;">{line.totalCount}×</td>
<td style="text-align: right;" class="price">{formatPrice(line.totalCents)}</td>
</tr>
{/each}
<tr style="font-weight: 700;">
<td>Firma-Gesamt</td>
<td></td>
<td style="text-align: right;" class="price">{formatPrice(companyReport.totalCents)}</td>
</tr>
</tbody>
</table>
</div>
{/each}
<div style="text-align: right; font-size: 1.2rem; font-weight: 700; padding: 16px;">
Gesamtsumme: <span class="price">{formatPrice(report.grandTotalCents)}</span>
</div>
{/if}
{/if}
<!-- Zugangslinks -->
{#if activeTab === 'links'}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2>Zugangslinks</h2>
<button class="btn btn-primary" on:click={openCreateLink}>+ Link erstellen</button>
</div>
<table class="admin-table">
<thead>
<tr><th>Beschreibung</th><th>Rolle</th><th>Link</th></tr>
</thead>
<tbody>
{#each accessLinks as link}
<tr>
<td>{link.description ?? '-'}</td>
<td>
<span class="badge badge-active">
{link.role === 'COMPANY_ADMIN' ? 'Firmen-Admin' : 'Anbieter-Admin'}
</span>
</td>
<td>
<code style="font-size: 0.8rem; word-break: break-all;">{buildLink(link)}</code>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
{/if}
</div>
<!-- Company Modal -->
{#if showCompanyModal}
<div class="modal-overlay" on:click|self={() => showCompanyModal = false}>
<div class="modal">
<h2>{editCompanyId ? 'Firma bearbeiten' : 'Neue Firma'}</h2>
<div class="form-group">
<label for="companyName">Firmenname</label>
<input id="companyName" type="text" bind:value={formCompanyName} />
</div>
<div class="modal-actions">
<button class="btn btn-secondary" on:click={() => showCompanyModal = false}>Abbrechen</button>
<button class="btn btn-primary" on:click={saveCompany}>Speichern</button>
</div>
</div>
</div>
{/if}
<!-- Product Modal -->
{#if showProductModal}
<div class="modal-overlay" on:click|self={() => showProductModal = false}>
<div class="modal">
<h2>{editProductId ? 'Produkt bearbeiten' : 'Neues Produkt'}</h2>
<div class="form-group">
<label for="productName">Produktname</label>
<input id="productName" type="text" bind:value={formProductName} />
</div>
<div class="form-group">
<label for="productPrice">Preis (Cent)</label>
<input id="productPrice" type="number" bind:value={formProductPrice} min="0" />
</div>
<div class="form-group">
<label for="productIcon">Icon</label>
<select id="productIcon" bind:value={formProductIcon}>
<option value="coffee">Kaffee ☕</option>
<option value="chocolate">Kakao 🍫</option>
<option value="tea">Tee 🍵</option>
</select>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" on:click={() => showProductModal = false}>Abbrechen</button>
<button class="btn btn-primary" on:click={saveProduct}>Speichern</button>
</div>
</div>
</div>
{/if}
<!-- Link Modal -->
{#if showLinkModal}
<div class="modal-overlay" on:click|self={() => showLinkModal = false}>
<div class="modal">
<h2>Neuer Zugangslink</h2>
<div class="form-group">
<label for="linkRole">Rolle</label>
<select id="linkRole" bind:value={formLinkRole}>
<option value="COMPANY_ADMIN">Firmen-Admin</option>
<option value="PROVIDER_ADMIN">Anbieter-Admin</option>
</select>
</div>
{#if formLinkRole === 'COMPANY_ADMIN'}
<div class="form-group">
<label for="linkCompany">Firma</label>
<select id="linkCompany" bind:value={formLinkCompanyId}>
{#each companies as c}
<option value={c.id}>{c.name}</option>
{/each}
</select>
</div>
{/if}
<div class="form-group">
<label for="linkDesc">Beschreibung</label>
<input id="linkDesc" type="text" bind:value={formLinkDescription} placeholder="z.B. Admin-Zugang für Firma X" />
</div>
<div class="modal-actions">
<button class="btn btn-secondary" on:click={() => showLinkModal = false}>Abbrechen</button>
<button class="btn btn-primary" on:click={saveLink}>Erstellen</button>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,47 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { api, type Employee, type Company } from '$lib/api/client';
let employees: Employee[] = [];
let companyName = '';
let loading = true;
$: companyId = Number($page.params.id);
onMount(async () => {
const [emps, companies] = await Promise.all([
api.getEmployees(companyId),
api.getCompanies()
]);
employees = emps;
const company = companies.find((c: Company) => c.id === companyId);
companyName = company?.name ?? '';
loading = false;
});
</script>
<svelte:head>
<title>{companyName} - Strichliste</title>
</svelte:head>
<div class="page-header" style="position: relative;">
<a href="/" class="back-btn" aria-label="Zurück"></a>
<h1>{companyName}</h1>
<p style="color: var(--color-text-muted); margin-top: 4px;">Mitarbeiter auswählen</p>
</div>
{#if loading}
<div style="text-align: center; padding: 48px;">
<p>Laden...</p>
</div>
{:else}
<div class="card-grid" style="padding: 24px;">
{#each employees as emp}
<a href="/company/{companyId}/tally?employee={emp.id}" class="card">
<div style="font-size: 2.5rem;">👤</div>
<h3>{emp.firstName} {emp.lastName}</h3>
</a>
{/each}
</div>
{/if}

View File

@@ -0,0 +1,107 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { api, type Product, type MonthlyTally, type Employee } from '$lib/api/client';
let products: Product[] = [];
let tallies: MonthlyTally[] = [];
let employee: Employee | null = null;
let loading = true;
let showToast = false;
let toastMessage = '';
$: companyId = Number($page.params.id);
$: employeeId = Number($page.url.searchParams.get('employee'));
const ICON_MAP: Record<string, string> = {
coffee: '☕',
chocolate: '🍫',
tea: '🍵'
};
onMount(async () => {
const [prods, emps] = await Promise.all([
api.getProducts(),
api.getEmployees(companyId)
]);
products = prods;
employee = emps.find((e: Employee) => e.id === employeeId) ?? null;
await loadTallies();
loading = false;
});
async function loadTallies() {
tallies = await api.getMonthlyTally(employeeId);
}
async function addTally(productId: number, productName: string) {
await api.createTally(employeeId, productId);
toastMessage = `${productName} hinzugefügt!`;
showToast = true;
setTimeout(() => { showToast = false; }, 2000);
await loadTallies();
}
function formatPrice(cents: number): string {
return (cents / 100).toFixed(2).replace('.', ',') + ' €';
}
</script>
<svelte:head>
<title>Produkt wählen - Strichliste</title>
</svelte:head>
<div class="page-header" style="position: relative;">
<a href="/company/{companyId}" class="back-btn" aria-label="Zurück"></a>
<h1>{employee ? `${employee.firstName} ${employee.lastName}` : ''}</h1>
<p style="color: var(--color-text-muted); margin-top: 4px;">Produkt auswählen</p>
</div>
{#if loading}
<div style="text-align: center; padding: 48px;">
<p>Laden...</p>
</div>
{:else}
<div class="card-grid" style="padding: 24px;">
{#each products as product}
<button class="card" on:click={() => addTally(product.id, product.name)}>
<div style="font-size: 2.5rem;">{ICON_MAP[product.iconPlaceholder] ?? '☕'}</div>
<h3>{product.name}</h3>
<span class="subtitle price">{formatPrice(product.priceCents)}</span>
</button>
{/each}
</div>
{#if tallies.length > 0}
<div style="padding: 24px;">
<h2 style="margin-bottom: 12px; font-size: 1.2rem;">Diesen Monat</h2>
<table class="admin-table">
<thead>
<tr>
<th>Produkt</th>
<th style="text-align: right;">Anzahl</th>
<th style="text-align: right;">Summe</th>
</tr>
</thead>
<tbody>
{#each tallies as tally}
<tr>
<td>{tally.productName}</td>
<td style="text-align: right;">{tally.count}×</td>
<td style="text-align: right;" class="price">{formatPrice(tally.totalCents)}</td>
</tr>
{/each}
<tr style="font-weight: 700;">
<td>Gesamt</td>
<td style="text-align: right;">{tallies.reduce((s, t) => s + t.count, 0)}×</td>
<td style="text-align: right;" class="price">{formatPrice(tallies.reduce((s, t) => s + t.totalCents, 0))}</td>
</tr>
</tbody>
</table>
</div>
{/if}
{/if}
{#if showToast}
<div class="toast">{toastMessage}</div>
{/if}