edit function

This commit is contained in:
2026-06-13 17:50:08 +02:00
parent 2c0bd2c9f1
commit 806c997b75
12 changed files with 497 additions and 7 deletions

View File

@@ -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<string | null>(null);
const [stations, setStations] = useState<number[]>(
cable.intermediateStops.map((s) => s.locationId)
);
const [isPending, startTransition] = useTransition();
const router = useRouter();
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
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 (
<div className="bg-white rounded-xl border border-slate-200 shadow-sm">
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-100">
<h2 className="text-base font-semibold text-slate-900">Kabel bearbeiten</h2>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-5">
<input type="hidden" name="id" value={cable.id} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<div className="md:col-span-2">
<label htmlFor="cable-identifier" className="block text-sm font-medium text-slate-700 mb-1.5">Bezeichnung <span className="text-red-500">*</span></label>
<input id="cable-identifier" name="identifier" type="text" defaultValue={cable.identifier} required className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
</div>
<div>
<label htmlFor="cable-start" className="block text-sm font-medium text-slate-700 mb-1.5">Start-Ort <span className="text-red-500">*</span></label>
<select id="cable-start" name="startLocationId" required defaultValue={String(cable.startLocationId)} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm bg-white">
<option value="" disabled>Ort auswählen</option>
{locations.map((loc) => (
<option key={loc.id} value={loc.id}>{loc.name}</option>
))}
</select>
</div>
<div>
<label htmlFor="cable-end" className="block text-sm font-medium text-slate-700 mb-1.5">End-Ort <span className="text-red-500">*</span></label>
<select id="cable-end" name="endLocationId" required defaultValue={String(cable.endLocationId)} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm bg-white">
<option value="" disabled>Ort auswählen</option>
{locations.map((loc) => (
<option key={loc.id} value={loc.id}>{loc.name}</option>
))}
</select>
</div>
<div className="md:col-span-2">
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-slate-700">Zwischenstationen</label>
<button type="button" onClick={() => setStations((prev) => [...prev, 0])} className="text-sm text-blue-600 hover:text-blue-800">+ hinzufügen</button>
</div>
<div className="space-y-3">
{stations.map((stationId, index) => (
<div key={index} className="grid grid-cols-[1fr_auto] gap-3 items-center">
<select name="intermediateLocationIds" value={stationId} onChange={(e) => {
const value = Number(e.target.value);
setStations((current) => { const next = [...current]; next[index] = value; return next; });
}} required className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm bg-white">
<option value={0} disabled>Ort auswählen</option>
{locations.map((loc) => (
<option key={loc.id} value={loc.id}>{loc.name}</option>
))}
</select>
<button type="button" onClick={() => setStations((current) => current.filter((_, idx) => idx !== index))} className="inline-flex items-center justify-center rounded-lg bg-slate-100 px-3 py-2 text-sm text-slate-700 hover:bg-slate-200">Entfernen</button>
</div>
))}
</div>
</div>
<div className="md:col-span-2">
<label htmlFor="cable-description" className="block text-sm font-medium text-slate-700 mb-1.5">Kurzbeschreibung <span className="text-slate-400 font-normal">(optional)</span></label>
<input id="cable-description" name="description" type="text" defaultValue={cable.description ?? ""} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
</div>
<div className="md:col-span-2">
<label htmlFor="cable-notes" className="block text-sm font-medium text-slate-700 mb-1.5">Notizen <span className="text-slate-400 font-normal">(optional, Markdown)</span></label>
<textarea id="cable-notes" name="notes" rows={3} defaultValue={cable.notes ?? ""} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm resize-y" />
</div>
</div>
{error && <p className="text-sm text-red-600 bg-red-50 border border-red-100 px-3 py-2 rounded-lg">{error}</p>}
<div className="flex items-center gap-3 pt-1">
<button type="submit" disabled={isPending} className="px-5 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg">{isPending ? "Wird gespeichert…" : "Speichern"}</button>
</div>
</form>
</div>
);
}

View File

@@ -1,5 +1,6 @@
import type { Core } from "@/generated/prisma/client";
import DeleteCoreButton from "./DeleteCoreButton";
import Link from "next/link";
import MarkdownContent from "@/components/ui/MarkdownContent";
interface CoresListProps {
@@ -68,8 +69,11 @@ export default function CoresList({ cores, cableId }: CoresListProps) {
)}
</div>
{/* Delete button */}
<DeleteCoreButton id={core.id} cableId={cableId} />
{/* Actions: edit + delete */}
<div className="flex items-center gap-2">
<Link href={`/cables/${cableId}/cores/${core.id}/edit`} className="text-sm text-blue-600 hover:text-blue-800">Bearbeiten</Link>
<DeleteCoreButton id={core.id} cableId={cableId} />
</div>
</div>
))}
</div>

View File

@@ -0,0 +1,51 @@
"use client";
import { useActionState, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { updateCore } from "@/actions/coreActions";
import type { FormState } from "@/actions/coreActions";
import type { Core } from "@/generated/prisma/client";
interface EditCoreFormProps {
core: Core;
}
export default function EditCoreForm({ core }: EditCoreFormProps) {
const [state, formAction] = useActionState(updateCore, {} as FormState);
const formRef = useRef<HTMLFormElement>(null);
const router = useRouter();
useEffect(() => {
if (state.success) {
// Navigate back to the cable detail page
router.push(`/cables/${core.cableId}`);
}
}, [state.success, router, core.cableId]);
return (
<form ref={formRef} action={formAction} className="space-y-3">
<input type="hidden" name="id" value={core.id} />
<input type="hidden" name="cableId" value={core.cableId} />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label htmlFor="core-color" className="block text-xs font-medium text-slate-600 mb-1">Farbe <span className="text-red-500">*</span></label>
<input id="core-color" name="color" type="text" defaultValue={core.color} required className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
</div>
<div>
<label htmlFor="core-notes" className="block text-xs font-medium text-slate-600 mb-1">Notizen <span className="text-slate-400 font-normal">(optional)</span></label>
<input id="core-notes" name="notes" type="text" defaultValue={core.notes ?? ""} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
</div>
</div>
{state.error && (
<p className="text-xs text-red-600 bg-red-50 border border-red-100 px-3 py-2 rounded-lg">{state.error}</p>
)}
<div className="flex items-center gap-3">
<button type="submit" className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg">Speichern</button>
</div>
</form>
);
}

View File

@@ -0,0 +1,74 @@
"use client";
import { useActionState, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { updateLocation } from "@/actions/locationActions";
import type { FormState } from "@/actions/locationActions";
import type { Location } from "@/generated/prisma/client";
interface EditLocationFormProps {
location: Location;
}
export default function EditLocationForm({ location }: EditLocationFormProps) {
const [state, formAction] = useActionState(updateLocation, {} as FormState);
const formRef = useRef<HTMLFormElement>(null);
const router = useRouter();
useEffect(() => {
if (state.success) {
// Navigate back to the locations list after successful update
router.push("/locations");
}
}, [state.success, router]);
return (
<div className="bg-white rounded-xl border border-slate-200 p-6 shadow-sm">
<h2 className="text-base font-semibold text-slate-900 mb-4">Ort bearbeiten</h2>
<form ref={formRef} action={formAction} className="space-y-4">
<input type="hidden" name="id" value={location.id} />
<div>
<label htmlFor="loc-name" className="block text-sm font-medium text-slate-700 mb-1">
Name <span className="text-red-500">*</span>
</label>
<input
id="loc-name"
name="name"
type="text"
defaultValue={location.name}
required
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder:text-slate-400"
/>
</div>
<div>
<label htmlFor="loc-description" className="block text-sm font-medium text-slate-700 mb-1">
Beschreibung <span className="text-slate-400 font-normal">(optional, Markdown)</span>
</label>
<textarea
id="loc-description"
name="description"
rows={3}
defaultValue={location.description ?? ""}
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder:text-slate-400 resize-y"
/>
</div>
{state.error && (
<p className="text-sm text-red-600 bg-red-50 border border-red-100 px-3 py-2 rounded-lg">{state.error}</p>
)}
<div className="flex items-center gap-3">
<button
type="submit"
disabled={false}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg"
>
Speichern
</button>
</div>
</form>
</div>
);
}

View File

@@ -1,5 +1,6 @@
import type { LocationWithCounts } from "@/lib/types";
import DeleteLocationButton from "./DeleteLocationButton";
import Link from "next/link";
interface LocationsTableProps {
locations: LocationWithCounts[];
@@ -73,10 +74,13 @@ export default function LocationsTable({ locations }: LocationsTableProps) {
</span>
</td>
<td className="px-6 py-4 text-right">
<DeleteLocationButton
id={location.id}
cableCount={totalCables}
/>
<div className="inline-flex items-center gap-2">
<Link href={`/locations/${location.id}/edit`} className="text-sm text-blue-600 hover:text-blue-800">Bearbeiten</Link>
<DeleteLocationButton
id={location.id}
cableCount={totalCables}
/>
</div>
</td>
</tr>
);