diff --git a/backend/igny8_core/modules/writer/serializers.py b/backend/igny8_core/modules/writer/serializers.py index f3698e61..06f14433 100644 --- a/backend/igny8_core/modules/writer/serializers.py +++ b/backend/igny8_core/modules/writer/serializers.py @@ -472,3 +472,63 @@ class ContentTaxonomyRelationSerializer(serializers.ModelSerializer): def get_taxonomy_type(self, obj): return obj.taxonomy.taxonomy_type if obj.taxonomy else None + +class UpdatedTasksSerializer(serializers.ModelSerializer): + """Updated Serializer for Tasks model with new fields.""" + cluster_name = serializers.SerializerMethodField() + sector_name = serializers.SerializerMethodField() + idea_title = serializers.SerializerMethodField() + site_id = serializers.IntegerField(write_only=True, required=False) + sector_id = serializers.IntegerField(write_only=True, required=False) + content_html = serializers.SerializerMethodField() + content_primary_keyword = serializers.SerializerMethodField() + content_secondary_keywords = serializers.SerializerMethodField() + content_taxonomies = serializers.SerializerMethodField() + content_attributes = serializers.SerializerMethodField() + + class Meta: + model = Tasks + fields = [ + 'id', + 'title', + 'description', + 'keywords', + 'cluster_id', + 'cluster_name', + 'sector_name', + 'idea_id', + 'idea_title', + 'content_structure', + 'content_type', + 'status', + 'content', + 'word_count', + 'meta_title', + 'meta_description', + 'content_html', + 'content_primary_keyword', + 'content_secondary_keywords', + 'content_taxonomies', + 'content_attributes', + 'assigned_post_id', + 'post_url', + 'created_at', + 'updated_at', + 'site_id', + 'sector_id', + 'account_id', + ] + read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] + + def get_content_taxonomies(self, obj): + """Fetch related taxonomies.""" + return ContentTaxonomyRelationSerializer( + obj.content.taxonomies.all(), many=True + ).data + + def get_content_attributes(self, obj): + """Fetch related attributes.""" + return ContentAttributeSerializer( + obj.content.attributes.all(), many=True + ).data + diff --git a/backend/migrations/0006_migrate_legacy_fields.py b/backend/migrations/0006_migrate_legacy_fields.py new file mode 100644 index 00000000..c5fa38d7 --- /dev/null +++ b/backend/migrations/0006_migrate_legacy_fields.py @@ -0,0 +1,48 @@ +from django.db import migrations + +def migrate_legacy_fields(apps, schema_editor): + Content = apps.get_model('igny8_core', 'Content') + Task = apps.get_model('igny8_core', 'Task') + + # Migrate legacy fields in Content model + for content in Content.objects.all(): + if content.categories: + # Convert JSON categories to ContentTaxonomy + categories = content.categories + for category in categories: + taxonomy, created = ContentTaxonomy.objects.get_or_create( + name=category['name'], + slug=category['slug'], + taxonomy_type='category' + ) + content.taxonomies.add(taxonomy) + + if content.tags: + # Convert JSON tags to ContentTaxonomy + tags = content.tags + for tag in tags: + taxonomy, created = ContentTaxonomy.objects.get_or_create( + name=tag['name'], + slug=tag['slug'], + taxonomy_type='tag' + ) + content.taxonomies.add(taxonomy) + + content.save() + + # Migrate legacy fields in Task model + for task in Task.objects.all(): + task.entity_type = task.content.entity_type + task.cluster_role = task.content.cluster_role + task.cluster_id = task.content.cluster_id + task.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('igny8_core', '0005_phase3_mark_deprecated_fields'), + ] + + operations = [ + migrations.RunPython(migrate_legacy_fields), + ] \ No newline at end of file diff --git a/frontend/src/pages/Writer/Dashboard.tsx b/frontend/src/pages/Writer/Dashboard.tsx index e6b6c5c6..933c48f9 100644 --- a/frontend/src/pages/Writer/Dashboard.tsx +++ b/frontend/src/pages/Writer/Dashboard.tsx @@ -23,7 +23,9 @@ import { import { fetchTasks, fetchContent, - fetchContentImages + fetchContentImages, + fetchTaxonomies, + fetchAttributes } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; @@ -66,6 +68,8 @@ interface WriterStats { avgGenerationTime: number; publishRate: number; }; + taxonomies: number; + attributes: number; } export default function WriterDashboard() { @@ -81,10 +85,12 @@ export default function WriterDashboard() { try { setLoading(true); - const [tasksRes, contentRes, imagesRes] = await Promise.all([ + const [tasksRes, contentRes, imagesRes, taxonomiesRes, attributesRes] = await Promise.all([ fetchTasks({ page_size: 1000, sector_id: activeSector?.id }), fetchContent({ page_size: 1000, sector_id: activeSector?.id }), - fetchContentImages({ sector_id: activeSector?.id }) + fetchContentImages({ sector_id: activeSector?.id }), + fetchTaxonomies({ sector_id: activeSector?.id }), + fetchAttributes({ sector_id: activeSector?.id }), ]); const tasks = tasksRes.results || []; @@ -143,6 +149,12 @@ export default function WriterDashboard() { const contentThisMonth = Math.floor(content.length * 0.7); const publishRate = content.length > 0 ? Math.round((published / content.length) * 100) : 0; + const taxonomies = taxonomiesRes.results || []; + const attributes = attributesRes.results || []; + + const taxonomyCount = taxonomies.length; + const attributeCount = attributes.length; + setStats({ tasks: { total: tasks.length, @@ -180,7 +192,9 @@ export default function WriterDashboard() { contentThisMonth, avgGenerationTime: 0, publishRate - } + }, + taxonomies: taxonomyCount, + attributes: attributeCount, }); setLastUpdated(new Date()); @@ -239,6 +253,24 @@ export default function WriterDashboard() { count: stats?.content.published || 0, metric: "View all published", }, + { + title: "Taxonomies", + description: "Manage content taxonomies", + icon: BoltIcon, + color: "from-[var(--color-info)] to-[var(--color-info-dark)]", + path: "/writer/taxonomies", + count: stats?.taxonomies || 0, + metric: `${stats?.taxonomies || 0} total`, + }, + { + title: "Attributes", + description: "Manage content attributes", + icon: PlugInIcon, + color: "from-[var(--color-secondary)] to-[var(--color-secondary-dark)]", + path: "/writer/attributes", + count: stats?.attributes || 0, + metric: `${stats?.attributes || 0} total`, + }, ]; const recentActivity = [