fitlers fixes

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-15 06:03:06 +00:00
parent 75785aa642
commit 51292bb1b3
17 changed files with 895 additions and 99 deletions

View File

@@ -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,
]);

View File

@@ -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,

View File

@@ -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;
}
}