Kunden-Tabs: ExternalLink-Icon neben jedem Reiter

Tabs-Komponente bekommt optionalen tabHrefBuilder(tabId)-Prop.
Wenn gesetzt, erscheint neben jedem Tab-Label ein kleines
ExternalLink-Icon, das den Tab via ?tab=<id> in einem neuen
Browser-Tab öffnet.

CustomerDetail übergibt den Builder. URL-Param wird eh schon
für den Tab-Sync genutzt – Anhängen reicht.

Click-stopPropagation verhindert, dass der Klick auf das Icon
gleichzeitig den Tab-Wechsel auslöst.
This commit is contained in:
2026-06-03 18:15:23 +02:00
parent fcc3b04725
commit 84cbf01706
3 changed files with 55 additions and 14 deletions
+8
View File
@@ -97,6 +97,14 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt ## ✅ Erledigt
- [x] **🆕 Kunden-Detail-Tabs: Pro-Tab-Link „in neuem Tab öffnen"**
- `Tabs`-Komponente um optionalen Prop `tabHrefBuilder(tabId)` erweitert.
Wenn gesetzt, erscheint neben jedem Tab-Label ein kleines
`ExternalLink`-Icon. CustomerDetail übergibt den Builder mit
`?tab=<id>`-Query-Param, der eh schon vom URL-Sync genutzt wird.
- `target="_blank"` + `rel="noopener noreferrer"` + Klick-stopPropagation,
damit der Tab-Wechsel nicht parallel zur Tab-Aktivierung passiert.
- [x] **🆕 Vertrag: Kunden-/Vertragsnummer bei Vertriebsplattform** - [x] **🆕 Vertrag: Kunden-/Vertragsnummer bei Vertriebsplattform**
- Zwei neue optionale Felder - Zwei neue optionale Felder
`Contract.customerNumberAtSalesPlatform` + `Contract.customerNumberAtSalesPlatform` +
+40 -13
View File
@@ -1,4 +1,5 @@
import { ReactNode, useState, useEffect } from 'react'; import { ReactNode, useState, useEffect } from 'react';
import { ExternalLink } from 'lucide-react';
interface Tab { interface Tab {
id: string; id: string;
@@ -11,9 +12,21 @@ interface TabsProps {
defaultTab?: string; defaultTab?: string;
activeTab?: string; activeTab?: string;
onTabChange?: (tabId: string) => void; onTabChange?: (tabId: string) => void;
/**
* Optional: liefert die URL, unter der ein einzelner Tab in einem
* neuen Tab geöffnet werden kann. Wenn gesetzt, erscheint neben jedem
* Tab-Label ein kleines „im neuen Tab öffnen"-Icon.
*/
tabHrefBuilder?: (tabId: string) => string;
} }
export default function Tabs({ tabs, defaultTab, activeTab: controlledTab, onTabChange }: TabsProps) { export default function Tabs({
tabs,
defaultTab,
activeTab: controlledTab,
onTabChange,
tabHrefBuilder,
}: TabsProps) {
const [internalTab, setInternalTab] = useState(defaultTab || tabs[0]?.id); const [internalTab, setInternalTab] = useState(defaultTab || tabs[0]?.id);
const activeTab = controlledTab ?? internalTab; const activeTab = controlledTab ?? internalTab;
@@ -31,19 +44,33 @@ export default function Tabs({ tabs, defaultTab, activeTab: controlledTab, onTab
return ( return (
<div> <div>
<div className="border-b border-gray-200"> <div className="border-b border-gray-200">
<nav className="flex -mb-px space-x-8"> <nav className="flex -mb-px space-x-6">
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <div key={tab.id} className="flex items-center gap-1">
key={tab.id} <button
onClick={() => handleTabChange(tab.id)} onClick={() => handleTabChange(tab.id)}
className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap ${ className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap ${
activeTab === tab.id activeTab === tab.id
? 'border-blue-500 text-blue-600' ? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
{tab.label} {tab.label}
</button> </button>
{tabHrefBuilder && (
<a
href={tabHrefBuilder(tab.id)}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="text-gray-400 hover:text-blue-600 p-0.5"
title={`${tab.label} in neuem Tab öffnen`}
aria-label={`${tab.label} in neuem Tab öffnen`}
>
<ExternalLink className="w-3.5 h-3.5" />
</a>
)}
</div>
))} ))}
</nav> </nav>
</div> </div>
@@ -411,7 +411,13 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
)} )}
<Card> <Card>
<Tabs tabs={tabs} defaultTab={defaultTab} activeTab={activeTab} onTabChange={handleTabChange} /> <Tabs
tabs={tabs}
defaultTab={defaultTab}
activeTab={activeTab}
onTabChange={handleTabChange}
tabHrefBuilder={(tabId) => `${location.pathname}?tab=${tabId}`}
/>
</Card> </Card>
<AddressModal <AddressModal