/** * Cluster Detail Page * Shows cluster information with tabs for Articles, Pages, Products, and Taxonomy * Route: /clusters/:id */ import { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Badge from '../../components/ui/badge/Badge'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { fetchAPI, Cluster, Content } from '../../services/api'; import { GridIcon, FileIcon, PageIcon, TagIcon, ChevronLeftIcon, EyeIcon, PencilIcon } from '../../icons'; type TabType = 'articles' | 'pages' | 'products' | 'taxonomy'; export default function ClusterDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const toast = useToast(); const [cluster, setCluster] = useState(null); const [content, setContent] = useState([]); const [loading, setLoading] = useState(true); const [contentLoading, setContentLoading] = useState(false); const [activeTab, setActiveTab] = useState('articles'); useEffect(() => { if (!id) { toast.error('Cluster ID is required'); navigate('/planner/clusters'); return; } const clusterId = parseInt(id, 10); if (isNaN(clusterId) || clusterId <= 0) { toast.error('Invalid cluster ID'); navigate('/planner/clusters'); return; } loadCluster(clusterId); }, [id, navigate, toast]); useEffect(() => { if (cluster) { loadContent(activeTab); } }, [cluster, activeTab]); const loadCluster = async (clusterId: number) => { try { setLoading(true); const data = await fetchAPI(`/v1/planner/clusters/${clusterId}/`); setCluster(data); } catch (error: any) { console.error('Error loading cluster:', error); toast.error(`Failed to load cluster: ${error.message || 'Unknown error'}`); navigate('/planner/clusters'); } finally { setLoading(false); } }; const loadContent = async (tab: TabType) => { if (!cluster) return; try { setContentLoading(true); const params = new URLSearchParams({ cluster_id: cluster.id.toString(), }); // Filter by content_type based on active tab switch (tab) { case 'articles': params.append('content_type', 'post'); break; case 'pages': params.append('content_type', 'page'); break; case 'products': params.append('content_type', 'product'); break; case 'taxonomy': // Show categories and tags params.append('content_type', 'category'); break; } const response = await fetchAPI(`/v1/writer/content/?${params.toString()}`); const contentList = Array.isArray(response?.results) ? response.results : Array.isArray(response) ? response : []; setContent(contentList); } catch (error: any) { console.error('Error loading content:', error); toast.error(`Failed to load content: ${error.message}`); setContent([]); } finally { setContentLoading(false); } }; const handleTabChange = (tab: TabType) => { setActiveTab(tab); }; if (loading) { return ( <>
Loading cluster...
); } if (!cluster) { return ( <>

Cluster not found

); } return ( <> {/* Back Button */}
, color: 'blue' }} hideSiteSector /> {/* Cluster Summary */}
Keywords
{cluster.keywords_count}
Total Volume
{cluster.volume.toLocaleString()}
Avg Difficulty
{cluster.difficulty}
Ideas
{cluster.ideas_count}
Content
{cluster.content_count}
{cluster.description && (
Description

{cluster.description}

)}
{cluster.sector_name && ( {cluster.sector_name} )} {cluster.status} Created {new Date(cluster.created_at).toLocaleDateString()}
{/* Tabs */}
{/* Content List */} {contentLoading ? (
Loading content...
) : content.length === 0 ? (

No {activeTab} found for this cluster

) : (
{content.map((item) => (

{item.title || `Content #${item.id}`}

{item.status} {item.content_type && ( {item.content_type} )} {item.content_structure && ( {item.content_structure} )} {item.source} {item.external_url && ( View Live )} {new Date(item.created_at).toLocaleDateString()}
{item.external_url && ( )}
))}
)} ); }