Files
Wabenhain/src/components/admin-archive.tsx
T
Nick SzaunigandClaude Opus 5.5 11eccec01b feat(ui): Polish-Durchgang Admin und Mobil, Erstattung nur mit Rueckfrage
Sicherheit: "Jetzt per Stripe erstatten" loeste die Rueckerstattung bisher
mit einem einzigen Klick aus. Erstatten und Ablehnen einer Retoure laufen
jetzt ueber ConfirmForm; die Rueckfrage nennt Bestellnummer und Betrag.

Admin: Produkte, Bestellungen und Retouren nutzen dieselben Bausteine wie
die uebrigen Listen (Seitenkopf, Suche mit Zusatzfiltern und Archiv-
Parameter, Status-Badges, Leerzustaende). Produkttabelle ohne seitliches
Scrollen bei 1440 px: Bearbeiten ueber den Produktnamen, Charge ohne
Umbruch. Geschenk-Markierung als SVG statt Emoji. Letzte rohe Knoepfe
(Bestelldetail, ApiCore, Inhalte) auf Button umgestellt.

Mobil (390 px): Ueberlauf auf /konto (Datenexport) und /kontakt
(E-Mail-Adresse) behoben. Tippziele unter 24 px vergroessert
(Zurueck-Link, "Zum Honig", Links auf der Produktseite). Wunschliste mit
SVG-Stern statt Unicode-Zeichen.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 21:24:55 +02:00

81 lines
2.9 KiB
TypeScript

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="link-draw cursor-pointer text-ink/70 hover:text-forest">
{archivedAt ? labels.restore : labels.archive}
</button>
</form>
);
}