Files
igny8/docs/30-FRONTEND/FILTERS-IMPLEMENTATION.md
2026-01-15 04:13:54 +00:00

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 TablePageTemplate component.


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

  1. Page Component defines filter state (useState)
  2. Config Function creates FilterConfig[] with options
  3. TablePageTemplate renders filter UI components
  4. Filter ChangeonFilterChange callback → 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

  1. Static Options: Filter options are hardcoded in config files, not dynamically loaded from backend
  2. No Cascading: Changing one filter doesn't update available options in other filters
  3. Difficulty Mapping: Backend filters by exact match, not by mapped ranges
  4. No Filter Persistence: Filters reset on page navigation

Planned Improvements

  1. Dynamic Filter Options API: Backend endpoint to return available options based on current data
  2. Cascading Filters: When status is selected, only show clusters/types that exist with that status
  3. URL State: Persist filter state in URL query parameters
  4. 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