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
+13 -10
View File
@@ -17,7 +17,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Modal } from "@/components/modal";
import { SegmentedRating } from "@/components/segmented-rating";
import { CiaBadge, CiaLegend, CriticalityPill, Pill, Tag } from "@/components/mockup-ui";
import { serviceRef } from "@/lib/supplier";
import { serviceRef, withQuery } from "@/lib/supplier";
import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk";
import { ISA_CONTROLS } from "@/lib/isa-controls";
@@ -37,7 +37,7 @@ const RACI_TONE = { PROVIDER: "info", US: "violet", SHARED: "warn" } as const;
/* ─────────────────────── Detail-Cockpit ─────────────────────── */
export async function ServiceDetailModal({ service, canWrite }: { service: ServiceAssetDetail; canWrite: boolean }) {
export async function ServiceDetailModal({ service, canWrite, backHref = "/suppliers?tab=services" }: { service: ServiceAssetDetail; canWrite: boolean; backHref?: string }) {
const t = await getTranslations("services");
const ta = await getTranslations("assets");
const tParty = await getTranslations("raciParty");
@@ -60,16 +60,16 @@ export async function ServiceDetailModal({ service, canWrite }: { service: Servi
<CriticalityPill level={p.criticality} label={tCrit(String(p.criticality))} />
</span>
}
closeHref="/suppliers?tab=services"
closeHref={backHref}
closeLabel={tp("close")}
footer={
<>
{canWrite && (
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?tab=services&edit=${service.id}`} />}>
<Button variant="outline" nativeButton={false} render={<Link href={withQuery(backHref, "edit", service.id)} />}>
<Pencil className="size-4" /> {tc("edit")}
</Button>
)}
<Button nativeButton={false} render={<Link href="/suppliers?tab=services" />}>{tp("close")}</Button>
<Button nativeButton={false} render={<Link href={backHref} />}>{tp("close")}</Button>
</>
}
>
@@ -334,10 +334,11 @@ export async function ServiceCreateModal({ providers }: { providers: { id: strin
);
}
export async function ServiceEditModal({ service, providers }: { service: ServiceAssetDetail; providers: { id: string; name: string }[] }) {
export async function ServiceEditModal({ service, providers, backHref = "/suppliers?tab=services" }: { service: ServiceAssetDetail; providers: { id: string; name: string }[]; backHref?: string }) {
const t = await getTranslations("services");
const tc = await getTranslations("common");
const FORM = "service-edit";
const detailHref = withQuery(backHref, "detail", service.id);
return (
<Modal
title={t("editTitle")}
@@ -346,22 +347,24 @@ export async function ServiceEditModal({ service, providers }: { service: Servic
<details className="relative">
<summary className="grid size-8 cursor-pointer list-none place-items-center rounded-md text-muted-foreground hover:bg-muted [&::-webkit-details-marker]:hidden"><Trash2 className="size-4" /></summary>
<div className="shadow-card absolute right-0 z-20 mt-1 w-48 rounded-xl border bg-card p-1.5">
<form action={deleteService.bind(null, service.id)}>
<form action={deleteService.bind(null, service.id, backHref)}>
<button type="submit" className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm text-destructive hover:bg-destructive/10"><Trash2 className="size-4" /> {tc("delete")}</button>
</form>
</div>
</details>
}
closeHref={`/suppliers?tab=services&detail=${service.id}`}
closeHref={detailHref}
closeLabel={t("close")}
footer={
<>
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?tab=services&detail=${service.id}`} />}>{tc("cancel")}</Button>
<Button variant="outline" nativeButton={false} render={<Link href={detailHref} />}>{tc("cancel")}</Button>
<Button type="submit" form={FORM}>{tc("save")}</Button>
</>
}
>
<form id={FORM} action={updateService.bind(null, service.id)} className="hidden" />
<form id={FORM} action={updateService.bind(null, service.id)} className="hidden">
<input type="hidden" name="returnTo" value={backHref} />
</form>
<div className="p-5">
<ServiceFields service={service} providers={providers} formId={FORM} />
</div>