feat: Complete Stage 2 frontend refactor
- Removed deprecated fields from Content and Task models, including entity_type, sync_status, and cluster_role. - Updated Content model to include new fields: content_type, content_structure, taxonomy_terms, source, external_id, and cluster_id. - Refactored Writer module components (Content, ContentView, Dashboard, Tasks) to align with new schema. - Enhanced Dashboard metrics and removed unused filters. - Implemented ClusterDetail page to display cluster information and associated content. - Updated API service interfaces to reflect changes in data structure. - Adjusted sorting and filtering logic across various components to accommodate new field names and types. - Improved user experience by providing loading states and error handling in data fetching.
This commit is contained in:
@@ -37,7 +37,6 @@ export default function Content() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [sourceFilter, setSourceFilter] = useState('');
|
||||
const [syncStatusFilter, setSyncStatusFilter] = useState('');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
|
||||
// Pagination state
|
||||
@@ -46,7 +45,7 @@ export default function Content() {
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
|
||||
// Sorting state
|
||||
const [sortBy, setSortBy] = useState<string>('generated_at');
|
||||
const [sortBy, setSortBy] = useState<string>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
@@ -59,13 +58,12 @@ export default function Content() {
|
||||
setLoading(true);
|
||||
setShowContent(false);
|
||||
try {
|
||||
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-generated_at';
|
||||
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-created_at';
|
||||
|
||||
const filters: ContentFilters = {
|
||||
...(searchTerm && { search: searchTerm }),
|
||||
...(statusFilter && { status: statusFilter }),
|
||||
...(sourceFilter && { source: sourceFilter }),
|
||||
...(syncStatusFilter && { sync_status: syncStatusFilter }),
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
ordering,
|
||||
@@ -224,7 +222,6 @@ export default function Content() {
|
||||
search: searchTerm,
|
||||
status: statusFilter,
|
||||
source: sourceFilter,
|
||||
sync_status: syncStatusFilter,
|
||||
}}
|
||||
onFilterChange={(key: string, value: any) => {
|
||||
if (key === 'search') {
|
||||
@@ -235,9 +232,6 @@ export default function Content() {
|
||||
} else if (key === 'source') {
|
||||
setSourceFilter(value);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'sync_status') {
|
||||
setSyncStatusFilter(value);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}}
|
||||
pagination={{
|
||||
@@ -257,7 +251,7 @@ export default function Content() {
|
||||
}}
|
||||
headerMetrics={headerMetrics}
|
||||
onRowAction={handleRowAction}
|
||||
getItemDisplayName={(row: ContentType) => row.meta_title || row.title || `Content #${row.id}`}
|
||||
getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`}
|
||||
/>
|
||||
|
||||
{/* Module Metrics Footer */}
|
||||
@@ -274,17 +268,10 @@ export default function Content() {
|
||||
{
|
||||
title: 'Draft',
|
||||
value: content.filter(c => c.status === 'draft').length.toLocaleString(),
|
||||
subtitle: `${content.filter(c => c.status === 'review').length} in review`,
|
||||
subtitle: `${content.filter(c => c.status === 'published').length} published`,
|
||||
icon: <TaskIcon className="w-5 h-5" />,
|
||||
accentColor: 'blue',
|
||||
},
|
||||
{
|
||||
title: 'Synced',
|
||||
value: content.filter(c => c.sync_status === 'synced').length.toLocaleString(),
|
||||
subtitle: `${content.filter(c => c.sync_status === 'pending').length} pending`,
|
||||
icon: <CheckCircleIcon className="w-5 h-5" />,
|
||||
accentColor: 'purple',
|
||||
},
|
||||
]}
|
||||
progress={{
|
||||
label: 'Content Publishing Progress',
|
||||
|
||||
@@ -57,8 +57,8 @@ export default function ContentView() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta
|
||||
title={content ? `${content.meta_title || content.title || `Content #${content.id}`} - IGNY8` : 'Content View - IGNY8'}
|
||||
description={content?.meta_description || 'View content details and metadata'}
|
||||
title={content ? `${content.title || `Content #${content.id}`} - IGNY8` : 'Content View - IGNY8'}
|
||||
description='View content details and metadata'
|
||||
/>
|
||||
<ContentViewTemplate
|
||||
content={content}
|
||||
|
||||
@@ -20,12 +20,13 @@ import {
|
||||
PaperPlaneIcon,
|
||||
PlugInIcon,
|
||||
} from "../../icons";
|
||||
import {
|
||||
import {
|
||||
fetchTasks,
|
||||
fetchContent,
|
||||
fetchContentImages,
|
||||
fetchTaxonomies,
|
||||
fetchAttributes
|
||||
// TODO: Stage 3/4 - Add when taxonomy and attribute endpoints are implemented
|
||||
// fetchTaxonomies,
|
||||
// fetchAttributes
|
||||
} from "../../services/api";
|
||||
import { useSiteStore } from "../../store/siteStore";
|
||||
import { useSectorStore } from "../../store/sectorStore";
|
||||
@@ -43,7 +44,6 @@ interface WriterStats {
|
||||
content: {
|
||||
total: number;
|
||||
drafts: number;
|
||||
review: number;
|
||||
published: number;
|
||||
totalWordCount: number;
|
||||
avgWordCount: number;
|
||||
@@ -85,12 +85,11 @@ export default function WriterDashboard() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const [tasksRes, contentRes, imagesRes, taxonomiesRes, attributesRes] = await Promise.all([
|
||||
// TODO: Stage 3/4 - Re-enable when taxonomy and attribute endpoints are implemented
|
||||
const [tasksRes, contentRes, imagesRes] = await Promise.all([
|
||||
fetchTasks({ page_size: 1000, sector_id: activeSector?.id }),
|
||||
fetchContent({ page_size: 1000, sector_id: activeSector?.id }),
|
||||
fetchContentImages({ sector_id: activeSector?.id }),
|
||||
fetchTaxonomies({ sector_id: activeSector?.id }),
|
||||
fetchAttributes({ sector_id: activeSector?.id }),
|
||||
]);
|
||||
|
||||
const tasks = tasksRes.results || [];
|
||||
@@ -101,9 +100,8 @@ export default function WriterDashboard() {
|
||||
let totalWordCount = 0;
|
||||
|
||||
tasks.forEach(t => {
|
||||
tasksByStatus[t.status || 'pending'] = (tasksByStatus[t.status || 'pending'] || 0) + 1;
|
||||
if (t.status === 'pending') pendingTasks++;
|
||||
else if (t.status === 'in_progress') inProgressTasks++;
|
||||
tasksByStatus[t.status || 'queued'] = (tasksByStatus[t.status || 'queued'] || 0) + 1;
|
||||
if (t.status === 'queued') pendingTasks++;
|
||||
else if (t.status === 'completed') completedTasks++;
|
||||
if (t.word_count) totalWordCount += t.word_count;
|
||||
});
|
||||
@@ -112,14 +110,12 @@ export default function WriterDashboard() {
|
||||
|
||||
const content = contentRes.results || [];
|
||||
let drafts = 0;
|
||||
let review = 0;
|
||||
let published = 0;
|
||||
let contentTotalWordCount = 0;
|
||||
const contentByType: Record<string, number> = {};
|
||||
|
||||
content.forEach(c => {
|
||||
if (c.status === 'draft') drafts++;
|
||||
else if (c.status === 'review') review++;
|
||||
else if (c.status === 'published') published++;
|
||||
if (c.word_count) contentTotalWordCount += c.word_count;
|
||||
});
|
||||
@@ -149,11 +145,9 @@ 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;
|
||||
// TODO: Stage 3/4 - Re-enable when taxonomy and attribute endpoints are implemented
|
||||
const taxonomyCount = 0; // taxonomiesRes.results?.length || 0
|
||||
const attributeCount = 0; // attributesRes.results?.length || 0
|
||||
|
||||
setStats({
|
||||
tasks: {
|
||||
@@ -168,7 +162,6 @@ export default function WriterDashboard() {
|
||||
content: {
|
||||
total: content.length,
|
||||
drafts,
|
||||
review,
|
||||
published,
|
||||
totalWordCount: contentTotalWordCount,
|
||||
avgWordCount: contentAvgWordCount,
|
||||
@@ -423,7 +416,7 @@ export default function WriterDashboard() {
|
||||
enabled: true
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['Drafts', 'In Review', 'Published'],
|
||||
categories: ['Drafts', 'Published'],
|
||||
labels: {
|
||||
style: {
|
||||
fontFamily: 'Outfit'
|
||||
@@ -444,7 +437,7 @@ export default function WriterDashboard() {
|
||||
|
||||
const series = [{
|
||||
name: 'Content',
|
||||
data: [stats.content.drafts, stats.content.review, stats.content.published]
|
||||
data: [stats.content.drafts, stats.content.published]
|
||||
}];
|
||||
|
||||
return { options, series };
|
||||
|
||||
@@ -49,7 +49,6 @@ export default function Tasks() {
|
||||
const [structureFilter, setStructureFilter] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState('');
|
||||
const [sourceFilter, setSourceFilter] = useState('');
|
||||
const [entityTypeFilter, setEntityTypeFilter] = useState(''); // Stage 3: Entity type filter
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
|
||||
// Pagination state
|
||||
@@ -71,8 +70,8 @@ export default function Tasks() {
|
||||
description: '',
|
||||
keywords: '',
|
||||
cluster_id: null,
|
||||
content_structure: 'blog_post',
|
||||
content_type: 'blog_post',
|
||||
content_structure: 'article',
|
||||
content_type: 'post',
|
||||
status: 'queued',
|
||||
word_count: 0,
|
||||
});
|
||||
@@ -145,7 +144,6 @@ export default function Tasks() {
|
||||
...(clusterFilter && { cluster_id: clusterFilter }),
|
||||
...(structureFilter && { content_structure: structureFilter }),
|
||||
...(typeFilter && { content_type: typeFilter }),
|
||||
...(entityTypeFilter && { entity_type: entityTypeFilter }), // Stage 3: Entity type filter
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
ordering,
|
||||
@@ -533,8 +531,8 @@ export default function Tasks() {
|
||||
description: '',
|
||||
keywords: '',
|
||||
cluster_id: null,
|
||||
content_structure: 'blog_post',
|
||||
content_type: 'blog_post',
|
||||
content_structure: 'article',
|
||||
content_type: 'post',
|
||||
status: 'queued',
|
||||
word_count: 0,
|
||||
});
|
||||
@@ -588,7 +586,6 @@ export default function Tasks() {
|
||||
content_structure: structureFilter,
|
||||
content_type: typeFilter,
|
||||
source: sourceFilter,
|
||||
entity_type: entityTypeFilter, // Stage 3: Entity type filter
|
||||
}}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
@@ -604,8 +601,6 @@ export default function Tasks() {
|
||||
setTypeFilter(stringValue);
|
||||
} else if (key === 'source') {
|
||||
setSourceFilter(stringValue);
|
||||
} else if (key === 'entity_type') { // Stage 3: Entity type filter
|
||||
setEntityTypeFilter(stringValue);
|
||||
}
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
@@ -616,8 +611,8 @@ export default function Tasks() {
|
||||
description: row.description || '',
|
||||
keywords: row.keywords || '',
|
||||
cluster_id: row.cluster_id || null,
|
||||
content_structure: row.content_structure || 'blog_post',
|
||||
content_type: row.content_type || 'blog_post',
|
||||
content_structure: row.content_structure || 'article',
|
||||
content_type: row.content_type || 'post',
|
||||
status: row.status || 'queued',
|
||||
word_count: row.word_count || 0,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user