hd-commerce: neutrales SQLite-Commerce-Backend (Admin + API + Demo-Storefront)

This commit is contained in:
2026-06-17 12:05:29 +00:00
commit 4e8a3ab105
43 changed files with 2689 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
---
import Admin from '../../layouts/Admin.astro';
import { dashboard, formatPrice } from '../../lib/store.js';
const d = dashboard();
const statusMap = { fulfilled: ['green', 'Erfüllt'], pending: ['amber', 'Offen'], cancelled: ['gray', 'Storniert'], refunded: ['red', 'Erstattet'] };
const fmtDate = (s) => new Date(s).toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' });
const kpis = [
{ label: 'Umsatz (gesamt)', val: formatPrice(d.revenueCents), sub: `${d.orderCount} Bestellungen` },
{ label: 'Bestellungen', val: d.orderCount, sub: `${d.pending} offen` },
{ label: 'Produkte', val: d.productCount, sub: 'aktiv im Shop' },
{ label: 'Kunden', val: d.customerCount, sub: 'registriert' },
];
---
<Admin title="Dashboard" active="dashboard">
<a slot="actions" class="s-btn s-btn-primary" href="/admin/produkte/neu">+ Produkt</a>
<div class="s-stack">
<div class="s-kpis">
{kpis.map((k) => (
<div class="s-kpi"><div class="s-kpi-label">{k.label}</div><div class="s-kpi-val">{k.val}</div><div class="s-kpi-sub">{k.sub}</div></div>
))}
</div>
<div class="s-card">
<div class="s-card-head">First-Party-Funnel (30 Tage)<a class="s-link" href="/admin/analytics">Details</a></div>
<div class="s-card-pad">
<div class="s-funnel-mini">
<div class="s-fm-step"><div class="v">{d.funnelMini.views}</div><div class="l">Aufrufe</div></div>
<div class="s-fm-arrow">→</div>
<div class="s-fm-step"><div class="v">{d.funnelMini.cart}</div><div class="l">In den Korb</div></div>
<div class="s-fm-arrow">→</div>
<div class="s-fm-step"><div class="v">{d.funnelMini.buy}</div><div class="l">Kauf</div></div>
</div>
</div>
</div>
<div class="s-grid" style="grid-template-columns:1.4fr 1fr">
<div class="s-card">
<div class="s-card-head">Neueste Bestellungen<a class="s-link" href="/admin/bestellungen">Alle</a></div>
<div class="s-table-wrap">
<table class="s-table">
<thead><tr><th>Bestellung</th><th>Kunde</th><th>Status</th><th class="num">Betrag</th></tr></thead>
<tbody>
{d.recentOrders.map((o) => (
<tr class="clk" onclick={`location.href='/admin/bestellungen/${o.id}'`}>
<td><b>{o.number}</b><div class="s-muted" style="font-size:12px">{fmtDate(o.created_at)}</div></td>
<td>{o.customer_name || '—'}</td>
<td><span class={`s-badge ${(statusMap[o.status]||['gray',o.status])[0]}`}>{(statusMap[o.status]||['',o.status])[1]}</span></td>
<td class="num">{formatPrice(o.total_cents)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div class="s-card">
<div class="s-card-head">Geringer Bestand</div>
<div class="s-table-wrap">
<table class="s-table">
<thead><tr><th>Produkt</th><th class="num">Bestand</th></tr></thead>
<tbody>
{d.lowStock.length === 0 ? (<tr><td colspan="2" class="s-empty">Alles gut bestückt 👍</td></tr>) :
d.lowStock.map((p) => (
<tr class="clk" onclick={`location.href='/admin/produkte/${p.id}'`}>
<td><div class="s-prodcell">{p.cardImage && <img src={p.cardImage} alt="" />}<span class="nm">{p.shortName || p.name}</span></div></td>
<td class="num"><span class={`s-badge ${p.stock <= 10 ? 'red' : 'amber'}`}>{p.stock}</span></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</Admin>