335 lines
13 KiB
Markdown
335 lines
13 KiB
Markdown
# 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
|