feat: Archiv-Tab fuer Bestellungen, Retouren und Produkte im Admin
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 3m24s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 3m24s
Abgeschlossene Eintraege wandern automatisch in einen Tab "Archiv", damit die aktiven Listen nur zeigen, was noch zu tun ist: - Bestellungen: versendet/abgeholt/storniert, keine offene Retoure, seit einem Monat nicht bearbeitet - Retouren: angenommen/abgelehnt/erstattet, seit einem Monat nicht bearbeitet - Produkte: seit einem Monat Bestand 0 (nur Admin-Sicht, Shop unveraendert) Die Automatik laeuft beim Oeffnen der jeweiligen Liste (hoechstens alle 5 Minuten) und holt Eintraege zurueck, sobald sie wieder offen sind. Zusaetzlich "Archivieren"/"Wiederherstellen" per Hand; von Hand archivierte Eintraege fasst die Automatik nicht an. Alle Verschiebungen landen im Audit-Log. Neue Felder archivedAt/archivedManually samt Index an orders, return_requests und products (Migration 20260925010000_archive_fields). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
01eaca39dc
commit
fb9c2353c6
14 files changed
+772
-54
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import Link from "next/link";
|
||||
import { setArchivedAction } from "@/app/admin/actions";
|
||||
import { ARCHIVE_VIEW_PARAM, ARCHIVE_VIEW_VALUE, type ArchiveKind } from "@/lib/archive";
|
||||
import { formatDate } from "@/lib/format";
|
||||
|
||||
/**
|
||||
* Tabs "Aktiv" / "Archiv" fuer die Admin-Listen Bestellungen, Retouren und
|
||||
* Produkte. Reine Links statt Client-State: der Tab steht in der URL
|
||||
* (?ansicht=archiv), bleibt also beim Neuladen, nach einer Server Action und
|
||||
* beim Teilen eines Links erhalten. Uebrige Filter (Suche, Status) bleiben
|
||||
* beim Wechsel stehen.
|
||||
*/
|
||||
export function AdminArchiveTabs({
|
||||
basePath,
|
||||
isArchive,
|
||||
counts,
|
||||
keepParams,
|
||||
labels,
|
||||
}: {
|
||||
basePath: string;
|
||||
isArchive: boolean;
|
||||
counts: { active: number; archived: number };
|
||||
keepParams: Record<string, string>;
|
||||
labels: { ariaLabel: string; active: string; archive: string };
|
||||
}) {
|
||||
const hrefFor = (archive: boolean): string => {
|
||||
const params = new URLSearchParams(Object.entries(keepParams).filter(([, value]) => value !== ""));
|
||||
if (archive) params.set(ARCHIVE_VIEW_PARAM, ARCHIVE_VIEW_VALUE);
|
||||
const query = params.toString();
|
||||
return query ? `${basePath}?${query}` : basePath;
|
||||
};
|
||||
|
||||
const tabClass = (selected: boolean): string =>
|
||||
`px-4 py-2 text-sm border-b-2 -mb-px transition-colors ${
|
||||
selected ? "border-forest text-forest" : "border-transparent text-ink/60 hover:text-forest"
|
||||
}`;
|
||||
|
||||
return (
|
||||
<nav aria-label={labels.ariaLabel} className="flex gap-1 border-b border-ink/10 mb-6">
|
||||
<Link href={hrefFor(false)} aria-current={!isArchive ? "page" : undefined} className={tabClass(!isArchive)}>
|
||||
{labels.active} <span className="text-xs text-ink/50">({counts.active})</span>
|
||||
</Link>
|
||||
<Link href={hrefFor(true)} aria-current={isArchive ? "page" : undefined} className={tabClass(isArchive)}>
|
||||
{labels.archive} <span className="text-xs text-ink/50">({counts.archived})</span>
|
||||
</Link>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
/** Knopf "Archivieren" bzw. "Wiederherstellen" samt Archivdatum. */
|
||||
export function AdminArchiveToggle({
|
||||
kind,
|
||||
id,
|
||||
archivedAt,
|
||||
archivedManually,
|
||||
labels,
|
||||
}: {
|
||||
kind: ArchiveKind;
|
||||
id: string;
|
||||
archivedAt: Date | null;
|
||||
archivedManually: boolean;
|
||||
labels: { archive: string; restore: string; archivedOn: (date: string) => string; manualHint: string };
|
||||
}) {
|
||||
return (
|
||||
<form action={setArchivedAction} className="inline">
|
||||
<input type="hidden" name="kind" value={kind} />
|
||||
<input type="hidden" name="id" value={id} />
|
||||
<input type="hidden" name="mode" value={archivedAt ? "restore" : "archive"} />
|
||||
{archivedAt ? (
|
||||
<span className="block text-xs text-ink/55 mb-1">
|
||||
{labels.archivedOn(formatDate(archivedAt))}
|
||||
{archivedManually ? ` · ${labels.manualHint}` : null}
|
||||
</span>
|
||||
) : null}
|
||||
<button type="submit" className="text-xs text-ink/65 underline hover:text-forest">
|
||||
{archivedAt ? labels.restore : labels.archive}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user