filters fixed for all pages of planner writer

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-15 06:19:20 +00:00
parent 51292bb1b3
commit a8309078c6
8 changed files with 372 additions and 206 deletions

View File

@@ -988,18 +988,43 @@ class ContentViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['get'], url_path='filter_options', url_name='filter_options') @action(detail=False, methods=['get'], url_path='filter_options', url_name='filter_options')
def filter_options(self, request): def filter_options(self, request):
""" """
Get distinct filter values from current data. Get distinct filter values from current data with cascading support.
Returns only values that exist in the current site's content. Returns only values that exist based on other active filters.
""" """
import logging import logging
from django.db.models import Q
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
queryset = self.get_queryset() queryset = self.get_queryset()
# Get distinct statuses # Get filter parameters for cascading
statuses = list(set(queryset.values_list('status', flat=True))) status_filter = request.query_params.get('status', '')
site_status_filter = request.query_params.get('site_status', '')
content_type_filter = request.query_params.get('content_type', '')
content_structure_filter = request.query_params.get('content_structure', '')
source_filter = request.query_params.get('source', '')
search = request.query_params.get('search', '')
# Apply search to base queryset
base_qs = queryset
if search:
base_qs = base_qs.filter(
Q(title__icontains=search) | Q(summary__icontains=search)
)
# Get statuses (filtered by other fields)
status_qs = base_qs
if site_status_filter:
status_qs = status_qs.filter(site_status=site_status_filter)
if content_type_filter:
status_qs = status_qs.filter(content_type=content_type_filter)
if content_structure_filter:
status_qs = status_qs.filter(content_structure=content_structure_filter)
if source_filter:
status_qs = status_qs.filter(source=source_filter)
statuses = list(set(status_qs.values_list('status', flat=True)))
statuses = sorted([s for s in statuses if s]) statuses = sorted([s for s in statuses if s])
status_labels = { status_labels = {
'draft': 'Draft', 'draft': 'Draft',
@@ -1012,8 +1037,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in statuses for s in statuses
] ]
# Get distinct site_status # Get site_statuses (filtered by other fields)
site_statuses = list(set(queryset.values_list('site_status', flat=True))) site_status_qs = base_qs
if status_filter:
site_status_qs = site_status_qs.filter(status=status_filter)
if content_type_filter:
site_status_qs = site_status_qs.filter(content_type=content_type_filter)
if content_structure_filter:
site_status_qs = site_status_qs.filter(content_structure=content_structure_filter)
if source_filter:
site_status_qs = site_status_qs.filter(source=source_filter)
site_statuses = list(set(site_status_qs.values_list('site_status', flat=True)))
site_statuses = sorted([s for s in site_statuses if s]) site_statuses = sorted([s for s in site_statuses if s])
site_status_labels = { site_status_labels = {
'not_published': 'Not Published', 'not_published': 'Not Published',
@@ -1027,8 +1061,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in site_statuses for s in site_statuses
] ]
# Get distinct content_types # Get content types (filtered by other fields)
content_types = list(set(queryset.values_list('content_type', flat=True))) type_qs = base_qs
if status_filter:
type_qs = type_qs.filter(status=status_filter)
if site_status_filter:
type_qs = type_qs.filter(site_status=site_status_filter)
if content_structure_filter:
type_qs = type_qs.filter(content_structure=content_structure_filter)
if source_filter:
type_qs = type_qs.filter(source=source_filter)
content_types = list(set(type_qs.values_list('content_type', flat=True)))
content_types = sorted([t for t in content_types if t]) content_types = sorted([t for t in content_types if t])
type_labels = { type_labels = {
'post': 'Post', 'post': 'Post',
@@ -1041,8 +1084,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for t in content_types for t in content_types
] ]
# Get distinct content_structures # Get content structures (filtered by other fields)
structures = list(set(queryset.values_list('content_structure', flat=True))) structure_qs = base_qs
if status_filter:
structure_qs = structure_qs.filter(status=status_filter)
if site_status_filter:
structure_qs = structure_qs.filter(site_status=site_status_filter)
if content_type_filter:
structure_qs = structure_qs.filter(content_type=content_type_filter)
if source_filter:
structure_qs = structure_qs.filter(source=source_filter)
structures = list(set(structure_qs.values_list('content_structure', flat=True)))
structures = sorted([s for s in structures if s]) structures = sorted([s for s in structures if s])
structure_labels = { structure_labels = {
'article': 'Article', 'guide': 'Guide', 'comparison': 'Comparison', 'article': 'Article', 'guide': 'Guide', 'comparison': 'Comparison',
@@ -1057,8 +1109,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in structures for s in structures
] ]
# Get distinct sources # Get sources (filtered by other fields)
sources = list(set(queryset.values_list('source', flat=True))) source_qs = base_qs
if status_filter:
source_qs = source_qs.filter(status=status_filter)
if site_status_filter:
source_qs = source_qs.filter(site_status=site_status_filter)
if content_type_filter:
source_qs = source_qs.filter(content_type=content_type_filter)
if content_structure_filter:
source_qs = source_qs.filter(content_structure=content_structure_filter)
sources = list(set(source_qs.values_list('source', flat=True)))
sources = sorted([s for s in sources if s]) sources = sorted([s for s in sources if s])
source_labels = { source_labels = {
'igny8': 'IGNY8', 'igny8': 'IGNY8',

View File

@@ -15,6 +15,7 @@ import {
bulkDeleteClusters, bulkDeleteClusters,
bulkUpdateClustersStatus, bulkUpdateClustersStatus,
autoGenerateIdeas, autoGenerateIdeas,
fetchPlannerClusterFilterOptions,
Cluster, Cluster,
ClusterFilters, ClusterFilters,
ClusterCreateData, ClusterCreateData,
@@ -91,73 +92,62 @@ export default function Clusters() {
const progressModal = useProgressModal(); const progressModal = useProgressModal();
const hasReloadedRef = useRef(false); const hasReloadedRef = useRef(false);
// Load dynamic filter options based on current site's data // Parse difficulty filter to min/max values
const loadFilterOptions = useCallback(async () => { // Backend uses: 1=0-10, 2=11-30, 3=31-50, 4=51-70, 5=71-100
const getDifficultyRange = useCallback((filter: string): { min?: number; max?: number } => {
if (!filter) return {};
const level = parseInt(filter, 10);
if (isNaN(level)) return {};
// Map difficulty level to raw difficulty range matching backend logic
const ranges: Record<number, { min: number; max: number }> = {
1: { min: 0, max: 10 },
2: { min: 11, max: 30 },
3: { min: 31, max: 50 },
4: { min: 51, max: 70 },
5: { min: 71, max: 100 },
};
return ranges[level] || {};
}, []);
// Load dynamic filter options based on current site's data and applied filters
// This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
difficulty_min?: number;
difficulty_max?: number;
volume_min?: number;
volume_max?: number;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const response = await fetch(`/api/v1/planner/clusters/filter_options/?site_id=${activeSite.id}`, { const options = await fetchPlannerClusterFilterOptions(activeSite.id, currentFilters);
credentials: 'include', setStatusOptions(options.statuses || []);
}); setDifficultyOptions(options.difficulties || []);
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setDifficultyOptions(result.data.difficulties || []);
}
} catch (error) { } catch (error) {
console.error('Error loading filter options:', error); console.error('Error loading filter options:', error);
} }
}, [activeSite]); }, [activeSite]);
// Load filter options when site changes // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions();
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when filters change (cascading) // Reload filter options when any filter changes (cascading filters)
useEffect(() => { useEffect(() => {
if (!activeSite) return; const { min: difficultyMin, max: difficultyMax } = getDifficultyRange(difficultyFilter);
loadFilterOptions({
const loadCascadingOptions = async () => { status: statusFilter || undefined,
try { difficulty_min: difficultyMin,
const params = new URLSearchParams(); difficulty_max: difficultyMax,
params.append('site_id', activeSite.id.toString()); volume_min: volumeMin !== '' ? Number(volumeMin) : undefined,
if (statusFilter) params.append('status', statusFilter); volume_max: volumeMax !== '' ? Number(volumeMax) : undefined,
if (difficultyFilter) { search: searchTerm || undefined,
// Map difficulty level to min/max range for backend });
const difficultyNum = parseInt(difficultyFilter); }, [statusFilter, difficultyFilter, volumeMin, volumeMax, searchTerm, loadFilterOptions, getDifficultyRange]);
const ranges: Record<number, { min: number; max: number }> = {
1: { min: 0, max: 10 },
2: { min: 11, max: 30 },
3: { min: 31, max: 50 },
4: { min: 51, max: 70 },
5: { min: 71, max: 100 },
};
const range = ranges[difficultyNum];
if (range) {
params.append('difficulty_min', range.min.toString());
params.append('difficulty_max', range.max.toString());
}
}
if (volumeMin) params.append('volume_min', volumeMin.toString());
if (volumeMax) params.append('volume_max', volumeMax.toString());
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/planner/clusters/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setDifficultyOptions(result.data.difficulties || []);
}
} catch (error) {
console.error('Error loading cascading filter options:', error);
}
};
loadCascadingOptions();
}, [activeSite, statusFilter, difficultyFilter, volumeMin, volumeMax, searchTerm]);
// Load total metrics for footer widget (site-wide totals, no sector filter) // Load total metrics for footer widget (site-wide totals, no sector filter)
const loadTotalMetrics = useCallback(async () => { const loadTotalMetrics = useCallback(async () => {

View File

@@ -107,12 +107,20 @@ export default function Ideas() {
loadClusters(); loadClusters();
}, []); }, []);
// Load dynamic filter options based on current site's data // Load dynamic filter options based on current site's data and applied filters
const loadFilterOptions = useCallback(async () => { // This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
content_type?: string;
content_structure?: string;
cluster?: string;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const options = await fetchPlannerIdeasFilterOptions(activeSite.id); const options = await fetchPlannerIdeasFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []); setStatusOptions(options.statuses || []);
setContentTypeOptions(options.content_types || []); setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []); setContentStructureOptions(options.content_structures || []);
@@ -122,42 +130,21 @@ export default function Ideas() {
} }
}, [activeSite]); }, [activeSite]);
// Load filter options when site changes // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions();
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when filters change (cascading) // Reload filter options when any filter changes (cascading filters)
useEffect(() => { useEffect(() => {
if (!activeSite) return; loadFilterOptions({
status: statusFilter || undefined,
const loadCascadingOptions = async () => { content_type: typeFilter || undefined,
try { content_structure: structureFilter || undefined,
const params = new URLSearchParams(); cluster: clusterFilter || undefined,
params.append('site_id', activeSite.id.toString()); search: searchTerm || undefined,
if (statusFilter) params.append('status', statusFilter); });
if (typeFilter) params.append('content_type', typeFilter); }, [statusFilter, typeFilter, structureFilter, clusterFilter, searchTerm, loadFilterOptions]);
if (structureFilter) params.append('content_structure', structureFilter);
if (clusterFilter) params.append('cluster', clusterFilter);
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/planner/ideas/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setContentTypeOptions(result.data.content_types || []);
setContentStructureOptions(result.data.content_structures || []);
setClusterOptions(result.data.clusters || []);
}
} catch (error) {
console.error('Error loading cascading filter options:', error);
}
};
loadCascadingOptions();
}, [activeSite, statusFilter, typeFilter, structureFilter, clusterFilter, searchTerm]);
// Load total metrics for footer widget (site-wide totals, no sector filter) // Load total metrics for footer widget (site-wide totals, no sector filter)
const loadTotalMetrics = useCallback(async () => { const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import { import {
fetchContent, fetchContent,
fetchImages, fetchImages,
fetchWriterContentFilterOptions,
Content, Content,
ContentListResponse, ContentListResponse,
ContentFilters, ContentFilters,
@@ -69,38 +70,44 @@ export default function Approved() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false); const [showContent, setShowContent] = useState(false);
// Load dynamic filter options with cascading // Load dynamic filter options based on current site's data and applied filters
const loadFilterOptions = useCallback(async () => { // This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
site_status?: string;
content_type?: string;
content_structure?: string;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const params = new URLSearchParams(); const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
params.append('site_id', activeSite.id.toString()); setStatusOptions(options.statuses || []);
if (statusFilter) params.append('status', statusFilter); setSiteStatusOptions(options.site_statuses || []);
if (siteStatusFilter) params.append('site_status', siteStatusFilter); setContentTypeOptions(options.content_types || []);
if (contentTypeFilter) params.append('content_type', contentTypeFilter); setContentStructureOptions(options.content_structures || []);
if (contentStructureFilter) params.append('content_structure', contentStructureFilter);
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/writer/content/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setSiteStatusOptions(result.data.site_statuses || []);
setContentTypeOptions(result.data.content_types || []);
setContentStructureOptions(result.data.content_structures || []);
}
} catch (error) { } catch (error) {
console.error('Error loading filter options:', error); console.error('Error loading filter options:', error);
} }
}, [activeSite, statusFilter, siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm]); }, [activeSite]);
// Load filter options when dependencies change // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions();
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
loadFilterOptions({
status: statusFilter || undefined,
site_status: siteStatusFilter || undefined,
content_type: contentTypeFilter || undefined,
content_structure: contentStructureFilter || undefined,
search: searchTerm || undefined,
});
}, [statusFilter, siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm, loadFilterOptions]);
// Load total metrics for footer widget and header metrics (not affected by pagination) // Load total metrics for footer widget and header metrics (not affected by pagination)
const loadTotalMetrics = useCallback(async () => { const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import { import {
fetchContent, fetchContent,
fetchImages, fetchImages,
fetchWriterContentFilterOptions,
Content as ContentType, Content as ContentType,
ContentFilters, ContentFilters,
generateImagePrompts, generateImagePrompts,
@@ -74,38 +75,46 @@ export default function Content() {
const progressModal = useProgressModal(); const progressModal = useProgressModal();
const hasReloadedRef = useRef(false); const hasReloadedRef = useRef(false);
// Load dynamic filter options // Load dynamic filter options based on current site's data and applied filters
const loadFilterOptions = useCallback(async () => { // This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
site_status?: string;
content_type?: string;
content_structure?: string;
source?: string;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const params = new URLSearchParams(); const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
params.append('site_id', activeSite.id.toString()); setStatusOptions(options.statuses || []);
if (statusFilter) params.append('status', statusFilter); setSourceOptions(options.sources || []);
if (sourceFilter) params.append('source', sourceFilter); setContentTypeOptions(options.content_types || []);
if (contentTypeFilter) params.append('content_type', contentTypeFilter); setContentStructureOptions(options.content_structures || []);
if (contentStructureFilter) params.append('content_structure', contentStructureFilter);
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/writer/content/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setSourceOptions(result.data.sources || []);
setContentTypeOptions(result.data.content_types || []);
setContentStructureOptions(result.data.content_structures || []);
}
} catch (error) { } catch (error) {
console.error('Error loading filter options:', error); console.error('Error loading filter options:', error);
} }
}, [activeSite, statusFilter, sourceFilter, contentTypeFilter, contentStructureFilter, searchTerm]); }, [activeSite]);
// Load filter options when dependencies change // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions();
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
loadFilterOptions({
status: statusFilter || undefined,
content_type: contentTypeFilter || undefined,
content_structure: contentStructureFilter || undefined,
source: sourceFilter || undefined,
search: searchTerm || undefined,
});
}, [statusFilter, contentTypeFilter, contentStructureFilter, sourceFilter, searchTerm, loadFilterOptions]);
// Load total metrics for footer widget and header metrics (site-wide totals, no sector filter) // Load total metrics for footer widget and header metrics (site-wide totals, no sector filter)
const loadTotalMetrics = useCallback(async () => { const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import { import {
fetchContent, fetchContent,
fetchImages, fetchImages,
fetchWriterContentFilterOptions,
Content, Content,
ContentListResponse, ContentListResponse,
ContentFilters, ContentFilters,
@@ -67,38 +68,44 @@ export default function Review() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false); const [showContent, setShowContent] = useState(false);
// Load dynamic filter options // Load dynamic filter options based on current site's data and applied filters
const loadFilterOptions = useCallback(async () => { // This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
site_status?: string;
content_type?: string;
content_structure?: string;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const params = new URLSearchParams(); const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
params.append('site_id', activeSite.id.toString()); setStatusOptions(options.statuses || []);
params.append('status', 'review'); // Always review status setSiteStatusOptions(options.site_statuses || []);
if (siteStatusFilter) params.append('site_status', siteStatusFilter); setContentTypeOptions(options.content_types || []);
if (contentTypeFilter) params.append('content_type', contentTypeFilter); setContentStructureOptions(options.content_structures || []);
if (contentStructureFilter) params.append('content_structure', contentStructureFilter);
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/writer/content/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setSiteStatusOptions(result.data.site_statuses || []);
setContentTypeOptions(result.data.content_types || []);
setContentStructureOptions(result.data.content_structures || []);
}
} catch (error) { } catch (error) {
console.error('Error loading filter options:', error); console.error('Error loading filter options:', error);
} }
}, [activeSite, siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm]); }, [activeSite]);
// Load filter options when dependencies change // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions({ status: 'review' }); // Always pass review status
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
loadFilterOptions({
status: 'review', // Always review status
site_status: siteStatusFilter || undefined,
content_type: contentTypeFilter || undefined,
content_structure: contentStructureFilter || undefined,
search: searchTerm || undefined,
});
}, [siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm, loadFilterOptions]);
// Load content - filtered for review status // Load content - filtered for review status
const loadContent = useCallback(async () => { const loadContent = useCallback(async () => {

View File

@@ -17,6 +17,7 @@ import {
bulkUpdateTasksStatus, bulkUpdateTasksStatus,
autoGenerateContent, autoGenerateContent,
autoGenerateImages, autoGenerateImages,
fetchWriterTaskFilterOptions,
Task, Task,
TasksFilters, TasksFilters,
TaskCreateData, TaskCreateData,
@@ -106,40 +107,47 @@ export default function Tasks() {
const hasReloadedRef = useRef<boolean>(false); const hasReloadedRef = useRef<boolean>(false);
// Load dynamic filter options with cascading // Load dynamic filter options based on current site's data and applied filters
const loadFilterOptions = useCallback(async () => { // This implements cascading filters - each filter's options reflect what's available
// given the other currently applied filters
const loadFilterOptions = useCallback(async (currentFilters?: {
status?: string;
content_type?: string;
content_structure?: string;
cluster?: string;
source?: string;
search?: string;
}) => {
if (!activeSite) return; if (!activeSite) return;
try { try {
const params = new URLSearchParams(); const options = await fetchWriterTaskFilterOptions(activeSite.id, currentFilters);
params.append('site_id', activeSite.id.toString()); setStatusOptions(options.statuses || []);
if (statusFilter) params.append('status', statusFilter); setContentTypeOptions(options.content_types || []);
if (typeFilter) params.append('content_type', typeFilter); setContentStructureOptions(options.content_structures || []);
if (structureFilter) params.append('content_structure', structureFilter); setClusterOptions(options.clusters || []);
if (clusterFilter) params.append('cluster', clusterFilter); setSourceOptions(options.sources || []);
if (sourceFilter) params.append('source', sourceFilter);
if (searchTerm) params.append('search', searchTerm);
const response = await fetch(`/api/v1/writer/tasks/filter_options/?${params.toString()}`, {
credentials: 'include',
});
const result = await response.json();
if (result.success && result.data) {
setStatusOptions(result.data.statuses || []);
setContentTypeOptions(result.data.content_types || []);
setContentStructureOptions(result.data.content_structures || []);
setClusterOptions(result.data.clusters || []);
setSourceOptions(result.data.sources || []);
}
} catch (error) { } catch (error) {
console.error('Error loading filter options:', error); console.error('Error loading filter options:', error);
} }
}, [activeSite, statusFilter, typeFilter, structureFilter, clusterFilter, sourceFilter, searchTerm]); }, [activeSite]);
// Load filter options when dependencies change // Load filter options when site changes (initial load with no filters)
useEffect(() => { useEffect(() => {
loadFilterOptions(); loadFilterOptions();
}, [loadFilterOptions]); }, [activeSite]);
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
loadFilterOptions({
status: statusFilter || undefined,
content_type: typeFilter || undefined,
content_structure: structureFilter || undefined,
cluster: clusterFilter || undefined,
source: sourceFilter || undefined,
search: searchTerm || undefined,
});
}, [statusFilter, typeFilter, structureFilter, clusterFilter, sourceFilter, searchTerm, loadFilterOptions]);

View File

@@ -789,11 +789,33 @@ export async function fetchPlannerKeywordFilterOptions(
// Clusters filter options // Clusters filter options
export interface PlannerClusterFilterOptions { export interface PlannerClusterFilterOptions {
statuses: FilterOption[]; statuses: FilterOption[];
difficulties: FilterOption[];
} }
export async function fetchPlannerClusterFilterOptions(siteId?: number): Promise<PlannerClusterFilterOptions> { export interface ClusterFilterOptionsRequest {
const queryParams = siteId ? `?site_id=${siteId}` : ''; status?: string;
return fetchAPI(`/v1/planner/clusters/filter_options/${queryParams}`); difficulty_min?: number;
difficulty_max?: number;
volume_min?: number;
volume_max?: number;
search?: string;
}
export async function fetchPlannerClusterFilterOptions(
siteId?: number,
filters?: ClusterFilterOptionsRequest
): Promise<PlannerClusterFilterOptions> {
const params = new URLSearchParams();
if (siteId) params.append('site_id', siteId.toString());
if (filters?.status) params.append('status', filters.status);
if (filters?.difficulty_min !== undefined) params.append('difficulty_min', filters.difficulty_min.toString());
if (filters?.difficulty_max !== undefined) params.append('difficulty_max', filters.difficulty_max.toString());
if (filters?.volume_min !== undefined) params.append('volume_min', filters.volume_min.toString());
if (filters?.volume_max !== undefined) params.append('volume_max', filters.volume_max.toString());
if (filters?.search) params.append('search', filters.search);
const queryString = params.toString();
return fetchAPI(`/v1/planner/clusters/filter_options/${queryString ? `?${queryString}` : ''}`);
} }
// Ideas filter options // Ideas filter options
@@ -804,9 +826,63 @@ export interface PlannerIdeasFilterOptions {
clusters: FilterOption[]; clusters: FilterOption[];
} }
export async function fetchPlannerIdeasFilterOptions(siteId?: number): Promise<PlannerIdeasFilterOptions> { export interface IdeasFilterOptionsRequest {
const queryParams = siteId ? `?site_id=${siteId}` : ''; status?: string;
return fetchAPI(`/v1/planner/ideas/filter_options/${queryParams}`); content_type?: string;
content_structure?: string;
cluster?: string;
search?: string;
}
export async function fetchPlannerIdeasFilterOptions(
siteId?: number,
filters?: IdeasFilterOptionsRequest
): Promise<PlannerIdeasFilterOptions> {
const params = new URLSearchParams();
if (siteId) params.append('site_id', siteId.toString());
if (filters?.status) params.append('status', filters.status);
if (filters?.content_type) params.append('content_type', filters.content_type);
if (filters?.content_structure) params.append('content_structure', filters.content_structure);
if (filters?.cluster) params.append('cluster', filters.cluster);
if (filters?.search) params.append('search', filters.search);
const queryString = params.toString();
return fetchAPI(`/v1/planner/ideas/filter_options/${queryString ? `?${queryString}` : ''}`);
}
// Tasks filter options (Writer module)
export interface WriterTaskFilterOptions {
statuses: FilterOption[];
content_types: FilterOption[];
content_structures: FilterOption[];
clusters: FilterOption[];
sources: FilterOption[];
}
export interface TaskFilterOptionsRequest {
status?: string;
content_type?: string;
content_structure?: string;
cluster?: string;
source?: string;
search?: string;
}
export async function fetchWriterTaskFilterOptions(
siteId?: number,
filters?: TaskFilterOptionsRequest
): Promise<WriterTaskFilterOptions> {
const params = new URLSearchParams();
if (siteId) params.append('site_id', siteId.toString());
if (filters?.status) params.append('status', filters.status);
if (filters?.content_type) params.append('content_type', filters.content_type);
if (filters?.content_structure) params.append('content_structure', filters.content_structure);
if (filters?.cluster) params.append('cluster', filters.cluster);
if (filters?.source) params.append('source', filters.source);
if (filters?.search) params.append('search', filters.search);
const queryString = params.toString();
return fetchAPI(`/v1/writer/tasks/filter_options/${queryString ? `?${queryString}` : ''}`);
} }
// Content filter options (Writer module) // Content filter options (Writer module)
@@ -818,9 +894,30 @@ export interface WriterContentFilterOptions {
sources: FilterOption[]; sources: FilterOption[];
} }
export async function fetchWriterContentFilterOptions(siteId?: number): Promise<WriterContentFilterOptions> { export interface ContentFilterOptionsRequest {
const queryParams = siteId ? `?site_id=${siteId}` : ''; status?: string;
return fetchAPI(`/v1/writer/content/filter_options/${queryParams}`); site_status?: string;
content_type?: string;
content_structure?: string;
source?: string;
search?: string;
}
export async function fetchWriterContentFilterOptions(
siteId?: number,
filters?: ContentFilterOptionsRequest
): Promise<WriterContentFilterOptions> {
const params = new URLSearchParams();
if (siteId) params.append('site_id', siteId.toString());
if (filters?.status) params.append('status', filters.status);
if (filters?.site_status) params.append('site_status', filters.site_status);
if (filters?.content_type) params.append('content_type', filters.content_type);
if (filters?.content_structure) params.append('content_structure', filters.content_structure);
if (filters?.source) params.append('source', filters.source);
if (filters?.search) params.append('search', filters.search);
const queryString = params.toString();
return fetchAPI(`/v1/writer/content/filter_options/${queryString ? `?${queryString}` : ''}`);
} }
// Clusters-specific API functions // Clusters-specific API functions