edit function
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -49,4 +49,37 @@ export async function createCore(
|
||||
export async function deleteCore(id: number, cableId: number): Promise<void> {
|
||||
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<FormState> {
|
||||
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." };
|
||||
}
|
||||
}
|
||||
@@ -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<FormState> {
|
||||
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).
|
||||
|
||||
29
src/app/cables/[id]/cores/[coreId]/edit/page.tsx
Normal file
29
src/app/cables/[id]/cores/[coreId]/edit/page.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-slate-900">Ader bearbeiten</h1>
|
||||
<p className="text-slate-500 mt-1 text-sm">Bearbeiten Sie die Farbe oder Notizen dieser Ader.</p>
|
||||
</div>
|
||||
<EditCoreForm core={core} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
src/app/cables/[id]/edit/page.tsx
Normal file
34
src/app/cables/[id]/edit/page.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-slate-900">Kabel bearbeiten</h1>
|
||||
<p className="text-slate-500 mt-1 text-sm">Bearbeiten Sie die Kabel-Metadaten und Zwischenstationen.</p>
|
||||
</div>
|
||||
<EditCableForm cable={cable as any} locations={locations} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -80,7 +80,10 @@ export default async function CableDetailPage({
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<DeleteCableButton id={cable.id} identifier={cable.identifier} />
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/cables/${cable.id}/edit`} className="text-sm text-blue-600 hover:text-blue-800">Bearbeiten</Link>
|
||||
<DeleteCableButton id={cable.id} identifier={cable.identifier} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Route visualization */}
|
||||
|
||||
28
src/app/locations/[id]/edit/page.tsx
Normal file
28
src/app/locations/[id]/edit/page.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-slate-900">Ort bearbeiten</h1>
|
||||
<p className="text-slate-500 mt-1 text-sm">Bearbeiten Sie die Details dieses Ortes.</p>
|
||||
</div>
|
||||
<EditLocationForm location={location} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
123
src/components/cables/EditCableForm.tsx
Normal file
123
src/components/cables/EditCableForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
51
src/components/cores/EditCoreForm.tsx
Normal file
51
src/components/cores/EditCoreForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
74
src/components/locations/EditLocationForm.tsx
Normal file
74
src/components/locations/EditLocationForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user