Ein E-Commerce-Admin-Dashboard in Next.js bauen: Bestellungen, Erstattungen und Bestand
Die schwierigen Teile eines Next.js-Shop-Admins: zwei Bestellstatus, teilweise fehlgeschlagene Massenaktionen, centgenaue Erstattungen, Bestandsanpassungen und Rabatte.
Preet Suthar · · 6 Min. Lesezeit
- Next.js
- React
- E-commerce
- Dashboard
Ein E-Commerce-Admin sieht aus wie eine CRUD-App: eine Tabelle mit Bestellungen, eine Seite pro Bestellung, ein Formular pro Produkt. Die Tabellen sind der einfache Teil. Schwierig ist, dass jede Zahl in einem Shop-Admin Geld oder Bestand ist, und beides muss stimmen, während im Hintergrund weiter Kunden einkaufen.
Das habe ich beim Bau der Admin-Seite eines Shops in Next.js gelernt: Bestelltabellen mit ehrlichen Status, eine Bestellseite, die zeigt, was noch zu tun ist, Erstattungen, die auf den Cent aufgehen, Bestandsänderungen, die laufende Bestellungen überstehen, und Rabatte, die Händler lesen können.
Bestellungen: zwei Status, nicht einer
Der erste Fehler ist eine einzelne status-Spalte. Eine Bestellung hat mindestens zwei unabhängige Zustände: Zahlung (ausstehend, autorisiert, bezahlt, teilweise erstattet, erstattet) und Fulfillment (nicht erfüllt, teilweise erfüllt, erfüllt). „Bezahlt und nicht erfüllt“ ist die Liste, die ein Händler jeden Morgen abarbeitet, und eine einzige Statusspalte kann sie nicht ausdrücken.
Zeig sie als zwei Pills. Lass nicht die Farbe die Bedeutung tragen. WCAG verlangt, dass Farbe nicht das einzige Mittel ist, um Informationen darzustellen, und „Teilweise bezahlt“ in Hellorange neben „Bezahlt“ in Grün ist für viele Menschen schwer zu unterscheiden. Ein Textlabel plus ein kleines Symbol, ein leerer, halber oder voller Kreis, ist auf einen Blick lesbar.
Gespeicherte Ansichten leben in der URL
Händler stöbern nicht in Bestellungen, sie arbeiten Listen ab: Nicht erfüllt, Unbezahlt, Offen. Eine gespeicherte Ansicht ist nichts weiter als Filter, eine Sortierung und eine Suche. Halte die ganze Abfrage in der URL, damit ein Link dieselbe Liste öffnet und „Zurück“ funktioniert, und lies sie in Next.js auf dem Server:
Weichen die aktuellen Filter von der gespeicherten Ansicht ab, markiere den Tab als geändert und biete „Zurücksetzen“, „Ansicht aktualisieren“ und „Als neue Ansicht speichern“ an, so wie eine Tabellenkalkulation dir zeigt, dass ein Filter aktiv ist.
Neue Bestellungen sollten die Zeile unter dem Cursor nicht verschieben
Bestellungen gehen ein, während der Händler auf die Liste schaut. Schieben neue Zeilen alles nach unten, wird die Checkbox, die er gerade anklicken wollte, zu einer anderen Bestellung. Solange eine Zeile fokussiert oder etwas ausgewählt ist, halte neue Bestellungen hinter einer Pill „1 neue Bestellung · Anzeigen“ zurück und füge sie erst oben ein, wenn nichts in Bearbeitung ist.
Massenaktionen scheitern teilweise
„6 Bestellungen als erfüllt markieren“ erfüllt manchmal nur 5. Eine ist zurückgestellt oder wurde vor einer Minute storniert. Entwirf die API dafür: Gib die IDs zurück, die fehlgeschlagen sind, jeweils mit Grund, und melde genau das.
Die Meldung schreibt sich dann von selbst: „5 Bestellungen erfüllt. 1 konnte nicht erfüllt werden: #1031 ist zurückgestellt“, mit einem Link zu dieser Bestellung. Ein Alles-oder-nichts-Fehlertoast würde den Händler raten lassen, welche durchgegangen sind.
Die Bestellseite zeigt, was noch offen ist
Teil auf der Bestellseite die Artikel danach auf, was mit ihnen passiert ist, nicht danach, wie sie gekauft wurden. Jede Sendung bekommt eine eigene Karte mit Versanddienstleister und Sendungsverfolgung. Erstattete Artikel bekommen ihre eigene. Und die wichtigste Karte ist die mit dem, was noch zu versenden ist:
Die Timeline darunter sollte aus den Ereignissen der Bestellung aufgebaut sein, mit kurzen Titeln aus den Daten („Zahlung über $564.41 erfasst“, „Mit UPS versendet“ mit verlinkter Sendungsnummer), der genauen Uhrzeit in einem Tooltip und wiederholten Ereignissen derselben Art zu einer Zeile zusammengefasst. Kommentare des Teams stehen im selben Feed und werden optimistisch gepostet, mit erneutem Versuch, falls sie fehlschlagen.
Die Erstattungsrechnung muss auf den Cent aufgehen
Bei Erstattungen rechnet Admin-Code am häufigsten mit Geld falsch. Ein Kunde hat drei Artikel mit $10 Bestellrabatt plus Steuer gekauft und schickt einen zurück. Wie viel bekommt er zurück?
Verteile Beträge auf Bestellebene im Verhältnis zum Preis jeder Position auf die Positionen. Rabatte nach Preis, Steuer nach Preis nach Rabatt. Beim Aufteilen in ganzzahlige Cent bleiben Reste übrig, und das Verfahren der größten Reste verteilt sie so, dass die Teile immer genau das Original ergeben:
allocate(1000, [3000, 3000, 3000]) ergibt [334, 333, 333], in der Summe also genau 1000.
Es gibt eine zweite Falle. Erstattest du heute eine Einheit einer Position und die anderen zwei nächste Woche, müssen die Summen dieselben sein, wie wenn du alle drei auf einmal erstattest. Gib jeder Einheit einen kumulativen Anteil am Anteil der Position, statt jede Erstattung für sich zu runden:
from ist die Anzahl der bereits erstatteten Einheiten. Egal wie die Erstattungen aufgeteilt werden, die Anteile ergeben zusammen lineShare.
Der Rest des Erstattungsbildschirms ist Zustandsbehandlung. Versand kann bis zum gezahlten Betrag abzüglich bereits erstatteter Beträge erstattet werden. Wurde eine Bestellung auf eine Geschenkkarte und eine Karte aufgeteilt, geht das Geld in einer festen Reihenfolge an die Zahlungsmethoden zurück. Schick einen Idempotenzschlüssel mit, damit eine wiederholte Anfrage nie doppelt erstattet, frag bei großen Erstattungen nach einer Bestätigung und zeig „ausstehend bei der Bank“ als eigenen Zustand statt als Erfolg oder Fehler.
Bestand: anpassen, nicht überschreiben
Der naive Bestandseditor schickt die neue Zahl: „Sand / Large ist 47“. Sind zwei Bestellungen eingegangen, während der Händler getippt hat, macht das Speichern von 47 sie stillschweigend rückgängig.
Schick Änderungen stattdessen als Anpassungen. „5 erhalten“ ist ein Delta, und Deltas lassen sich gefahrlos auf alles anwenden, was in der Zwischenzeit passiert ist. Meint der Händler wirklich einen absoluten Wert, „Auf 47 setzen“ nach einer Inventur, wandle ihn in ein Delta gegenüber der Menge um, die er gesehen hat, und lass den Server ablehnen, falls sich diese Menge verändert hat:
Eine abgelehnte Zeile bleibt mit einer Meldung wie „Menge wurde von einem anderen Gerät auf 40 geändert“ im Entwurf, und „Erneut anwenden“ schickt sie noch einmal, aufbauend auf der neuen Zahl. Die inventoryAdjustQuantities-Mutation von Shopify funktioniert mit ihrem Feld changeFromQuantity genauso.
Teil außerdem die Zahl auf. Physischer Bestand ist, was im Lager liegt, aber gebundener Bestand ist schon verkauft, und nicht verfügbarer Bestand ist beschädigt oder zurückgestellt. Händler müssen den verfügbaren Bestand getrennt vom physischen sehen, sonst verkaufen sie Ware, die schon verkauft ist.
Rabatte als Satz
Rabattformulare sind meist eine lange Liste von Feldern, und Händler wissen nicht, was sie gebaut haben, bis sich ein Kunde beschwert. Formuliere das Angebot als einen Satz mit editierbaren Teilen, „Kunden erhalten 15 % Rabatt auf Ring Vase und Glass Carafe“, pack Mindestbestellwert, Kundenberechtigung, Nutzungslimits, Kombinierbarkeit und Zeitplan in eingeklappte Zeilen, die ihren aktuellen Wert zeigen, und zeig eine Vorschau eines Beispiel-Checkouts mit angewendetem Rabatt.
Warne vor riskanten Konfigurationen wie 100 % Rabatt ohne Nutzungslimit, und plane in der Zeitzone des Shops, nicht in der des Browsers.
Ein Store, jede Seite
Das letzte Stück ist Konsistenz. Erstattest du einen Artikel, sollten sich der Zahlungsstatus der Bestellung, ihre Timeline, der Gesamtumsatz des Kunden und der zurückgebuchte Bestand gemeinsam ändern. Lass jede Mutation die Entitäten zurückgeben, die sie geändert hat, und aktualisiere oder revalidiere jede Ansicht, die sie zeigt. Und blockiere das Wegnavigieren von ungespeicherten Produkt-, Bestands- oder Rabattänderungen, auch über den Zurück-Button des Browsers.
Die passenden HextaUI-Blöcke
Um genau diese Probleme herum sind die Admin-Blöcke in HextaUI Pro gebaut. Orders Table hat gespeicherte Ansichten in der URL, getrennte Pills für Zahlung und Fulfillment mit Fortschrittssymbolen und Massenaktionen, die Teilfehler melden. Refund rechnet in calculateRefund(order, selection), einer reinen Funktion mit Tests, die das Verfahren der größten Reste nutzt. Inventory speichert Anpassungen statt Überschreibungen und lässt Händler abgelehnte Zeilen erneut anwenden. Discount Builder bearbeitet das Angebot als Satz neben einer Live-Vorschau des Checkouts.
Commerce Dashboard fügt sie zusammen mit Order Detail, Product Editor und Sales Overview zu einem Shop-Admin auf einem gemeinsamen Store zusammen, standardmäßig mit Hash-Routen oder über seine Router-Prop mit deinen Next.js-Routen, und mit Beispielen, die jede Aktion auf die Shopify Admin API abbilden. Die Live-Vorschau ist das vollständige Dashboard, du kannst also eine Bestellung erstatten und zusehen, wie sich jede Seite aktualisiert.
Häufige Fragen
Sollte eine Bestellung einen Status haben oder zwei? Mindestens zwei: Zahlung und Fulfillment sind unabhängig voneinander, und Händler filtern nach Kombinationen davon.
Wie verteile ich einen Rabatt auf erstattete Artikel? In ganzzahligen Cent, im Verhältnis zum Preis jeder Position, mit dem Verfahren der größten Reste, damit die Teile exakt aufgehen.
Sollten Bestandsänderungen die neue Menge oder die Differenz senden? Die Differenz. Deltas überstehen Bestellungen, die eingehen, während der Händler bearbeitet; ein „Setzen auf“-Wert sollte gegen die Menge geprüft werden, auf der er basiert.
Wo sollten die Filter eines Admins liegen? In der URL, damit Ansichten geteilt, neu geladen und mit „Zurück“ navigiert werden können.