Rollen-Farben statt reiner Markenfarben, damit hell und dunkel aus einem Satz Klassen entstehen: - heading (Ueberschriften, Links, Rahmen): hell Waldgruen, dunkel Creme - surface (abgesetzte Flaeche): hell Creme, dunkel angehobenes Dunkelgruen - forest bleibt die dunkle Markenflaeche (Hero, Baender, Knoepfe, Admin-Leiste), cream der helle Text darauf - honey-deep als fester Honigton fuer Flaechen; honey-dark (Text) wird im Dunkelmodus heller und bleibt lesbar Codemod ueber src: text-/border-forest -> *-heading, bg-cream -> bg-surface, bg-honey-dark -> bg-honey-deep. Im hellen Modus haben die Rollen exakt die bisherigen Werte - das Erscheinungsbild bleibt gleich. Umschalter (components/theme-toggle.tsx) Automatisch -> Hell -> Dunkel in Kopfzeile, Mobilmenue und Admin-Leiste. Die Wahl liegt im Cookie "theme" (nur light/dark, geprueft in lib/theme.ts); der Server setzt data-theme schon beim Ausliefern, nichts blitzt hell auf. "Automatisch" folgt der Systemeinstellung. Gewaehlter und automatischer Dunkelblock in globals.css werden per Test auf Gleichheit geprueft. Logo, Biene und Select-Pfeil nutzen die Rollenfarben. In der Kopfzeile steht "Honig entdecken" zwischen 1280 und 1535 px nicht mehr doppelt neben "Shop" (sonst stiess die Navigation an), der Slogan erscheint erst ab 1680 px. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
117 lines
5.7 KiB
TypeScript
117 lines
5.7 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import { BackLink } from "@/components/back-link";
|
|
import { ConfirmForm } from "@/components/confirm-form";
|
|
import { formatDate } from "@/lib/format";
|
|
import { Button } from "@/components/ui";
|
|
import { AdminField, AdminSwitch, AdminTextarea } from "@/components/admin-form";
|
|
import {
|
|
AdminActionButton,
|
|
AdminActionLink,
|
|
AdminActionNote,
|
|
AdminBadge,
|
|
AdminEmptyState,
|
|
AdminPageHeader,
|
|
AdminRowActions,
|
|
} from "@/components/admin-list";
|
|
import { createHarvestAction, toggleHarvestPublishedAction, deleteHarvestAction } from "../actions";
|
|
import { getLocale } from "@/i18n/locale";
|
|
import { getDictionary } from "@/i18n/get-dictionary";
|
|
|
|
export default async function AdminHarvestsPage() {
|
|
const t = getDictionary(await getLocale());
|
|
const harvests = await prisma.harvest.findMany({
|
|
orderBy: [{ year: "desc" }, { harvestDate: "desc" }],
|
|
include: { _count: { select: { batches: true, products: true } } },
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
<BackLink href="/admin" label={t.adminHarvestsPage.backToDashboard} />
|
|
<AdminPageHeader title={t.adminHarvestsPage.title} />
|
|
|
|
|
|
<div className="overflow-x-auto bg-background border border-ink/10">
|
|
<table className="w-full text-sm">
|
|
<thead className="text-left text-ink/65 border-b border-ink/10">
|
|
<tr>
|
|
<th className="p-4">{t.adminHarvestsPage.colType}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colYear}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colHarvestDate}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colLocation}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colBatches}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colProducts}</th>
|
|
<th className="p-4">{t.adminHarvestsPage.colStatus}</th>
|
|
<th className="p-4" />
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-ink/10">
|
|
{harvests.map((harvest) => (
|
|
<tr key={harvest.id} className="transition-colors hover:bg-ink/[0.03]">
|
|
<td className="p-4 text-heading">{harvest.type}</td>
|
|
<td className="p-4 tabular-nums">{harvest.year}</td>
|
|
<td className="p-4 whitespace-nowrap tabular-nums">{formatDate(harvest.harvestDate)}</td>
|
|
<td className="p-4">{harvest.location}</td>
|
|
<td className="p-4 tabular-nums">{harvest._count.batches}</td>
|
|
<td className="p-4 tabular-nums">{harvest._count.products}</td>
|
|
<td className="p-4">
|
|
{harvest.isPublished ? (
|
|
<AdminBadge tone="positive">{t.adminHarvestsPage.statusPublished}</AdminBadge>
|
|
) : (
|
|
<AdminBadge tone="pending">{t.adminHarvestsPage.statusDraft}</AdminBadge>
|
|
)}
|
|
</td>
|
|
<td className="p-4">
|
|
<AdminRowActions>
|
|
<AdminActionLink href={`/admin/ernten/${harvest.id}`}>{t.adminHarvestsPage.edit}</AdminActionLink>
|
|
<form action={toggleHarvestPublishedAction}>
|
|
<input type="hidden" name="id" value={harvest.id} />
|
|
{harvest.isPublished ? null : <input type="hidden" name="isPublished" value="on" />}
|
|
<AdminActionButton tone={harvest.isPublished ? "muted" : "default"}>
|
|
{harvest.isPublished ? t.adminHarvestsPage.unpublish : t.adminHarvestsPage.publish}
|
|
</AdminActionButton>
|
|
</form>
|
|
{harvest._count.batches === 0 && harvest._count.products === 0 ? (
|
|
<ConfirmForm
|
|
action={deleteHarvestAction}
|
|
confirmMessage={t.adminHarvestsPage.deleteConfirm(`${harvest.type} ${harvest.year}`)}
|
|
>
|
|
<input type="hidden" name="id" value={harvest.id} />
|
|
<AdminActionButton tone="danger">{t.adminHarvestsPage.delete}</AdminActionButton>
|
|
</ConfirmForm>
|
|
) : (
|
|
// Statt den Knopf wortlos wegzulassen: der Grund steht da.
|
|
<AdminActionNote>{t.adminHarvestsPage.notDeletable}</AdminActionNote>
|
|
)}
|
|
</AdminRowActions>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
{harvests.length === 0 ? <AdminEmptyState message={t.adminHarvestsPage.noHarvestsYet} /> : null}
|
|
</div>
|
|
|
|
<div className="mt-10 max-w-xl">
|
|
<div className="bg-background border border-ink/10 p-6">
|
|
<h2 className="font-serif text-xl text-heading mb-4">{t.adminHarvestsPage.newHarvestHeading}</h2>
|
|
<form action={createHarvestAction} className="space-y-4">
|
|
<div className="grid grid-cols-2 items-end gap-4">
|
|
<AdminField name="type" label={t.adminHarvestsPage.typeLabel} required />
|
|
<AdminField name="year" label={t.adminHarvestsPage.yearLabel} type="number" required />
|
|
</div>
|
|
<AdminField name="harvestDate" label={t.adminHarvestsPage.harvestDateLabel} type="date" required />
|
|
<AdminField name="location" label={t.adminHarvestsPage.locationLabel} defaultValue="Altenmünster" />
|
|
<AdminTextarea name="description" label={t.adminHarvestsPage.descriptionLabel} required />
|
|
<AdminSwitch name="isPublished" label={t.adminHarvestsPage.isPublishedLabel} defaultChecked />
|
|
<div className="flex justify-end border-t border-ink/10 pt-4">
|
|
<Button type="submit">{t.adminHarvestsPage.create}</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|