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:
1 parent
a09057d960
commit
b7a75dddca
12 files changed
+364
-524
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user