refactor: move integrations overview from main nav into admin area

Remove the top-level "Integrationen" sidebar entry and add an "Uebersicht"
tab to the admin integrations page with summary stats and integration cards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:32:40 +01:00
parent d8a3c03554
commit b1238b7bb8
2 changed files with 86 additions and 28 deletions

View File

@@ -16,6 +16,10 @@ import {
Eye,
EyeOff,
Loader2,
Plug,
CheckCircle2,
AlertCircle,
LayoutGrid,
type LucideIcon,
} from 'lucide-react';
@@ -41,7 +45,7 @@ import {
} from '@/components/ui/select';
import { Skeleton } from '@/components/ui/skeleton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { IntegrationStatusBadge } from '@/components/integrations';
import { IntegrationStatusBadge, IntegrationCard } from '@/components/integrations';
import { useAllIntegrationStatuses } from '@/hooks/integrations';
import {
useCredentials,
@@ -459,11 +463,14 @@ function IntegrationPanel({ integrationType }: IntegrationPanelProps) {
* Lists each integration as a tab; each tab loads its own credential
* state so queries are isolated and only triggered when the tab is visited.
*/
export function AdminIntegrationsContent({ locale: _locale }: AdminIntegrationsContentProps) {
export function AdminIntegrationsContent({ locale }: AdminIntegrationsContentProps) {
const t = useTranslations('integrations');
const tAdmin = useTranslations('admin');
const { data: integrations, isLoading } = useAllIntegrationStatuses();
const connectedCount = integrations?.filter((i) => i.status === 'connected').length ?? 0;
const errorCount = integrations?.filter((i) => i.status === 'error').length ?? 0;
return (
<div className="container mx-auto space-y-8 py-6">
{/* Header */}
@@ -501,8 +508,12 @@ export function AdminIntegrationsContent({ locale: _locale }: AdminIntegrationsC
))}
</div>
) : (
<Tabs defaultValue="plentyone" className="space-y-4">
<Tabs defaultValue="overview" className="space-y-4">
<TabsList className="flex-wrap">
<TabsTrigger value="overview" className="gap-2">
<LayoutGrid className="h-4 w-4" />
{t('overview')}
</TabsTrigger>
{integrations?.map((config) => {
const meta = integrationMeta[config.type];
const Icon = meta.icon;
@@ -515,6 +526,78 @@ export function AdminIntegrationsContent({ locale: _locale }: AdminIntegrationsC
})}
</TabsList>
{/* Overview tab */}
<TabsContent value="overview">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="space-y-6"
>
{/* Summary Stats */}
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{t('allIntegrations')}</CardTitle>
<Plug className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{integrations?.length ?? 0}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{t('connected')}</CardTitle>
<CheckCircle2 className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-500">{connectedCount}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{t('error')}</CardTitle>
<AlertCircle className="h-4 w-4 text-destructive" />
</CardHeader>
<CardContent>
<div className={cn('text-2xl font-bold', errorCount > 0 && 'text-destructive')}>
{errorCount}
</div>
</CardContent>
</Card>
</div>
{/* Integration Cards Grid */}
<motion.div
initial="hidden"
animate="visible"
variants={{
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 },
},
}}
className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"
>
{integrations?.map((config) => (
<motion.div
key={config.id}
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
>
<IntegrationCard config={config} locale={locale} showActions={false} />
</motion.div>
))}
</motion.div>
</motion.div>
</TabsContent>
{/* Individual integration tabs */}
{integrations?.map((config) => (
<TabsContent key={config.type} value={config.type}>
<motion.div

View File

@@ -25,7 +25,6 @@ import {
Network,
Building2,
UserPlus,
MessageSquare,
type LucideIcon,
} from 'lucide-react';
@@ -106,30 +105,6 @@ const mainNavItems: NavItem[] = [
},
],
},
{
key: 'integrations',
href: '/integrations',
icon: Plug,
requiredRoles: ['manager', 'admin'],
children: [
{
key: 'overview',
href: '/integrations',
icon: Plug,
exactMatch: true,
},
{
key: 'plentyOne',
href: '/integrations/plentyone',
icon: Building2,
},
{
key: 'zulip',
href: '/integrations/zulip',
icon: MessageSquare,
},
],
},
];
const bottomNavItems: NavItem[] = [