diff --git a/src/pages/verwaltung/index.astro b/src/pages/verwaltung/index.astro
index d1b596f..382f651 100644
--- a/src/pages/verwaltung/index.astro
+++ b/src/pages/verwaltung/index.astro
@@ -158,7 +158,10 @@ const lang: Locale = "de";
Noch keine ausgewerteten Verkäufe.
Hier siehst du bald, welche Produkte je Kategorie am besten laufen (stornierte Bestellungen zählen nicht mit).
-
+ {/* Jede Kategorie ist jetzt eine eigene Kachel im selben Stil wie die
+ Bestellstatus-Kacheln oben — Klick öffnet direkt darunter die volle Produktliste. */}
+
+
@@ -173,7 +176,8 @@ const lang: Locale = "de";
Noch keine Bestellungen.
Sobald Bestellungen eingehen, siehst du hier den Anteil je Land (DE/AT/CH/LU).
-
+
+
@@ -188,7 +192,8 @@ const lang: Locale = "de";
Noch keine wiederkehrende Kundschaft.
Sobald jemand zum zweiten Mal bestellt, erscheint sie hier — sortiert nach bisherigem Umsatz.
-
+
+
@@ -296,6 +301,12 @@ const lang: Locale = "de";
let alleBestellungen = [];
let bekannteIds = new Set();
let erstesLaden = true;
+ // Merkt sich die zuletzt berechneten Auswertungsdaten, damit die Detail-Panels (Klick auf eine
+ // Kategorie-/Land-/Kundschafts-Kachel) darauf zugreifen können, ohne alles neu zu berechnen.
+ let letzteKategorien = [];
+ let letzteLaender = [];
+ let letzteKunden = [];
+ let letzteAktive = [];
async function ladeUndRendere() {
loadingEl.style.display = "";
@@ -359,6 +370,15 @@ const lang: Locale = "de";
function render() {
const aktive = alleBestellungen.filter((b) => b.status !== "storniert");
+ letzteAktive = aktive;
+
+ // Die Auswertungs-Detail-Panels zeigen ggf. veraltete Daten, sobald sich die Bestellliste
+ // ändert (z.B. neuer Status) — bei jedem Rendern lieber wieder einklappen, statt falsche
+ // Zahlen stehen zu lassen. Ein erneuter Klick zeigt die aktuellen Daten sofort wieder an.
+ ["kat", "land", "kunden"].forEach((gruppe) => {
+ document.getElementById(`verwaltung-${gruppe}-detail`)?.setAttribute("hidden", "");
+ });
+ document.querySelectorAll(".verwaltung-stat-tile.is-active").forEach((t) => t.classList.remove("is-active"));
// ── Buckets für die Stat-Kacheln + Filterleiste ──
const buckets = { offen: 0, inBearbeitung: 0, versendet: 0, abgeschlossen: 0, storniert: 0 };
@@ -502,10 +522,13 @@ const lang: Locale = "de";
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) })),
+ // Volle Liste (nicht mehr auf 4 gekürzt) — die Kachel selbst zeigt nur die Summe, die
+ // vollständige Rangliste erscheint erst im aufklappbaren Detail-Panel darunter.
+ produkte: sortiert.map((p) => ({ ...p, prozent: Math.round((p.menge / maxMenge) * 100) })),
};
})
.sort((a, b) => b.gesamtMenge - a.gesamtMenge);
+ letzteKategorien = auswertungKategorien;
const katGrid = document.getElementById("verwaltung-kat-grid");
const katEmpty = document.getElementById("verwaltung-kat-empty");
@@ -515,17 +538,11 @@ const lang: Locale = "de";
} else {
katEmpty.style.display = "none";
katGrid.innerHTML = auswertungKategorien.map((k) => `
-
-
${k.icon} ${escapeHtml(k.name)}
-
- ${k.produkte.map((p) => `
-
-
${escapeHtml(p.name)} ${p.menge}× verkauft
-
-
- `).join("")}
-
-
+
+ ${k.icon}
+ ${k.gesamtMenge}
+ ${escapeHtml(k.name)}
+
`).join("");
}
@@ -540,20 +557,22 @@ const lang: Locale = "de";
count, prozent: aktive.length > 0 ? Math.round((count / aktive.length) * 100) : 0,
balken: Math.round((count / maxLandCount) * 100),
}));
+ letzteLaender = auswertungLaender;
- const landList = document.getElementById("verwaltung-land-list");
+ const landGrid = document.getElementById("verwaltung-land-grid");
const landEmpty = document.getElementById("verwaltung-land-empty");
if (auswertungLaender.length === 0) {
- landList.innerHTML = "";
+ landGrid.innerHTML = "";
landEmpty.style.display = "";
} else {
landEmpty.style.display = "none";
- landList.innerHTML = auswertungLaender.map((l) => `
-
-
${l.flagge} ${escapeHtml(l.name)} ${l.count} Bestellungen · ${l.prozent}%
-
-
- `).join("");
+ landGrid.innerHTML = auswertungLaender.map((l) => `
+
+ ${l.flagge}
+ ${l.count}
+ ${escapeHtml(l.name)} · ${l.prozent}%
+
+ `).join("");
}
// ── NEU: Beste Kundinnen (Umsatz pro E-Mail, nur wer mehr als 1× bestellt hat) ──
@@ -569,25 +588,93 @@ const lang: Locale = "de";
.map(([email, v]) => ({ email, ...v }))
.sort((a, b) => b.summe - a.summe)
.slice(0, 8);
- const maxKundenUmsatz = Math.max(1, ...besteKunden.map((k) => k.summe));
+ letzteKunden = besteKunden;
const kundenEmpty = document.getElementById("verwaltung-kunden-empty");
- const kundenList = document.getElementById("verwaltung-kunden-list");
+ const kundenGrid = document.getElementById("verwaltung-kunden-grid");
if (besteKunden.length === 0) {
- kundenList.innerHTML = "";
+ kundenGrid.innerHTML = "";
kundenEmpty.style.display = "";
} else {
kundenEmpty.style.display = "none";
- kundenList.innerHTML = besteKunden.map((k) => `
-
-
👑 ${escapeHtml(k.name)} (${escapeHtml(k.email)}) ${formatPrice(k.summe, lang)} · ${k.anzahl}× bestellt
-
-
+ kundenGrid.innerHTML = besteKunden.map((k) => `
+
+ 👑
+ ${formatPrice(k.summe, lang)}
+ ${escapeHtml(k.name)} · ${k.anzahl}×
+
`).join("");
}
wendeFilterAn();
}
+ // ── Detail-Panels für die Auswertungs-Kacheln: erscheinen genau unter der jeweiligen Kachel-
+ // Reihe (siehe .verwaltung-analyse-detail), sobald man draufklickt — mit der VOLLSTÄNDIGEN
+ // Liste (nicht mehr nur den Top 4/8 wie vorher direkt auf der Kachel). ──
+ function zeigeKatDetail(slug) {
+ const k = letzteKategorien.find((x) => x.slug === slug);
+ const panel = document.getElementById("verwaltung-kat-detail");
+ if (!k) { panel.setAttribute("hidden", ""); return; }
+ panel.innerHTML = `
+
+
${k.icon} ${escapeHtml(k.name)} — ${k.produkte.length} Produkt${k.produkte.length === 1 ? "" : "e"}
+ ✕
+
+
+ ${k.produkte.map((p) => `
+
+
${escapeHtml(p.name)} ${p.menge}× verkauft
+
+
+ `).join("")}
+
+ `;
+ panel.removeAttribute("hidden");
+ document.querySelectorAll("[data-kat-tile]").forEach((t) => t.classList.toggle("is-active", t.dataset.katTile === slug));
+ }
+
+ function zeigeLandDetail(land) {
+ const l = letzteLaender.find((x) => x.land === land);
+ const panel = document.getElementById("verwaltung-land-detail");
+ if (!l) { panel.setAttribute("hidden", ""); return; }
+ const bestellungen = letzteAktive.filter((b) => b.land === land).sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
+ panel.innerHTML = `
+
+
${l.flagge} ${escapeHtml(l.name)} — ${bestellungen.length} Bestellung${bestellungen.length === 1 ? "" : "en"}
+ ✕
+
+ ${bestellungen.map((b) => `
+
+ ${escapeHtml(b.bestellnummer || `#${b.id}`)} · ${escapeHtml(b.kunde_name)}
+ ${new Date(b.created_at).toLocaleDateString("de-DE")} · ${formatPrice(Number(b.summe), lang)}
+
+ `).join("")}
+ `;
+ panel.removeAttribute("hidden");
+ document.querySelectorAll("[data-land-tile]").forEach((t) => t.classList.toggle("is-active", t.dataset.landTile === land));
+ }
+
+ function zeigeKundeDetail(email) {
+ const k = letzteKunden.find((x) => x.email === email);
+ const panel = document.getElementById("verwaltung-kunden-detail");
+ if (!k) { panel.setAttribute("hidden", ""); return; }
+ const bestellungen = letzteAktive.filter((b) => b.kunde_email === email).sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
+ panel.innerHTML = `
+
+
👑 ${escapeHtml(k.name)} (${escapeHtml(email)}) — ${bestellungen.length} Bestellung${bestellungen.length === 1 ? "" : "en"}
+ ✕
+
+ ${bestellungen.map((b) => `
+
+ ${escapeHtml(b.bestellnummer || `#${b.id}`)}
+ ${new Date(b.created_at).toLocaleDateString("de-DE")} · ${formatPrice(Number(b.summe), lang)}
+
+ `).join("")}
+ `;
+ panel.removeAttribute("hidden");
+ document.querySelectorAll("[data-kunden-tile]").forEach((t) => t.classList.toggle("is-active", t.dataset.kundenTile === email));
+ }
+
function escapeHtml(str) {
return String(str ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
}
@@ -716,6 +803,50 @@ const lang: Locale = "de";
zeitraumSelect.value = zeitraumBtn.dataset.zeitraum;
wendeFilterAn();
document.getElementById("verwaltung-order-list")?.scrollIntoView({ behavior: "smooth", block: "start" });
+ return;
+ }
+
+ // ── Auswertungs-Kacheln: Klick öffnet das Detail-Panel direkt darunter, nochmal draufklicken
+ // klappt es wieder ein. ──
+ const katTile = e.target.closest("[data-kat-tile]");
+ if (katTile) {
+ const schonOffen = katTile.classList.contains("is-active");
+ if (schonOffen) {
+ document.getElementById("verwaltung-kat-detail").setAttribute("hidden", "");
+ katTile.classList.remove("is-active");
+ } else {
+ zeigeKatDetail(katTile.dataset.katTile);
+ }
+ return;
+ }
+ const landTile = e.target.closest("[data-land-tile]");
+ if (landTile) {
+ const schonOffen = landTile.classList.contains("is-active");
+ if (schonOffen) {
+ document.getElementById("verwaltung-land-detail").setAttribute("hidden", "");
+ landTile.classList.remove("is-active");
+ } else {
+ zeigeLandDetail(landTile.dataset.landTile);
+ }
+ return;
+ }
+ const kundenTile = e.target.closest("[data-kunden-tile]");
+ if (kundenTile) {
+ const schonOffen = kundenTile.classList.contains("is-active");
+ if (schonOffen) {
+ document.getElementById("verwaltung-kunden-detail").setAttribute("hidden", "");
+ kundenTile.classList.remove("is-active");
+ } else {
+ zeigeKundeDetail(kundenTile.dataset.kundenTile);
+ }
+ return;
+ }
+ const closeDetailBtn = e.target.closest("[data-close-detail]");
+ if (closeDetailBtn) {
+ const gruppe = closeDetailBtn.dataset.closeDetail;
+ document.getElementById(`verwaltung-${gruppe}-detail`)?.setAttribute("hidden", "");
+ document.querySelectorAll(`[data-${gruppe}-tile]`).forEach((t) => t.classList.remove("is-active"));
+ return;
}
});
diff --git a/src/styles/global.css b/src/styles/global.css
index b061765..60a7ba9 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -3034,6 +3034,55 @@ a:focus-visible, button:focus-visible {
.verwaltung-auswertung-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; margin-top: 1.2rem; }
.verwaltung-kat-card { display: flex; flex-direction: column; gap: 0.9rem; }
.verwaltung-kat-card h3 { margin: 0; }
+
+/* Auswertungs-Kacheln (Bestseller je Kategorie / Herkunft / Stammkundschaft) — sehen jetzt
+ bewusst genauso aus wie die Bestellstatus-Kacheln oben (.verwaltung-stat-tile), auf Wunsch
+ ("sollen aussehen wie auf screen2"), statt eigener Bar-Listen direkt in der Kiste. Klick auf
+ eine Kachel öffnet direkt darunter (".verwaltung-analyse-detail") die vollständige Liste. */
+.verwaltung-analyse-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.9rem; margin: 1.2rem 0; }
+.verwaltung-stat-tile.is-active {
+ border-color: color-mix(in srgb, var(--box-color, var(--c-accent)) 70%, transparent);
+ background: color-mix(in srgb, var(--box-color, var(--c-accent)) 14%, var(--c-anthracite-light));
+}
+.verwaltung-analyse-detail {
+ margin-top: 0.4rem;
+ padding: 1.2rem 1.3rem;
+ border-radius: var(--radius-m);
+ border: 1.5px solid color-mix(in srgb, var(--box-color, var(--c-accent)) 40%, transparent);
+ background: color-mix(in srgb, var(--box-color, var(--c-accent)) 6%, var(--c-anthracite));
+ animation: verwaltung-analyse-detail-in 0.2s var(--ease);
+}
+.verwaltung-analyse-detail[hidden] { display: none; }
+@keyframes verwaltung-analyse-detail-in {
+ from { opacity: 0; transform: translateY(-6px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+.verwaltung-analyse-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 0.9rem; }
+.verwaltung-analyse-detail-head h3 { margin: 0; font-size: 1.05rem; }
+.verwaltung-analyse-close {
+ flex-shrink: 0;
+ background: transparent;
+ border: none;
+ color: var(--c-text-muted);
+ font-size: 1.2rem;
+ line-height: 1;
+ padding: 0.2rem 0.4rem;
+ cursor: pointer;
+ transition: color 0.15s var(--ease);
+}
+.verwaltung-analyse-close:hover { color: var(--c-text); }
+.verwaltung-analyse-order-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.6rem 0;
+ border-bottom: 1px solid rgba(244, 241, 247, 0.08);
+ font-size: 0.9rem;
+}
+.verwaltung-analyse-order-row:last-child { border-bottom: none; }
+@media (prefers-reduced-motion: reduce) { .verwaltung-analyse-detail { animation: none; } }
+
.verwaltung-bar-list { display: flex; flex-direction: column; gap: 0.8rem; }
.verwaltung-bar-head { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.9rem; margin-bottom: 0.3rem; }
.verwaltung-bar-track { height: 9px; border-radius: 999px; background: rgba(244, 241, 247, 0.08); overflow: hidden; }