1307 lines
36 KiB
Markdown
1307 lines
36 KiB
Markdown
# Writer Module Comprehensive Refactoring Plan
|
|
|
|
**Date:** December 2024
|
|
**Purpose:** Complete refactoring of Writer module pages (Tasks, Content, Images, Published) based on deep analysis and user requirements
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines a comprehensive refactoring plan for the IGNY8 Writer module. After deep analysis of all Writer pages, configurations, and data flows, we've identified critical issues and improvements needed to create a cohesive, efficient content workflow from task creation through WordPress publishing.
|
|
|
|
**Key Objectives:**
|
|
1. Fix critical bugs (bulk select, delete functions)
|
|
2. Restructure Published page to show Content (not Tasks)
|
|
3. Implement proper WordPress publishing workflow
|
|
4. Add "review" status to content lifecycle
|
|
5. Improve UX with better status indicators and content viewing
|
|
|
|
---
|
|
|
|
## Current State Analysis
|
|
|
|
### Page Structure Overview
|
|
|
|
```
|
|
Writer Module Flow:
|
|
Tasks (queued/completed) → Content (draft/published) → Images (pending/generated/failed) → Published (?)
|
|
```
|
|
|
|
### 1. Tasks Page (`Tasks.tsx` - 787 lines)
|
|
|
|
**Purpose:** Task queue management for content generation
|
|
|
|
**Current Implementation:**
|
|
- ✅ Loads Tasks table via `fetchTasks()` API
|
|
- ✅ Status workflow: `queued` → `completed`
|
|
- ✅ Row actions: Edit, Generate Content
|
|
- ✅ Bulk actions: Update Status, Export
|
|
- ✅ Progress modal for AI functions
|
|
- ✅ Selection and pagination working
|
|
- ✅ AI logs when Resource Debug enabled
|
|
|
|
**Issues:**
|
|
- None critical
|
|
|
|
**Data Model:**
|
|
```typescript
|
|
interface Task {
|
|
id: number;
|
|
title: string;
|
|
cluster: string;
|
|
taxonomy: string;
|
|
content_type: string;
|
|
content_structure: string;
|
|
status: 'queued' | 'completed';
|
|
word_count: number;
|
|
created_at: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Content Page (`Content.tsx` - 315 lines)
|
|
|
|
**Purpose:** Content management and editing
|
|
|
|
**Current Implementation:**
|
|
- ✅ Loads Content table via `fetchContent()` API
|
|
- ✅ Status workflow: `draft` → `published`
|
|
- ✅ Row actions: Edit, View on WordPress (if published), Generate Image Prompts
|
|
- ✅ Bulk actions: Update Status, Export, Publish Selected
|
|
- ✅ Progress modal for image prompt generation
|
|
- ✅ Selection and pagination working
|
|
|
|
**Issues:**
|
|
1. ❌ **No content viewer link** - Title column not clickable to view content
|
|
2. ❌ **Missing status indicators** - No visual feedback for:
|
|
- Prompt generation status (pending/complete)
|
|
- Image generation status (pending/generating/complete)
|
|
3. ⚠️ **Publish action ambiguity** - Bulk "Publish Selected" action exists but unclear what it does
|
|
|
|
**Data Model:**
|
|
```typescript
|
|
interface ContentType {
|
|
id: number;
|
|
title: string;
|
|
sector: string;
|
|
content_type: string;
|
|
content_structure: string;
|
|
cluster: string;
|
|
taxonomy: string;
|
|
status: 'draft' | 'published';
|
|
word_count: number;
|
|
source: string;
|
|
created_at: string;
|
|
external_id?: string | null; // WordPress post ID
|
|
external_url?: string | null; // WordPress post URL
|
|
sync_status?: string;
|
|
}
|
|
```
|
|
|
|
**Configuration (`content.config.tsx` - 376 lines):**
|
|
- Columns: title, sector, content_type, content_structure, cluster, taxonomy, status, word_count, source, created_at
|
|
- Missing columns: prompts_status, images_status
|
|
|
|
---
|
|
|
|
### 3. Images Page (`Images.tsx` - 738 lines)
|
|
|
|
**Purpose:** Image management grouped by content
|
|
|
|
**Current Implementation:**
|
|
- ✅ Loads ContentImagesGroup via `fetchContentImages()` API
|
|
- ✅ Client-side filtering, sorting, pagination
|
|
- ✅ Row actions: Publish to WordPress, Update Status
|
|
- ✅ Bulk actions: Bulk Publish Ready to WordPress
|
|
- ✅ Image generation functionality with queue modal
|
|
- ✅ AI logs when Resource Debug enabled
|
|
|
|
**Issues:**
|
|
1. ❌ **Bulk select checkbox not working** - Root cause: `ContentImagesGroup` interface has `content_id` but TablePageTemplate expects `id` field for selection
|
|
2. ❌ **Delete functions not working** - No delete handlers implemented
|
|
3. ⚠️ **Wrong location for WordPress publishing** - Should be on Published page, not here
|
|
|
|
**Data Model:**
|
|
```typescript
|
|
interface ContentImagesGroup {
|
|
content_id: number; // ⚠️ No 'id' field - breaks selection!
|
|
content_title: string;
|
|
featured_image: ContentImage | null;
|
|
in_article_images: ContentImage[];
|
|
overall_status: 'pending' | 'partial' | 'complete' | 'failed';
|
|
// Missing fields for WordPress publishing context:
|
|
external_id?: string;
|
|
external_url?: string;
|
|
sync_status?: string;
|
|
status?: 'draft' | 'published' | 'review';
|
|
}
|
|
|
|
interface ContentImage {
|
|
id: number;
|
|
image_url: string | null;
|
|
image_path: string | null;
|
|
prompt: string | null;
|
|
status: 'pending' | 'generated' | 'failed';
|
|
position: number;
|
|
}
|
|
```
|
|
|
|
**Configuration (`images.config.tsx`):**
|
|
- Columns: content_title, featured_image, in_article_1-5, overall_status, actions
|
|
- Row actions: publish_wordpress, update_status
|
|
- Bulk actions: bulk_publish_wordpress
|
|
|
|
---
|
|
|
|
### 4. Published Page (`Published.tsx` - 13 lines)
|
|
|
|
**Purpose:** Show published content with WordPress publishing capabilities
|
|
|
|
**Current Implementation:**
|
|
```tsx
|
|
import Tasks from './Tasks';
|
|
|
|
export default function Published() {
|
|
return <Tasks />;
|
|
}
|
|
```
|
|
|
|
**Issues:**
|
|
1. ❌ **Wrong table loaded** - Renders `<Tasks />` instead of Content table
|
|
2. ❌ **No WordPress publishing UI** - Should have edit and publish functionality
|
|
3. ❌ **No published item indicators** - Missing visual styling for published items
|
|
|
|
**What It Should Be:**
|
|
- Load Content table filtered by status
|
|
- Show WordPress publishing actions
|
|
- Allow editing before publishing
|
|
- Display WordPress publish status
|
|
- Show external URL links
|
|
|
|
---
|
|
|
|
### 5. Table Actions Configuration (`table-actions.config.tsx` - 359 lines)
|
|
|
|
**Current Implementation:**
|
|
- ✅ `/writer/tasks` - edit, generate_content
|
|
- ✅ `/writer/content` - edit, view_on_wordpress, generate_image_prompts, publish
|
|
- ✅ `/writer/published` - edit (minimal)
|
|
- ✅ `/writer/images` - publish_wordpress, update_status
|
|
|
|
**Issues:**
|
|
- ⚠️ Published page actions too minimal
|
|
- ⚠️ Images page has publishing (should be removed)
|
|
|
|
---
|
|
|
|
## Root Cause Analysis
|
|
|
|
### Issue #1: Images Page Bulk Select Not Working
|
|
|
|
**Root Cause:** Data model mismatch
|
|
- `ContentImagesGroup` uses `content_id` as primary identifier
|
|
- `TablePageTemplate` expects `id` field for selection (`selectedIds` array)
|
|
- No `id` field exists in `ContentImagesGroup`
|
|
|
|
**Solution Options:**
|
|
1. **Option A (Recommended):** Add `id` field to `ContentImagesGroup` that mirrors `content_id`
|
|
2. **Option B:** Modify TablePageTemplate to accept custom ID field name
|
|
3. **Option C:** Transform data in Images.tsx to add `id: content_id`
|
|
|
|
**Recommended Fix:** Option C (least invasive)
|
|
```typescript
|
|
const transformedImages = images.map(group => ({
|
|
...group,
|
|
id: group.content_id // Add id field for TablePageTemplate
|
|
}));
|
|
```
|
|
|
|
---
|
|
|
|
### Issue #2: Images Page Delete Not Working
|
|
|
|
**Root Cause:** No delete handlers implemented
|
|
- TablePageTemplate supports `onDelete` and `onBulkDelete` props
|
|
- Images.tsx doesn't pass these handlers
|
|
- No API endpoints being called
|
|
|
|
**Solution:** Implement delete handlers using Content API
|
|
```typescript
|
|
const handleDelete = async (id: number) => {
|
|
await deleteContent(id); // Delete content (cascade deletes images)
|
|
loadImages();
|
|
};
|
|
|
|
const handleBulkDelete = async (ids: number[]) => {
|
|
await bulkDeleteContent(ids);
|
|
loadImages();
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### Issue #3: Published Page Structure
|
|
|
|
**Root Cause:** Placeholder implementation
|
|
- Published.tsx is just a wrapper around Tasks component
|
|
- No actual published content filtering
|
|
- No WordPress publishing UI
|
|
|
|
**Solution:** Complete reimplementation
|
|
- Duplicate Content.tsx structure
|
|
- Add WordPress-specific actions
|
|
- Filter for published/review status
|
|
- Add visual indicators
|
|
|
|
---
|
|
|
|
### Issue #4: Missing "Review" Status
|
|
|
|
**Root Cause:** Status workflow incomplete
|
|
- Current: Task (queued → completed) → Content (draft → published)
|
|
- Missing: Content (draft → **review** → published)
|
|
- No auto-status change when images generated
|
|
|
|
**Solution:**
|
|
1. Add "review" status to Content model (backend)
|
|
2. Update API to support new status
|
|
3. Add auto-transition: when images generated, change status from draft to review
|
|
4. Update all frontend status filters and badges
|
|
|
|
---
|
|
|
|
## Detailed Implementation Plan
|
|
|
|
### Priority 1: Critical Bug Fixes
|
|
|
|
#### Task 1.1: Fix Images Page Bulk Select
|
|
**Files to modify:**
|
|
- `frontend/src/pages/Writer/Images.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// In loadImages callback, transform data
|
|
const transformedResults = paginatedResults.map(group => ({
|
|
...group,
|
|
id: group.content_id // Add id field for selection
|
|
}));
|
|
setImages(transformedResults);
|
|
```
|
|
|
|
**Testing:**
|
|
- [ ] Bulk select checkbox appears and works
|
|
- [ ] Can select/deselect all
|
|
- [ ] Can select individual rows
|
|
- [ ] Selected IDs are content_id values
|
|
|
|
---
|
|
|
|
#### Task 1.2: Fix Images Page Delete Functions
|
|
**Files to modify:**
|
|
- `frontend/src/pages/Writer/Images.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// Add delete handlers
|
|
const handleDelete = useCallback(async (id: number) => {
|
|
try {
|
|
await deleteContent(id);
|
|
toast.success('Content and images deleted successfully');
|
|
loadImages();
|
|
} catch (error: any) {
|
|
toast.error(`Failed to delete: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}, [loadImages, toast]);
|
|
|
|
const handleBulkDelete = useCallback(async (ids: number[]) => {
|
|
try {
|
|
const result = await bulkDeleteContent(ids);
|
|
toast.success(`Deleted ${result.deleted_count} content items and their images`);
|
|
loadImages();
|
|
return result;
|
|
} catch (error: any) {
|
|
toast.error(`Failed to bulk delete: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}, [loadImages, toast]);
|
|
|
|
// In TablePageTemplate
|
|
<TablePageTemplate
|
|
// ... existing props
|
|
onDelete={handleDelete}
|
|
onBulkDelete={handleBulkDelete}
|
|
/>
|
|
```
|
|
|
|
**Testing:**
|
|
- [ ] Single delete works (deletes content + images)
|
|
- [ ] Bulk delete works
|
|
- [ ] Confirmation modals appear
|
|
- [ ] Data refreshes after delete
|
|
|
|
---
|
|
|
|
### Priority 2: Published Page Restructuring
|
|
|
|
#### Task 2.1: Reimplement Published Page
|
|
**Files to modify:**
|
|
- `frontend/src/pages/Writer/Published.tsx` (complete rewrite)
|
|
|
|
**Implementation:**
|
|
```typescript
|
|
/**
|
|
* Published Page - Built with TablePageTemplate
|
|
* Shows published/review content with WordPress publishing capabilities
|
|
*/
|
|
|
|
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
import TablePageTemplate from '../../templates/TablePageTemplate';
|
|
import {
|
|
fetchContent,
|
|
ContentType,
|
|
ContentListResponse,
|
|
ContentFilters,
|
|
publishToWordPress, // Use unified publisher API
|
|
} from '../../services/api';
|
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
|
import { FileIcon, CheckCircleIcon, TaskIcon, ImageIcon } from '../../icons';
|
|
import { createPublishedPageConfig } from '../../config/pages/published.config'; // New config file
|
|
import PageHeader from '../../components/common/PageHeader';
|
|
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
|
|
import { useNavigate } from 'react-router';
|
|
|
|
export default function Published() {
|
|
const toast = useToast();
|
|
const navigate = useNavigate();
|
|
|
|
// Data state
|
|
const [content, setContent] = useState<ContentType[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// Filter state - default to published/review status
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [statusFilter, setStatusFilter] = useState('published'); // Default filter
|
|
const [publishStatusFilter, setPublishStatusFilter] = useState(''); // WordPress publish status
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
|
|
// Pagination state
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [totalCount, setTotalCount] = useState(0);
|
|
const pageSize = 20;
|
|
|
|
// Sorting state
|
|
const [sortBy, setSortBy] = useState<string>('created_at');
|
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
|
const [showContent, setShowContent] = useState(false);
|
|
|
|
// Load content - filtered for published/review
|
|
const loadContent = useCallback(async () => {
|
|
setLoading(true);
|
|
setShowContent(false);
|
|
try {
|
|
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-created_at';
|
|
|
|
const filters: ContentFilters = {
|
|
...(searchTerm && { search: searchTerm }),
|
|
// Filter for published or review status only
|
|
...(statusFilter && { status: statusFilter }),
|
|
page: currentPage,
|
|
page_size: pageSize,
|
|
ordering,
|
|
};
|
|
|
|
const data: ContentListResponse = await fetchContent(filters);
|
|
setContent(data.results || []);
|
|
setTotalCount(data.count || 0);
|
|
setTotalPages(Math.ceil((data.count || 0) / pageSize));
|
|
|
|
setTimeout(() => {
|
|
setShowContent(true);
|
|
setLoading(false);
|
|
}, 100);
|
|
} catch (error: any) {
|
|
console.error('Error loading content:', error);
|
|
toast.error(`Failed to load content: ${error.message}`);
|
|
setShowContent(true);
|
|
setLoading(false);
|
|
}
|
|
}, [currentPage, statusFilter, sortBy, sortDirection, searchTerm, toast, pageSize]);
|
|
|
|
useEffect(() => {
|
|
loadContent();
|
|
}, [loadContent]);
|
|
|
|
// Handle sorting
|
|
const handleSort = (field: string, direction: 'asc' | 'desc') => {
|
|
setSortBy(field || 'created_at');
|
|
setSortDirection(direction);
|
|
setCurrentPage(1);
|
|
};
|
|
|
|
// Row action handler
|
|
const handleRowAction = useCallback(async (action: string, row: ContentType) => {
|
|
if (action === 'publish_wordpress') {
|
|
try {
|
|
const result = await publishToWordPress([row.id]);
|
|
if (result.success) {
|
|
toast.success(`Published "${row.title}" to WordPress`);
|
|
loadContent();
|
|
} else {
|
|
toast.error(result.error || 'Failed to publish');
|
|
}
|
|
} catch (error: any) {
|
|
toast.error(`Failed to publish: ${error.message}`);
|
|
}
|
|
} else if (action === 'view_on_wordpress') {
|
|
if (row.external_url) {
|
|
window.open(row.external_url, '_blank');
|
|
}
|
|
} else if (action === 'edit') {
|
|
// Navigate to content editor
|
|
navigate(`/writer/content?id=${row.id}`);
|
|
}
|
|
}, [toast, loadContent, navigate]);
|
|
|
|
// Bulk WordPress publish
|
|
const handleBulkPublishWordPress = useCallback(async (ids: string[]) => {
|
|
try {
|
|
const numIds = ids.map(id => parseInt(id));
|
|
const result = await publishToWordPress(numIds);
|
|
if (result.success) {
|
|
toast.success(`Published ${result.published_count || ids.length} items to WordPress`);
|
|
loadContent();
|
|
} else {
|
|
toast.error(result.error || 'Failed to bulk publish');
|
|
}
|
|
} catch (error: any) {
|
|
toast.error(`Failed to bulk publish: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}, [toast, loadContent]);
|
|
|
|
// Create page config
|
|
const pageConfig = useMemo(() => {
|
|
return createPublishedPageConfig({
|
|
searchTerm,
|
|
setSearchTerm,
|
|
statusFilter,
|
|
setStatusFilter,
|
|
publishStatusFilter,
|
|
setPublishStatusFilter,
|
|
setCurrentPage,
|
|
});
|
|
}, [searchTerm, statusFilter, publishStatusFilter]);
|
|
|
|
// Calculate header metrics
|
|
const headerMetrics = useMemo(() => {
|
|
if (!pageConfig?.headerMetrics) return [];
|
|
return pageConfig.headerMetrics.map((metric) => ({
|
|
label: metric.label,
|
|
value: metric.calculate({ content, totalCount }),
|
|
accentColor: metric.accentColor,
|
|
}));
|
|
}, [pageConfig?.headerMetrics, content, totalCount]);
|
|
|
|
// Writer navigation tabs
|
|
const writerTabs = [
|
|
{ label: 'Tasks', path: '/writer/tasks', icon: <TaskIcon /> },
|
|
{ label: 'Content', path: '/writer/content', icon: <FileIcon /> },
|
|
{ label: 'Images', path: '/writer/images', icon: <ImageIcon /> },
|
|
{ label: 'Published', path: '/writer/published', icon: <CheckCircleIcon /> },
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<PageHeader
|
|
title="Published Content"
|
|
badge={{ icon: <CheckCircleIcon />, color: 'green' }}
|
|
navigation={<ModuleNavigationTabs tabs={writerTabs} />}
|
|
/>
|
|
<TablePageTemplate
|
|
columns={pageConfig.columns}
|
|
data={content}
|
|
loading={loading}
|
|
showContent={showContent}
|
|
filters={pageConfig.filters}
|
|
filterValues={{
|
|
search: searchTerm,
|
|
status: statusFilter,
|
|
publishStatus: publishStatusFilter,
|
|
}}
|
|
onFilterChange={(key: string, value: any) => {
|
|
if (key === 'search') {
|
|
setSearchTerm(value);
|
|
} else if (key === 'status') {
|
|
setStatusFilter(value);
|
|
setCurrentPage(1);
|
|
} else if (key === 'publishStatus') {
|
|
setPublishStatusFilter(value);
|
|
setCurrentPage(1);
|
|
}
|
|
}}
|
|
pagination={{
|
|
currentPage,
|
|
totalPages,
|
|
totalCount,
|
|
onPageChange: setCurrentPage,
|
|
}}
|
|
selection={{
|
|
selectedIds,
|
|
onSelectionChange: setSelectedIds,
|
|
}}
|
|
sorting={{
|
|
sortBy,
|
|
sortDirection,
|
|
onSort: handleSort,
|
|
}}
|
|
headerMetrics={headerMetrics}
|
|
onRowAction={handleRowAction}
|
|
onBulkAction={async (action: string, ids: string[]) => {
|
|
if (action === 'bulk_publish_wordpress') {
|
|
await handleBulkPublishWordPress(ids);
|
|
}
|
|
}}
|
|
getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 2.2: Create Published Page Configuration
|
|
**Files to create:**
|
|
- `frontend/src/config/pages/published.config.tsx`
|
|
|
|
**Implementation:**
|
|
```typescript
|
|
import { ColumnConfig } from '../../templates/TablePageTemplate';
|
|
import { ContentType } from '../../services/api';
|
|
import { Badge } from '../../components/ui/badge';
|
|
import { ExternalLinkIcon, CheckCircleIcon } from '../../icons';
|
|
|
|
export function createPublishedPageConfig(params: {
|
|
searchTerm: string;
|
|
setSearchTerm: (value: string) => void;
|
|
statusFilter: string;
|
|
setStatusFilter: (value: string) => void;
|
|
publishStatusFilter: string;
|
|
setPublishStatusFilter: (value: string) => void;
|
|
setCurrentPage: (page: number) => void;
|
|
}) {
|
|
const columns: ColumnConfig[] = [
|
|
{
|
|
key: 'title',
|
|
label: 'Title',
|
|
sortable: true,
|
|
render: (value: string, row: ContentType) => (
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium">{value}</span>
|
|
{row.external_url && (
|
|
<a
|
|
href={row.external_url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-blue-500 hover:text-blue-600"
|
|
>
|
|
<ExternalLinkIcon className="w-4 h-4" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: 'status',
|
|
label: 'Content Status',
|
|
sortable: true,
|
|
badge: true,
|
|
render: (value: string) => (
|
|
<Badge
|
|
variant={value === 'published' ? 'success' : value === 'review' ? 'warning' : 'default'}
|
|
>
|
|
{value}
|
|
</Badge>
|
|
),
|
|
},
|
|
{
|
|
key: 'sync_status',
|
|
label: 'WordPress Status',
|
|
sortable: false,
|
|
render: (value: string, row: ContentType) => {
|
|
if (row.external_id) {
|
|
return (
|
|
<Badge variant="success">
|
|
<CheckCircleIcon className="w-3 h-3 mr-1" />
|
|
Published
|
|
</Badge>
|
|
);
|
|
}
|
|
return (
|
|
<Badge variant="default">Not Published</Badge>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
key: 'content_type',
|
|
label: 'Type',
|
|
sortable: true,
|
|
},
|
|
{
|
|
key: 'word_count',
|
|
label: 'Words',
|
|
sortable: true,
|
|
numeric: true,
|
|
},
|
|
{
|
|
key: 'created_at',
|
|
label: 'Created',
|
|
sortable: true,
|
|
date: true,
|
|
},
|
|
];
|
|
|
|
const filters = [
|
|
{
|
|
key: 'search',
|
|
label: 'Search',
|
|
type: 'text' as const,
|
|
placeholder: 'Search published content...',
|
|
},
|
|
{
|
|
key: 'status',
|
|
label: 'Content Status',
|
|
type: 'select' as const,
|
|
options: [
|
|
{ value: '', label: 'All' },
|
|
{ value: 'review', label: 'In Review' },
|
|
{ value: 'published', label: 'Published' },
|
|
],
|
|
},
|
|
{
|
|
key: 'publishStatus',
|
|
label: 'WordPress Status',
|
|
type: 'select' as const,
|
|
options: [
|
|
{ value: '', label: 'All' },
|
|
{ value: 'published', label: 'Published to WP' },
|
|
{ value: 'not_published', label: 'Not Published' },
|
|
],
|
|
},
|
|
];
|
|
|
|
const headerMetrics = [
|
|
{
|
|
label: 'Total Published',
|
|
calculate: (data: { totalCount: number }) => data.totalCount,
|
|
accentColor: 'green' as const,
|
|
},
|
|
{
|
|
label: 'On WordPress',
|
|
calculate: (data: { content: ContentType[] }) =>
|
|
data.content.filter(c => c.external_id).length,
|
|
accentColor: 'blue' as const,
|
|
},
|
|
{
|
|
label: 'In Review',
|
|
calculate: (data: { content: ContentType[] }) =>
|
|
data.content.filter(c => c.status === 'review').length,
|
|
accentColor: 'amber' as const,
|
|
},
|
|
];
|
|
|
|
return {
|
|
columns,
|
|
filters,
|
|
headerMetrics,
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 2.3: Update Table Actions for Published Page
|
|
**Files to modify:**
|
|
- `frontend/src/config/pages/table-actions.config.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
'/writer/published': {
|
|
rowActions: [
|
|
{
|
|
key: 'edit',
|
|
label: 'Edit Content',
|
|
icon: EditIcon,
|
|
variant: 'primary',
|
|
},
|
|
{
|
|
key: 'publish_wordpress',
|
|
label: 'Publish to WordPress',
|
|
icon: <ArrowRightIcon className="w-5 h-5" />,
|
|
variant: 'success',
|
|
shouldShow: (row: any) => !row.external_id, // Only show if not published
|
|
},
|
|
{
|
|
key: 'view_on_wordpress',
|
|
label: 'View on WordPress',
|
|
icon: <ExternalLinkIcon className="w-5 h-5" />,
|
|
variant: 'secondary',
|
|
shouldShow: (row: any) => !!row.external_id, // Only show if published
|
|
},
|
|
],
|
|
bulkActions: [
|
|
{
|
|
key: 'bulk_publish_wordpress',
|
|
label: 'Publish to WordPress',
|
|
icon: <ArrowRightIcon className="w-4 h-4" />,
|
|
variant: 'success',
|
|
},
|
|
{
|
|
key: 'update_status',
|
|
label: 'Update Status',
|
|
icon: <CheckCircleIcon className="w-4 h-4 text-success-500" />,
|
|
variant: 'secondary',
|
|
},
|
|
{
|
|
key: 'export',
|
|
label: 'Export Selected',
|
|
icon: <DownloadIcon className="w-4 h-4 text-blue-light-500" />,
|
|
variant: 'secondary',
|
|
},
|
|
],
|
|
},
|
|
```
|
|
|
|
---
|
|
|
|
### Priority 3: Content Page Enhancements
|
|
|
|
#### Task 3.1: Add Content Viewer Link
|
|
**Files to modify:**
|
|
- `frontend/src/config/pages/content.config.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// In columns array, update title column
|
|
{
|
|
key: 'title',
|
|
label: 'Title',
|
|
sortable: true,
|
|
render: (value: string, row: ContentType) => (
|
|
<button
|
|
onClick={() => {
|
|
// Open content viewer modal
|
|
if (params.onViewContent) {
|
|
params.onViewContent(row);
|
|
}
|
|
}}
|
|
className="text-blue-500 hover:text-blue-600 hover:underline text-left font-medium"
|
|
>
|
|
{value}
|
|
</button>
|
|
),
|
|
},
|
|
```
|
|
|
|
**Files to modify:**
|
|
- `frontend/src/pages/Writer/Content.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// Add state for content viewer modal
|
|
const [isViewerModalOpen, setIsViewerModalOpen] = useState(false);
|
|
const [viewerContentId, setViewerContentId] = useState<number | null>(null);
|
|
|
|
// Add handler
|
|
const handleViewContent = useCallback((row: ContentType) => {
|
|
setViewerContentId(row.id);
|
|
setIsViewerModalOpen(true);
|
|
}, []);
|
|
|
|
// Update pageConfig call
|
|
const pageConfig = useMemo(() => {
|
|
return createContentPageConfig({
|
|
// ... existing params
|
|
onViewContent: handleViewContent,
|
|
});
|
|
}, [/* deps */, handleViewContent]);
|
|
|
|
// Add ContentViewerModal component (import from shared components)
|
|
<ContentViewerModal
|
|
isOpen={isViewerModalOpen}
|
|
onClose={() => {
|
|
setIsViewerModalOpen(false);
|
|
setViewerContentId(null);
|
|
}}
|
|
contentId={viewerContentId}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 3.2: Add Status Indicator Columns
|
|
**Files to modify:**
|
|
- `frontend/src/config/pages/content.config.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// Add after 'status' column
|
|
{
|
|
key: 'prompts_status',
|
|
label: 'Prompts',
|
|
sortable: false,
|
|
render: (value: any, row: ContentType) => {
|
|
// Check if prompts exist (need to add this to API response)
|
|
const hasPrompts = row.image_prompts_count > 0;
|
|
return (
|
|
<Badge variant={hasPrompts ? 'success' : 'default'}>
|
|
{hasPrompts ? (
|
|
<>
|
|
<CheckCircleIcon className="w-3 h-3 mr-1" />
|
|
{row.image_prompts_count} prompts
|
|
</>
|
|
) : (
|
|
'No prompts'
|
|
)}
|
|
</Badge>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
key: 'images_status',
|
|
label: 'Images',
|
|
sortable: false,
|
|
render: (value: any, row: ContentType) => {
|
|
const generatedCount = row.images_generated_count || 0;
|
|
const totalCount = row.images_total_count || 0;
|
|
|
|
if (totalCount === 0) {
|
|
return <Badge variant="default">No images</Badge>;
|
|
}
|
|
|
|
const isComplete = generatedCount === totalCount;
|
|
const isPartial = generatedCount > 0 && generatedCount < totalCount;
|
|
|
|
return (
|
|
<Badge variant={isComplete ? 'success' : isPartial ? 'warning' : 'default'}>
|
|
{isComplete && <CheckCircleIcon className="w-3 h-3 mr-1" />}
|
|
{generatedCount}/{totalCount}
|
|
</Badge>
|
|
);
|
|
},
|
|
},
|
|
```
|
|
|
|
**Backend Changes Needed:**
|
|
- Add fields to Content API response:
|
|
- `image_prompts_count`
|
|
- `images_generated_count`
|
|
- `images_total_count`
|
|
|
|
---
|
|
|
|
### Priority 4: Add "Review" Status Workflow
|
|
|
|
#### Task 4.1: Backend Model Changes
|
|
**Files to modify:**
|
|
- `backend/igny8_core/modules/writer/models.py`
|
|
|
|
**Changes:**
|
|
```python
|
|
class Content(models.Model):
|
|
STATUS_CHOICES = [
|
|
('draft', 'Draft'),
|
|
('review', 'In Review'), # NEW STATUS
|
|
('published', 'Published'),
|
|
]
|
|
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='draft',
|
|
db_index=True,
|
|
)
|
|
```
|
|
|
|
**Migration:**
|
|
```bash
|
|
cd backend
|
|
python manage.py makemigrations writer
|
|
python manage.py migrate writer
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 4.2: Auto-Status Change on Image Generation
|
|
**Files to modify:**
|
|
- `backend/igny8_core/modules/writer/services/image_generation.py` (or wherever images are generated)
|
|
|
|
**Changes:**
|
|
```python
|
|
def on_images_generated(content_id):
|
|
"""Called when all images for content are successfully generated"""
|
|
content = Content.objects.get(id=content_id)
|
|
|
|
# Auto-transition draft → review when images complete
|
|
if content.status == 'draft':
|
|
content.status = 'review'
|
|
content.save(update_fields=['status'])
|
|
|
|
# Optional: Create notification/log entry
|
|
logger.info(f"Content {content_id} auto-transitioned to 'review' after image generation")
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 4.3: Frontend Status Updates
|
|
**Files to modify:**
|
|
- All status badge renderers
|
|
- All status filter options
|
|
- All status update modals
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// Update status options everywhere
|
|
const STATUS_OPTIONS = [
|
|
{ value: 'draft', label: 'Draft' },
|
|
{ value: 'review', label: 'In Review' }, // NEW
|
|
{ value: 'published', label: 'Published' },
|
|
];
|
|
|
|
// Update badge variants
|
|
const getStatusVariant = (status: string) => {
|
|
switch (status) {
|
|
case 'draft': return 'default';
|
|
case 'review': return 'warning'; // NEW
|
|
case 'published': return 'success';
|
|
default: return 'default';
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### Priority 5: Remove WordPress Publishing from Images Page
|
|
|
|
#### Task 5.1: Update Images Page Configuration
|
|
**Files to modify:**
|
|
- `frontend/src/config/pages/table-actions.config.tsx`
|
|
|
|
**Changes:**
|
|
```typescript
|
|
'/writer/images': {
|
|
rowActions: [
|
|
{
|
|
key: 'update_status',
|
|
label: 'Update Status',
|
|
icon: <CheckCircleIcon className="w-5 h-5" />,
|
|
variant: 'primary',
|
|
},
|
|
// REMOVED: publish_wordpress action
|
|
],
|
|
bulkActions: [
|
|
// REMOVED: bulk_publish_wordpress action
|
|
],
|
|
},
|
|
```
|
|
|
|
---
|
|
|
|
#### Task 5.2: Clean Up Images Page Code
|
|
**Files to modify:**
|
|
- `frontend/src/pages/Writer/Images.tsx`
|
|
|
|
**Changes:**
|
|
- Remove WordPress publishing handlers
|
|
- Remove related imports
|
|
- Simplify row action handler
|
|
- Remove WordPress-related state
|
|
|
|
---
|
|
|
|
### Priority 6: Visual Indicators for Published Items
|
|
|
|
#### Task 6.1: Add Published Badge to All Tables
|
|
**Files to modify:**
|
|
- All page configs (tasks, content, images, published)
|
|
|
|
**Changes:**
|
|
```typescript
|
|
// Add to title or status column render
|
|
{
|
|
key: 'title',
|
|
label: 'Title',
|
|
render: (value: string, row: any) => (
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium">{value}</span>
|
|
{row.external_id && (
|
|
<Badge variant="success" size="sm">
|
|
<CheckCircleIcon className="w-3 h-3 mr-1" />
|
|
WP
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Checklist
|
|
|
|
### Phase 1: Critical Bugs (Week 1)
|
|
- [ ] Fix Images page bulk select (add `id` field)
|
|
- [ ] Fix Images page delete functions
|
|
- [ ] Test both fixes thoroughly
|
|
|
|
### Phase 2: Published Page (Week 1-2)
|
|
- [ ] Create `published.config.tsx`
|
|
- [ ] Rewrite `Published.tsx` component
|
|
- [ ] Update table actions config
|
|
- [ ] Add WordPress publishing handlers
|
|
- [ ] Test all functionality
|
|
|
|
### Phase 3: Content Enhancements (Week 2)
|
|
- [ ] Add content viewer link to title column
|
|
- [ ] Create/import ContentViewerModal
|
|
- [ ] Add backend fields for prompt/image counts
|
|
- [ ] Add status indicator columns
|
|
- [ ] Update content.config.tsx
|
|
- [ ] Test viewer and indicators
|
|
|
|
### Phase 4: Review Status (Week 2-3)
|
|
- [ ] Create Django migration for new status
|
|
- [ ] Update backend model
|
|
- [ ] Add auto-transition logic
|
|
- [ ] Update all frontend status options
|
|
- [ ] Update all status badge renders
|
|
- [ ] Update filters across all pages
|
|
- [ ] Test complete workflow
|
|
|
|
### Phase 5: WordPress Publishing Migration (Week 3)
|
|
- [ ] Remove WordPress actions from Images page
|
|
- [ ] Remove WordPress code from Images.tsx
|
|
- [ ] Verify Published page has all WordPress functionality
|
|
- [ ] Test end-to-end publishing workflow
|
|
|
|
### Phase 6: Visual Polish (Week 3)
|
|
- [ ] Add published badges to all table titles
|
|
- [ ] Add WordPress status indicators
|
|
- [ ] Add color coding for statuses
|
|
- [ ] Add icons for published items
|
|
- [ ] Polish UI across all pages
|
|
|
|
### Phase 7: Testing & Documentation (Week 4)
|
|
- [ ] Full regression testing
|
|
- [ ] User acceptance testing
|
|
- [ ] Update user documentation
|
|
- [ ] Create migration guide for users
|
|
- [ ] Deploy to staging
|
|
- [ ] Final production deployment
|
|
|
|
---
|
|
|
|
## API Endpoints Required
|
|
|
|
### New Endpoints
|
|
```
|
|
POST /v1/publisher/publish/
|
|
- Publish content to WordPress
|
|
- Body: { content_id, destinations: ['wordpress'] }
|
|
- Response: { success, data: { external_id, external_url }, error }
|
|
|
|
GET /v1/writer/content/{id}/
|
|
- Get single content with full details
|
|
- Response includes: prompts_count, images_count, etc.
|
|
|
|
PATCH /v1/writer/content/{id}/
|
|
- Update content status
|
|
- Body: { status: 'draft' | 'review' | 'published' }
|
|
```
|
|
|
|
### Enhanced Endpoints
|
|
```
|
|
GET /v1/writer/content/
|
|
- Add fields to response:
|
|
- image_prompts_count
|
|
- images_generated_count
|
|
- images_total_count
|
|
- sync_status
|
|
- external_id
|
|
- external_url
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow Diagrams
|
|
|
|
### Current Flow
|
|
```
|
|
Tasks (queued)
|
|
→ Generate Content
|
|
→ Tasks (completed) + Content (draft)
|
|
→ Generate Image Prompts
|
|
→ Content (draft) + ImagePrompts
|
|
→ Generate Images
|
|
→ Content (draft) + Images
|
|
→ [Manual publish from Images page]
|
|
→ WordPress
|
|
```
|
|
|
|
### New Flow (After Refactoring)
|
|
```
|
|
Tasks (queued)
|
|
→ Generate Content
|
|
→ Tasks (completed) + Content (draft)
|
|
→ Generate Image Prompts
|
|
→ Content (draft) + ImagePrompts
|
|
→ Generate Images
|
|
→ Content (review) + Images ← AUTO STATUS CHANGE
|
|
→ [Review in Published page]
|
|
→ [Edit if needed]
|
|
→ [Publish to WordPress from Published page]
|
|
→ Content (published) + WordPress Post
|
|
```
|
|
|
|
---
|
|
|
|
## Risk Assessment
|
|
|
|
### High Risk
|
|
1. **Database Migration for Review Status**
|
|
- Mitigation: Test on staging first, have rollback plan
|
|
- Impact: Could affect existing content if not handled properly
|
|
|
|
2. **Breaking Changes to Content API**
|
|
- Mitigation: Add new fields as optional, maintain backward compatibility
|
|
- Impact: Other parts of app might depend on current response shape
|
|
|
|
### Medium Risk
|
|
1. **Auto-Status Transition Logic**
|
|
- Mitigation: Make it configurable, add feature flag
|
|
- Impact: Could change status unexpectedly if logic is wrong
|
|
|
|
2. **WordPress Publishing Removal from Images**
|
|
- Mitigation: Ensure Published page fully functional before removing
|
|
- Impact: Users might look for publish button in wrong place
|
|
|
|
### Low Risk
|
|
1. **UI Changes (badges, indicators)**
|
|
- Mitigation: Can be easily reverted
|
|
- Impact: Purely cosmetic
|
|
|
|
2. **Content Viewer Modal**
|
|
- Mitigation: Independent feature, doesn't affect core functionality
|
|
- Impact: Just adds convenience
|
|
|
|
---
|
|
|
|
## Success Metrics
|
|
|
|
### Functional Metrics
|
|
- [ ] All bulk select checkboxes working across all pages
|
|
- [ ] Delete functions working (single and bulk)
|
|
- [ ] Published page shows Content table (not Tasks)
|
|
- [ ] WordPress publishing only available on Published page
|
|
- [ ] "Review" status visible and functional
|
|
- [ ] Auto-status change working when images generated
|
|
- [ ] Content viewer accessible from title links
|
|
- [ ] Status indicators showing prompt/image progress
|
|
|
|
### User Experience Metrics
|
|
- [ ] Reduced clicks to publish content (consolidated on one page)
|
|
- [ ] Clear visual feedback for publish status
|
|
- [ ] Intuitive workflow: draft → review → published
|
|
- [ ] Easy access to content viewing
|
|
- [ ] Clear status progression indicators
|
|
|
|
### Technical Metrics
|
|
- [ ] No console errors
|
|
- [ ] All API calls successful
|
|
- [ ] Proper error handling throughout
|
|
- [ ] Consistent response times
|
|
- [ ] Proper loading states
|
|
|
|
---
|
|
|
|
## Rollback Plan
|
|
|
|
If critical issues arise:
|
|
|
|
1. **Phase 1-2 Issues (Bugs/Published Page):**
|
|
- Revert Published.tsx to `<Tasks />` wrapper
|
|
- Disable new delete handlers
|
|
- Restore selection functionality
|
|
|
|
2. **Phase 4 Issues (Review Status):**
|
|
- Rollback database migration
|
|
- Restore previous status options
|
|
- Disable auto-transition logic
|
|
|
|
3. **Phase 5 Issues (WordPress Migration):**
|
|
- Re-enable WordPress publishing on Images page
|
|
- Disable on Published page temporarily
|
|
|
|
---
|
|
|
|
## Future Enhancements (Post-Refactoring)
|
|
|
|
1. **Bulk Edit Mode** - Edit multiple content items at once
|
|
2. **Scheduling** - Schedule WordPress publishing for future dates
|
|
3. **Publishing Templates** - Save WordPress settings as templates
|
|
4. **Draft Revisions** - Track content changes before publishing
|
|
5. **Publishing Analytics** - Track WordPress publish success rates
|
|
6. **Multi-destination Publishing** - Publish to multiple WordPress sites
|
|
7. **Content Preview** - Preview how content will look on WordPress
|
|
8. **SEO Checker** - Validate SEO before publishing
|
|
|
|
---
|
|
|
|
## Appendix: File Inventory
|
|
|
|
### Files to Modify
|
|
```
|
|
frontend/src/pages/Writer/
|
|
- Tasks.tsx (minor - visual indicators)
|
|
- Content.tsx (major - viewer link, status columns)
|
|
- Images.tsx (major - fix select, delete, remove WP)
|
|
- Published.tsx (complete rewrite)
|
|
|
|
frontend/src/config/pages/
|
|
- content.config.tsx (add columns, viewer link)
|
|
- images.config.tsx (minor updates)
|
|
- table-actions.config.tsx (update all writer sections)
|
|
- published.config.tsx (NEW FILE)
|
|
|
|
backend/igny8_core/modules/writer/
|
|
- models.py (add review status)
|
|
- services/image_generation.py (auto-status change)
|
|
- serializers.py (add new fields)
|
|
- views.py (update filters)
|
|
```
|
|
|
|
### Files to Create
|
|
```
|
|
frontend/src/config/pages/published.config.tsx
|
|
frontend/src/components/common/ContentViewerModal.tsx (if doesn't exist)
|
|
backend/igny8_core/modules/writer/migrations/XXXX_add_review_status.py
|
|
```
|
|
|
|
### Total Estimated Changes
|
|
- **Modified Files:** ~15
|
|
- **New Files:** ~3
|
|
- **Lines Changed:** ~2,000
|
|
- **Estimated Hours:** 40-60 hours
|
|
- **Estimated Calendar Time:** 3-4 weeks
|
|
|
|
---
|
|
|
|
**End of Document**
|
|
|
|
*Last Updated: December 2024*
|
|
*Document Version: 1.0*
|
|
*Author: AI Assistant (Deep Analysis Mode)*
|