12 KiB
IGNY8 Filters Implementation
Last Updated: January 15, 2026
Version: 1.0.0
This document describes the current filter implementation across all pages that use the
TablePageTemplatecomponent.
Architecture Overview
Component Hierarchy
Page Component (e.g., Keywords.tsx)
└── createPageConfig() function
├── Returns: columns, filters, formFields, headerMetrics
└── Uses: handlers (state setters, filter values)
└── TablePageTemplate.tsx
├── Renders filters based on FilterConfig[]
├── Manages filter visibility toggle
└── Passes filterValues and onFilterChange to children
Filter Flow
- Page Component defines filter state (
useState) - Config Function creates
FilterConfig[]with options - TablePageTemplate renders filter UI components
- Filter Change →
onFilterChangecallback → update state → re-fetch data
Filter Types
| Type | Component | Description |
|---|---|---|
text |
<Input> |
Text search input |
select |
<SelectDropdown> |
Single-select dropdown |
custom |
customRender() |
Custom component (e.g., volume range) |
daterange |
(planned) | Date range picker |
range |
(planned) | Numeric range |
Backend FilterSet Classes
Planner Module (/backend/igny8_core/modules/planner/views.py)
KeywordsFilter
class KeywordsFilter(django_filters.FilterSet):
class Meta:
model = Keywords
fields = ['status', 'cluster_id', 'seed_keyword__country', 'seed_keyword_id', 'created_at__gte', 'created_at__lte']
ClustersFilter
class ClustersFilter(django_filters.FilterSet):
class Meta:
model = Clusters
fields = ['status', 'created_at__gte', 'created_at__lte']
ContentIdeasFilter
class ContentIdeasFilter(django_filters.FilterSet):
class Meta:
model = ContentIdeas
fields = ['status', 'keyword_cluster_id', 'content_type', 'content_structure', 'created_at__gte', 'created_at__lte']
Writer Module (/backend/igny8_core/modules/writer/views.py)
TasksFilter
class TasksFilter(django_filters.FilterSet):
class Meta:
model = Tasks
fields = ['status', 'cluster_id', 'content_type', 'content_structure', 'created_at__gte', 'created_at__lte']
ImagesFilter
class ImagesFilter(django_filters.FilterSet):
class Meta:
model = Images
fields = ['task_id', 'content_id', 'image_type', 'status', 'created_at__gte', 'created_at__lte']
ContentFilter
class ContentFilter(django_filters.FilterSet):
class Meta:
model = Content
fields = ['cluster_id', 'status', 'content_type', 'content_structure', 'source', 'created_at__gte', 'created_at__lte']
Page-by-Page Filter Implementation
1. Keywords Page (/planner/keywords)
Config File: frontend/src/config/pages/keywords.config.tsx
Page File: frontend/src/pages/Planner/Keywords.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | new, mapped |
status |
country |
select | US, CA, GB, AE, AU, IN, PK |
seed_keyword__country |
difficulty |
select | 1-5 (mapped labels) |
Custom in get_queryset() |
cluster |
select | Dynamic (cluster list) | cluster_id |
volume |
custom | Min/Max inputs | Custom volume_min, volume_max |
Special Handling:
- Difficulty filter uses 1-5 scale that maps to raw score ranges (0-10, 11-30, 31-50, 51-70, 71-100)
- Volume range uses custom dropdown with min/max inputs
2. Clusters Page (/planner/clusters)
Config File: frontend/src/config/pages/clusters.config.tsx
Page File: frontend/src/pages/Planner/Clusters.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | new, mapped |
status |
difficulty |
select | 1-5 (mapped labels) |
Custom filtering |
volume |
custom | Min/Max inputs | Custom volume_min, volume_max |
3. Ideas Page (/planner/ideas)
Config File: frontend/src/config/pages/ideas.config.tsx
Page File: frontend/src/pages/Planner/Ideas.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | new, queued, completed |
status |
content_structure |
select | 14 structure types | content_structure |
content_type |
select | post, page, product, taxonomy |
content_type |
keyword_cluster_id |
select | Dynamic (cluster list) | keyword_cluster_id |
Structure Options:
- Post:
article,guide,comparison,review,listicle - Page:
landing_page,business_page,service_page,general,cluster_hub - Product:
product_page - Taxonomy:
category_archive,tag_archive,attribute_archive
4. Content Page (/writer/content)
Config File: frontend/src/config/pages/content.config.tsx
Page File: frontend/src/pages/Writer/Content.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | draft, published |
status |
content_type |
select | post, page, product, taxonomy |
content_type |
content_structure |
select | 14 structure types | content_structure |
source |
select | igny8, wordpress |
source |
5. Review Page (/writer/review)
Config File: frontend/src/config/pages/review.config.tsx
Page File: frontend/src/pages/Writer/Review.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | draft, review, approved, published |
status |
site_status |
select | not_published, scheduled, publishing, published, failed |
site_status |
content_type |
select | From CONTENT_TYPE_OPTIONS |
content_type |
content_structure |
select | From ALL_CONTENT_STRUCTURES |
content_structure |
6. Approved Page (/writer/approved)
Config File: frontend/src/config/pages/approved.config.tsx
Page File: frontend/src/pages/Writer/Approved.tsx
| Filter Key | Type | Options | Backend Field |
|---|---|---|---|
search |
text | - | search (SearchFilter) |
status |
select | draft, review, approved, published |
status |
site_status |
select | not_published, scheduled, publishing, published, failed |
site_status |
content_type |
select | From CONTENT_TYPE_OPTIONS |
content_type |
content_structure |
select | From ALL_CONTENT_STRUCTURES |
content_structure |
Shared Constants
File: frontend/src/config/structureMapping.ts
export const CONTENT_TYPE_OPTIONS = [
{ value: 'post', label: 'Post' },
{ value: 'page', label: 'Page' },
{ value: 'product', label: 'Product' },
{ value: 'taxonomy', label: 'Taxonomy' },
];
export const ALL_CONTENT_STRUCTURES = [
{ value: 'article', label: 'Article' },
{ value: 'guide', label: 'Guide' },
{ value: 'comparison', label: 'Comparison' },
{ value: 'review', label: 'Review' },
{ value: 'listicle', label: 'Listicle' },
{ value: 'landing_page', label: 'Landing Page' },
{ value: 'business_page', label: 'Business Page' },
{ value: 'service_page', label: 'Service Page' },
{ value: 'general', label: 'General' },
{ value: 'cluster_hub', label: 'Cluster Hub' },
{ value: 'product_page', label: 'Product Page' },
{ value: 'category_archive', label: 'Category Archive' },
{ value: 'tag_archive', label: 'Tag Archive' },
{ value: 'attribute_archive', label: 'Attribute Archive' },
];
Difficulty Mapping
File: frontend/src/utils/difficulty.ts
The difficulty filter uses a 1-5 scale with human-readable labels:
| Value | Label | Raw Score Range |
|---|---|---|
| 1 | Very Easy | 0-10 |
| 2 | Easy | 11-30 |
| 3 | Medium | 31-50 |
| 4 | Hard | 51-70 |
| 5 | Very Hard | 71-100 |
Important: The database stores raw SEO difficulty scores (0-100), but the UI displays and filters using the 1-5 scale. The mapping must be consistent between frontend display and backend filtering.
Filter State Management Pattern
Each page follows this pattern:
// 1. Define filter state
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('');
const [difficultyFilter, setDifficultyFilter] = useState('');
// 2. Pass to config function
const config = createPageConfig({
searchTerm,
setSearchTerm,
statusFilter,
setStatusFilter,
// ...handlers
});
// 3. Config returns filters array
filters: [
{ key: 'search', type: 'text', placeholder: '...' },
{ key: 'status', type: 'select', options: [...] },
]
// 4. TablePageTemplate renders filters
// 5. onFilterChange triggers state update
// 6. useEffect with dependencies re-fetches data
API Query Parameters
When filters are applied, the frontend constructs API calls like:
GET /api/v1/planner/keywords/?status=new&cluster_id=123&search=term&page=1&page_size=50
GET /api/v1/planner/clusters/?status=mapped&difficulty=3&volume_min=100
GET /api/v1/planner/ideas/?content_structure=guide&content_type=post
GET /api/v1/writer/content/?status=draft&source=igny8
TablePageTemplate Filter Rendering
The template handles filter rendering in renderFiltersRow():
{filters.map((filter) => {
if (filter.type === 'text') {
return <Input ... />;
}
if (filter.type === 'select') {
return <SelectDropdown ... />;
}
if (filter.type === 'custom' && filter.customRender) {
return filter.customRender();
}
})}
Current Limitations
- Static Options: Filter options are hardcoded in config files, not dynamically loaded from backend
- No Cascading: Changing one filter doesn't update available options in other filters
- Difficulty Mapping: Backend filters by exact match, not by mapped ranges
- No Filter Persistence: Filters reset on page navigation
Planned Improvements
- Dynamic Filter Options API: Backend endpoint to return available options based on current data
- Cascading Filters: When status is selected, only show clusters/types that exist with that status
- URL State: Persist filter state in URL query parameters
- Filter Presets: Save commonly used filter combinations
File References
| Component | Path |
|---|---|
| TablePageTemplate | frontend/src/templates/TablePageTemplate.tsx |
| Keywords Config | frontend/src/config/pages/keywords.config.tsx |
| Clusters Config | frontend/src/config/pages/clusters.config.tsx |
| Ideas Config | frontend/src/config/pages/ideas.config.tsx |
| Content Config | frontend/src/config/pages/content.config.tsx |
| Review Config | frontend/src/config/pages/review.config.tsx |
| Approved Config | frontend/src/config/pages/approved.config.tsx |
| Structure Mapping | frontend/src/config/structureMapping.ts |
| Difficulty Utils | frontend/src/utils/difficulty.ts |
| Planner Views | backend/igny8_core/modules/planner/views.py |
| Writer Views | backend/igny8_core/modules/writer/views.py |