13 KiB
Footer Widgets Audit - Complete Analysis
Date: January 10, 2026
Purpose: Document all footer widgets across Planner and Writer pages to identify data conflicts
SUMMARY
All Planner and Writer pages use StandardThreeWidgetFooter component which displays:
- Widget 1 (Left): Page Progress - page-specific metrics
- Widget 2 (Middle): Module Stats - uses
StandardizedModuleWidget - Widget 3 (Right): Workflow Completion - uses
WorkflowCompletionWidgetviauseWorkflowStatshook
PLANNER MODULE PAGES
Page 1: Keywords (/planner/keywords)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Keywords | totalCount |
Local state (line 49) | All keywords for site+sector on current page |
| Clustered | totalClustered |
Local state (line 50) | Keywords with status='mapped' |
| Unmapped | totalUnmapped |
Local state (line 51) | Keywords without cluster_id |
| Volume | totalVolume |
Calculated from keywords | Sum of search volumes |
| Progress % | Calculated | (totalClustered / totalCount) * 100 |
- |
Data Loading: Lines 132-183
- Loads keywords via
fetchKeywords({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Calculates totals from loaded data
- Issue: Only calculates from CURRENT PAGE data, not all keywords
Widget 2: Module Stats
| Field | Value | Source |
|---|---|---|
| Type | "planner" | Hardcoded prop |
| Component | StandardizedModuleWidget | Centralized component |
Widget 3: Workflow Completion
Uses useWorkflowStats hook
| Field | API Endpoint | Filter |
|---|---|---|
| Keywords Total | /v1/planner/keywords/ |
site_id only (NO sector) |
| Keywords Clustered | /v1/planner/keywords/?status=mapped |
site_id only |
| Clusters Created | /v1/planner/clusters/ |
site_id only |
| Ideas Generated | /v1/planner/ideas/ |
site_id only |
| Content Drafts | /v1/writer/content/?status=draft |
site_id only |
| Content Review | /v1/writer/content/?status=review |
site_id only |
| Content Published | /v1/writer/content/?status__in=approved,published |
site_id only |
| Images Created | /v1/writer/images/ |
site_id only |
Source: frontend/src/hooks/useWorkflowStats.ts (lines 144-234)
- SECTOR FILTERED: No - intentionally site-wide for consistency
- Date Filtered: Yes - supports Today, 7d, 30d, 90d, all
Page 2: Clusters (/planner/clusters)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Clusters | totalCount |
Local state (line 46) | All clusters for site+sector |
| With Ideas | totalWithIdeas |
Local state (line 47) | Clusters with ideas_count > 0 |
| Keywords | Calculated | Sum of all clusters' keywords_count | From loaded clusters |
| Ready | totalReady |
Local state (line 48) | Clusters with ideas_count === 0 |
| Progress % | Calculated | (totalWithIdeas / totalCount) * 100 |
- |
Data Loading: Lines 94-127
- Loads clusters via
fetchClusters({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Calculates totals from loaded data
- Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as Keywords page
Page 3: Ideas (/planner/ideas)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Ideas | totalCount |
Local state (line 45) | All ideas for site+sector |
| In Tasks | totalInTasks |
Local state (line 46) | Ideas with task_id not null |
| Pending | totalPending |
Local state (line 47) | Ideas without task_id |
| From Clusters | clusters.length |
Loaded clusters count | Unique clusters |
| Progress % | Calculated | (totalInTasks / totalCount) * 100 |
- |
Data Loading: Lines 87-133
- Loads ideas via
fetchContentIdeas({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Loads clusters separately
- Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as above
WRITER MODULE PAGES
Page 4: Tasks (/writer/tasks)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Tasks | totalCount |
Local state (line 47) | All tasks for site+sector |
| Drafted | totalDrafted |
Local state (line 48) | Tasks with content created |
| Pending | totalPending |
Local state (line 49) | Tasks without content |
| Priority | totalPriority |
Local state (line 50) | Tasks with priority=true |
| Progress % | Calculated | (totalDrafted / totalCount) * 100 |
- |
Data Loading: Lines 139-182
- Loads tasks via
fetchTasks({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Calculates totals from loaded data
- Issue: Only calculates from CURRENT PAGE data
Widget 2: Module Stats
| Field | Value | Source |
|---|---|---|
| Type | "writer" | Hardcoded prop |
| Component | StandardizedModuleWidget | Centralized component |
Widget 3: Workflow Completion
Same as Planner pages - uses useWorkflowStats hook
Page 5: Content/Drafts (/writer/content)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Content | totalCount |
Local state (line 43) | All content for site+sector |
| Published | totalPublished |
Local state (line 44) | Content with site_status='published' |
| In Review | totalInReview |
Local state (line 45) | Content with status='review' |
| Approved | totalApproved |
Local state (line 46) | Content with status='approved' |
| Progress % | Calculated | (totalPublished / totalCount) * 100 |
- |
Data Loading: Lines 84-112
- Loads content via
fetchContent({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as Tasks page
Page 6: Review (/writer/review)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| In Review | totalCount |
Local state (line 39) | Content with status='review' |
| Approved | totalApproved |
Local state (line 40) | From review that moved to approved |
| Pending | totalPending |
Local state (line 41) | Still in review status |
| Priority | totalPriority |
Local state (line 42) | With priority flag |
| Progress % | Calculated | (totalApproved / totalCount) * 100 |
- |
Data Loading: Lines 77-105
- Loads review content via
fetchContent({ site_id, sector_id, status: 'review', page, page_size }) - SECTOR FILTERED: Yes - uses
activeSector.id - Pre-filtered: Only loads status='review'
- Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as other Writer pages
Page 7: Approved (/writer/approved)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Approved | totalCount |
Local state (line 39) | Content with status='approved' |
| Published | totalPublished |
Local state (line 40) | With site_status='published' |
| Scheduled | totalScheduled |
Local state (line 41) | With site_status='scheduled' |
| Ready | totalReady |
Local state (line 42) | With site_status='not_published' |
| Progress % | Calculated | (totalPublished / totalCount) * 100 |
- |
Data Loading: Lines 77-107
- Loads approved content via
fetchContent({ site_id, sector_id, status: 'approved', page, page_size }) - SECTOR FILTERED: Yes - uses
activeSector.id - Pre-filtered: Only loads status='approved'
- Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as other Writer pages
Page 8: Images (/writer/images)
Widget 1: Page Progress
| Field | Value | Source | Filter/Criteria |
|---|---|---|---|
| Images | totalCount |
Local state (line 44) | All images for site+sector |
| Featured | totalFeatured |
Local state (line 45) | Images with image_type='featured' |
| In-Article | totalInArticle |
Local state (line 46) | Images with image_type='in_article' |
| Linked | totalLinked |
Local state (line 47) | Images with content_id not null |
| Progress % | Calculated | (totalLinked / totalCount) * 100 |
- |
Data Loading: Lines 98-144
- Loads images via
fetchImages({ site_id, sector_id, page, page_size, ...filters }) - SECTOR FILTERED: Yes - uses
activeSector.id - Issue: Only calculates from CURRENT PAGE data
Widget 2 & 3: Same as other Writer pages
ROOT CAUSES OF DATA CONFLICTS
Problem 1: Page-Level vs Site-Wide Data
Conflict: Widget 1 (Page Progress) shows page-filtered counts, Widget 3 (Workflow) shows site-wide counts
| Widget | Scope | Sector Filter | Date Filter | Data Source |
|---|---|---|---|---|
| Widget 1 (Page Progress) | Current page only | YES | NO | Local state from paginated API |
| Widget 2 (Module Stats) | Site-wide | NO | NO | Centralized hook (StandardizedModuleWidget) |
| Widget 3 (Workflow) | Site-wide | NO | YES (optional) | useWorkflowStats hook |
Example Conflict:
- Keywords page shows "17 Keywords" in Page Progress (Widget 1) ← from current page
- Workflow widget shows "17 Keywords Clustered" (Widget 3) ← from ALL keywords site-wide
- If user is on page 2, Widget 1 shows page 2 keywords, but Widget 3 shows total site keywords
Problem 2: Sector Filtering Inconsistency
Conflict: Widget 1 filters by sector, Widget 3 does NOT
| Component | Sector Filtered? | Reasoning |
|---|---|---|
| Page Progress (Widget 1) | ✅ YES | Shows current page data which is sector-filtered |
| Module Stats (Widget 2) | ❌ NO | Centralized module-level stats |
| Workflow Widget (Widget 3) | ❌ NO | Intentionally site-wide for consistency across pages |
User's Point: If only 1 sector exists, sector filter doesn't matter - but data STILL conflicts because Widget 1 shows PAGINATED data
Problem 3: Pagination vs Total Counts
Critical Issue: Widget 1 calculates totals from current page data only, not all records
Example on Keywords page (lines 182-183):
setTotalCount(keywords.length); // ← Only current page!
setTotalClustered(keywords.filter(k => k.status === 'mapped').length); // ← Only current page!
Should be:
setTotalCount(response.count); // ← Total from API
setTotalClustered(/* need separate API call or response field */);
Problem 4: Different Time Ranges
| Widget | Time Filtering |
|---|---|
| Widget 1 | NO time filter - shows ALL data for site+sector |
| Widget 3 | YES time filter - supports Today, 7d, 30d, 90d buttons |
RECOMMENDED FIXES
Fix 1: Make Page Progress Show Site-Wide Totals
Current: Calculates from paginated data
Should Be: Use response.count from API for totals
Files to Fix:
frontend/src/pages/Planner/Keywords.tsxfrontend/src/pages/Planner/Clusters.tsxfrontend/src/pages/Planner/Ideas.tsxfrontend/src/pages/Writer/Tasks.tsxfrontend/src/pages/Writer/Content.tsxfrontend/src/pages/Writer/Review.tsxfrontend/src/pages/Writer/Approved.tsxfrontend/src/pages/Writer/Images.tsx
Change Pattern:
// OLD (WRONG):
setTotalCount(items.length); // Only current page
// NEW (CORRECT):
setTotalCount(response.count); // Total count from API
Fix 2: Document That Widgets Show Different Scopes
Add tooltips/help text:
- Widget 1: "Page statistics (current filters)"
- Widget 3: "Site-wide workflow progress (all sectors)"
Fix 3: Consider Adding Sector Filter Option to Widget 3
Alternative: Add toggle in Workflow Widget to switch between:
- Site-wide (current behavior)
- Current sector only (match Widget 1)
ADDITIONAL FINDINGS
Publishing Tab Issues
File: frontend/src/pages/Sites/Settings.tsx
Issue: Day selection and time slot changes auto-save immediately instead of waiting for "Save Publishing Settings" button
Lines with auto-save:
- Line 1195: Publishing days button click calls
savePublishingSettings({ publish_days: newDays }) - Line 1224: Remove time slot calls
savePublishingSettings({ publish_time_slots: newSlots }) - Line 1236: Add time slot calls
savePublishingSettings({ publish_time_slots: newSlots })
Fix: Remove savePublishingSettings() calls from these onChange handlers, let user click the Save button at line 1278
SUMMARY TABLE: ALL PAGES
| Page | Widget 1 Scope | Widget 1 Sector Filter | Widget 3 Scope | Widget 3 Sector Filter | Conflict? |
|---|---|---|---|---|---|
| Keywords | Current page | YES | Site-wide | NO | ✅ YES |
| Clusters | Current page | YES | Site-wide | NO | ✅ YES |
| Ideas | Current page | YES | Site-wide | NO | ✅ YES |
| Tasks | Current page | YES | Site-wide | NO | ✅ YES |
| Content | Current page | YES | Site-wide | NO | ✅ YES |
| Review | Current page | YES | Site-wide | NO | ✅ YES |
| Approved | Current page | YES | Site-wide | NO | ✅ YES |
| Images | Current page | YES | Site-wide | NO | ✅ YES |
Conclusion: ALL pages have the pagination vs site-wide conflict. The sector filtering is actually a secondary issue.