refactor-migration again
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
import { formatRelativeDate } from '../../utils/date';
|
||||
import { Content } from '../../services/api';
|
||||
import { CONTENT_TYPE_OPTIONS, STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping';
|
||||
|
||||
export interface ColumnConfig {
|
||||
key: string;
|
||||
@@ -122,21 +123,11 @@ export const createContentPageConfig = (
|
||||
sortable: true,
|
||||
sortField: 'content_type',
|
||||
width: '120px',
|
||||
render: (value: string) => {
|
||||
const typeLabels: Record<string, string> = {
|
||||
'post': 'Post',
|
||||
'page': 'Page',
|
||||
'product': 'Product',
|
||||
'service': 'Service',
|
||||
'category': 'Category',
|
||||
'tag': 'Tag',
|
||||
};
|
||||
return (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{typeLabels[value] || value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
render: (value: string) => (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{TYPE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'content_structure',
|
||||
@@ -144,20 +135,11 @@ export const createContentPageConfig = (
|
||||
sortable: true,
|
||||
sortField: 'content_structure',
|
||||
width: '150px',
|
||||
render: (value: string) => {
|
||||
const structureLabels: Record<string, string> = {
|
||||
'article': 'Article',
|
||||
'listicle': 'Listicle',
|
||||
'guide': 'Guide',
|
||||
'comparison': 'Comparison',
|
||||
'product_page': 'Product Page',
|
||||
};
|
||||
return (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{structureLabels[value] || value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
render: (value: string) => (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{STRUCTURE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'cluster_name',
|
||||
@@ -332,12 +314,7 @@ export const createContentPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Types' },
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'service', label: 'Service' },
|
||||
{ value: 'category', label: 'Category' },
|
||||
{ value: 'tag', label: 'Tag' },
|
||||
...CONTENT_TYPE_OPTIONS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -347,10 +324,19 @@ export const createContentPageConfig = (
|
||||
options: [
|
||||
{ value: '', label: 'All Structures' },
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ 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' },
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -224,25 +224,36 @@ export const createIdeasPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Structures' },
|
||||
// Post
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ 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',
|
||||
label: 'Type',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Types' },
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'service', label: 'Service' },
|
||||
{ value: 'category', label: 'Category' },
|
||||
{ value: 'tag', label: 'Tag' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -286,11 +297,24 @@ export const createIdeasPageConfig = (
|
||||
onChange: (value: any) =>
|
||||
handlers.setFormData({ ...handlers.formData, content_structure: value }),
|
||||
options: [
|
||||
// Post
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -304,9 +328,7 @@ export const createIdeasPageConfig = (
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'service', label: 'Service' },
|
||||
{ value: 'category', label: 'Category' },
|
||||
{ value: 'tag', label: 'Tag' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
import { formatRelativeDate } from '../../utils/date';
|
||||
import { Task, Cluster } from '../../services/api';
|
||||
import { CONTENT_TYPE_OPTIONS, CONTENT_STRUCTURE_BY_TYPE, STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping';
|
||||
|
||||
export interface ColumnConfig {
|
||||
key: string;
|
||||
@@ -164,21 +165,11 @@ export const createTasksPageConfig = (
|
||||
sortable: true,
|
||||
sortField: 'content_type',
|
||||
width: '120px',
|
||||
render: (value: string) => {
|
||||
const typeLabels: Record<string, string> = {
|
||||
'post': 'Post',
|
||||
'page': 'Page',
|
||||
'product': 'Product',
|
||||
'service': 'Service',
|
||||
'category': 'Category',
|
||||
'tag': 'Tag',
|
||||
};
|
||||
return (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{typeLabels[value] || value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
render: (value: string) => (
|
||||
<Badge color="primary" size="sm" variant="light">
|
||||
{TYPE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'content_structure',
|
||||
@@ -186,20 +177,11 @@ export const createTasksPageConfig = (
|
||||
sortable: true,
|
||||
sortField: 'content_structure',
|
||||
width: '150px',
|
||||
render: (value: string) => {
|
||||
const structureLabels: Record<string, string> = {
|
||||
'article': 'Article',
|
||||
'listicle': 'Listicle',
|
||||
'guide': 'Guide',
|
||||
'comparison': 'Comparison',
|
||||
'product_page': 'Product Page',
|
||||
};
|
||||
return (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{structureLabels[value] || value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
render: (value: string) => (
|
||||
<Badge color="info" size="sm" variant="light">
|
||||
{STRUCTURE_LABELS[value] || value || '-'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
...statusColumn,
|
||||
@@ -335,12 +317,7 @@ export const createTasksPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Types' },
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'service', label: 'Service' },
|
||||
{ value: 'category', label: 'Category' },
|
||||
{ value: 'tag', label: 'Tag' },
|
||||
...CONTENT_TYPE_OPTIONS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -350,10 +327,19 @@ export const createTasksPageConfig = (
|
||||
options: [
|
||||
{ value: '', label: 'All Structures' },
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ 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' },
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -422,10 +408,19 @@ export const createTasksPageConfig = (
|
||||
handlers.setFormData({ ...handlers.formData, content_structure: value }),
|
||||
options: [
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ 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' },
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -435,14 +430,7 @@ export const createTasksPageConfig = (
|
||||
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: 'service', label: 'Service' },
|
||||
{ value: 'category', label: 'Category' },
|
||||
{ value: 'tag', label: 'Tag' },
|
||||
],
|
||||
options: CONTENT_TYPE_OPTIONS,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
|
||||
70
frontend/src/config/structureMapping.ts
Normal file
70
frontend/src/config/structureMapping.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Structure mapping configuration
|
||||
* Maps content types to their valid structures and provides label mappings
|
||||
*/
|
||||
|
||||
export const CONTENT_TYPE_OPTIONS = [
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
];
|
||||
|
||||
export const CONTENT_STRUCTURE_BY_TYPE: Record<string, Array<{ value: string; label: string }>> = {
|
||||
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' },
|
||||
],
|
||||
};
|
||||
|
||||
export const ALL_CONTENT_STRUCTURES = Object.values(CONTENT_STRUCTURE_BY_TYPE).flat();
|
||||
|
||||
export const STRUCTURE_LABELS: Record<string, string> = {
|
||||
// Post
|
||||
article: 'Article',
|
||||
guide: 'Guide',
|
||||
comparison: 'Comparison',
|
||||
review: 'Review',
|
||||
listicle: 'Listicle',
|
||||
// Page
|
||||
landing_page: 'Landing Page',
|
||||
business_page: 'Business Page',
|
||||
service_page: 'Service Page',
|
||||
general: 'General',
|
||||
cluster_hub: 'Cluster Hub',
|
||||
// Product
|
||||
product_page: 'Product Page',
|
||||
// Taxonomy
|
||||
category_archive: 'Category Archive',
|
||||
tag_archive: 'Tag Archive',
|
||||
attribute_archive: 'Attribute Archive',
|
||||
};
|
||||
|
||||
export const TYPE_LABELS: Record<string, string> = {
|
||||
post: 'Post',
|
||||
page: 'Page',
|
||||
product: 'Product',
|
||||
taxonomy: 'Taxonomy',
|
||||
};
|
||||
|
||||
export const getStructureOptions = (contentType: string) => {
|
||||
return CONTENT_STRUCTURE_BY_TYPE[contentType] || [];
|
||||
};
|
||||
@@ -265,8 +265,8 @@ export default function Ideas() {
|
||||
setFormData({
|
||||
idea_title: '',
|
||||
description: '',
|
||||
content_structure: 'blog_post',
|
||||
content_type: 'blog_post',
|
||||
content_structure: 'article',
|
||||
content_type: 'post',
|
||||
target_keywords: '',
|
||||
keyword_cluster_id: null,
|
||||
status: 'new',
|
||||
@@ -343,7 +343,7 @@ export default function Ideas() {
|
||||
onEdit={(row) => {
|
||||
setEditingIdea(row);
|
||||
setFormData({
|
||||
idea_title: row.idea_title || '',
|
||||
idea_title: row.idea_title,
|
||||
description: row.description || '',
|
||||
content_structure: row.content_structure || 'article',
|
||||
content_type: row.content_type || 'post',
|
||||
|
||||
@@ -760,14 +760,14 @@ export default function DebugStatus() {
|
||||
Database Schema Mapping Errors
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||
If you see errors about missing fields like <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">entity_type</code>,
|
||||
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">cluster_role</code>, or
|
||||
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">html_content</code>:
|
||||
If you see errors about missing fields like <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_type</code>,
|
||||
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">content_structure</code>, or
|
||||
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">content_html</code>:
|
||||
</p>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
|
||||
<li>Check that model fields have correct <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">db_column</code> attributes</li>
|
||||
<li>Check that model fields match database column names</li>
|
||||
<li>Verify database columns exist with <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">SELECT column_name FROM information_schema.columns</code></li>
|
||||
<li>Review <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">DATABASE_SCHEMA_FIELD_MAPPING_GUIDE.md</code> in repo root</li>
|
||||
<li>All field names now match database (no db_column mappings)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -779,8 +779,8 @@ export default function DebugStatus() {
|
||||
If API endpoints return 500 errors with AttributeError or similar:
|
||||
</p>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
|
||||
<li>Search codebase for old field names: <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">entity_type</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">cluster_role</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">html_content</code></li>
|
||||
<li>Replace with new names: <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_type</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_structure</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_html</code></li>
|
||||
<li>All field names now standardized: <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_type</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_structure</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_html</code></li>
|
||||
<li>Old names removed: entity_type, site_entity_type, cluster_role, html_content</li>
|
||||
<li>Check views, services, and serializers in writer/planner/integration modules</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -484,9 +484,9 @@ export default function PostEditor() {
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">Entity Type:</span>
|
||||
<span className="text-gray-600 dark:text-gray-400">Content Type:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">
|
||||
{validationResult.metadata.entity_type || 'Not set'}
|
||||
{validationResult.metadata.content_type || 'Not set'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -594,8 +594,8 @@ export default function PostEditor() {
|
||||
Entity Type
|
||||
</div>
|
||||
<div className="text-sm text-gray-900 dark:text-white">
|
||||
{content.entity_type ? (
|
||||
content.entity_type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||
{content.content_type ? (
|
||||
content.content_type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||
) : (
|
||||
<span className="text-gray-400 dark:text-gray-500 italic">Not set</span>
|
||||
)}
|
||||
@@ -611,9 +611,9 @@ export default function PostEditor() {
|
||||
{content.cluster_name ? (
|
||||
<>
|
||||
{content.cluster_name}
|
||||
{content.cluster_role && (
|
||||
{content.content_structure && (
|
||||
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
({content.cluster_role})
|
||||
({content.content_structure})
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -780,8 +780,8 @@ export interface ContentIdea {
|
||||
id: number;
|
||||
idea_title: string;
|
||||
description?: string | null;
|
||||
content_structure: string;
|
||||
content_type: string;
|
||||
content_type: string; // post, page, product, taxonomy
|
||||
content_structure: string; // article, guide, comparison, review, etc.
|
||||
target_keywords?: string | null;
|
||||
keyword_cluster_id?: number | null;
|
||||
keyword_cluster_name?: string | null;
|
||||
@@ -798,8 +798,8 @@ export interface ContentIdea {
|
||||
export interface ContentIdeaCreateData {
|
||||
idea_title: string;
|
||||
description?: string | null;
|
||||
content_structure?: string;
|
||||
content_type?: string;
|
||||
content_structure?: string;
|
||||
target_keywords?: string | null;
|
||||
keyword_cluster_id?: number | null;
|
||||
status?: string;
|
||||
|
||||
Reference in New Issue
Block a user