refactor(admin): ApiCore-Synchronisierung nur noch einmal auf dem Dashboard
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m25s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m25s
Derselbe "Alles synchronisieren"-Knopf stand auf /admin/produkte und /admin/ernten und sah dadurch wie zwei verschiedene Abgleiche aus. Er sitzt jetzt einmal auf dem Dashboard, das Ergebnis des Laufs erscheint dort oben. Die Action springt standardmaessig nach /admin zurueck; die alten Ziele bleiben in der Whitelist erlaubt. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
11eccec01b
commit
ae2726b0ee
5 files changed
+21
-18
No files matched your search
@@ -370,11 +370,13 @@ async function syncBatches(sources: ApiCoreBottlingBatchWithHarvestId[]): Promis
|
|||||||
* Formularfeld und waere sonst eine offene Weiterleitung — ein praeparierter
|
* Formularfeld und waere sonst eine offene Weiterleitung — ein praeparierter
|
||||||
* Wert koennte Admins auf eine fremde Seite schicken.
|
* Wert koennte Admins auf eine fremde Seite schicken.
|
||||||
*/
|
*/
|
||||||
const RETURN_TARGETS = ["/admin/ernten", "/admin/produkte", "/admin/chargen"] as const;
|
// "/admin" ist der Normalfall (der eine Sync-Knopf sitzt auf dem Dashboard);
|
||||||
|
// die uebrigen bleiben erlaubt, damit alte Lesezeichen/Formulare nicht ins Leere laufen.
|
||||||
|
const RETURN_TARGETS = ["/admin", "/admin/ernten", "/admin/produkte", "/admin/chargen"] as const;
|
||||||
|
|
||||||
function returnTargetFrom(formData: FormData): string {
|
function returnTargetFrom(formData: FormData): string {
|
||||||
const requested = formData.get("returnTo");
|
const requested = formData.get("returnTo");
|
||||||
return RETURN_TARGETS.find((target) => target === requested) ?? "/admin/ernten";
|
return RETURN_TARGETS.find((target) => target === requested) ?? "/admin";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ import {
|
|||||||
AdminRowActions,
|
AdminRowActions,
|
||||||
} from "@/components/admin-list";
|
} from "@/components/admin-list";
|
||||||
import { createHarvestAction, toggleHarvestPublishedAction, deleteHarvestAction } from "../actions";
|
import { createHarvestAction, toggleHarvestPublishedAction, deleteHarvestAction } from "../actions";
|
||||||
import { ApiCoreSyncPanel, ApiCoreSyncResult } from "@/components/apicore-sync-panel";
|
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
|
||||||
export default async function AdminHarvestsPage(props: PageProps<"/admin/ernten">) {
|
export default async function AdminHarvestsPage() {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
const searchParams = await props.searchParams;
|
|
||||||
const harvests = await prisma.harvest.findMany({
|
const harvests = await prisma.harvest.findMany({
|
||||||
orderBy: [{ year: "desc" }, { harvestDate: "desc" }],
|
orderBy: [{ year: "desc" }, { harvestDate: "desc" }],
|
||||||
include: { _count: { select: { batches: true, products: true } } },
|
include: { _count: { select: { batches: true, products: true } } },
|
||||||
@@ -31,7 +29,6 @@ export default async function AdminHarvestsPage(props: PageProps<"/admin/ernten"
|
|||||||
<BackLink href="/admin" label={t.adminHarvestsPage.backToDashboard} />
|
<BackLink href="/admin" label={t.adminHarvestsPage.backToDashboard} />
|
||||||
<AdminPageHeader title={t.adminHarvestsPage.title} />
|
<AdminPageHeader title={t.adminHarvestsPage.title} />
|
||||||
|
|
||||||
<ApiCoreSyncResult searchParams={searchParams} />
|
|
||||||
|
|
||||||
<div className="overflow-x-auto bg-background border border-ink/10">
|
<div className="overflow-x-auto bg-background border border-ink/10">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
@@ -94,7 +91,7 @@ export default async function AdminHarvestsPage(props: PageProps<"/admin/ernten"
|
|||||||
{harvests.length === 0 ? <AdminEmptyState message={t.adminHarvestsPage.noHarvestsYet} /> : null}
|
{harvests.length === 0 ? <AdminEmptyState message={t.adminHarvestsPage.noHarvestsYet} /> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-10 grid md:grid-cols-2 gap-6">
|
<div className="mt-10 max-w-xl">
|
||||||
<div className="bg-background border border-ink/10 p-6">
|
<div className="bg-background border border-ink/10 p-6">
|
||||||
<h2 className="font-serif text-xl text-forest mb-4">{t.adminHarvestsPage.newHarvestHeading}</h2>
|
<h2 className="font-serif text-xl text-forest mb-4">{t.adminHarvestsPage.newHarvestHeading}</h2>
|
||||||
<form action={createHarvestAction} className="space-y-4">
|
<form action={createHarvestAction} className="space-y-4">
|
||||||
@@ -112,7 +109,6 @@ export default async function AdminHarvestsPage(props: PageProps<"/admin/ernten"
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ApiCoreSyncPanel returnTo="/admin/ernten" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+11
-1
@@ -1,4 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ApiCoreSyncPanel, ApiCoreSyncResult } from "@/components/apicore-sync-panel";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { formatPrice } from "@/lib/format";
|
import { formatPrice } from "@/lib/format";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
@@ -100,7 +101,8 @@ function daysAgo(n: number): Date {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminDashboardPage() {
|
export default async function AdminDashboardPage(props: PageProps<"/admin">) {
|
||||||
|
const searchParams = await props.searchParams;
|
||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const t = getDictionary(locale);
|
const t = getDictionary(locale);
|
||||||
const [
|
const [
|
||||||
@@ -306,6 +308,9 @@ export default async function AdminDashboardPage() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Ergebnis des ApiCore-Laufs (nur direkt nach dem Klick sichtbar). */}
|
||||||
|
<ApiCoreSyncResult searchParams={searchParams} />
|
||||||
|
|
||||||
{/* Fehlen Dateien, zu denen die Datenbank noch Pfade haelt, ist das
|
{/* Fehlen Dateien, zu denen die Datenbank noch Pfade haelt, ist das
|
||||||
fast immer ein nicht persistentes Upload-Verzeichnis (in Kubernetes
|
fast immer ein nicht persistentes Upload-Verzeichnis (in Kubernetes
|
||||||
ein "emptyDir" statt eines PersistentVolumeClaim). Ohne diesen
|
ein "emptyDir" statt eines PersistentVolumeClaim). Ohne diesen
|
||||||
@@ -576,6 +581,11 @@ export default async function AdminDashboardPage() {
|
|||||||
{t.adminDashboardPage.viewAllOrders}
|
{t.adminDashboardPage.viewAllOrders}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Der eine Sync-Knopf fuer Ernten, Chargen und Produkte. */}
|
||||||
|
<div className="mt-10 max-w-3xl">
|
||||||
|
<ApiCoreSyncPanel returnTo="/admin" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { BackLink } from "@/components/back-link";
|
|||||||
import { formatPrice } from "@/lib/format";
|
import { formatPrice } from "@/lib/format";
|
||||||
import { ConfirmForm } from "@/components/confirm-form";
|
import { ConfirmForm } from "@/components/confirm-form";
|
||||||
import { deleteProductAction } from "../actions";
|
import { deleteProductAction } from "../actions";
|
||||||
import { ApiCoreSyncPanel, ApiCoreSyncResult } from "@/components/apicore-sync-panel";
|
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
import { isArchiveView } from "@/lib/archive";
|
import { isArchiveView } from "@/lib/archive";
|
||||||
@@ -49,7 +48,6 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
|
|||||||
<BackLink href="/admin" label={t.adminProductsPage.backToDashboard} />
|
<BackLink href="/admin" label={t.adminProductsPage.backToDashboard} />
|
||||||
<AdminPageHeader title={t.adminProductsPage.title} action={newProduct} />
|
<AdminPageHeader title={t.adminProductsPage.title} action={newProduct} />
|
||||||
|
|
||||||
<ApiCoreSyncResult searchParams={searchParams} />
|
|
||||||
|
|
||||||
<AdminArchiveTabs
|
<AdminArchiveTabs
|
||||||
basePath="/admin/produkte"
|
basePath="/admin/produkte"
|
||||||
@@ -155,9 +153,6 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-10 max-w-3xl">
|
|
||||||
<ApiCoreSyncPanel returnTo="/admin/produkte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,10 @@ import { Button } from "@/components/ui";
|
|||||||
/**
|
/**
|
||||||
* Der eine Sync-Knopf und die Anzeige seines Ergebnisses.
|
* Der eine Sync-Knopf und die Anzeige seines Ergebnisses.
|
||||||
*
|
*
|
||||||
* Gemeinsam genutzt von /admin/ernten und /admin/produkte: es ist derselbe
|
* Sitzt genau einmal: auf dem Admin-Dashboard. Ernten, Chargen und Produkte
|
||||||
* Vorgang, egal von welcher Seite er ausgeloest wird — Ernten, Chargen und
|
* laufen ohnehin immer zusammen durch (siehe syncApiCoreAction) — derselbe
|
||||||
* Produkte laufen ohnehin immer zusammen. Zwei getrennte Bloecke in zwei
|
* Knopf zusaetzlich auf /admin/ernten und /admin/produkte sah aus wie zwei
|
||||||
* Seiten waeren dieselbe Anzeige zweimal gepflegt.
|
* verschiedene Synchronisierungen.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Rohwerte aus der Adresszeile — Next liefert Query-Parameter so aus. */
|
/** Rohwerte aus der Adresszeile — Next liefert Query-Parameter so aus. */
|
||||||
|
|||||||
Reference in new issue
Block a user