feat(admin): Formulare fuer Gutscheine, Versand, Benutzer, Ernten und Chargen vereinheitlicht

Alle Anlege- und Bearbeiten-Formulare nutzen jetzt die Bausteine aus
components/admin-form.tsx wie das Produktformular: Rahmen mit fester
Speicherleiste, Abschnitte mit Serif-Ueberschrift (Gutschein: Code und
Rabatt / Bedingungen / Zeitraum und Status; Ernte: Angaben / Bilder /
Sichtbarkeit; Charge: Abfuellung und Bestand / Bilder), Schalter statt
Checkbox, buendige Eingabefelder in Rastern.

Benutzer bearbeiten: Sicherheitsbereich mit Status-Badges, Passwort-Reset
als Sekundaerknopf, Sperren/Loeschen in eigenem abgesetzten Bereich.
AdminField/AdminSelect koennen jetzt min und einen Hinweistext.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 21:14:10 +02:00
1 parent a09057d960
commit b7a75dddca
12 files changed
+364 -524

No files matched your search

+47 -76
View File
@@ -2,13 +2,23 @@ import { notFound } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { asStringArray } from "@/lib/json-array";
import { BackLink } from "@/components/back-link";
import { Button } from "@/components/ui";
import { AdminImageField } from "@/components/admin-image-field";
import {
AdminField,
AdminFieldset,
AdminFormPanel,
AdminHint,
AdminSwitch,
AdminTextarea,
} from "@/components/admin-form";
import { updateHarvestAction, toggleHarvestPublishedAction } from "../../actions";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
export default async function AdminHarvestEditPage(props: PageProps<"/admin/ernten/[id]">) {
const t = getDictionary(await getLocale());
const th = t.adminHarvestEditPage;
const { id } = await props.params;
const rawHarvest = await prisma.harvest.findUnique({ where: { id } });
if (!rawHarvest) notFound();
@@ -17,102 +27,63 @@ export default async function AdminHarvestEditPage(props: PageProps<"/admin/ernt
const harvestDateValue = harvest.harvestDate.toISOString().slice(0, 10);
return (
<div className="max-w-xl">
<BackLink href="/admin/ernten" label={t.adminHarvestEditPage.backToHarvests} />
<h1 className="text-2xl text-forest mb-8">{t.adminHarvestEditPage.title}</h1>
<div className="max-w-2xl">
<BackLink href="/admin/ernten" label={th.backToHarvests} />
<h1 className="text-2xl text-forest mb-8">{th.title}</h1>
{harvest.isPublished ? null : (
<div className="mb-6 bg-honey/10 border border-honey/30 px-4 py-3 flex flex-col sm:flex-row sm:items-center gap-3 justify-between">
<p className="text-sm text-honey-dark">{t.adminHarvestEditPage.draftNotice}</p>
<div className="mb-6 flex flex-col justify-between gap-3 border border-honey/40 bg-honey/5 px-4 py-3 sm:flex-row sm:items-center">
<p className="text-sm text-honey-dark">{th.draftNotice}</p>
<form action={toggleHarvestPublishedAction}>
<input type="hidden" name="id" value={harvest.id} />
<input type="hidden" name="isPublished" value="on" />
<button type="submit" className="bg-forest text-cream px-4 py-2 text-sm whitespace-nowrap">
{t.adminHarvestEditPage.publishNow}
</button>
<Button type="submit" className="whitespace-nowrap px-5 py-2.5">
{th.publishNow}
</Button>
</form>
</div>
)}
<form action={updateHarvestAction} className="space-y-4 bg-background border border-ink/10 p-6">
<AdminFormPanel action={updateHarvestAction} actions={<Button type="submit">{th.save}</Button>}>
<input type="hidden" name="id" value={harvest.id} />
<div className="grid grid-cols-2 gap-4">
<label className="block text-sm">
<span className="text-ink/70">{t.adminHarvestEditPage.typeLabel}</span>
<input
name="type"
defaultValue={harvest.type}
<AdminFieldset legend={th.sectionDetails}>
<div className="grid grid-cols-2 items-end gap-4">
<AdminField name="type" label={th.typeLabel} defaultValue={harvest.type} required />
<AdminField name="year" label={th.yearLabel} type="number" defaultValue={String(harvest.year)} required />
</div>
<div className="grid items-end gap-4 sm:grid-cols-2">
<AdminField
name="harvestDate"
label={th.harvestDateLabel}
type="date"
defaultValue={harvestDateValue}
required
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="text-ink/70">{t.adminHarvestEditPage.yearLabel}</span>
<input
name="year"
type="number"
defaultValue={harvest.year}
required
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2"
/>
</label>
</div>
<label className="block text-sm">
<span className="text-ink/70">{t.adminHarvestEditPage.harvestDateLabel}</span>
<input
name="harvestDate"
type="date"
defaultValue={harvestDateValue}
required
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="text-ink/70">{t.adminHarvestEditPage.locationLabel}</span>
<input
name="location"
defaultValue={harvest.location}
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="text-ink/70">{t.adminHarvestEditPage.descriptionLabel}</span>
<textarea
<AdminField name="location" label={th.locationLabel} defaultValue={harvest.location} />
</div>
<AdminTextarea
name="description"
label={th.descriptionLabel}
defaultValue={harvest.description}
required
rows={4}
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2"
required
/>
</label>
</AdminFieldset>
{/* Siehe Kommentar im Chargen-Formular: zwei benannte Slots statt
einer Liste, damit sich das zweite Bild auch ohne erstes setzen
laesst. Slot 1 erscheint zusaetzlich auf /ernten und der
Ernten-Detailseite. */}
<fieldset className="border border-ink/10 p-4">
<legend className="px-1 text-sm text-ink/70">{t.adminHarvestEditPage.imagesLabel}</legend>
<div className="space-y-4">
<AdminImageField
name="images"
label={t.adminHarvestEditPage.image1Label}
initialValue={harvest.images[0] ?? ""}
/>
<AdminImageField
name="images"
label={t.adminHarvestEditPage.image2Label}
initialValue={harvest.images[1] ?? ""}
/>
</div>
</fieldset>
<label className="flex items-center gap-2 text-sm text-ink/70">
<input type="checkbox" name="isPublished" defaultChecked={harvest.isPublished} />
{t.adminHarvestEditPage.isPublishedLabel}
</label>
<p className="text-xs text-ink/70">{t.adminHarvestEditPage.isPublishedHint}</p>
<button type="submit" className="bg-forest text-cream px-6 py-2 text-sm">
{t.adminHarvestEditPage.save}
</button>
</form>
<AdminFieldset legend={th.imagesLabel}>
<AdminImageField name="images" label={th.image1Label} initialValue={harvest.images[0] ?? ""} />
<AdminImageField name="images" label={th.image2Label} initialValue={harvest.images[1] ?? ""} />
</AdminFieldset>
<AdminFieldset legend={th.sectionVisibility} last>
<AdminSwitch name="isPublished" label={th.isPublishedLabel} defaultChecked={harvest.isPublished} />
<AdminHint>{th.isPublishedHint}</AdminHint>
</AdminFieldset>
</AdminFormPanel>
</div>
);
}