import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs'; import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; import { linkerApi } from '../../api/linker.api'; import { fetchContent, Content as ContentType } from '../../services/api'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { SourceBadge, ContentSource } from '../../components/content/SourceBadge'; import { LinkResults } from '../../components/linker/LinkResults'; import { PlugInIcon, CheckCircleIcon, FileIcon } from '../../icons'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; export default function LinkerContentList() { const navigate = useNavigate(); const toast = useToast(); const { activeSector } = useSectorStore(); const { pageSize } = usePageSizeStore(); const [content, setContent] = useState([]); const [loading, setLoading] = useState(true); const [processing, setProcessing] = useState(null); const [linkResults, setLinkResults] = useState>({}); const [currentPage, setCurrentPage] = useState(1); const [totalCount, setTotalCount] = useState(0); const loadContent = useCallback(async () => { setLoading(true); try { const data = await fetchContent({ page: currentPage, page_size: pageSize, sector_id: activeSector?.id, }); setContent(data.results || []); setTotalCount(data.count || 0); } catch (error: any) { console.error('Error loading content:', error); toast.error(`Failed to load content: ${error.message}`); } finally { setLoading(false); } }, [currentPage, pageSize, activeSector, toast]); useEffect(() => { loadContent(); }, [loadContent]); const handleLink = async (contentId: number) => { try { setProcessing(contentId); const result = await linkerApi.process(contentId); setLinkResults(prev => ({ ...prev, [contentId]: result, })); toast.success(`Added ${result.links_added || 0} link${result.links_added !== 1 ? 's' : ''} to content`); // Refresh content list await loadContent(); } catch (error: any) { console.error('Error linking content:', error); toast.error(`Failed to link content: ${error.message}`); } finally { setProcessing(null); } }; const handleBatchLink = async (contentIds: number[]) => { try { setProcessing(-1); // Special value for batch const results = await linkerApi.batchProcess(contentIds); let totalLinks = 0; results.forEach((result: any) => { setLinkResults(prev => ({ ...prev, [result.content_id]: result, })); totalLinks += result.links_added || 0; }); toast.success(`Added ${totalLinks} link${totalLinks !== 1 ? 's' : ''} to ${results.length} content item${results.length !== 1 ? 's' : ''}`); // Refresh content list await loadContent(); } catch (error: any) { console.error('Error batch linking content:', error); toast.error(`Failed to link content: ${error.message}`); } finally { setProcessing(null); } }; return ( <>
}, ]} />} /> {loading ? (

Loading content...

) : (
{content.map((item) => { const result = linkResults[item.id]; const isProcessing = processing === item.id; return ( ); })}
Title Source Cluster Links Version Actions
{item.title || 'Untitled'}
{item.cluster_name ? ( {item.cluster_name} ) : ( - )} {item.internal_links?.length || 0} {item.linker_version || 0}
{/* Pagination */} {totalCount > pageSize && (
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, totalCount)} of {totalCount} results
)} {/* Link Results */} {Object.keys(linkResults).length > 0 && (

Recent Results

{Object.entries(linkResults).slice(-3).map(([contentId, result]) => ( ))}
)}
)} {/* Module Metrics Footer */} (c.internal_links?.length || 0) > 0).length} with links`, icon: , accentColor: 'blue', href: '/linker/content', }, { title: 'Links Added', value: content.reduce((sum, c) => sum + (c.internal_links?.length || 0), 0).toLocaleString(), subtitle: `${Object.keys(linkResults).length} processed`, icon: , accentColor: 'purple', }, { title: 'Avg Links/Content', value: content.length > 0 ? (content.reduce((sum, c) => sum + (c.internal_links?.length || 0), 0) / content.length).toFixed(1) : '0', subtitle: `${content.filter(c => c.linker_version && c.linker_version > 0).length} optimized`, icon: , accentColor: 'green', }, ]} progress={{ label: 'Content Linking Progress', value: totalCount > 0 ? Math.round((content.filter(c => (c.internal_links?.length || 0) > 0).length / totalCount) * 100) : 0, color: 'primary', }} />
); }