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,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Plug,
|
||||||
|
CheckCircle2,
|
||||||
|
AlertCircle,
|
||||||
|
LayoutGrid,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
@@ -41,7 +45,7 @@ import {
|
|||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
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 { useAllIntegrationStatuses } from '@/hooks/integrations';
|
||||||
import {
|
import {
|
||||||
useCredentials,
|
useCredentials,
|
||||||
@@ -459,11 +463,14 @@ function IntegrationPanel({ integrationType }: IntegrationPanelProps) {
|
|||||||
* Lists each integration as a tab; each tab loads its own credential
|
* 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.
|
* 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 t = useTranslations('integrations');
|
||||||
const tAdmin = useTranslations('admin');
|
const tAdmin = useTranslations('admin');
|
||||||
const { data: integrations, isLoading } = useAllIntegrationStatuses();
|
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 (
|
return (
|
||||||
<div className="container mx-auto space-y-8 py-6">
|
<div className="container mx-auto space-y-8 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -501,8 +508,12 @@ export function AdminIntegrationsContent({ locale: _locale }: AdminIntegrationsC
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tabs defaultValue="plentyone" className="space-y-4">
|
<Tabs defaultValue="overview" className="space-y-4">
|
||||||
<TabsList className="flex-wrap">
|
<TabsList className="flex-wrap">
|
||||||
|
<TabsTrigger value="overview" className="gap-2">
|
||||||
|
<LayoutGrid className="h-4 w-4" />
|
||||||
|
{t('overview')}
|
||||||
|
</TabsTrigger>
|
||||||
{integrations?.map((config) => {
|
{integrations?.map((config) => {
|
||||||
const meta = integrationMeta[config.type];
|
const meta = integrationMeta[config.type];
|
||||||
const Icon = meta.icon;
|
const Icon = meta.icon;
|
||||||
@@ -515,6 +526,78 @@ export function AdminIntegrationsContent({ locale: _locale }: AdminIntegrationsC
|
|||||||
})}
|
})}
|
||||||
</TabsList>
|
</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) => (
|
{integrations?.map((config) => (
|
||||||
<TabsContent key={config.type} value={config.type}>
|
<TabsContent key={config.type} value={config.type}>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
Network,
|
Network,
|
||||||
Building2,
|
Building2,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
MessageSquare,
|
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react';
|
} 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[] = [
|
const bottomNavItems: NavItem[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user