fine tuning
This commit is contained in:
224
frontend/src/config/pages/review.config.tsx
Normal file
224
frontend/src/config/pages/review.config.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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;
|
||||
}): ReviewPageConfig {
|
||||
const showSectorColumn = !params.activeSector;
|
||||
|
||||
const columns: ColumnConfig[] = [
|
||||
{
|
||||
key: 'categories',
|
||||
label: 'Categories',
|
||||
sortable: false,
|
||||
width: '180px',
|
||||
render: (_value: any, row: Content) => {
|
||||
const categories = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'category') || [];
|
||||
if (!categories.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{categories.map((cat: any) => (
|
||||
<span key={cat.id} className="px-2 py-0.5 bg-purple-50 dark:bg-purple-900/20 text-purple-700 dark:text-purple-300 rounded-full text-xs font-medium">{cat.name}</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
toggleable: true,
|
||||
defaultVisible: false,
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
label: 'Tags',
|
||||
sortable: false,
|
||||
width: '180px',
|
||||
render: (_value: any, row: Content) => {
|
||||
const tags = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'tag') || [];
|
||||
if (!tags.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{tags.map((tag: any) => (
|
||||
<span key={tag.id} className="px-2 py-0.5 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300 rounded-full text-xs font-medium">{tag.name}</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
toggleable: true,
|
||||
defaultVisible: false,
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true,
|
||||
sortField: 'title',
|
||||
toggleable: true,
|
||||
toggleContentKey: 'content_html',
|
||||
toggleContentLabel: 'Generated Content',
|
||||
render: (value: string, row: Content) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-gray-900 dark:text-white">
|
||||
{value || `Content #${row.id}`}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'content_type',
|
||||
label: 'Type',
|
||||
sortable: true,
|
||||
sortField: 'content_type',
|
||||
width: '120px',
|
||||
render: (value: string) => (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{TYPE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'content_structure',
|
||||
label: 'Structure',
|
||||
sortable: true,
|
||||
sortField: 'content_structure',
|
||||
width: '150px',
|
||||
render: (value: string) => (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{STRUCTURE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'cluster_name',
|
||||
label: 'Cluster',
|
||||
sortable: false,
|
||||
width: '150px',
|
||||
render: (_value: any, row: Content) => {
|
||||
const clusterName = row.cluster_name;
|
||||
if (!clusterName) {
|
||||
return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
||||
}
|
||||
return (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{clusterName}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'word_count',
|
||||
label: 'Words',
|
||||
sortable: true,
|
||||
sortField: 'word_count',
|
||||
numeric: true,
|
||||
width: '100px',
|
||||
render: (value: number) => (
|
||||
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">
|
||||
{value?.toLocaleString() || 0}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
label: 'Created',
|
||||
sortable: true,
|
||||
sortField: 'created_at',
|
||||
date: true,
|
||||
align: 'right',
|
||||
render: (value: string) => (
|
||||
<span className="text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||
{formatRelativeDate(value)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (showSectorColumn) {
|
||||
columns.splice(4, 0, {
|
||||
key: 'sector_name',
|
||||
label: 'Sector',
|
||||
sortable: false,
|
||||
width: '120px',
|
||||
render: (value: string, row: Content) => (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{row.sector_name || '-'}
|
||||
</Badge>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
columns,
|
||||
filters: [
|
||||
{
|
||||
key: 'search',
|
||||
label: 'Search',
|
||||
type: 'text',
|
||||
placeholder: 'Search content...',
|
||||
},
|
||||
],
|
||||
headerMetrics: [
|
||||
{
|
||||
label: 'Total Ready',
|
||||
accentColor: 'blue',
|
||||
calculate: ({ totalCount }) => totalCount,
|
||||
},
|
||||
{
|
||||
label: 'Has Images',
|
||||
accentColor: 'green',
|
||||
calculate: ({ content }) => content.filter(c => c.has_generated_images).length,
|
||||
},
|
||||
{
|
||||
label: 'Optimized',
|
||||
accentColor: 'purple',
|
||||
calculate: ({ content }) => content.filter(c => (c as any).optimization_score >= 80).length,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -341,18 +341,6 @@ const tableActionsConfigs: Record<string, TableActionsConfig> = {
|
||||
},
|
||||
'/writer/images': {
|
||||
rowActions: [
|
||||
{
|
||||
key: 'publish_wordpress',
|
||||
label: 'Publish to WordPress',
|
||||
icon: <ArrowRightIcon className="w-5 h-5" />,
|
||||
variant: 'success',
|
||||
shouldShow: (row: any) => {
|
||||
// Only show if content is completed and not already published to WordPress
|
||||
return row.status === 'published' &&
|
||||
(!row.external_id || !row.external_url) &&
|
||||
(!row.sync_status || row.sync_status !== 'published');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'update_status',
|
||||
label: 'Update Status',
|
||||
@@ -360,10 +348,21 @@ const tableActionsConfigs: Record<string, TableActionsConfig> = {
|
||||
variant: 'primary',
|
||||
},
|
||||
],
|
||||
bulkActions: [],
|
||||
},
|
||||
'/writer/review': {
|
||||
rowActions: [
|
||||
{
|
||||
key: 'publish_wordpress',
|
||||
label: 'Publish to WordPress',
|
||||
icon: <ArrowRightIcon className="w-5 h-5" />,
|
||||
variant: 'success',
|
||||
},
|
||||
],
|
||||
bulkActions: [
|
||||
{
|
||||
key: 'bulk_publish_wordpress',
|
||||
label: 'Publish Ready to WordPress',
|
||||
label: 'Publish to WordPress',
|
||||
icon: <ArrowRightIcon className="w-5 h-5" />,
|
||||
variant: 'success',
|
||||
},
|
||||
@@ -376,3 +375,4 @@ const tableActionsConfigs: Record<string, TableActionsConfig> = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user