diff --git a/prisma/migrations/20260912020343_add_batch_images/migration.sql b/prisma/migrations/20260912020343_add_batch_images/migration.sql new file mode 100644 index 0000000..abf77d1 --- /dev/null +++ b/prisma/migrations/20260912020343_add_batch_images/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - Made the column `images` on table `harvests` required. This step will fail if there are existing NULL values in that column. + - Made the column `tasteTags` on table `products` required. This step will fail if there are existing NULL values in that column. + - Made the column `images` on table `products` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "batches" ADD COLUMN "images" JSONB NOT NULL DEFAULT '[]'; + +-- AlterTable +ALTER TABLE "harvests" ALTER COLUMN "images" SET NOT NULL; + +-- AlterTable +ALTER TABLE "products" ALTER COLUMN "tasteTags" SET NOT NULL, +ALTER COLUMN "images" SET NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 505ffd3..78a6ef5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -171,6 +171,11 @@ model Batch { bestBeforeDate DateTime glassCount Int remainingGlasses Int + /// Liste von Bild-URLs als JSON-Array (String[] ist nicht Provider-uebergreifend: + /// MySQL kennt keine nativen Skalar-Arrays) — siehe lib/json-array.ts fuer den + /// Normalisierungs-Helfer beim Lesen. Eigene Fotos dieser konkreten Charge + /// (z.B. Etikett/Abfuellung), unabhaengig von Harvest.images. + images Json @default("[]") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/app/admin/actions/harvests-batches.ts b/src/app/admin/actions/harvests-batches.ts index 6becd5a..69cc23f 100644 --- a/src/app/admin/actions/harvests-batches.ts +++ b/src/app/admin/actions/harvests-batches.ts @@ -147,6 +147,7 @@ export async function createBatchAction(formData: FormData): Promise { bestBeforeDate: new Date(str(formData, "bestBeforeDate")), glassCount: num(formData, "glassCount"), remainingGlasses: num(formData, "remainingGlasses"), + images: strArray(formData, "images").filter(isValidUploadPath), }, }); @@ -167,6 +168,7 @@ export async function updateBatchAction(formData: FormData): Promise { bestBeforeDate: new Date(str(formData, "bestBeforeDate")), glassCount: num(formData, "glassCount"), remainingGlasses: num(formData, "remainingGlasses"), + images: strArray(formData, "images").filter(isValidUploadPath), }, }); diff --git a/src/app/admin/chargen/[id]/page.tsx b/src/app/admin/chargen/[id]/page.tsx index 4b5f38e..9de8387 100644 --- a/src/app/admin/chargen/[id]/page.tsx +++ b/src/app/admin/chargen/[id]/page.tsx @@ -1,6 +1,8 @@ import { notFound } from "next/navigation"; import { prisma } from "@/lib/prisma"; +import { asStringArray } from "@/lib/json-array"; import { BackLink } from "@/components/back-link"; +import { AdminImageListField } from "@/components/admin-image-list-field"; import { updateBatchAction } from "../../actions"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; @@ -8,8 +10,9 @@ import { getDictionary } from "@/i18n/get-dictionary"; export default async function AdminBatchEditPage(props: PageProps<"/admin/chargen/[id]">) { const t = getDictionary(await getLocale()); const { id } = await props.params; - const batch = await prisma.batch.findUnique({ where: { id }, include: { harvest: true } }); - if (!batch) notFound(); + const rawBatch = await prisma.batch.findUnique({ where: { id }, include: { harvest: true } }); + if (!rawBatch) notFound(); + const batch = { ...rawBatch, images: asStringArray(rawBatch.images) }; return (
@@ -64,6 +67,11 @@ export default async function AdminBatchEditPage(props: PageProps<"/admin/charge />
+ diff --git a/src/app/charge/[code]/page.tsx b/src/app/charge/[code]/page.tsx index 89f90e1..146b6b3 100644 --- a/src/app/charge/[code]/page.tsx +++ b/src/app/charge/[code]/page.tsx @@ -39,6 +39,9 @@ export default async function BatchPage(props: PageProps<"/charge/[code]">) { { label: t.chargePage.stepFilling, value: formatDate(batch.fillDate, locale) }, { label: t.chargePage.stepBestBefore, value: formatDate(batch.bestBeforeDate, locale) }, ]; + // Eigene Chargenfotos (z.B. Etikett/Abfuellung dieser konkreten Charge) + // gehen vor, falls gepflegt — sonst wie bisher die Erntebilder. + const displayImages = batch.images.length > 0 ? batch.images : batch.harvest.images; return (
@@ -57,7 +60,7 @@ export default async function BatchPage(props: PageProps<"/charge/[code]">) {
) { & { + images: string[]; harvest: HarvestWithImages; products: (Omit & { images: string[] })[]; }) @@ -164,5 +165,9 @@ export async function getBatchByCode( include: { harvest: true, products: true }, }); if (!batch) return null; - return { ...batch, harvest: withImages(batch.harvest), products: batch.products.map(withImages) }; + return { + ...withImages(batch), + harvest: withImages(batch.harvest), + products: batch.products.map(withImages), + }; }