import { useState, useCallback, useEffect } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' import { Eye, Trash2, Send, RefreshCw, ExternalLink, FileText, Filter, Sparkles, ImageIcon, Maximize2, X, } from 'lucide-react' import { Header } from '../components/layout/Header' import { Card, CardContent, Badge, Button, EmptyState, PageLoader, CopyscapeStatusBadge } from '../components/common' import { RegenerateImageDialog, ImageLightbox } from '../components/generator' import { endpoints, getErrorMessage } from '../api/client' import { formatDate, formatTimeAgo, truncate } from '../utils' import { t, tf } from '../lib/i18n' import type { GeneratedPost, GeneratedPostStatus } from '../types' function getStatusConfig(): Record { return { published: { label: t('Published'), variant: 'success' }, draft: { label: t('Draft'), variant: 'default' }, pending: { label: t('Pending approval'), variant: 'warning' }, scheduled: { label: t('Scheduled'), variant: 'info' }, failed: { label: t('Failed'), variant: 'error' }, } } type FilterStatus = 'all' | GeneratedPostStatus export default function GeneratedContentPage() { const navigate = useNavigate() const [searchParams, setSearchParams] = useSearchParams() const planIdFilter = searchParams.get('planId') ? parseInt(searchParams.get('planId')!, 10) : null const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) const [loadingMore, setLoadingMore] = useState(false) const [filterStatus, setFilterStatus] = useState('all') const [isRefreshing, setIsRefreshing] = useState(false) const [actionLoading, setActionLoading] = useState(null) const [lightboxTarget, setLightboxTarget] = useState(null) const [regenerateTarget, setRegenerateTarget] = useState(null) const [error, setError] = useState(null) const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(false) const [total, setTotal] = useState(0) const [statusCounts, setStatusCounts] = useState({ total: 0, published: 0, draft: 0, pending: 0, scheduled: 0, failed: 0, }) const PER_PAGE = 20 const loadPosts = useCallback(async (pageNum: number, append = false) => { if (append) { setLoadingMore(true) } else { setLoading(true) } setError(null) try { const response = await endpoints.getGenerated({ planId: planIdFilter || undefined, status: filterStatus !== 'all' ? filterStatus : undefined, page: pageNum, perPage: PER_PAGE, }) const raw = response.data as any const items = (raw?.items ?? []) as GeneratedPost[] if (append) { setPosts(prev => [...prev, ...items]) } else { setPosts(items) } setTotal(raw?.total ?? items.length) setHasMore(raw?.has_more ?? false) if (raw?.status_counts) { setStatusCounts(raw.status_counts) } } catch (err) { if (!append) setPosts([]) setError(getErrorMessage(err, t('Failed to load'))) } finally { setLoading(false) setLoadingMore(false) } }, [planIdFilter, filterStatus]) useEffect(() => { setPage(1) loadPosts(1) }, [loadPosts]) const handleRefresh = useCallback(async () => { setIsRefreshing(true) setPage(1) await loadPosts(1) setIsRefreshing(false) }, [loadPosts]) const handleLoadMore = () => { const nextPage = page + 1 setPage(nextPage) loadPosts(nextPage, true) } const handleDelete = async (id: number) => { if (!confirm(t('Delete this content?'))) return const deletedPost = posts.find(p => p.id === id) setActionLoading(id) try { await endpoints.deleteGenerated(id) setPosts(prev => prev.filter(p => p.id !== id)) setTotal(prev => Math.max(0, prev - 1)) if (deletedPost) { setStatusCounts(prev => { if (!prev) return prev const status = deletedPost.status || 'draft' return { ...prev, total: Math.max(0, (prev.total || 0) - 1), [status]: Math.max(0, ((prev as any)[status] || 0) - 1), } }) } } catch (err) { setError(getErrorMessage(err, t('Failed to delete'))) } setActionLoading(null) } const handlePublish = async (id: number) => { setActionLoading(id) try { await endpoints.publishGenerated(id) setPage(1) await loadPosts(1) } catch (err) { setError(getErrorMessage(err, t('Publish failed'))) } setActionLoading(null) } const handleFilterChange = (newFilter: FilterStatus) => { setFilterStatus(newFilter) } if (loading && posts.length === 0) return const statusConfig = getStatusConfig() return (
{/* Error banner */} {error && (
{error}
)} {/* Stats row */}
{([ { label: t('Total'), value: statusCounts.total, filter: 'all' as FilterStatus, variant: 'primary' as const }, { label: t('Published'), value: statusCounts.published, filter: 'published' as FilterStatus, variant: 'success' as const }, { label: t('Draft'), value: statusCounts.draft, filter: 'draft' as FilterStatus, variant: 'default' as const }, { label: t('Pending'), value: statusCounts.pending, filter: 'pending' as FilterStatus, variant: 'warning' as const }, { label: t('Failed'), value: statusCounts.failed, filter: 'failed' as FilterStatus, variant: 'error' as const }, ]).map(s => ( ))}
{/* Plan filter chip */} {planIdFilter && (
{tf('Showing articles from plan #%d', planIdFilter)}
)} {/* Filter bar */} {(posts.length > 0 || filterStatus !== 'all') && (
{tf('Showing %d of %d', posts.length, total)}
)} {/* Content list */} {posts.length === 0 ? ( navigate('/generate'), } : undefined} /> ) : (
{posts.map(post => { const config = statusConfig[post.status] || statusConfig.draft const isActioning = actionLoading === post.id return (
{/* Image thumbnail — click opens lightbox or generate dialog */} {post.wp_post_id ? ( ) : (
)}

{(post as any).title || (post.generated_content ? truncate(extractTitle(post.generated_content), 80) : tf('Content #%d', post.id)) }

{config.label}
{t('Type:')} {post.post_type} {formatTimeAgo(post.created_at)} {post.published_at && ( {t('Published:')} {formatDate(post.published_at)} )} {post.plan_id && post.plan_id > 0 && ( {tf('Plan #%d', post.plan_id)} )}
{post.error_message && (

{post.error_message}

)}
{post.wp_post_id && ( )} {post.wp_post_id && ( )} {(post.status === 'draft' || post.status === 'pending') && ( )}
) })} {/* Load More */} {hasMore && (
)}
)}
{/* Image Lightbox */} {lightboxTarget?.featured_image_url && ( ) : '') } isOpen={!!lightboxTarget} onClose={() => setLightboxTarget(null)} onRegenerate={() => { setLightboxTarget(null) setRegenerateTarget(lightboxTarget) }} /> )} {/* Regenerate Image Dialog */} {regenerateTarget?.wp_post_id && ( ) : '') } currentImageUrl={regenerateTarget.featured_image_url} isOpen={!!regenerateTarget} onClose={() => setRegenerateTarget(null)} onSuccess={(newUrl, thumbUrl) => { setPosts(prev => prev.map(p => p.id === regenerateTarget.id ? { ...p, featured_image_url: newUrl, featured_image_thumbnail: thumbUrl || newUrl } : p ) ) setRegenerateTarget(null) }} /> )}
) } function extractTitle(raw: string | Record): string { if (typeof raw === 'object' && raw !== null) { if (raw.title) return String(raw.title) const content = raw.content as string | undefined if (content) return extractTitle(content) return '' } const html = raw as string const match = html.match(/]*>(.*?)<\/h[12]>/i) if (match) return match[1].replace(/<[^>]+>/g, '') const textMatch = html.replace(/<[^>]+>/g, ' ').trim() return textMatch.slice(0, 100) }