fitlers fixes
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
Cluster,
|
||||
ClusterFilters,
|
||||
ClusterCreateData,
|
||||
FilterOption,
|
||||
} from '../../services/api';
|
||||
import FormModal from '../../components/common/FormModal';
|
||||
import ProgressModal from '../../components/common/ProgressModal';
|
||||
@@ -28,7 +29,6 @@ import { createClustersPageConfig } from '../../config/pages/clusters.config';
|
||||
import { useSectorStore } from '../../store/sectorStore';
|
||||
import { useSiteStore } from '../../store/siteStore';
|
||||
import { usePageSizeStore } from '../../store/pageSizeStore';
|
||||
import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter';
|
||||
|
||||
@@ -49,6 +49,11 @@ export default function Clusters() {
|
||||
const [totalVolume, setTotalVolume] = useState(0);
|
||||
const [totalKeywords, setTotalKeywords] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend based on current data)
|
||||
// Initialize as undefined to distinguish "not loaded yet" from "loaded but empty array"
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [difficultyOptions, setDifficultyOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -86,6 +91,74 @@ 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);
|
||||
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)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
@@ -145,13 +218,18 @@ export default function Clusters() {
|
||||
// Add difficulty range filter
|
||||
if (difficultyFilter) {
|
||||
const difficultyNum = parseInt(difficultyFilter);
|
||||
const label = getDifficultyLabelFromNumber(difficultyNum);
|
||||
if (label !== null) {
|
||||
const range = getDifficultyRange(label);
|
||||
if (range) {
|
||||
filters.difficulty_min = range.min;
|
||||
filters.difficulty_max = range.max;
|
||||
}
|
||||
// Map difficulty level (1-5) directly to raw difficulty range (0-100)
|
||||
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) {
|
||||
filters.difficulty_min = range.min;
|
||||
filters.difficulty_max = range.max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +460,9 @@ export default function Clusters() {
|
||||
setCurrentPage,
|
||||
loadClusters,
|
||||
onGenerateIdeas: (clusterId: number) => handleRowAction('generate_ideas', { id: clusterId } as Cluster),
|
||||
// Dynamic filter options
|
||||
statusOptions,
|
||||
difficultyOptions,
|
||||
});
|
||||
}, [
|
||||
activeSector,
|
||||
@@ -395,6 +476,8 @@ export default function Clusters() {
|
||||
tempVolumeMin,
|
||||
tempVolumeMax,
|
||||
loadClusters,
|
||||
statusOptions,
|
||||
difficultyOptions,
|
||||
handleRowAction,
|
||||
]);
|
||||
|
||||
|
||||
@@ -52,10 +52,11 @@ export default function Ideas() {
|
||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||
|
||||
// Dynamic filter options
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[]>([]);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<FilterOption[]>([]);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<FilterOption[]>([]);
|
||||
const [clusterOptions, setClusterOptions] = useState<FilterOption[]>([]);
|
||||
// Initialize as undefined to distinguish "not loaded yet" from "loaded but empty array"
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [clusterOptions, setClusterOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -126,6 +127,38 @@ export default function Ideas() {
|
||||
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 (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]);
|
||||
|
||||
// Load total metrics for footer widget (site-wide totals, no sector filter)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
@@ -185,6 +218,7 @@ export default function Ideas() {
|
||||
...(clusterFilter && { keyword_cluster_id: clusterFilter }),
|
||||
...(structureFilter && { content_structure: structureFilter }),
|
||||
...(typeFilter && { content_type: typeFilter }),
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
ordering,
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useSectorStore } from '../../store/sectorStore';
|
||||
import { usePageSizeStore } from '../../store/pageSizeStore';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter';
|
||||
import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty';
|
||||
import { getDifficultyNumber } from '../../utils/difficulty';
|
||||
import FormModal from '../../components/common/FormModal';
|
||||
import ProgressModal from '../../components/common/ProgressModal';
|
||||
import { useProgressModal } from '../../hooks/useProgressModal';
|
||||
@@ -57,10 +57,11 @@ export default function Keywords() {
|
||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend based on current data)
|
||||
const [countryOptions, setCountryOptions] = useState<FilterOption[]>([]);
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[]>([]);
|
||||
const [clusterOptions, setClusterOptions] = useState<FilterOption[]>([]);
|
||||
const [difficultyOptions, setDifficultyOptions] = useState<FilterOption[]>([]);
|
||||
// Initialize as undefined to distinguish "not loaded yet" from "loaded but empty array"
|
||||
const [countryOptions, setCountryOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [clusterOptions, setClusterOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
const [difficultyOptions, setDifficultyOptions] = useState<FilterOption[] | undefined>(undefined);
|
||||
|
||||
// Filter state - match Keywords.tsx
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -133,6 +134,9 @@ export default function Keywords() {
|
||||
cluster_id?: string;
|
||||
difficulty_min?: number;
|
||||
difficulty_max?: number;
|
||||
volume_min?: number;
|
||||
volume_max?: number;
|
||||
search?: string;
|
||||
}) => {
|
||||
if (!activeSite) return;
|
||||
|
||||
@@ -178,8 +182,11 @@ export default function Keywords() {
|
||||
cluster_id: clusterFilter || undefined,
|
||||
difficulty_min: difficultyMin,
|
||||
difficulty_max: difficultyMax,
|
||||
volume_min: volumeMin !== '' ? Number(volumeMin) : undefined,
|
||||
volume_max: volumeMax !== '' ? Number(volumeMax) : undefined,
|
||||
search: searchTerm || undefined,
|
||||
});
|
||||
}, [statusFilter, countryFilter, clusterFilter, difficultyFilter, loadFilterOptions, getDifficultyRange]);
|
||||
}, [statusFilter, countryFilter, clusterFilter, difficultyFilter, volumeMin, volumeMax, searchTerm, loadFilterOptions, getDifficultyRange]);
|
||||
|
||||
// Load total metrics for footer widget (site-wide totals, no sector filter)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
@@ -251,13 +258,18 @@ export default function Keywords() {
|
||||
// Add difficulty range filter
|
||||
if (difficultyFilter) {
|
||||
const difficultyNum = parseInt(difficultyFilter);
|
||||
const label = getDifficultyLabelFromNumber(difficultyNum);
|
||||
if (label !== null) {
|
||||
const range = getDifficultyRange(label);
|
||||
if (range) {
|
||||
filters.difficulty_min = range.min;
|
||||
filters.difficulty_max = range.max;
|
||||
}
|
||||
// Map difficulty level (1-5) directly to raw difficulty range (0-100)
|
||||
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) {
|
||||
filters.difficulty_min = range.min;
|
||||
filters.difficulty_max = range.max;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ export default function Approved() {
|
||||
const [totalPublished, setTotalPublished] = useState(0);
|
||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend)
|
||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [siteStatusOptions, setSiteStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState(''); // Status filter (draft/review/approved/published)
|
||||
@@ -63,6 +69,39 @@ 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 () => {
|
||||
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 || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite, statusFilter, siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm]);
|
||||
|
||||
// Load filter options when dependencies change
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
|
||||
// Load total metrics for footer widget and header metrics (not affected by pagination)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -46,10 +46,18 @@ export default function Content() {
|
||||
const [totalPublished, setTotalPublished] = useState(0);
|
||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend)
|
||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [sourceOptions, setSourceOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('draft');
|
||||
const [sourceFilter, setSourceFilter] = useState('');
|
||||
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
||||
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
|
||||
// Pagination state
|
||||
@@ -66,6 +74,39 @@ export default function Content() {
|
||||
const progressModal = useProgressModal();
|
||||
const hasReloadedRef = useRef(false);
|
||||
|
||||
// Load dynamic filter options
|
||||
const loadFilterOptions = useCallback(async () => {
|
||||
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 || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite, statusFilter, sourceFilter, contentTypeFilter, contentStructureFilter, searchTerm]);
|
||||
|
||||
// Load filter options when dependencies change
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
|
||||
// Load total metrics for footer widget and header metrics (site-wide totals, no sector filter)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
@@ -131,6 +172,8 @@ export default function Content() {
|
||||
...(searchTerm && { search: searchTerm }),
|
||||
...(statusFilter && { status: statusFilter }),
|
||||
...(sourceFilter && { source: sourceFilter }),
|
||||
...(contentTypeFilter && { content_type: contentTypeFilter }),
|
||||
...(contentStructureFilter && { content_structure: contentStructureFilter }),
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
ordering,
|
||||
@@ -151,7 +194,7 @@ export default function Content() {
|
||||
setShowContent(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [currentPage, statusFilter, sortBy, sortDirection, searchTerm, activeSector, pageSize, toast]);
|
||||
}, [currentPage, statusFilter, sourceFilter, contentTypeFilter, contentStructureFilter, sortBy, sortDirection, searchTerm, activeSector, pageSize, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
loadContent();
|
||||
@@ -216,13 +259,32 @@ export default function Content() {
|
||||
setStatusFilter,
|
||||
setCurrentPage,
|
||||
onRowClick: handleRowClick,
|
||||
// Dynamic filter options
|
||||
statusOptions,
|
||||
sourceOptions,
|
||||
contentTypeOptions,
|
||||
contentStructureOptions,
|
||||
// Filter values and setters
|
||||
contentTypeFilter,
|
||||
setContentTypeFilter,
|
||||
contentStructureFilter,
|
||||
setContentStructureFilter,
|
||||
sourceFilter,
|
||||
setSourceFilter,
|
||||
});
|
||||
}, [
|
||||
activeSector,
|
||||
searchTerm,
|
||||
statusFilter,
|
||||
handleRowClick,
|
||||
]);
|
||||
statusOptions,
|
||||
sourceOptions,
|
||||
contentTypeOptions,
|
||||
contentStructureOptions,
|
||||
contentTypeFilter,
|
||||
contentStructureFilter,
|
||||
sourceFilter,
|
||||
});
|
||||
|
||||
// Calculate header metrics - use totals from API calls (not page data)
|
||||
// This ensures metrics show correct totals across all pages, not just current page
|
||||
|
||||
@@ -43,9 +43,18 @@ export default function Review() {
|
||||
const [totalPublished, setTotalPublished] = useState(0);
|
||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend)
|
||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [siteStatusOptions, setSiteStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
|
||||
// Filter state - default to review status
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('review'); // Default to review
|
||||
const [siteStatusFilter, setSiteStatusFilter] = useState('');
|
||||
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
||||
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
|
||||
// Pagination state
|
||||
@@ -58,6 +67,39 @@ export default function Review() {
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
// Load dynamic filter options
|
||||
const loadFilterOptions = useCallback(async () => {
|
||||
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 || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite, siteStatusFilter, contentTypeFilter, contentStructureFilter, searchTerm]);
|
||||
|
||||
// Load filter options when dependencies change
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
|
||||
// Load content - filtered for review status
|
||||
const loadContent = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -68,6 +110,9 @@ export default function Review() {
|
||||
const filters: ContentFilters = {
|
||||
...(searchTerm && { search: searchTerm }),
|
||||
status: 'review', // Always filter for review status
|
||||
...(siteStatusFilter && { site_status: siteStatusFilter }),
|
||||
...(contentTypeFilter && { content_type: contentTypeFilter }),
|
||||
...(contentStructureFilter && { content_structure: contentStructureFilter }),
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
ordering,
|
||||
@@ -88,7 +133,7 @@ export default function Review() {
|
||||
setShowContent(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [currentPage, sortBy, sortDirection, searchTerm, pageSize, toast]);
|
||||
}, [currentPage, siteStatusFilter, contentTypeFilter, contentStructureFilter, sortBy, sortDirection, searchTerm, pageSize, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
loadContent();
|
||||
@@ -163,8 +208,32 @@ export default function Review() {
|
||||
setStatusFilter,
|
||||
setCurrentPage,
|
||||
onRowClick: handleRowClick,
|
||||
// Dynamic filter options
|
||||
statusOptions,
|
||||
siteStatusOptions,
|
||||
contentTypeOptions,
|
||||
contentStructureOptions,
|
||||
// Filter values and setters
|
||||
siteStatusFilter,
|
||||
setSiteStatusFilter,
|
||||
contentTypeFilter,
|
||||
setContentTypeFilter,
|
||||
contentStructureFilter,
|
||||
setContentStructureFilter,
|
||||
}),
|
||||
[activeSector, searchTerm, statusFilter, handleRowClick]
|
||||
[
|
||||
activeSector,
|
||||
searchTerm,
|
||||
statusFilter,
|
||||
handleRowClick,
|
||||
statusOptions,
|
||||
siteStatusOptions,
|
||||
contentTypeOptions,
|
||||
contentStructureOptions,
|
||||
siteStatusFilter,
|
||||
contentTypeFilter,
|
||||
contentStructureFilter,
|
||||
]
|
||||
);
|
||||
|
||||
// Header metrics (calculated from loaded data)
|
||||
|
||||
@@ -60,6 +60,13 @@ export default function Tasks() {
|
||||
const [totalProcessing, setTotalProcessing] = useState(0);
|
||||
const [totalCompleted, setTotalCompleted] = useState(0);
|
||||
|
||||
// Dynamic filter options (loaded from backend)
|
||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [clusterOptions, setClusterOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
const [sourceOptions, setSourceOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -99,6 +106,41 @@ export default function Tasks() {
|
||||
|
||||
const hasReloadedRef = useRef<boolean>(false);
|
||||
|
||||
// Load dynamic filter options with cascading
|
||||
const loadFilterOptions = useCallback(async () => {
|
||||
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 || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite, statusFilter, typeFilter, structureFilter, clusterFilter, sourceFilter, searchTerm]);
|
||||
|
||||
// Load filter options when dependencies change
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
|
||||
|
||||
|
||||
// Load clusters for filter dropdown
|
||||
|
||||
Reference in New Issue
Block a user