Lieferanten-/IT-Service-Cockpit direkt auf der Asset-Seite öffnen

Statt beim Klick auf ein Lieferanten-/IT-Service-Asset zur Lieferanten-Seite zu
wechseln, wird das Fach-Cockpit jetzt als Popup auf /assets gerendert; Schließen,
Bearbeiten, Speichern und Löschen bleiben auf der Asset-Seite.

- backHref-Prop an SupplierDetail/Edit- und ServiceDetail/Edit-Modal: alle
  Schließen-/Bearbeiten-/Abbrechen-Links leiten sich davon ab (Default /suppliers)
- withQuery-Helper (lib/supplier) für korrekte ?/&-Verkettung
- updateSupplier/updateService lesen returnTo (verstecktes Feld) und leiten dorthin
  zurück; deleteSupplier/deleteService bekommen returnTo-Parameter
- Kind-Actions revalidieren zusätzlich /assets, damit das Popup dort aktuell bleibt
- SUPPLIER_INCLUDE/SERVICE_INCLUDE in lib/supplier-include ausgelagert und von
  beiden Seiten genutzt
- assets/page.tsx bestimmt die Popup-Art vorab (supplier/service/asset) und rendert
  das passende Cockpit mit backHref="/assets"; profillose Alt-Assets bleiben Assets

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 13:55:46 +02:00
co-authored by Claude Opus 4.8
parent db710c43cf
commit 2706424c08
8 changed files with 172 additions and 98 deletions
+8 -3
View File
@@ -7,6 +7,7 @@ import { requireSession } from "@/server/auth";
import { dbForTenant, prisma } from "@/server/db";
import { requirePermission } from "@/server/rbac";
import { writeAuditLog } from "@/server/audit";
import { withQuery } from "@/lib/supplier";
const level = z.coerce.number().int().min(1).max(4);
@@ -73,10 +74,11 @@ export async function updateService(assetId: string, formData: FormData) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "it_service", entityId: assetId, before, after: { ...asset, ...profile } });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect(`/suppliers?tab=services&detail=${assetId}`);
const returnTo = (formData.get("returnTo") as string) || "/suppliers?tab=services";
redirect(withQuery(returnTo, "detail", assetId));
}
export async function deleteService(assetId: string) {
export async function deleteService(assetId: string, returnTo: string = "/suppliers?tab=services") {
const session = await requireSession();
requirePermission(session, "supplier:write");
const db = dbForTenant(session.user.tenantId);
@@ -86,7 +88,7 @@ export async function deleteService(assetId: string) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "it_service", entityId: assetId, before });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect("/suppliers?tab=services");
redirect(returnTo);
}
export async function addRaci(assetId: string, formData: FormData) {
@@ -107,6 +109,7 @@ export async function addRaci(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "service_raci", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
/** Verantwortung einer RACI-Zeile per Klick ändern (PROVIDER/US/SHARED durchschalten). */
@@ -120,6 +123,7 @@ export async function cycleRaci(assetId: string, raciId: string) {
await db.serviceControlResponsibility.update({ where: { id: raciId }, data: { responsibility: next[row.responsibility] } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "service_raci", entityId: assetId, after: { responsibility: next[row.responsibility] } });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function deleteRaci(assetId: string, raciId: string) {
@@ -129,4 +133,5 @@ export async function deleteRaci(assetId: string, raciId: string) {
await db.serviceControlResponsibility.delete({ where: { id: raciId } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "service_raci", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
+12 -3
View File
@@ -7,6 +7,7 @@ import { requireSession } from "@/server/auth";
import { dbForTenant, prisma } from "@/server/db";
import { requirePermission } from "@/server/rbac";
import { writeAuditLog } from "@/server/audit";
import { withQuery } from "@/lib/supplier";
const level = z.coerce.number().int().min(1).max(4);
@@ -110,10 +111,11 @@ export async function updateSupplier(assetId: string, formData: FormData) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "supplier", entityId: assetId, before, after: { ...asset, ...profile } });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect(`/suppliers?detail=${assetId}`);
const returnTo = (formData.get("returnTo") as string) || "/suppliers";
redirect(withQuery(returnTo, "detail", assetId));
}
export async function deleteSupplier(assetId: string) {
export async function deleteSupplier(assetId: string, returnTo: string = "/suppliers") {
const session = await requireSession();
requirePermission(session, "supplier:write");
const db = dbForTenant(session.user.tenantId);
@@ -123,7 +125,7 @@ export async function deleteSupplier(assetId: string) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "supplier", entityId: assetId, before });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect("/suppliers");
redirect(returnTo);
}
async function ensure(session: Awaited<ReturnType<typeof requireSession>>, assetId: string) {
@@ -154,6 +156,7 @@ export async function addContract(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "contract", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addNda(assetId: string, formData: FormData) {
@@ -174,6 +177,7 @@ export async function addNda(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "nda", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addEvidence(assetId: string, formData: FormData) {
@@ -193,6 +197,7 @@ export async function addEvidence(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "supplier_evidence", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addAssessment(assetId: string, formData: FormData) {
@@ -212,6 +217,7 @@ export async function addAssessment(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "supplier_assessment", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addDecision(assetId: string, formData: FormData) {
@@ -230,6 +236,7 @@ export async function addDecision(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "management_decision", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
/** Gate-Kompensation: echtes Risiko im Risikomodul anlegen und mit dem Lieferanten verknüpfen. */
@@ -282,6 +289,7 @@ export async function approveMaturity(assetId: string, computed: number, formDat
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "maturity_assessment", entityId: assetId, after: { computed, isbValue, justification } });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function deleteChild(
@@ -303,4 +311,5 @@ export async function deleteChild(
await map[kind].delete({ where: { id } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: `supplier_${kind}`, entityId: id });
revalidatePath("/suppliers");
revalidatePath("/assets");
}