/** * Review Page Configuration * Centralized config for Review page table, filters, and actions */ import { Content } from '../../services/api'; import Badge from '../../components/ui/badge/Badge'; import { formatRelativeDate } from '../../utils/date'; import { CheckCircleIcon } from '../../icons'; import { STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping'; export interface ColumnConfig { key: string; label: string; sortable?: boolean; sortField?: string; align?: 'left' | 'center' | 'right'; width?: string; numeric?: boolean; date?: boolean; render?: (value: any, row: any) => React.ReactNode; toggleable?: boolean; toggleContentKey?: string; toggleContentLabel?: string; defaultVisible?: boolean; } export interface FilterConfig { key: string; label: string; type: 'text' | 'select'; placeholder?: string; options?: Array<{ value: string; label: string }>; } export interface HeaderMetricConfig { label: string; accentColor: 'blue' | 'green' | 'amber' | 'purple'; calculate: (data: { content: Content[]; totalCount: number }) => number; } export interface ReviewPageConfig { columns: ColumnConfig[]; filters: FilterConfig[]; headerMetrics: HeaderMetricConfig[]; } export function createReviewPageConfig(params: { searchTerm: string; setSearchTerm: (value: string) => void; statusFilter: string; setStatusFilter: (value: string) => void; setCurrentPage: (page: number) => void; activeSector: { id: number; name: string } | null; onRowClick?: (row: Content) => void; }): ReviewPageConfig { const showSectorColumn = !params.activeSector; const columns: ColumnConfig[] = [ { key: 'title', label: 'Content Idea Title', sortable: true, sortField: 'title', width: '400px', render: (value: string, row: Content) => (
{params.onRowClick ? ( ) : ( {value || `Content #${row.id}`} )}
), }, { key: 'categories', label: 'Categories', sortable: false, render: (_value: any, row: Content) => { const categories = row.categories || []; if (!categories || categories.length === 0) { return -; } return (
{categories.slice(0, 2).map((category, index) => ( {category} ))} {categories.length > 2 && ( +{categories.length - 2} )}
); }, }, { key: 'tags', label: 'Tags', sortable: false, render: (_value: any, row: Content) => { const tags = row.tags || []; if (!tags || tags.length === 0) { return -; } return (
{tags.slice(0, 2).map((tag, index) => ( {tag} ))} {tags.length > 2 && ( +{tags.length - 2} )}
); }, }, { key: 'content_type', label: 'Type', sortable: true, sortField: 'content_type', render: (value: string) => { const label = TYPE_LABELS[value] || value || '-'; const properCase = label.charAt(0).toUpperCase() + label.slice(1); return ( {properCase} ); }, }, { key: 'content_structure', label: 'Structure', sortable: true, sortField: 'content_structure', render: (value: string) => { const label = STRUCTURE_LABELS[value] || value || '-'; const properCase = label.split(/[_\s]+/).map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' '); return ( {properCase} ); }, }, { key: 'cluster_name', label: 'Cluster', sortable: false, render: (_value: any, row: Content) => { const clusterName = row.cluster_name; if (!clusterName) { return -; } return ( {clusterName} ); }, }, { key: 'status', label: 'Status', sortable: true, sortField: 'status', render: (value: string, row: Content) => { const status = value || 'draft'; const statusColors: Record = { draft: 'gray', review: 'blue', published: 'green', scheduled: 'amber', archived: 'red', }; const color = statusColors[status] || 'gray'; const label = status.charAt(0).toUpperCase() + status.slice(1); return (
{label} {row.external_id && ( )}
); }, }, { key: 'word_count', label: 'Words', sortable: true, sortField: 'word_count', numeric: true, align: 'center' as const, headingAlign: 'center' as const, render: (value: number) => ( {value?.toLocaleString() || 0} ), }, { key: 'created_at', label: 'Created', sortable: true, sortField: 'created_at', date: true, width: '130px', hasActions: true, render: (value: string) => ( {formatRelativeDate(value)} ), }, ]; if (showSectorColumn) { columns.splice(4, 0, { key: 'sector_name', label: 'Sector', sortable: false, width: '120px', render: (value: string, row: Content) => ( {row.sector_name || '-'} ), }); } return { columns, filters: [ { key: 'search', label: 'Search', type: 'text', placeholder: 'Search content...', }, ], headerMetrics: [ { label: 'Content', accentColor: 'blue', calculate: ({ totalCount }) => totalCount, tooltip: 'Total content items tracked. Overall volume across all stages.', }, { label: 'Draft', accentColor: 'amber', calculate: ({ content }) => content.filter(c => c.status === 'draft').length, tooltip: 'Content written, images not generated. Generate images to move to review.', }, { label: 'In Review', accentColor: 'purple', calculate: ({ content }) => content.filter(c => c.status === 'review').length, tooltip: 'Images generated, awaiting approval. Review and approve to publish.', }, { label: 'Approved', accentColor: 'green', calculate: ({ content }) => content.filter(c => c.status === 'approved').length, tooltip: 'Approved content awaiting publishing. Publish to site when ready.', }, { label: 'Published', accentColor: 'green', calculate: ({ content }) => content.filter(c => c.status === 'published').length, tooltip: 'Live content on your website. Successfully published and accessible.', }, { label: 'Total Images', accentColor: 'blue', calculate: ({ content }) => content.filter(c => c.has_generated_images).length, tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ], }; }