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
+16 -11
View File
@@ -25,6 +25,7 @@ import { Pill } from "@/components/mockup-ui";
import { SupplierRequirements } from "@/components/supplier-cockpit";
import {
supplierRef,
withQuery,
SUPPLIER_LIFECYCLE_TONE,
protectionLevel,
PROTECTION_LABEL,
@@ -80,7 +81,7 @@ function contextOf(s: SupplierAssetDetail) {
/* ─────────────────────── Cockpit (Detail) ─────────────────────── */
export async function SupplierDetailModal({ supplier, canWrite }: { supplier: SupplierAssetDetail; canWrite: boolean }) {
export async function SupplierDetailModal({ supplier, canWrite, backHref = "/suppliers" }: { supplier: SupplierAssetDetail; canWrite: boolean; backHref?: string }) {
const t = await getTranslations("suppliers");
const tLife = await getTranslations("supplierStatus");
const tEKind = await getTranslations("evidenceKind");
@@ -94,8 +95,9 @@ export async function SupplierDetailModal({ supplier, canWrite }: { supplier: Su
const computed = computedMaturity(level, ctx);
const reqs = REQUIREMENTS.map((r) => ({ key: r.key, label: r.label, control: r.control, tier: r.tier, theme: r.theme, met: r.met(ctx) }));
const gateEvidenceHref = `/suppliers?edit=${supplier.id}`;
const gateDecisionHref = `/suppliers?edit=${supplier.id}`;
const editHref = withQuery(backHref, "edit", supplier.id);
const gateEvidenceHref = editHref;
const gateDecisionHref = editHref;
return (
<Modal
@@ -108,16 +110,16 @@ export async function SupplierDetailModal({ supplier, canWrite }: { supplier: Su
<Pill tone={CONFORMITY_TONE[conf]}>{CONFORMITY_LABEL[conf]}</Pill>
</span>
}
closeHref="/suppliers"
closeHref={backHref}
closeLabel={t("close")}
footer={
<>
{canWrite && (
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?edit=${supplier.id}`} />}>
<Button variant="outline" nativeButton={false} render={<Link href={editHref} />}>
<Pencil className="size-4" /> {tc("edit")}
</Button>
)}
<Button nativeButton={false} render={<Link href="/suppliers" />}>{t("close")}</Button>
<Button nativeButton={false} render={<Link href={backHref} />}>{t("close")}</Button>
</>
}
>
@@ -311,12 +313,13 @@ export async function SupplierCreateModal() {
/* ─────────────────────── Bearbeiten ─────────────────────── */
export async function SupplierEditModal({ supplier }: { supplier: SupplierAssetDetail }) {
export async function SupplierEditModal({ supplier, backHref = "/suppliers" }: { supplier: SupplierAssetDetail; backHref?: string }) {
const t = await getTranslations("suppliers");
const tc = await getTranslations("common");
const tEKind = await getTranslations("evidenceKind");
const tAType = await getTranslations("assessmentType");
const FORM = "supplier-edit";
const detailHref = withQuery(backHref, "detail", supplier.id);
const disc =
"cursor-pointer list-none rounded-lg border border-[var(--panel-brd)] bg-[var(--elevated)] px-3 py-1.5 text-[12.5px] font-semibold text-muted-foreground hover:text-foreground [&::-webkit-details-marker]:hidden";
@@ -328,22 +331,24 @@ export async function SupplierEditModal({ supplier }: { supplier: SupplierAssetD
<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={deleteSupplier.bind(null, supplier.id)}>
<form action={deleteSupplier.bind(null, supplier.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?detail=${supplier.id}`}
closeHref={detailHref}
closeLabel={t("close")}
footer={
<>
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?detail=${supplier.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={updateSupplier.bind(null, supplier.id)} className="hidden" />
<form id={FORM} action={updateSupplier.bind(null, supplier.id)} className="hidden">
<input type="hidden" name="returnTo" value={backHref} />
</form>
<div className="space-y-6 p-5">
<SupplierFields supplier={supplier} formId={FORM} />