first working release

This commit is contained in:
2026-06-13 15:11:49 +02:00
parent b97d574697
commit 550adb5114
29 changed files with 4438 additions and 131 deletions

View File

@@ -0,0 +1,58 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
// Discriminated union return type allows the client to branch on success/failure
export type CreateCableResult =
| { success: true; cableId: number }
| { success: false; error: string };
/**
* Creates a new Cable and returns either the new cable's ID (on success)
* or an error message (on validation/DB failure).
* The client component handles navigation to the detail page on success.
*/
export async function createCable(
formData: FormData
): Promise<CreateCableResult> {
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"));
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." };
}
try {
const cable = await prisma.cable.create({
data: {
identifier,
description: description || null,
notes: notes || null,
startLocationId,
endLocationId,
},
});
revalidatePath("/cables");
return { success: true, cableId: cable.id };
} catch (e) {
console.error("createCable failed:", e);
return { success: false, error: "Fehler beim Speichern des Kabels." };
}
}
/**
* Deletes a Cable and all its Cores (via Prisma's onDelete: Cascade).
* Navigation back to /cables is handled by the client component.
*/
export async function deleteCable(id: number): Promise<void> {
await prisma.cable.delete({ where: { id } });
revalidatePath("/cables");
}

View File

@@ -0,0 +1,52 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export type FormState = {
error?: string;
success?: boolean;
};
/**
* Adds a Core/Wire to an existing Cable. Designed for useActionState.
*/
export async function createCore(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const cableId = Number(formData.get("cableId"));
const color = (formData.get("color") as string)?.trim();
const notes = (formData.get("notes") as string)?.trim();
if (!color) {
return { error: "Farbe ist erforderlich." };
}
if (!cableId || isNaN(cableId)) {
return { error: "Ungültige Kabel-ID." };
}
try {
await prisma.core.create({
data: {
cableId,
color,
notes: notes || null,
},
});
revalidatePath(`/cables/${cableId}`);
return { success: true };
} catch (e) {
console.error("createCore failed:", e);
return { error: "Fehler beim Speichern der Ader." };
}
}
/**
* Deletes a single Core by ID.
*/
export async function deleteCore(id: number, cableId: number): Promise<void> {
await prisma.core.delete({ where: { id } });
revalidatePath(`/cables/${cableId}`);
}

View File

@@ -0,0 +1,52 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
// Shared form state shape used with useActionState
export type FormState = {
error?: string;
success?: boolean;
};
/**
* Creates a new Location. Designed for use with React's useActionState hook.
*/
export async function createLocation(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const name = (formData.get("name") as string)?.trim();
const description = (formData.get("description") as string)?.trim();
if (!name) {
return { error: "Name ist erforderlich." };
}
try {
await prisma.location.create({
data: {
name,
description: description || null,
},
});
// Invalidate both pages that show location data
revalidatePath("/locations");
revalidatePath("/cables");
return { success: true };
} catch (e) {
console.error("createLocation failed:", e);
return { error: "Fehler beim Anlegen des Ortes. Bitte erneut versuchen." };
}
}
/**
* 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).
*/
export async function deleteLocation(id: number): Promise<void> {
await prisma.location.delete({ where: { id } });
revalidatePath("/locations");
revalidatePath("/cables");
}