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')
def filter_options(self, request):
"""
Get distinct filter values from current data.
Returns only values that exist in the current site's content.
Get distinct filter values from current data with cascading support.
Returns only values that exist based on other active filters.
"""
import logging
from django.db.models import Q
logger = logging.getLogger(__name__)
try:
queryset = self.get_queryset()
# Get distinct statuses
statuses = list(set(queryset.values_list('status', flat=True)))
# Get filter parameters for cascading
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])
status_labels = {
'draft': 'Draft',
@@ -1012,8 +1037,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in statuses
]
# Get distinct site_status
site_statuses = list(set(queryset.values_list('site_status', flat=True)))
# Get site_statuses (filtered by other fields)
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_status_labels = {
'not_published': 'Not Published',
@@ -1027,8 +1061,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in site_statuses
]
# Get distinct content_types
content_types = list(set(queryset.values_list('content_type', flat=True)))
# Get content types (filtered by other fields)
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])
type_labels = {
'post': 'Post',
@@ -1041,8 +1084,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for t in content_types
]
# Get distinct content_structures
structures = list(set(queryset.values_list('content_structure', flat=True)))
# Get content structures (filtered by other fields)
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])
structure_labels = {
'article': 'Article', 'guide': 'Guide', 'comparison': 'Comparison',
@@ -1057,8 +1109,17 @@ class ContentViewSet(SiteSectorModelViewSet):
for s in structures
]
# Get distinct sources
sources = list(set(queryset.values_list('source', flat=True)))
# Get sources (filtered by other fields)
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])
source_labels = {
'igny8': 'IGNY8',

View File

@@ -15,6 +15,7 @@ import {
bulkDeleteClusters,
bulkUpdateClustersStatus,
autoGenerateIdeas,
fetchPlannerClusterFilterOptions,
Cluster,
ClusterFilters,
ClusterCreateData,
@@ -91,41 +92,13 @@ export default function Clusters() {
const progressModal = useProgressModal();
const hasReloadedRef = useRef(false);
// Load dynamic filter options based on current site's data
const loadFilterOptions = useCallback(async () => {
if (!activeSite) return;
try {
const response = await fetch(`/api/v1/planner/clusters/filter_options/?site_id=${activeSite.id}`, {
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 filter options:', error);
}
}, [activeSite]);
// Load filter options when site changes
useEffect(() => {
loadFilterOptions();
}, [loadFilterOptions]);
// Reload filter options when filters change (cascading)
useEffect(() => {
if (!activeSite) return;
const loadCascadingOptions = async () => {
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
if (statusFilter) params.append('status', statusFilter);
if (difficultyFilter) {
// Map difficulty level to min/max range for backend
const difficultyNum = parseInt(difficultyFilter);
// Parse difficulty filter to min/max values
// 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 },
@@ -133,31 +106,48 @@ export default function Clusters() {
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);
return ranges[level] || {};
}, []);
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 || []);
}
// 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;
try {
const options = await fetchPlannerClusterFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setDifficultyOptions(options.difficulties || []);
} catch (error) {
console.error('Error loading cascading filter options:', error);
console.error('Error loading filter options:', error);
}
};
}, [activeSite]);
loadCascadingOptions();
}, [activeSite, statusFilter, difficultyFilter, volumeMin, volumeMax, searchTerm]);
// Load filter options when site changes (initial load with no filters)
useEffect(() => {
loadFilterOptions();
}, [activeSite]);
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
const { min: difficultyMin, max: difficultyMax } = getDifficultyRange(difficultyFilter);
loadFilterOptions({
status: statusFilter || undefined,
difficulty_min: difficultyMin,
difficulty_max: difficultyMax,
volume_min: volumeMin !== '' ? Number(volumeMin) : undefined,
volume_max: volumeMax !== '' ? Number(volumeMax) : undefined,
search: searchTerm || undefined,
});
}, [statusFilter, difficultyFilter, volumeMin, volumeMax, searchTerm, loadFilterOptions, getDifficultyRange]);
// Load total metrics for footer widget (site-wide totals, no sector filter)
const loadTotalMetrics = useCallback(async () => {

View File

@@ -107,12 +107,20 @@ export default function Ideas() {
loadClusters();
}, []);
// Load dynamic filter options based on current site's data
const loadFilterOptions = useCallback(async () => {
// 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;
content_type?: string;
content_structure?: string;
cluster?: string;
search?: string;
}) => {
if (!activeSite) return;
try {
const options = await fetchPlannerIdeasFilterOptions(activeSite.id);
const options = await fetchPlannerIdeasFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []);
@@ -122,42 +130,21 @@ export default function Ideas() {
}
}, [activeSite]);
// Load filter options when site changes
// Load filter options when site changes (initial load with no filters)
useEffect(() => {
loadFilterOptions();
}, [loadFilterOptions]);
}, [activeSite]);
// Reload filter options when filters change (cascading)
// Reload filter options when any filter changes (cascading filters)
useEffect(() => {
if (!activeSite) return;
const loadCascadingOptions = async () => {
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
if (statusFilter) params.append('status', statusFilter);
if (typeFilter) params.append('content_type', typeFilter);
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',
loadFilterOptions({
status: statusFilter || undefined,
content_type: typeFilter || undefined,
content_structure: structureFilter || undefined,
cluster: clusterFilter || undefined,
search: searchTerm || undefined,
});
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]);
}, [statusFilter, typeFilter, structureFilter, clusterFilter, searchTerm, loadFilterOptions]);
// Load total metrics for footer widget (site-wide totals, no sector filter)
const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchContent,
fetchImages,
fetchWriterContentFilterOptions,
Content,
ContentListResponse,
ContentFilters,
@@ -69,38 +70,44 @@ export default function Approved() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false);
// Load dynamic filter options with cascading
const loadFilterOptions = useCallback(async () => {
// 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;
site_status?: string;
content_type?: string;
content_structure?: string;
search?: string;
}) => {
if (!activeSite) return;
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
if (statusFilter) params.append('status', statusFilter);
if (siteStatusFilter) params.append('site_status', siteStatusFilter);
if (contentTypeFilter) params.append('content_type', contentTypeFilter);
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 || []);
}
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setSiteStatusOptions(options.site_statuses || []);
setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []);
} catch (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(() => {
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)
const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchContent,
fetchImages,
fetchWriterContentFilterOptions,
Content as ContentType,
ContentFilters,
generateImagePrompts,
@@ -74,38 +75,46 @@ export default function Content() {
const progressModal = useProgressModal();
const hasReloadedRef = useRef(false);
// Load dynamic filter options
const loadFilterOptions = useCallback(async () => {
// 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;
site_status?: string;
content_type?: string;
content_structure?: string;
source?: string;
search?: string;
}) => {
if (!activeSite) return;
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
if (statusFilter) params.append('status', statusFilter);
if (sourceFilter) params.append('source', sourceFilter);
if (contentTypeFilter) params.append('content_type', contentTypeFilter);
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 || []);
}
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setSourceOptions(options.sources || []);
setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []);
} catch (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(() => {
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)
const loadTotalMetrics = useCallback(async () => {

View File

@@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchContent,
fetchImages,
fetchWriterContentFilterOptions,
Content,
ContentListResponse,
ContentFilters,
@@ -67,38 +68,44 @@ export default function Review() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false);
// Load dynamic filter options
const loadFilterOptions = useCallback(async () => {
// 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;
site_status?: string;
content_type?: string;
content_structure?: string;
search?: string;
}) => {
if (!activeSite) return;
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
params.append('status', 'review'); // Always review status
if (siteStatusFilter) params.append('site_status', siteStatusFilter);
if (contentTypeFilter) params.append('content_type', contentTypeFilter);
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 || []);
}
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setSiteStatusOptions(options.site_statuses || []);
setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []);
} catch (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(() => {
loadFilterOptions();
}, [loadFilterOptions]);
loadFilterOptions({ status: 'review' }); // Always pass review status
}, [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
const loadContent = useCallback(async () => {

View File

@@ -17,6 +17,7 @@ import {
bulkUpdateTasksStatus,
autoGenerateContent,
autoGenerateImages,
fetchWriterTaskFilterOptions,
Task,
TasksFilters,
TaskCreateData,
@@ -106,40 +107,47 @@ export default function Tasks() {
const hasReloadedRef = useRef<boolean>(false);
// Load dynamic filter options with cascading
const loadFilterOptions = useCallback(async () => {
// 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;
content_type?: string;
content_structure?: string;
cluster?: string;
source?: string;
search?: string;
}) => {
if (!activeSite) return;
try {
const params = new URLSearchParams();
params.append('site_id', activeSite.id.toString());
if (statusFilter) params.append('status', statusFilter);
if (typeFilter) params.append('content_type', typeFilter);
if (structureFilter) params.append('content_structure', structureFilter);
if (clusterFilter) params.append('cluster', clusterFilter);
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 || []);
}
const options = await fetchWriterTaskFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setContentTypeOptions(options.content_types || []);
setContentStructureOptions(options.content_structures || []);
setClusterOptions(options.clusters || []);
setSourceOptions(options.sources || []);
} catch (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(() => {
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
export interface PlannerClusterFilterOptions {
statuses: FilterOption[];
difficulties: FilterOption[];
}
export async function fetchPlannerClusterFilterOptions(siteId?: number): Promise<PlannerClusterFilterOptions> {
const queryParams = siteId ? `?site_id=${siteId}` : '';
return fetchAPI(`/v1/planner/clusters/filter_options/${queryParams}`);
export interface ClusterFilterOptionsRequest {
status?: string;
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
@@ -804,9 +826,63 @@ export interface PlannerIdeasFilterOptions {
clusters: FilterOption[];
}
export async function fetchPlannerIdeasFilterOptions(siteId?: number): Promise<PlannerIdeasFilterOptions> {
const queryParams = siteId ? `?site_id=${siteId}` : '';
return fetchAPI(`/v1/planner/ideas/filter_options/${queryParams}`);
export interface IdeasFilterOptionsRequest {
status?: string;
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)
@@ -818,9 +894,30 @@ export interface WriterContentFilterOptions {
sources: FilterOption[];
}
export async function fetchWriterContentFilterOptions(siteId?: number): Promise<WriterContentFilterOptions> {
const queryParams = siteId ? `?site_id=${siteId}` : '';
return fetchAPI(`/v1/writer/content/filter_options/${queryParams}`);
export interface ContentFilterOptionsRequest {
status?: string;
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