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

@@ -15,6 +15,7 @@ import {
bulkDeleteClusters,
bulkUpdateClustersStatus,
autoGenerateIdeas,
fetchPlannerClusterFilterOptions,
Cluster,
ClusterFilters,
ClusterCreateData,
@@ -91,73 +92,62 @@ 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 () => {
// 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 },
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;
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 || []);
}
const options = await fetchPlannerClusterFilterOptions(activeSite.id, currentFilters);
setStatusOptions(options.statuses || []);
setDifficultyOptions(options.difficulties || []);
} catch (error) {
console.error('Error loading filter options:', error);
}
}, [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 (difficultyFilter) {
// Map difficulty level to min/max range for backend
const difficultyNum = parseInt(difficultyFilter);
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]);
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',
});
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]);
loadFilterOptions({
status: statusFilter || undefined,
content_type: typeFilter || undefined,
content_structure: structureFilter || undefined,
cluster: clusterFilter || undefined,
search: searchTerm || undefined,
});
}, [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]);