From 806c997b7567796b578421241d0fcfb116bb87ba Mon Sep 17 00:00:00 2001 From: Luis Date: Sat, 13 Jun 2026 17:50:08 +0200 Subject: [PATCH] edit function --- src/actions/cableActions.ts | 71 ++++++++++ src/actions/coreActions.ts | 33 +++++ src/actions/locationActions.ts | 36 +++++ .../cables/[id]/cores/[coreId]/edit/page.tsx | 29 +++++ src/app/cables/[id]/edit/page.tsx | 34 +++++ src/app/cables/[id]/page.tsx | 5 +- src/app/locations/[id]/edit/page.tsx | 28 ++++ src/components/cables/EditCableForm.tsx | 123 ++++++++++++++++++ src/components/cores/CoresList.tsx | 8 +- src/components/cores/EditCoreForm.tsx | 51 ++++++++ src/components/locations/EditLocationForm.tsx | 74 +++++++++++ src/components/locations/LocationsTabe.tsx | 12 +- 12 files changed, 497 insertions(+), 7 deletions(-) create mode 100644 src/app/cables/[id]/cores/[coreId]/edit/page.tsx create mode 100644 src/app/cables/[id]/edit/page.tsx create mode 100644 src/app/locations/[id]/edit/page.tsx create mode 100644 src/components/cables/EditCableForm.tsx create mode 100644 src/components/cores/EditCoreForm.tsx create mode 100644 src/components/locations/EditLocationForm.tsx diff --git a/src/actions/cableActions.ts b/src/actions/cableActions.ts index 83d6eec..0b83698 100644 --- a/src/actions/cableActions.ts +++ b/src/actions/cableActions.ts @@ -75,6 +75,77 @@ export async function createCable( } } +/** + * Updates an existing Cable. Expects a FormData containing `id` and the + * same fields as createCable. Returns success or error for client to handle. + */ +export async function updateCable( + formData: FormData +): Promise<{ success: true } | { success: false; error: string }> { + const id = Number(formData.get("id")); + const identifier = (formData.get("identifier") as string)?.trim(); + const description = (formData.get("description") as string)?.trim(); + const notes = (formData.get("notes") as string)?.trim(); + const startLocationId = Number(formData.get("startLocationId")); + const endLocationId = Number(formData.get("endLocationId")); + const intermediateLocationIds = formData + .getAll("intermediateLocationIds") + .map((value) => Number(value)) + .filter((id) => Boolean(id)); + + if (!id || isNaN(id)) { + return { success: false, error: "Ungültige Kabel-ID." }; + } + if (!identifier) { + return { success: false, error: "Bezeichnung ist erforderlich." }; + } + if (!startLocationId || !endLocationId) { + return { success: false, error: "Start- und End-Ort müssen ausgewählt werden." }; + } + if (intermediateLocationIds.length > 0) { + if (intermediateLocationIds.some((loc) => loc === startLocationId || loc === endLocationId)) { + return { success: false, error: "Zwischenstationen dürfen nicht der Start- oder End-Ort sein." }; + } + const hasDuplicate = new Set(intermediateLocationIds).size !== intermediateLocationIds.length; + if (hasDuplicate) { + return { success: false, error: "Zwischenstationen dürfen nicht doppelt vorkommen." }; + } + } + + try { + // Update main cable fields + await prisma.cable.update({ + where: { id }, + data: { + identifier, + description: description || null, + notes: notes || null, + startLocationId, + endLocationId, + }, + }); + + // Replace intermediate stops: remove existing and create new ordered stops + await prisma.cableStop.deleteMany({ where: { cableId: id } }); + if (intermediateLocationIds.length) { + await prisma.cableStop.createMany({ + data: intermediateLocationIds.map((locationId, index) => ({ + cableId: id, + locationId, + order: index, + })), + }); + } + + revalidatePath("/cables"); + revalidatePath(`/cables/${id}`); + return { success: true }; + } catch (e) { + console.error("updateCable failed:", e); + return { success: false, error: "Fehler beim Aktualisieren des Kabels." }; + } +} + /** * Deletes a Cable and all its Cores (via Prisma's onDelete: Cascade). * Navigation back to /cables is handled by the client component. diff --git a/src/actions/coreActions.ts b/src/actions/coreActions.ts index 0ec5f54..ac26d0c 100644 --- a/src/actions/coreActions.ts +++ b/src/actions/coreActions.ts @@ -49,4 +49,37 @@ export async function createCore( export async function deleteCore(id: number, cableId: number): Promise { await prisma.core.delete({ where: { id } }); revalidatePath(`/cables/${cableId}`); +} + +/** + * Updates an existing Core. Designed for use with useActionState. + */ +export async function updateCore( + prevState: FormState, + formData: FormData +): Promise { + const id = Number(formData.get("id")); + const cableId = Number(formData.get("cableId")); + const color = (formData.get("color") as string)?.trim(); + const notes = (formData.get("notes") as string)?.trim(); + + if (!id || isNaN(id)) { + return { error: "Ungültige Ader-ID." }; + } + if (!color) { + return { error: "Farbe ist erforderlich." }; + } + + try { + await prisma.core.update({ + where: { id }, + data: { color, notes: notes || null }, + }); + + if (cableId && !isNaN(cableId)) revalidatePath(`/cables/${cableId}`); + return { success: true }; + } catch (e) { + console.error("updateCore failed:", e); + return { error: "Fehler beim Aktualisieren der Ader." }; + } } \ No newline at end of file diff --git a/src/actions/locationActions.ts b/src/actions/locationActions.ts index dce5037..dd16e57 100644 --- a/src/actions/locationActions.ts +++ b/src/actions/locationActions.ts @@ -41,6 +41,42 @@ export async function createLocation( } } +/** + * Updates an existing Location. Designed for use with useActionState. + */ +export async function updateLocation( + prevState: FormState, + formData: FormData +): Promise { + const id = Number(formData.get("id")); + const name = (formData.get("name") as string)?.trim(); + const description = (formData.get("description") as string)?.trim(); + + if (!id || isNaN(id)) { + return { error: "Ungültige Ort-ID." }; + } + if (!name) { + return { error: "Name ist erforderlich." }; + } + + try { + await prisma.location.update({ + where: { id }, + data: { + name, + description: description || null, + }, + }); + + revalidatePath("/locations"); + revalidatePath("/cables"); + return { success: true }; + } catch (e) { + console.error("updateLocation failed:", e); + return { error: "Fehler beim Aktualisieren des Ortes." }; + } +} + /** * Deletes a Location by ID. The UI prevents calling this if the location * is still referenced by cables (enforced via disabled state on the button). diff --git a/src/app/cables/[id]/cores/[coreId]/edit/page.tsx b/src/app/cables/[id]/cores/[coreId]/edit/page.tsx new file mode 100644 index 0000000..f041c6d --- /dev/null +++ b/src/app/cables/[id]/cores/[coreId]/edit/page.tsx @@ -0,0 +1,29 @@ +export const dynamic = "force-dynamic"; + +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/prisma"; +import EditCoreForm from "@/components/cores/EditCoreForm"; + +interface EditCorePageProps { + params: Promise<{ id: string; coreId: string }>; +} + +export default async function EditCorePage({ params }: EditCorePageProps) { + const { id, coreId } = await params; + const cableId = parseInt(id, 10); + const coreIdNum = parseInt(coreId, 10); + if (isNaN(cableId) || isNaN(coreIdNum)) notFound(); + + const core = await prisma.core.findUnique({ where: { id: coreIdNum } }); + if (!core || core.cableId !== cableId) notFound(); + + return ( +
+
+

Ader bearbeiten

+

Bearbeiten Sie die Farbe oder Notizen dieser Ader.

+
+ +
+ ); +} diff --git a/src/app/cables/[id]/edit/page.tsx b/src/app/cables/[id]/edit/page.tsx new file mode 100644 index 0000000..6d11ffa --- /dev/null +++ b/src/app/cables/[id]/edit/page.tsx @@ -0,0 +1,34 @@ +export const dynamic = "force-dynamic"; + +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/prisma"; +import EditCableForm from "@/components/cables/EditCableForm"; +import type { Location } from "@/generated/prisma/client"; + +interface EditCablePageProps { + params: Promise<{ id: string }>; +} + +export default async function EditCablePage({ params }: EditCablePageProps) { + const { id } = await params; + const cableId = parseInt(id, 10); + if (isNaN(cableId)) notFound(); + + const cable = await prisma.cable.findUnique({ + where: { id: cableId }, + include: { intermediateStops: { orderBy: { order: "asc" } } }, + }); + if (!cable) notFound(); + + const locations = (await prisma.location.findMany({ orderBy: { name: "asc" } })) as Location[]; + + return ( +
+
+

Kabel bearbeiten

+

Bearbeiten Sie die Kabel-Metadaten und Zwischenstationen.

+
+ +
+ ); +} diff --git a/src/app/cables/[id]/page.tsx b/src/app/cables/[id]/page.tsx index b6d8351..d87472a 100644 --- a/src/app/cables/[id]/page.tsx +++ b/src/app/cables/[id]/page.tsx @@ -80,7 +80,10 @@ export default async function CableDetailPage({ })}

- +
+ Bearbeiten + +
{/* Route visualization */} diff --git a/src/app/locations/[id]/edit/page.tsx b/src/app/locations/[id]/edit/page.tsx new file mode 100644 index 0000000..02b6d6a --- /dev/null +++ b/src/app/locations/[id]/edit/page.tsx @@ -0,0 +1,28 @@ +export const dynamic = "force-dynamic"; + +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/prisma"; +import EditLocationForm from "@/components/locations/EditLocationForm"; + +interface EditLocationPageProps { + params: Promise<{ id: string }>; +} + +export default async function EditLocationPage({ params }: EditLocationPageProps) { + const { id } = await params; + const locationId = parseInt(id, 10); + if (isNaN(locationId)) notFound(); + + const location = await prisma.location.findUnique({ where: { id: locationId } }); + if (!location) notFound(); + + return ( +
+
+

Ort bearbeiten

+

Bearbeiten Sie die Details dieses Ortes.

+
+ +
+ ); +} diff --git a/src/components/cables/EditCableForm.tsx b/src/components/cables/EditCableForm.tsx new file mode 100644 index 0000000..a223c9e --- /dev/null +++ b/src/components/cables/EditCableForm.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import type { Location } from "@/generated/prisma/client"; +import { updateCable } from "@/actions/cableActions"; + +interface EditCableFormProps { + cable: { + id: number; + identifier: string; + description?: string | null; + notes?: string | null; + startLocationId: number; + endLocationId: number; + intermediateStops: { locationId: number }[]; + }; + locations: Location[]; +} + +export default function EditCableForm({ cable, locations }: EditCableFormProps) { + const [isOpen] = useState(true); + const [error, setError] = useState(null); + const [stations, setStations] = useState( + cable.intermediateStops.map((s) => s.locationId) + ); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + setError(null); + + startTransition(async () => { + const result = await updateCable(formData); + if (!result.success) { + setError(result.error); + } else { + router.push(`/cables/${cable.id}`); + } + }); + } + + if (!isOpen) return null; + + return ( +
+
+

Kabel bearbeiten

+
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ {stations.map((stationId, index) => ( +
+ + +
+ ))} +
+
+ +
+ + +
+ +
+ +