/** * Ideas Page Configuration * Centralized config for Ideas page table, filters, and actions */ import React from 'react'; import { Link } from 'react-router-dom'; import { titleColumn, sectorColumn, statusColumn, createdWithActionsColumn, } from '../snippets/columns.snippets'; import Badge from '../../components/ui/badge/Badge'; import { formatRelativeDate } from '../../utils/date'; import { ContentIdea, Cluster } from '../../services/api'; export interface ColumnConfig { key: string; label: string; sortable?: boolean; sortField?: string; align?: 'left' | 'center' | 'right'; width?: string; render?: (value: any, row: any) => React.ReactNode; defaultVisible?: boolean; // Whether column is visible by default (default: true) } export interface FormFieldConfig { key: string; label: string; type: 'text' | 'number' | 'select' | 'textarea'; placeholder?: string; required?: boolean; value: any; onChange: (value: any) => void; options?: Array<{ value: string; label: string }>; } export interface FilterConfig { key: string; label: string; type: 'text' | 'select'; placeholder?: string; options?: Array<{ value: string; label: string }>; dynamicOptions?: string; } export interface HeaderMetricConfig { label: string; value: number; accentColor: 'blue' | 'green' | 'amber' | 'purple'; calculate: (data: { ideas: any[]; totalCount: number }) => number; } export interface IdeasPageConfig { columns: ColumnConfig[]; filters: FilterConfig[]; formFields: (clusters: Array<{ id: number; name: string }>) => FormFieldConfig[]; headerMetrics: HeaderMetricConfig[]; } export const createIdeasPageConfig = ( handlers: { clusters: Array<{ id: number; name: string }>; activeSector: { id: number; name: string } | null; formData: { idea_title: string; description?: string | null; content_structure: string; content_type: string; target_keywords?: string | null; keyword_cluster_id?: number | null; status: string; estimated_word_count?: number; }; setFormData: React.Dispatch>; searchTerm: string; setSearchTerm: (value: string) => void; statusFilter: string; setStatusFilter: (value: string) => void; clusterFilter: string; setClusterFilter: (value: string) => void; structureFilter: string; setStructureFilter: (value: string) => void; typeFilter: string; setTypeFilter: (value: string) => void; setCurrentPage: (page: number) => void; } ): IdeasPageConfig => { const showSectorColumn = !handlers.activeSector; // Show when viewing all sectors return { columns: [ { ...titleColumn, key: 'idea_title', label: 'Content Idea Title', sortable: true, sortField: 'idea_title', width: '400px', toggleable: true, // Enable toggle for this column toggleContentKey: 'description', // Use description field for toggle content toggleContentLabel: 'Content Outline', // Label for expanded content render: (value: string) => ( {value} ), }, // Sector column - only show when viewing all sectors ...(showSectorColumn ? [{ ...sectorColumn, render: (value: string, row: ContentIdea) => ( {row.sector_name || '-'} ), }] : []), { key: 'content_structure', label: 'Structure', sortable: false, // Backend doesn't support sorting by content_structure sortField: 'content_structure', render: (value: string) => { const label = value?.replace('_', ' ') || '-'; const properCase = label.split(/[_\s]+/).map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' '); return ( {properCase} ); }, }, { key: 'content_type', label: 'Type', sortable: false, // Backend doesn't support sorting by content_type sortField: 'content_type', render: (value: string) => { const label = value?.replace('_', ' ') || '-'; const properCase = label.charAt(0).toUpperCase() + label.slice(1); return ( {properCase} ); }, }, { key: 'target_keywords', label: 'Keywords', sortable: false, render: (value: string) => ( {value || '-'} ), }, { key: 'keyword_cluster_name', label: 'Cluster', sortable: false, // Backend doesn't support sorting by keyword_cluster_id sortField: 'keyword_cluster_id', render: (_value: string, row: ContentIdea) => { if (row.keyword_cluster_id && row.keyword_cluster_name) { return ( {row.keyword_cluster_name} ); } return '-'; }, }, { ...statusColumn, sortable: false, // Backend doesn't support sorting by status sortField: 'status', render: (value: string) => { const statusColors: Record = { 'new': 'amber', 'queued': 'info', 'completed': 'success', }; const properCase = value ? value.charAt(0).toUpperCase() + value.slice(1) : '-'; return ( {properCase} ); }, }, { key: 'estimated_word_count', label: 'Words', sortable: true, sortField: 'estimated_word_count', align: 'center' as const, headingAlign: 'center' as const, render: (value: number) => value.toLocaleString(), }, { ...createdWithActionsColumn, sortable: true, sortField: 'created_at', width: '130px', render: (value: string) => formatRelativeDate(value), }, // Optional columns - hidden by default { key: 'updated_at', label: 'Modified', sortable: false, // Backend doesn't support sorting by updated_at sortField: 'updated_at', defaultVisible: false, render: (value: string) => formatRelativeDate(value), }, ], filters: [ { key: 'search', label: 'Search', type: 'text', placeholder: 'Search ideas...', }, { key: 'status', label: 'Status', type: 'select', options: [ { value: '', label: 'All Status' }, { value: 'new', label: 'New' }, { value: 'queued', label: 'Queued' }, { value: 'completed', label: 'Completed' }, ], }, { key: 'content_structure', label: 'Structure', type: 'select', options: [ { value: '', label: 'All Structures' }, // Post { value: 'article', label: 'Article' }, { value: 'guide', label: 'Guide' }, { value: 'comparison', label: 'Comparison' }, { value: 'review', label: 'Review' }, { value: 'listicle', label: 'Listicle' }, // Page { value: 'landing_page', label: 'Landing Page' }, { value: 'business_page', label: 'Business Page' }, { value: 'service_page', label: 'Service Page' }, { value: 'general', label: 'General' }, { value: 'cluster_hub', label: 'Cluster Hub' }, // Product { value: 'product_page', label: 'Product Page' }, // Taxonomy { value: 'category_archive', label: 'Category Archive' }, { value: 'tag_archive', label: 'Tag Archive' }, { value: 'attribute_archive', label: 'Attribute Archive' }, ], }, { key: 'content_type', label: 'Type', type: 'select', options: [ { value: '', label: 'All Types' }, { value: 'post', label: 'Post' }, { value: 'page', label: 'Page' }, { value: 'product', label: 'Product' }, { value: 'taxonomy', label: 'Taxonomy' }, ], }, { key: 'keyword_cluster_id', label: 'Cluster', type: 'select', options: (() => { return [ { value: '', label: 'All Clusters' }, ...handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name })), ]; })(), dynamicOptions: 'clusters', }, ], formFields: (clusters: Array<{ id: number; name: string }>) => [ { key: 'idea_title', label: 'Title', type: 'text', placeholder: 'Enter idea title', required: true, value: handlers.formData.idea_title || '', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, idea_title: value }), }, { key: 'description', label: 'Description', type: 'textarea', placeholder: 'Enter description', value: handlers.formData.description || '', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, description: value }), }, { key: 'content_structure', label: 'Content Structure', type: 'select', value: handlers.formData.content_structure || 'article', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, content_structure: value }), options: [ // Post { value: 'article', label: 'Article' }, { value: 'guide', label: 'Guide' }, { value: 'comparison', label: 'Comparison' }, { value: 'review', label: 'Review' }, { value: 'listicle', label: 'Listicle' }, // Page { value: 'landing_page', label: 'Landing Page' }, { value: 'business_page', label: 'Business Page' }, { value: 'service_page', label: 'Service Page' }, { value: 'general', label: 'General' }, { value: 'cluster_hub', label: 'Cluster Hub' }, // Product { value: 'product_page', label: 'Product Page' }, // Taxonomy { value: 'category_archive', label: 'Category Archive' }, { value: 'tag_archive', label: 'Tag Archive' }, { value: 'attribute_archive', label: 'Attribute Archive' }, ], }, { key: 'content_type', label: 'Content Type', type: 'select', value: handlers.formData.content_type || 'post', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, content_type: value }), options: [ { value: 'post', label: 'Post' }, { value: 'page', label: 'Page' }, { value: 'product', label: 'Product' }, { value: 'taxonomy', label: 'Taxonomy' }, ], }, { key: 'target_keywords', label: 'Target Keywords', type: 'text', placeholder: 'Enter keywords (comma-separated)', value: handlers.formData.target_keywords || '', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, target_keywords: value }), }, { key: 'keyword_cluster_id', label: 'Cluster', type: 'select', value: handlers.formData.keyword_cluster_id?.toString() || '', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, keyword_cluster_id: value ? parseInt(value) : null, }), options: [ { value: '', label: 'No Cluster' }, ...clusters.map((c) => ({ value: c.id.toString(), label: c.name })), ], }, { key: 'estimated_word_count', label: 'Estimated Word Count', type: 'number', value: handlers.formData.estimated_word_count || 1000, onChange: (value: any) => handlers.setFormData({ ...handlers.formData, estimated_word_count: value ? parseInt(value) : 1000 }), }, { key: 'status', label: 'Status', type: 'select', value: handlers.formData.status || 'new', onChange: (value: any) => handlers.setFormData({ ...handlers.formData, status: value }), options: [ { value: 'new', label: 'New' }, { value: 'queued', label: 'Queued' }, { value: 'completed', label: 'Completed' }, ], }, ], headerMetrics: [ { label: 'Ideas', value: 0, accentColor: 'blue' as const, calculate: (data) => data.totalCount || 0, tooltip: 'Total content ideas generated. Ideas become tasks in the content queue for writing.', }, { label: 'New', value: 0, accentColor: 'amber' as const, calculate: (data) => data.ideas.filter((i: ContentIdea) => i.status === 'new').length, tooltip: 'New ideas waiting for review. Approve ideas to queue them for content creation.', }, { label: 'Queued', value: 0, accentColor: 'blue' as const, calculate: (data) => data.ideas.filter((i: ContentIdea) => i.status === 'queued').length, tooltip: 'Ideas queued for content generation. These will be converted to writing tasks automatically.', }, { label: 'Completed', value: 0, accentColor: 'green' as const, calculate: (data) => data.ideas.filter((i: ContentIdea) => i.status === 'completed').length, tooltip: 'Ideas that have been successfully turned into content. Track your content creation progress.', }, ], }; };