Rollen-Farben statt reiner Markenfarben, damit hell und dunkel aus einem
Satz Klassen entstehen:
- heading (Ueberschriften, Links, Rahmen): hell Waldgruen, dunkel Creme
- surface (abgesetzte Flaeche): hell Creme, dunkel angehobenes Dunkelgruen
- forest bleibt die dunkle Markenflaeche (Hero, Baender, Knoepfe,
Admin-Leiste), cream der helle Text darauf
- honey-deep als fester Honigton fuer Flaechen; honey-dark (Text) wird
im Dunkelmodus heller und bleibt lesbar
Codemod ueber src: text-/border-forest -> *-heading, bg-cream ->
bg-surface, bg-honey-dark -> bg-honey-deep. Im hellen Modus haben die
Rollen exakt die bisherigen Werte - das Erscheinungsbild bleibt gleich.
Umschalter (components/theme-toggle.tsx) Automatisch -> Hell -> Dunkel in
Kopfzeile, Mobilmenue und Admin-Leiste. Die Wahl liegt im Cookie "theme"
(nur light/dark, geprueft in lib/theme.ts); der Server setzt data-theme
schon beim Ausliefern, nichts blitzt hell auf. "Automatisch" folgt der
Systemeinstellung. Gewaehlter und automatischer Dunkelblock in
globals.css werden per Test auf Gleichheit geprueft.
Logo, Biene und Select-Pfeil nutzen die Rollenfarben. In der Kopfzeile
steht "Honig entdecken" zwischen 1280 und 1535 px nicht mehr doppelt
neben "Shop" (sonst stiess die Navigation an), der Slogan erscheint erst
ab 1680 px.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Derselbe "Alles synchronisieren"-Knopf stand auf /admin/produkte und
/admin/ernten und sah dadurch wie zwei verschiedene Abgleiche aus. Er
sitzt jetzt einmal auf dem Dashboard, das Ergebnis des Laufs erscheint
dort oben. Die Action springt standardmaessig nach /admin zurueck; die
alten Ziele bleiben in der Whitelist erlaubt.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Neue gemeinsame Bausteine in components/admin-list.tsx: Seitenkopf mit
Hauptaktion, Suchleiste, Status-Badge (gleiche Form wie der Bestellstatus,
wartende Eintraege mit pulsierendem Punkt), Leerzustand mit Wabe und
naechstem Schritt sowie Zeilenaktionen mit einheitlichem Unterstrich-
Hover (Loeschen rot, Sperren/Zurueckziehen gedaempft).
Umgestellt: Bewertungen (SVG-Sterne statt Unicode), Benutzer, Gutscheine
(neu: Status "Abgelaufen"), Versand, Ernten, Chargen (Anlegeformulare auf
admin-form), Firmenanfragen (Status als Badge, beschriftete Auswahl) und
Audit-Log. Zahlen und Daten in Tabellen mit tabular-nums.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Viele Aktionen gaben keine Rueckmeldung (Retoure annehmen, Archiv,
Bewertungen, Adressen, Newsletter, Kundensperre ...) oder meldeten Erfolg,
obwohl die zugehoerige E-Mail nie rausging.
- lib/flash: Meldung als kurzlebiges Cookie; Toast im Root-Layout
(FlashToast). Ersetzt ?message=/?error= in der URL — darueber liess sich
bisher beliebiger Text als Systemmeldung im Wabenhain-Layout anzeigen
(Phishing-Hebel). Server Components leiten ueber /api/hinweis mit festen
Codes und festen Zielen weiter.
- fail()/succeed() zentral und asynchron; ein Quelltext-Test stellt sicher,
dass jeder Aufruf mit await/return erfolgt (sonst liefe eine Action nach
einem Fehler weiter).
- sendMail meldet, ob die Mail angenommen wurde; Aktionen mit Mail zeigen
ehrlich an, wenn der Versand scheitert (z.B. "Link erneut senden").
- Jede Server Action meldet jetzt Erfolg oder den Grund des Abbruchs.
- SubmitFeedback: geklickter Knopf zeigt sofort einen Ladezustand und ist
bis zur Antwort gegen Doppelklick gesperrt.
- 2FA: nach dem Einrichten zurueck ins Profil statt auf die Seite mit
"Deaktivieren".
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Bisher gab es zwei Knoepfe auf zwei Seiten, und ihre Reihenfolge war nicht
beliebig: eine Charge braucht ihre Ernte als Pflicht-Fremdschluessel, ein
Produkt entsteht je Abfuellung und braucht die zugehoerige Charge. Wer mit
den Produkten anfing, bekam reihenweise "wartet auf den Ernten-Sync" und
musste danach noch einmal klicken. Diese Reihenfolge gehoert in den Ablauf,
nicht in den Kopf der bedienenden Person.
syncApiCoreAction ersetzt beide Actions und laeuft Ernten -> Chargen ->
Produkte in einem Durchgang. "Alles synchronisieren" steht auf beiden Seiten
und springt danach dorthin zurueck, von wo er ausgeloest wurde. Das Ziel
kommt aus einem Formularfeld und wird gegen eine feste Liste geprueft — ein
ungeprueftes returnTo waere eine offene Weiterleitung.
Nebeneffekt: die Abfuellungen werden nur noch EINMAL von ApiCore geladen.
Vorher holten Chargen- und Produkt-Sync dieselbe Liste getrennt.
Knopf und Ergebnisanzeige liegen jetzt in einer gemeinsamen Komponente
(components/apicore-sync-panel.tsx) statt zweimal in zwei Seiten. Die
Ergebnisse stehen als ein Block beieinander, je eine Zeile fuer Ernten,
Chargen und Produkte, plus die beiden Hinweise (Abfuellung ohne Charge,
ausgelaufener Artikel) als Hinweis statt als Fehler.
Damit wurden zehn i18n-Schluessel ueberfluessig, die vorher die beiden
getrennten Anzeigen bedienten; sie sind aus dem Vertrag und beiden
Sprachdateien entfernt.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Fuer Ernten und Chargen gab es das Loeschen bereits, fuer Produkte nicht.
Neue Server Action deleteProductAction samt Knopf in der Produktliste,
im selben Muster wie die beiden vorhandenen: Bestaetigungsdialog,
Audit-Log-Eintrag, Pruefung vor dem Loeschen.
Geloescht werden kann nur, was nie bestellt wurde. OrderItem verweist mit
onDelete: Restrict auf das Produkt, und das ist Absicht: Bestellpositionen
und die daraus erzeugten Rechnungen sind Belege ueber einen vergangenen
Vorgang. Verschwaende das Produkt darunter weg, verloere eine bereits
gestellte Rechnung ihren Bezug — bei zehn Jahren Aufbewahrungspflicht
(§ 147 AO) keine Option. Die Meldung sagt deshalb nicht nur "geht nicht",
sondern nennt den Ausweg: inaktiv schalten, dann verschwindet das Produkt
aus dem Shop und der Beleg bleibt.
Mit dem Produkt gehen Lagerbestand, Bewertungen, Wunschlisten-Eintraege und
Verfuegbarkeits-Benachrichtigungen (onDelete: Cascade). Hochgeladene Bilder
bleiben liegen: sie koennen anderswo eingebunden sein, und eine verwaiste
Datei ist harmloser als ein totes Bild an anderer Stelle.
Auf allen drei Seiten fehlte bisher ausserdem die Erklaerung, warum der
Loeschknopf manchmal gar nicht da ist — er verschwand wortlos, sobald etwas
darauf verwies. Jetzt steht an seiner Stelle der Grund ("bereits verkauft"
bzw. "in Verwendung").
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Neue deleteHarvestAction/deleteBatchAction, jeweils mit vorheriger
Referenzpruefung statt rohem Datenbankfehler: eine Ernte laesst sich nur
loeschen, wenn keine Chargen/Produkte mehr darauf verweisen, eine Charge
nur, wenn sie keinem Produkt zugeordnet ist und in keiner Bestellung
verwendet wurde (Chargen-Code steht auf Rechnungen/QR-Codes und darf aus
Nachweispflicht nie verschwinden). Loeschen-Button erscheint in den
Listen nur, wenn die Loeschung tatsaechlich moeglich ist, mit
Bestaetigungsdialog wie beim bestehenden Benutzer-Loeschen.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Der bestehende Ernten-Sync (admin/ernten) laedt jetzt zusaetzlich
Chargen aus ApiCore. Fachliche Zuordnung recherchiert direkt im
ApiCore-Backend-Quellcode (../ApiCore/ApiCore-Backend): Wabenhains
"Charge" (model Batch) entspricht ApiCores "BottlingBatch"
(Schleuderung -> HoneyBatch -> Abfuellung -> BottlingBatch), NICHT
"HoneyBatch" selbst — die Felder passen fast 1:1 (lotNumber/code,
date/fillDate, bestBeforeDate/bestBeforeDate, jarCount/glassCount).
- lib/apicore-client.ts: fetchApiCoreBottlingBatches() laedt
GET /bottling-batches (inkl. verknuepfter HoneyBatch) und
GET /extractions (inkl. verknuepfter Ernten) und loest daraus die
zugehoerige ApiCore-Ernten-ID pro Charge auf.
- admin/actions/apicore-sync.ts: Chargen-Sync laeuft nach dem
Ernten-Sync (damit frisch angelegte Ernten schon als harvestId
verfuegbar sind), legt Chargen NUR NEU an (nie Update) — anders als
Harvest hat Batch kein isPublished-Feld, ein Update wuerde
insbesondere remainingGlasses (echter, durch Verkaeufe veraenderter
Lagerbestand) stillschweigend zuruecksetzen. Nicht-fatal: schlaegt
nur der Chargen-Teil fehl, bleiben bereits synchronisierte Ernten
erhalten.
- admin/ernten/page.tsx + i18n: zeigt Chargen-Sync-Ergebnis separat an.
- .env.example: zwei zusaetzliche ApiCore-Berechtigungen dokumentiert
(extractions:read, bottling:read).
Verifiziert: tsc --noEmit clean, 118/118 Tests gruen, pnpm build clean.
Konnte nicht gegen einen echten ApiCore-Server getestet werden (siehe
bereits bestehende Einschraenkung beim Ernten-Sync) — Feldnamen/Shape
direkt aus ApiCore-Backends Prisma-Schema und Service-Code entnommen.
Co-Authored-By: Claude Sonnet 5 <[email protected]>