Files
igny8/frontend/src/pages/Linker/Dashboard.tsx
IGNY8 VPS (Salman) 4f7ab9c606 stlyes fixes
2025-12-29 19:52:51 +00:00

171 lines
6.4 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import PageMeta from '../../components/common/PageMeta';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import PageHeader from '../../components/common/PageHeader';
import { FileTextIcon, ArrowRightIcon, PlugInIcon, ArrowUpIcon } from '../../icons';
import { fetchContent } from '../../services/api';
import { useSiteStore } from '../../store/siteStore';
import { useSectorStore } from '../../store/sectorStore';
interface LinkerStats {
totalLinked: number;
totalLinks: number;
averageLinksPerContent: number;
contentWithLinks: number;
contentWithoutLinks: number;
}
export default function LinkerDashboard() {
const navigate = useNavigate();
const { activeSite } = useSiteStore();
const { activeSector } = useSectorStore();
const [stats, setStats] = useState<LinkerStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchDashboardData();
}, [activeSite, activeSector]);
const fetchDashboardData = async () => {
try {
setLoading(true);
// Fetch content to calculate stats
const contentRes = await fetchContent({
page_size: 1000,
sector_id: activeSector?.id,
});
const content = contentRes.results || [];
// Calculate stats
const contentWithLinks = content.filter(c => c.internal_links && c.internal_links.length > 0);
const totalLinks = content.reduce((sum, c) => sum + (c.internal_links?.length || 0), 0);
const averageLinksPerContent = contentWithLinks.length > 0
? (totalLinks / contentWithLinks.length)
: 0;
setStats({
totalLinked: contentWithLinks.length,
totalLinks,
averageLinksPerContent: parseFloat(averageLinksPerContent.toFixed(1)),
contentWithLinks: contentWithLinks.length,
contentWithoutLinks: content.length - contentWithLinks.length,
});
} catch (error: any) {
console.error('Error loading linker stats:', error);
} finally {
setLoading(false);
}
};
return (
<>
<PageMeta title="Internal Linking Dashboard" description="Track your internal linking progress" />
<div className="space-y-6">
<div className="flex items-center justify-between mb-6">
<PageHeader
title="Internal Linking Dashboard"
lastUpdated={new Date()}
badge={{
icon: <PlugInIcon />,
color: 'blue',
}}
/>
<Link
to="/linker/content"
className="inline-flex items-center gap-2 px-4 py-2 bg-brand-500 text-white rounded-lg hover:bg-brand-600 transition-colors"
>
<PlugInIcon />
View Content
</Link>
</div>
<p className="text-gray-600 dark:text-gray-400 mb-6">
Manage internal linking for your content
</p>
{loading ? (
<div className="text-center py-12">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-brand-500"></div>
<p className="mt-2 text-gray-600 dark:text-gray-400">Loading stats...</p>
</div>
) : stats ? (
<>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<EnhancedMetricCard
title="Total Linked"
value={stats.totalLinked.toString()}
subtitle={`${stats.contentWithoutLinks} without links`}
icon={<FileTextIcon className="w-6 h-6" />}
accentColor="blue"
onClick={() => navigate('/linker/content')}
/>
<EnhancedMetricCard
title="Total Links"
value={stats.totalLinks.toString()}
subtitle="Internal links created"
icon={<PlugInIcon className="w-6 h-6" />}
accentColor="purple"
onClick={() => navigate('/linker/content')}
/>
<EnhancedMetricCard
title="Avg Links/Content"
value={stats.averageLinksPerContent.toString()}
subtitle="Average per linked content"
icon={<ArrowUpIcon className="w-6 h-6" />}
accentColor="green"
onClick={() => navigate('/linker/content')}
/>
</div>
{/* Quick Actions */}
<ComponentCard title="Quick Actions" className="mt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Link
to="/linker/content"
className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<div className="flex items-center gap-3">
<PlugInIcon className="w-5 h-5 text-brand-500" />
<div>
<h3 className="font-medium text-gray-900 dark:text-white">Link Content</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">Process content for internal linking</p>
</div>
</div>
<ArrowRightIcon className="w-5 h-5 text-gray-400" />
</Link>
<Link
to="/writer/content"
className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<div className="flex items-center gap-3">
<FileTextIcon className="w-5 h-5 text-purple-500" />
<div>
<h3 className="font-medium text-gray-900 dark:text-white">View Content</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">Browse all content items</p>
</div>
</div>
<ArrowRightIcon className="w-5 h-5 text-gray-400" />
</Link>
</div>
</ComponentCard>
</>
) : (
<div className="text-center py-12">
<p className="text-gray-600 dark:text-gray-400">No data available</p>
</div>
)}
</div>
</>
);
}