fixes but still nto fixed
This commit is contained in:
@@ -94,7 +94,7 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<div className={`relative flex-shrink-0 ${className}`}>
|
||||
{/* Trigger Button - styled like igny8-select-styled */}
|
||||
<button
|
||||
ref={buttonRef}
|
||||
@@ -102,7 +102,7 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
|
||||
onClick={() => !disabled && setIsOpen(!isOpen)}
|
||||
disabled={disabled}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={`igny8-select-styled w-full appearance-none rounded-lg border border-gray-300 bg-transparent px-3 pr-10 shadow-theme-xs focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-800 ${
|
||||
className={`igny8-select-styled w-auto min-w-[120px] max-w-[360px] appearance-none rounded-lg border border-gray-300 bg-transparent px-3 pr-10 shadow-theme-xs focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-800 ${
|
||||
className.includes('text-base') ? 'h-11 py-2.5 text-base' : 'h-9 py-2 text-sm'
|
||||
} ${
|
||||
isPlaceholder
|
||||
@@ -124,7 +124,7 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="absolute z-50 left-0 right-0 mt-1 rounded-lg border border-gray-200 bg-white shadow-theme-lg dark:border-gray-800 dark:bg-gray-dark overflow-hidden max-h-60 overflow-y-auto"
|
||||
className="absolute z-50 left-0 mt-1 min-w-full rounded-lg border border-gray-200 bg-white shadow-theme-lg dark:border-gray-800 dark:bg-gray-dark overflow-hidden max-h-60 overflow-y-auto"
|
||||
>
|
||||
<div className="py-1">
|
||||
{options.map((option) => {
|
||||
|
||||
@@ -86,6 +86,11 @@ export const createIdeasPageConfig = (
|
||||
typeFilter: string;
|
||||
setTypeFilter: (value: string) => void;
|
||||
setCurrentPage: (page: number) => void;
|
||||
// Dynamic filter options
|
||||
statusOptions?: Array<{ value: string; label: string }>;
|
||||
contentTypeOptions?: Array<{ value: string; label: string }>;
|
||||
contentStructureOptions?: Array<{ value: string; label: string }>;
|
||||
clusterOptions?: Array<{ value: string; label: string }>;
|
||||
}
|
||||
): IdeasPageConfig => {
|
||||
const showSectorColumn = !handlers.activeSector; // Show when viewing all sectors
|
||||
@@ -233,9 +238,14 @@ export const createIdeasPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Status' },
|
||||
{ value: 'new', label: 'New' },
|
||||
{ value: 'queued', label: 'Queued' },
|
||||
{ value: 'completed', label: 'Completed' },
|
||||
...(handlers.statusOptions && handlers.statusOptions.length > 0
|
||||
? handlers.statusOptions
|
||||
: [
|
||||
{ value: 'new', label: 'New' },
|
||||
{ value: 'queued', label: 'Queued' },
|
||||
{ value: 'completed', label: 'Completed' },
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -244,24 +254,25 @@ export const createIdeasPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Structures' },
|
||||
// Post
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
// Page
|
||||
{ value: 'landing_page', label: 'Landing Page' },
|
||||
{ value: 'business_page', label: 'Business Page' },
|
||||
{ value: 'service_page', label: 'Service Page' },
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||
// Product
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
// Taxonomy
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
...(handlers.contentStructureOptions && handlers.contentStructureOptions.length > 0
|
||||
? handlers.contentStructureOptions
|
||||
: [
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ value: 'landing_page', label: 'Landing Page' },
|
||||
{ value: 'business_page', label: 'Business Page' },
|
||||
{ value: 'service_page', label: 'Service Page' },
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -270,23 +281,29 @@ export const createIdeasPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Types' },
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
...(handlers.contentTypeOptions && handlers.contentTypeOptions.length > 0
|
||||
? handlers.contentTypeOptions
|
||||
: [
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'keyword_cluster_id',
|
||||
label: 'Cluster',
|
||||
type: 'select',
|
||||
options: (() => {
|
||||
return [
|
||||
{ value: '', label: 'All Clusters' },
|
||||
...handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name })),
|
||||
];
|
||||
})(),
|
||||
dynamicOptions: 'clusters',
|
||||
options: [
|
||||
{ value: '', label: 'All Clusters' },
|
||||
...(handlers.clusterOptions && handlers.clusterOptions.length > 0
|
||||
? handlers.clusterOptions
|
||||
: handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name }))
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
formFields: (clusters: Array<{ id: number; name: string }>) => [
|
||||
|
||||
@@ -140,6 +140,7 @@ export const createKeywordsPageConfig = (
|
||||
countryOptions?: Array<{ value: string; label: string }>;
|
||||
statusOptions?: Array<{ value: string; label: string }>;
|
||||
clusterOptions?: Array<{ value: string; label: string }>;
|
||||
difficultyOptions?: Array<{ value: string; label: string }>;
|
||||
}
|
||||
): KeywordsPageConfig => {
|
||||
const showSectorColumn = !handlers.activeSector; // Show when viewing all sectors
|
||||
@@ -308,11 +309,16 @@ export const createKeywordsPageConfig = (
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Difficulty' },
|
||||
{ value: '1', label: '1 - Very Easy' },
|
||||
{ value: '2', label: '2 - Easy' },
|
||||
{ value: '3', label: '3 - Medium' },
|
||||
{ value: '4', label: '4 - Hard' },
|
||||
{ value: '5', label: '5 - Very Hard' },
|
||||
...(handlers.difficultyOptions && handlers.difficultyOptions.length > 0
|
||||
? handlers.difficultyOptions
|
||||
: [
|
||||
{ value: '1', label: '1 - Very Easy' },
|
||||
{ value: '2', label: '2 - Easy' },
|
||||
{ value: '3', label: '3 - Medium' },
|
||||
{ value: '4', label: '4 - Hard' },
|
||||
{ value: '5', label: '5 - Very Hard' },
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -448,19 +454,6 @@ export const createKeywordsPageConfig = (
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'cluster_id',
|
||||
label: 'Cluster',
|
||||
type: 'select',
|
||||
options: (() => {
|
||||
// Dynamically generate options from current clusters
|
||||
return [
|
||||
{ value: '', label: 'All Clusters' },
|
||||
...handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name })),
|
||||
];
|
||||
})(),
|
||||
className: 'w-40',
|
||||
},
|
||||
],
|
||||
headerMetrics: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,87 @@
|
||||
/**
|
||||
* Structure mapping configuration
|
||||
* Maps content types to their valid structures and provides label mappings
|
||||
* Also contains shared filter options for reuse across pages
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// SHARED FILTER OPTIONS - Use these in page configs to avoid duplication
|
||||
// ============================================================================
|
||||
|
||||
/** Status options for Keywords page */
|
||||
export const KEYWORD_STATUS_OPTIONS = [
|
||||
{ value: '', label: 'All Status' },
|
||||
{ value: 'new', label: 'New' },
|
||||
{ value: 'mapped', label: 'Mapped' },
|
||||
];
|
||||
|
||||
/** Status options for Clusters page */
|
||||
export const CLUSTER_STATUS_OPTIONS = [
|
||||
{ value: '', label: 'All Status' },
|
||||
{ value: 'new', label: 'New' },
|
||||
{ value: 'mapped', label: 'Mapped' },
|
||||
];
|
||||
|
||||
/** Status options for Ideas page */
|
||||
export const IDEAS_STATUS_OPTIONS = [
|
||||
{ value: '', label: 'All Status' },
|
||||
{ value: 'new', label: 'New' },
|
||||
{ value: 'queued', label: 'Queued' },
|
||||
{ value: 'completed', label: 'Completed' },
|
||||
];
|
||||
|
||||
/** Status options for Content/Review/Approved pages */
|
||||
export const CONTENT_STATUS_OPTIONS = [
|
||||
{ value: '', label: 'All Status' },
|
||||
{ value: 'draft', label: 'Draft' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'approved', label: 'Approved' },
|
||||
{ value: 'published', label: 'Published' },
|
||||
];
|
||||
|
||||
/** Site status options for content publishing */
|
||||
export const SITE_STATUS_OPTIONS = [
|
||||
{ value: '', label: 'All Site Status' },
|
||||
{ value: 'not_published', label: 'Not Published' },
|
||||
{ value: 'scheduled', label: 'Scheduled' },
|
||||
{ value: 'publishing', label: 'Publishing' },
|
||||
{ value: 'published', label: 'Published' },
|
||||
{ value: 'failed', label: 'Failed' },
|
||||
];
|
||||
|
||||
/** Source options for content */
|
||||
export const SOURCE_OPTIONS = [
|
||||
{ value: '', label: 'All Sources' },
|
||||
{ value: 'igny8', label: 'IGNY8' },
|
||||
{ value: 'wordpress', label: 'WordPress' },
|
||||
];
|
||||
|
||||
/** Country options - used for keywords */
|
||||
export const COUNTRY_OPTIONS = [
|
||||
{ value: '', label: 'All Countries' },
|
||||
{ value: 'US', label: 'United States' },
|
||||
{ value: 'CA', label: 'Canada' },
|
||||
{ value: 'GB', label: 'United Kingdom' },
|
||||
{ value: 'AE', label: 'United Arab Emirates' },
|
||||
{ value: 'AU', label: 'Australia' },
|
||||
{ value: 'IN', label: 'India' },
|
||||
{ value: 'PK', label: 'Pakistan' },
|
||||
];
|
||||
|
||||
/** Difficulty options (1-5 scale) */
|
||||
export const DIFFICULTY_OPTIONS = [
|
||||
{ value: '', label: 'All Difficulty' },
|
||||
{ value: '1', label: '1 - Very Easy' },
|
||||
{ value: '2', label: '2 - Easy' },
|
||||
{ value: '3', label: '3 - Medium' },
|
||||
{ value: '4', label: '4 - Hard' },
|
||||
{ value: '5', label: '5 - Very Hard' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// CONTENT TYPE AND STRUCTURE OPTIONS
|
||||
// ============================================================================
|
||||
|
||||
export const CONTENT_TYPE_OPTIONS = [
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
@@ -10,6 +89,35 @@ export const CONTENT_TYPE_OPTIONS = [
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
];
|
||||
|
||||
/** Content type filter options (with "All" option) */
|
||||
export const CONTENT_TYPE_FILTER_OPTIONS = [
|
||||
{ value: '', label: 'All Types' },
|
||||
...CONTENT_TYPE_OPTIONS,
|
||||
];
|
||||
|
||||
/** Content structure filter options (with "All" option) */
|
||||
export const CONTENT_STRUCTURE_FILTER_OPTIONS = [
|
||||
{ value: '', label: 'All Structures' },
|
||||
// Post structures
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
// Page structures
|
||||
{ value: 'landing_page', label: 'Landing Page' },
|
||||
{ value: 'business_page', label: 'Business Page' },
|
||||
{ value: 'service_page', label: 'Service Page' },
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||
// Product structures
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
// Taxonomy structures
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
];
|
||||
|
||||
export const CONTENT_STRUCTURE_BY_TYPE: Record<string, Array<{ value: string; label: string }>> = {
|
||||
post: [
|
||||
{ value: 'article', label: 'Article' },
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
ContentIdeaCreateData,
|
||||
fetchClusters,
|
||||
Cluster,
|
||||
fetchPlannerIdeasFilterOptions,
|
||||
FilterOption,
|
||||
} from '../../services/api';
|
||||
import FormModal from '../../components/common/FormModal';
|
||||
import ProgressModal from '../../components/common/ProgressModal';
|
||||
@@ -49,6 +51,12 @@ export default function Ideas() {
|
||||
const [totalPending, setTotalPending] = useState(0);
|
||||
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[]>([]);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -85,7 +93,7 @@ export default function Ideas() {
|
||||
// Progress modal for AI functions
|
||||
const progressModal = useProgressModal();
|
||||
|
||||
// Load clusters for filter dropdown
|
||||
// Load clusters for form dropdown (all clusters)
|
||||
useEffect(() => {
|
||||
const loadClusters = async () => {
|
||||
try {
|
||||
@@ -98,6 +106,26 @@ export default function Ideas() {
|
||||
loadClusters();
|
||||
}, []);
|
||||
|
||||
// Load dynamic filter options based on current site's data
|
||||
const loadFilterOptions = useCallback(async () => {
|
||||
if (!activeSite) return;
|
||||
|
||||
try {
|
||||
const options = await fetchPlannerIdeasFilterOptions(activeSite.id);
|
||||
setStatusOptions(options.statuses || []);
|
||||
setContentTypeOptions(options.content_types || []);
|
||||
setContentStructureOptions(options.content_structures || []);
|
||||
setClusterOptions(options.clusters || []);
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite]);
|
||||
|
||||
// Load filter options when site changes
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
|
||||
// Load total metrics for footer widget (site-wide totals, no sector filter)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
@@ -302,8 +330,13 @@ export default function Ideas() {
|
||||
typeFilter,
|
||||
setTypeFilter,
|
||||
setCurrentPage,
|
||||
// Dynamic filter options
|
||||
statusOptions,
|
||||
contentTypeOptions,
|
||||
contentStructureOptions,
|
||||
clusterOptions,
|
||||
});
|
||||
}, [clusters, activeSector, formData, searchTerm, statusFilter, clusterFilter, structureFilter, typeFilter]);
|
||||
}, [clusters, activeSector, formData, searchTerm, statusFilter, clusterFilter, structureFilter, typeFilter, statusOptions, contentTypeOptions, contentStructureOptions, clusterOptions]);
|
||||
|
||||
// Calculate header metrics - use totalInTasks/totalPending from API calls (not page data)
|
||||
// This ensures metrics show correct totals across all pages, not just current page
|
||||
|
||||
@@ -60,6 +60,7 @@ export default function Keywords() {
|
||||
const [countryOptions, setCountryOptions] = useState<FilterOption[]>([]);
|
||||
const [statusOptions, setStatusOptions] = useState<FilterOption[]>([]);
|
||||
const [clusterOptions, setClusterOptions] = useState<FilterOption[]>([]);
|
||||
const [difficultyOptions, setDifficultyOptions] = useState<FilterOption[]>([]);
|
||||
|
||||
// Filter state - match Keywords.tsx
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -123,24 +124,62 @@ export default function Keywords() {
|
||||
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;
|
||||
country?: string;
|
||||
cluster_id?: string;
|
||||
difficulty_min?: number;
|
||||
difficulty_max?: number;
|
||||
}) => {
|
||||
if (!activeSite) return;
|
||||
|
||||
try {
|
||||
const options = await fetchPlannerKeywordFilterOptions(activeSite.id);
|
||||
const options = await fetchPlannerKeywordFilterOptions(activeSite.id, currentFilters);
|
||||
setCountryOptions(options.countries || []);
|
||||
setStatusOptions(options.statuses || []);
|
||||
setClusterOptions(options.clusters || []);
|
||||
setDifficultyOptions(options.difficulties || []);
|
||||
} catch (error) {
|
||||
console.error('Error loading filter options:', error);
|
||||
}
|
||||
}, [activeSite]);
|
||||
|
||||
// Load filter options when site changes
|
||||
// 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 filter options when site changes (initial load with no filters)
|
||||
useEffect(() => {
|
||||
loadFilterOptions();
|
||||
}, [loadFilterOptions]);
|
||||
}, [activeSite]);
|
||||
|
||||
// Reload filter options when any filter changes (cascading filters)
|
||||
useEffect(() => {
|
||||
const { min: difficultyMin, max: difficultyMax } = getDifficultyRange(difficultyFilter);
|
||||
loadFilterOptions({
|
||||
status: statusFilter || undefined,
|
||||
country: countryFilter || undefined,
|
||||
cluster_id: clusterFilter || undefined,
|
||||
difficulty_min: difficultyMin,
|
||||
difficulty_max: difficultyMax,
|
||||
});
|
||||
}, [statusFilter, countryFilter, clusterFilter, difficultyFilter, loadFilterOptions, getDifficultyRange]);
|
||||
|
||||
// Load total metrics for footer widget (site-wide totals, no sector filter)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
@@ -553,6 +592,7 @@ export default function Keywords() {
|
||||
countryOptions,
|
||||
statusOptions,
|
||||
clusterOptions,
|
||||
difficultyOptions,
|
||||
});
|
||||
}, [
|
||||
clusters,
|
||||
@@ -573,6 +613,7 @@ export default function Keywords() {
|
||||
countryOptions,
|
||||
statusOptions,
|
||||
clusterOptions,
|
||||
difficultyOptions,
|
||||
]);
|
||||
|
||||
// Calculate header metrics - use totalClustered/totalUnmapped from API calls (not page data)
|
||||
@@ -715,7 +756,7 @@ export default function Keywords() {
|
||||
status: statusFilter,
|
||||
country: countryFilter,
|
||||
difficulty: difficultyFilter,
|
||||
cluster_id: clusterFilter,
|
||||
cluster: clusterFilter,
|
||||
volumeMin: volumeMin,
|
||||
volumeMax: volumeMax,
|
||||
}}
|
||||
@@ -741,7 +782,7 @@ export default function Keywords() {
|
||||
} else if (key === 'difficulty') {
|
||||
setDifficultyFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'cluster_id') {
|
||||
} else if (key === 'cluster') {
|
||||
setClusterFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
|
||||
@@ -751,11 +751,70 @@ export interface PlannerKeywordFilterOptions {
|
||||
countries: FilterOption[];
|
||||
statuses: FilterOption[];
|
||||
clusters: FilterOption[];
|
||||
difficulties: FilterOption[];
|
||||
}
|
||||
|
||||
export async function fetchPlannerKeywordFilterOptions(siteId?: number): Promise<PlannerKeywordFilterOptions> {
|
||||
// Filter options request with current filter state for cascading
|
||||
export interface KeywordFilterOptionsRequest {
|
||||
site_id?: number;
|
||||
status?: string;
|
||||
country?: string;
|
||||
difficulty_min?: number;
|
||||
difficulty_max?: number;
|
||||
cluster_id?: string;
|
||||
}
|
||||
|
||||
export async function fetchPlannerKeywordFilterOptions(
|
||||
siteId?: number,
|
||||
filters?: KeywordFilterOptionsRequest
|
||||
): Promise<PlannerKeywordFilterOptions> {
|
||||
const params = new URLSearchParams();
|
||||
if (siteId) params.append('site_id', siteId.toString());
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
if (filters?.country) params.append('country', filters.country);
|
||||
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?.cluster_id) params.append('cluster_id', filters.cluster_id);
|
||||
|
||||
const queryString = params.toString();
|
||||
return fetchAPI(`/v1/planner/keywords/filter_options/${queryString ? `?${queryString}` : ''}`);
|
||||
}
|
||||
|
||||
// Clusters filter options
|
||||
export interface PlannerClusterFilterOptions {
|
||||
statuses: FilterOption[];
|
||||
}
|
||||
|
||||
export async function fetchPlannerClusterFilterOptions(siteId?: number): Promise<PlannerClusterFilterOptions> {
|
||||
const queryParams = siteId ? `?site_id=${siteId}` : '';
|
||||
return fetchAPI(`/v1/planner/keywords/filter_options/${queryParams}`);
|
||||
return fetchAPI(`/v1/planner/clusters/filter_options/${queryParams}`);
|
||||
}
|
||||
|
||||
// Ideas filter options
|
||||
export interface PlannerIdeasFilterOptions {
|
||||
statuses: FilterOption[];
|
||||
content_types: FilterOption[];
|
||||
content_structures: FilterOption[];
|
||||
clusters: FilterOption[];
|
||||
}
|
||||
|
||||
export async function fetchPlannerIdeasFilterOptions(siteId?: number): Promise<PlannerIdeasFilterOptions> {
|
||||
const queryParams = siteId ? `?site_id=${siteId}` : '';
|
||||
return fetchAPI(`/v1/planner/ideas/filter_options/${queryParams}`);
|
||||
}
|
||||
|
||||
// Content filter options (Writer module)
|
||||
export interface WriterContentFilterOptions {
|
||||
statuses: FilterOption[];
|
||||
site_statuses: FilterOption[];
|
||||
content_types: FilterOption[];
|
||||
content_structures: FilterOption[];
|
||||
sources: FilterOption[];
|
||||
}
|
||||
|
||||
export async function fetchWriterContentFilterOptions(siteId?: number): Promise<WriterContentFilterOptions> {
|
||||
const queryParams = siteId ? `?site_id=${siteId}` : '';
|
||||
return fetchAPI(`/v1/writer/content/filter_options/${queryParams}`);
|
||||
}
|
||||
|
||||
// Clusters-specific API functions
|
||||
|
||||
@@ -517,18 +517,23 @@
|
||||
SECTION 6: FORM ELEMENT STYLES
|
||||
=================================================================== */
|
||||
|
||||
/* Styled Select/Dropdown with custom chevron */
|
||||
.igny8-select-styled {
|
||||
/* Styled Select/Dropdown with custom chevron - for native <select> elements */
|
||||
select.igny8-select-styled {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%23647085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: right 12px center !important;
|
||||
padding-right: 36px !important;
|
||||
}
|
||||
|
||||
.dark .igny8-select-styled {
|
||||
.dark select.igny8-select-styled {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%2398A2B3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
|
||||
}
|
||||
|
||||
/* For button-based selects (SelectDropdown), icon is rendered by component */
|
||||
button.igny8-select-styled {
|
||||
/* No background-image - ChevronDownIcon is rendered inside the component */
|
||||
}
|
||||
|
||||
/* Checkbox styling */
|
||||
.tableCheckbox:checked ~ span span { @apply opacity-100; }
|
||||
.tableCheckbox:checked ~ span { @apply border-brand-500 bg-brand-500; }
|
||||
|
||||
@@ -736,8 +736,8 @@ export default function TablePageTemplate({
|
||||
{/* Filters Row - Below action buttons, left aligned with shadow */}
|
||||
{showFilters && (renderFilters || filters.length > 0) && (
|
||||
<div className="flex justify-start py-1.5 mb-2.5">
|
||||
<div className="bg-gray-50 dark:bg-gray-800/30 rounded-lg px-4 py-2 border border-gray-200 dark:border-gray-700 shadow-md">
|
||||
<div className="flex gap-3 items-center flex-wrap">
|
||||
<div className="inline-flex bg-gray-50 dark:bg-gray-800/30 rounded-lg px-4 py-2 border border-gray-200 dark:border-gray-700 shadow-md">
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
{renderFilters ? (
|
||||
renderFilters
|
||||
) : (
|
||||
@@ -757,7 +757,7 @@ export default function TablePageTemplate({
|
||||
onChange={(e) => {
|
||||
onFilterChange?.(filter.key, e.target.value);
|
||||
}}
|
||||
className="w-full sm:flex-1 h-8"
|
||||
className="w-48 h-8"
|
||||
/>
|
||||
);
|
||||
} else if (filter.type === 'select') {
|
||||
@@ -772,7 +772,6 @@ export default function TablePageTemplate({
|
||||
const newValue = value === null || value === undefined ? '' : String(value);
|
||||
onFilterChange?.(filter.key, newValue);
|
||||
}}
|
||||
className={filter.className || "w-full sm:flex-1"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user