--- import Layout from "../../layouts/Layout.astro"; import { getProduct } from "../../data/products"; import { getCategory } from "../../data/categories"; import { formatPrice } from "../../i18n/format"; import type { Locale } from "../../i18n/config"; const lang: Locale = "de"; // ─── Interne Bestellverwaltung — NUR für qciga/vanvan (siehe functions/_middleware.js, diese // Seite bleibt immer hinter dem Zugangscode, auch wenn der Rest der Seite mal öffentlich wird). ─── // // TODO Phase 2: Es gibt noch keine echte, geräteübergreifende Bestell-Datenbank (siehe // previewNote in konto/angemeldet.astro) — Bestellungen aus dem Warenkorb landen aktuell nirgends // zentral, jede Person sieht nur ihre eigenen im eigenen Browser. Diese Seite zeigt deshalb // realistische BEISPIELDATEN, exakt in der Struktur, die eine echte Bestellliste später haben // wird (Cloudflare Worker + D1, siehe Tech-Stack-Entscheidung) — Layout/Auswertung sind schon // jetzt "fertig", es muss später nur noch echte Daten statt der Beispiele einspeisen. type Status = "bezahlt" | "bearbeitung" | "versandVorbereitet" | "versendet" | "abgeschlossen"; type Land = "de" | "at" | "ch" | "lu"; interface BeispielBestellung { nummer: string; datum: string; // YYYY-MM-DD land: Land; status: Status; artikel: { slug: string; menge: number }[]; } const bestellungenRoh: BeispielBestellung[] = [ { nummer: "VDB-2026-0201", datum: "2026-08-02", land: "de", status: "bezahlt", artikel: [{ slug: "kleiner-teddy", menge: 2 }] }, { nummer: "VDB-2026-0200", datum: "2026-08-02", land: "at", status: "bearbeitung", artikel: [{ slug: "haekeldecke-boho", menge: 1 }, { slug: "baumwollgarn-bundle", menge: 1 }] }, { nummer: "VDB-2026-0199", datum: "2026-08-01", land: "de", status: "versandVorbereitet", artikel: [{ slug: "hasidog-plushie-blaueroverall", menge: 1 }] }, { nummer: "VDB-2026-0198", datum: "2026-08-01", land: "ch", status: "versendet", artikel: [{ slug: "makramee-ohrringe", menge: 2 }, { slug: "perlen-armband-hellblau", menge: 1 }] }, { nummer: "VDB-2026-0197", datum: "2026-07-31", land: "de", status: "abgeschlossen", artikel: [{ slug: "grosser-teddy", menge: 1 }] }, { nummer: "VDB-2026-0196", datum: "2026-07-31", land: "de", status: "abgeschlossen", artikel: [{ slug: "fuchs-plushie-mini", menge: 3 }] }, { nummer: "VDB-2026-0195", datum: "2026-07-30", land: "lu", status: "bezahlt", artikel: [{ slug: "stoffreste-bundle", menge: 1 }, { slug: "haekelnadel-set-ergonomisch", menge: 1 }] }, { nummer: "VDB-2026-0194", datum: "2026-07-30", land: "de", status: "abgeschlossen", artikel: [{ slug: "wolke-plushie-lavendel", menge: 1 }] }, { nummer: "VDB-2026-0193", datum: "2026-07-29", land: "at", status: "versendet", artikel: [{ slug: "kleiner-teddy", menge: 1 }] }, { nummer: "VDB-2026-0192", datum: "2026-07-29", land: "de", status: "abgeschlossen", artikel: [{ slug: "hasidog-plushie-blaueroverall", menge: 2 }] }, { nummer: "VDB-2026-0191", datum: "2026-07-28", land: "ch", status: "bearbeitung", artikel: [{ slug: "haekel-untersetzer-set", menge: 1 }, { slug: "baumwollgarn-bundle", menge: 2 }] }, { nummer: "VDB-2026-0190", datum: "2026-07-28", land: "de", status: "abgeschlossen", artikel: [{ slug: "perlen-armband-hellblau", menge: 3 }] }, { nummer: "VDB-2026-0189", datum: "2026-07-27", land: "de", status: "abgeschlossen", artikel: [{ slug: "fuchs-plushie-mini", menge: 1 }] }, { nummer: "VDB-2026-0188", datum: "2026-07-26", land: "de", status: "abgeschlossen", artikel: [{ slug: "grosser-teddy", menge: 2 }] }, { nummer: "VDB-2026-0187", datum: "2026-07-25", land: "at", status: "abgeschlossen", artikel: [{ slug: "makramee-ohrringe", menge: 1 }] }, { nummer: "VDB-2026-0186", datum: "2026-07-24", land: "de", status: "abgeschlossen", artikel: [{ slug: "hasidog-plushie-blaueroverall", menge: 1 }, { slug: "kleiner-teddy", menge: 1 }] }, { nummer: "VDB-2026-0185", datum: "2026-07-23", land: "ch", status: "abgeschlossen", artikel: [{ slug: "haekeldecke-boho", menge: 1 }] }, { nummer: "VDB-2026-0184", datum: "2026-07-22", land: "de", status: "versendet", artikel: [{ slug: "stoffreste-bundle", menge: 2 }] }, { nummer: "VDB-2026-0183", datum: "2026-07-21", land: "de", status: "abgeschlossen", artikel: [{ slug: "fuchs-plushie-mini", menge: 2 }] }, { nummer: "VDB-2026-0182", datum: "2026-07-20", land: "lu", status: "abgeschlossen", artikel: [{ slug: "wolke-plushie-lavendel", menge: 1 }, { slug: "perlen-armband-hellblau", menge: 1 }] }, ]; const bestellungen = bestellungenRoh.map((b) => { const artikel = b.artikel.map((a) => { const p = getProduct(a.slug); return { ...a, name: p?.name[lang] ?? a.slug, preis: p?.preis ?? 0, kategorie: p?.kategorie ?? "" }; }); return { ...b, artikel, summe: artikel.reduce((s, a) => s + a.preis * a.menge, 0) }; }); // Vier Buckets statt der fünf feingranularen Status — genau wie gewünscht: offen / in Bearbeitung // / versendet / abgeschlossen. "bearbeitung" + "versandVorbereitet" zählen zusammen als // "in Bearbeitung" (beides "wird gerade vorbereitet", nur mit unterschiedlichem Detailgrad). function bucket(status: Status): "offen" | "inBearbeitung" | "versendet" | "abgeschlossen" { if (status === "bezahlt") return "offen"; if (status === "bearbeitung" || status === "versandVorbereitet") return "inBearbeitung"; if (status === "versendet") return "versendet"; return "abgeschlossen"; } const buckets = { offen: 0, inBearbeitung: 0, versendet: 0, abgeschlossen: 0 }; bestellungen.forEach((b) => { buckets[bucket(b.status)]++; }); const gesamtumsatz = bestellungen.reduce((s, b) => s + b.summe, 0); const abgeschlosseneUmsaetze = bestellungen.filter((b) => b.status === "abgeschlossen"); const durchschnittsbestellwert = bestellungen.length > 0 ? gesamtumsatz / bestellungen.length : 0; const statusLabel: Record = { bezahlt: "Bezahlt", bearbeitung: "In Bearbeitung", versandVorbereitet: "Versand wird vorbereitet", versendet: "Versendet", abgeschlossen: "Abgeschlossen", }; // ── Auswertung 1: meistverkaufte Produkte je Kategorie (nach verkaufter Menge) ── const mengeProSlug = new Map(); bestellungen.forEach((b) => b.artikel.forEach((a) => mengeProSlug.set(a.slug, (mengeProSlug.get(a.slug) ?? 0) + a.menge))); const kategorienMitProdukten = new Map(); mengeProSlug.forEach((menge, slug) => { const p = getProduct(slug); if (!p) return; const liste = kategorienMitProdukten.get(p.kategorie) ?? []; liste.push({ slug, name: p.name[lang], menge }); kategorienMitProdukten.set(p.kategorie, liste); }); const auswertungKategorien = Array.from(kategorienMitProdukten.entries()) .map(([kategorieSlug, produkte]) => { const kat = getCategory(kategorieSlug); const sortiert = produkte.sort((a, b) => b.menge - a.menge); const maxMenge = sortiert[0]?.menge ?? 1; return { slug: kategorieSlug, name: kat?.name[lang] ?? kategorieSlug, icon: kat?.icon ?? "📦", gesamtMenge: sortiert.reduce((s, p) => s + p.menge, 0), produkte: sortiert.slice(0, 4).map((p) => ({ ...p, prozent: Math.round((p.menge / maxMenge) * 100) })), }; }) .sort((a, b) => b.gesamtMenge - a.gesamtMenge); // ── Auswertung 2: Bestellungen nach Land ── const landName: Record = { de: "Deutschland", at: "Österreich", ch: "Schweiz", lu: "Luxemburg" }; const landFlagge: Record = { de: "🇩🇪", at: "🇦🇹", ch: "🇨🇭", lu: "🇱🇺" }; const landCounts = new Map(); bestellungen.forEach((b) => landCounts.set(b.land, (landCounts.get(b.land) ?? 0) + 1)); const maxLandCount = Math.max(...Array.from(landCounts.values())); const auswertungLaender = (Array.from(landCounts.entries()) as [Land, number][]) .sort((a, b) => b[1] - a[1]) .map(([land, count]) => ({ land, name: landName[land], flagge: landFlagge[land], count, prozent: Math.round((count / bestellungen.length) * 100), balken: Math.round((count / maxLandCount) * 100), })); ---

🔒 Interner Bereich, nur für dich und VanVan sichtbar (siehe Zugangscode-Gate). Es gibt noch keine echte, geräteübergreifende Bestell-Datenbank (Phase 1) — die Bestellungen und Auswertungen unten sind realistische BEISPIELDATEN, damit du siehst, wie die Übersicht aussehen wird. Echte Bestellungen laufen automatisch hier ein, sobald die Zahlungs- und Datenbank-Anbindung (Cloudflare Worker + D1) in Phase 2 steht.

Verwaltung

📊 Bestellverwaltung

Behalte jederzeit den Überblick: wie viele Bestellungen offen, in Bearbeitung, versendet oder abgeschlossen sind — plus eine Auswertung, was sich am besten verkauft und woher eure Kundschaft kommt.

{formatPrice(gesamtumsatz, lang)} Gesamtumsatz ({bestellungen.length} Bestellungen)

📦 Alle Bestellungen

{bestellungen.map((b) => (
{landFlagge[b.land]} {b.nummer} {b.datum} · {landName[b.land]}
{statusLabel[b.status]}
    {b.artikel.map((a) => (
  • {a.menge}× {a.name}{formatPrice(a.preis * a.menge, lang)}
  • ))}
Summe: {formatPrice(b.summe, lang)}
))}

🏆 Meistverkaufte Produkte je Kategorie

{auswertungKategorien.map((k) => (

{k.icon} {k.name}

{k.produkte.map((p) => (
{p.name}{p.menge}× verkauft
))}
))}

🌍 Bestellungen nach Land

{auswertungLaender.map((l) => (
{l.flagge} {l.name}{l.count} Bestellungen · {l.prozent}%
))}