# 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: 1. **Widget 1 (Left)**: Page Progress - page-specific metrics 2. **Widget 2 (Middle)**: Module Stats - uses `StandardizedModuleWidget` 3. **Widget 3 (Right)**: Workflow Completion - uses `WorkflowCompletionWidget` via `useWorkflowStats` hook --- ## 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): ```typescript setTotalCount(keywords.length); // ← Only current page! setTotalClustered(keywords.filter(k => k.status === 'mapped').length); // ← Only current page! ``` Should be: ```typescript 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.tsx` - `frontend/src/pages/Planner/Clusters.tsx` - `frontend/src/pages/Planner/Ideas.tsx` - `frontend/src/pages/Writer/Tasks.tsx` - `frontend/src/pages/Writer/Content.tsx` - `frontend/src/pages/Writer/Review.tsx` - `frontend/src/pages/Writer/Approved.tsx` - `frontend/src/pages/Writer/Images.tsx` **Change Pattern:** ```typescript // 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. --- ## END OF AUDIT