/** * Content Page Configuration * Centralized config for Content page table, filters, and actions */ import React from 'react'; import { titleColumn, statusColumn, createdColumn, wordCountColumn, sectorColumn, } from '../snippets/columns.snippets'; import Badge from '../../components/ui/badge/Badge'; import { formatRelativeDate } from '../../utils/date'; import { Content } from '../../services/api'; import { FileIcon, MoreDotIcon } from '../../icons'; export interface ColumnConfig { key: string; label: string; sortable?: boolean; sortField?: string; align?: 'left' | 'center' | 'right'; width?: string; render?: (value: any, row: any) => React.ReactNode; toggleable?: boolean; toggleContentKey?: string; toggleContentLabel?: string; } export interface FilterConfig { key: string; label: string; type: 'text' | 'select'; placeholder?: string; options?: Array<{ value: string; label: string }>; } export interface HeaderMetricConfig { label: string; value: number; accentColor: 'blue' | 'green' | 'amber' | 'purple'; calculate: (data: { content: any[]; totalCount: number }) => number; } export interface ContentPageConfig { columns: ColumnConfig[]; filters: FilterConfig[]; headerMetrics: HeaderMetricConfig[]; } const getList = (primary?: string[], fallback?: any): string[] => { if (primary && primary.length > 0) return primary; if (!fallback) return []; if (Array.isArray(fallback)) return fallback; return []; }; const renderBadgeList = (items: string[], emptyLabel = '-') => { if (!items || items.length === 0) { return {emptyLabel}; } return (
{items.map((item, index) => ( {item} ))}
); }; export const createContentPageConfig = ( handlers: { activeSector: { id: number; name: string } | null; searchTerm: string; setSearchTerm: (value: string) => void; statusFilter: string; setStatusFilter: (value: string) => void; setCurrentPage: (page: number) => void; } ): ContentPageConfig => { const showSectorColumn = !handlers.activeSector; const statusColors: Record = { draft: 'warning', review: 'info', publish: 'success', }; return { columns: [ { ...titleColumn, sortable: true, sortField: 'title', toggleable: true, toggleContentKey: 'html_content', toggleContentLabel: 'Generated Content', render: (value: string, row: Content) => (
{row.meta_title || row.title || row.task_title || `Task #${row.task_id}`}
{row.meta_description && (
{row.meta_description}
)}
), }, ...(showSectorColumn ? [{ ...sectorColumn, render: (value: string, row: Content) => ( {row.sector_name || '-'} ), }] : []), { key: 'primary_keyword', label: 'Primary Keyword', sortable: false, width: '150px', render: (value: string, row: Content) => ( row.primary_keyword ? ( {row.primary_keyword} ) : ( - ) ), }, { key: 'secondary_keywords', label: 'Secondary Keywords', sortable: false, width: '200px', render: (_value: any, row: Content) => { const secondaryKeywords = getList( row.secondary_keywords, row.metadata?.secondary_keywords ); return renderBadgeList(secondaryKeywords); }, }, { key: 'tags', label: 'Tags', sortable: false, width: '150px', render: (_value: any, row: Content) => { const tags = getList(row.tags, row.metadata?.tags); return renderBadgeList(tags); }, }, { key: 'categories', label: 'Categories', sortable: false, width: '150px', render: (_value: any, row: Content) => { const categories = getList(row.categories, row.metadata?.categories); return renderBadgeList(categories); }, }, { ...wordCountColumn, sortable: true, sortField: 'word_count', render: (value: number) => value?.toLocaleString() ?? '-', }, { ...statusColumn, sortable: true, sortField: 'status', render: (value: string) => { const status = value || 'draft'; const color = statusColors[status] || 'primary'; const label = status.replace('_', ' ').replace(/^\w/, (c) => c.toUpperCase()); return ( {label} ); }, }, { ...createdColumn, sortable: true, sortField: 'generated_at', label: 'Generated', align: 'right', render: (value: string, row: Content) => { const hasPrompts = row.has_image_prompts || false; const hasImages = row.has_generated_images || false; return (
{formatRelativeDate(value)}
{/* Prompt Icon */}
{/* Image Icon */}
); }, }, ], filters: [ { key: 'search', label: 'Search', type: 'text', placeholder: 'Search content...', }, { key: 'status', label: 'Status', type: 'select', options: [ { value: '', label: 'All Status' }, { value: 'draft', label: 'Draft' }, { value: 'review', label: 'Review' }, { value: 'publish', label: 'Publish' }, ], }, ], headerMetrics: [ { label: 'Total Content', value: 0, accentColor: 'blue' as const, calculate: (data) => data.totalCount || 0, }, { label: 'Draft', value: 0, accentColor: 'warning' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'draft').length, }, { label: 'Review', value: 0, accentColor: 'info' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'review').length, }, { label: 'Published', value: 0, accentColor: 'success' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'publish').length, }, ], }; };