feat: API-Keys in Einstellungen gruppiert nach Kunde

This commit is contained in:
cynfo3000 2026-03-10 00:36:31 +01:00
parent cdb2443d8f
commit 56cb19d052

View File

@ -347,16 +347,42 @@ function APIKeysSection() {
</div>
)}
{/* Key Liste */}
{/* Key Liste — gruppiert nach Kunde */}
{loading ? (
<div className="px-4 py-3 text-gray-500 text-xs">Laden...</div>
) : keys.length === 0 ? (
<div className="px-4 py-4 text-center text-gray-500 text-xs">Keine API-Keys vorhanden</div>
) : (
<div className="divide-y divide-gray-800/60">
{keys.map((k) => {
) : (() => {
// Gruppen bauen: null-Kunde zuerst, dann nach Kundennummer sortiert
const groups = {}
keys.forEach(k => {
const gid = k.customer_id ?? '__global__'
if (!groups[gid]) groups[gid] = []
groups[gid].push(k)
})
const sorted = Object.entries(groups).sort(([a], [b]) => {
if (a === '__global__') return -1
if (b === '__global__') return 1
const ca = customers.find(c => c.id === parseInt(a))
const cb = customers.find(c => c.id === parseInt(b))
return (ca?.number || '').localeCompare(cb?.number || '')
})
return (
<div className="divide-y divide-gray-800">
{sorted.map(([gid, gkeys]) => {
const customer = gid !== '__global__' ? customers.find(c => c.id === parseInt(gid)) : null
return (
<div key={gid}>
{/* Gruppen-Header */}
<div className="px-4 py-1.5 bg-gray-800/40 flex items-center gap-2">
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wide">
{customer ? `${customer.number}${customer.name}` : 'Global'}
</span>
<span className="text-[10px] text-gray-700">({gkeys.length})</span>
</div>
{/* Keys der Gruppe */}
{gkeys.map(k => {
const perm = PERM_LABELS[k.permissions] || PERM_LABELS.read
const customer = k.customer_id ? customers.find(c => c.id === k.customer_id) : null
const lastUsedAgo = k.last_used
? (() => {
const s = (Date.now() - new Date(k.last_used)) / 1000
@ -372,36 +398,35 @@ function APIKeysSection() {
{perm.label}
</span>
<span className="text-xs text-gray-300 flex-1 min-w-0 truncate">{k.name}</span>
{customer && (
<span className="text-[10px] text-gray-600 flex-shrink-0 hidden sm:block">{customer.number}</span>
)}
<code className="text-xs text-gray-600 font-mono flex-shrink-0">
{k.key.substring(0, 8)}{k.key.substring(k.key.length - 4)}
</code>
{lastUsedAgo && (
<span className="text-[10px] text-gray-700 flex-shrink-0 hidden md:block" title={`Zuletzt benutzt: ${new Date(k.last_used).toLocaleString('de-DE')}`}>
<span className="text-[10px] text-gray-700 flex-shrink-0 hidden md:block"
title={`Zuletzt: ${new Date(k.last_used).toLocaleString('de-DE')}`}>
{lastUsedAgo}
</span>
)}
<button
onClick={() => copyText(k.key, k.id)}
<button onClick={() => copyText(k.key, k.id)}
className="text-gray-600 hover:text-orange-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100"
title="Key kopieren"
>
title="Key kopieren">
{copied === k.id ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
</button>
<button
onClick={() => handleDelete(k.id, k.name)}
<button onClick={() => handleDelete(k.id, k.name)}
className="text-gray-700 hover:text-red-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100"
title="Key loeschen"
>
title="Key loeschen">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
)
})}
</div>
)}
)
})}
</div>
)
})()
}
</div>
)
}