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:
@@ -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
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
Reference in New Issue
Block a user