Refactor keyword handling: Replace 'intent' with 'country' across backend and frontend
- Updated AutomationService to include estimated_word_count. - Increased stage_1_batch_size from 20 to 50 in AutomationViewSet. - Changed Keywords model to replace 'intent' property with 'country'. - Adjusted ClusteringService to allow a maximum of 50 keywords for clustering. - Modified admin and management commands to remove 'intent' and use 'country' instead. - Updated serializers to reflect the change from 'intent' to 'country'. - Adjusted views and filters to use 'country' instead of 'intent'. - Updated frontend forms, filters, and pages to replace 'intent' with 'country'. - Added migration to remove 'intent' field and add 'country' field to SeedKeyword model.
This commit is contained in:
@@ -55,16 +55,19 @@ export const getKeywordFormConfig = (options?: {
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select',
|
||||
value: 'informational',
|
||||
value: 'US',
|
||||
onChange: () => {},
|
||||
options: [
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
|
||||
@@ -161,7 +161,7 @@ export function useImportExport(
|
||||
</p>
|
||||
{filename === 'keywords' && (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300 mt-2 p-2 bg-blue-50 dark:bg-blue-900/20 rounded border border-blue-200 dark:border-blue-800">
|
||||
<strong>Expected columns:</strong> keyword, volume, difficulty, intent, status
|
||||
<strong>Expected columns:</strong> keyword, volume, difficulty, country, status
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
keywordColumn,
|
||||
volumeColumn,
|
||||
difficultyColumn,
|
||||
intentColumn,
|
||||
countryColumn,
|
||||
clusterColumn,
|
||||
sectorColumn,
|
||||
statusColumn,
|
||||
@@ -106,7 +106,7 @@ export const createKeywordsPageConfig = (
|
||||
keyword?: string;
|
||||
volume?: number | null;
|
||||
difficulty?: number | null;
|
||||
intent?: string;
|
||||
country?: string;
|
||||
cluster_id?: number | null;
|
||||
status: string;
|
||||
};
|
||||
@@ -116,8 +116,8 @@ export const createKeywordsPageConfig = (
|
||||
setSearchTerm: (value: string) => void;
|
||||
statusFilter: string;
|
||||
setStatusFilter: (value: string) => void;
|
||||
intentFilter: string;
|
||||
setIntentFilter: (value: string) => void;
|
||||
countryFilter: string;
|
||||
setCountryFilter: (value: string) => void;
|
||||
difficultyFilter: string;
|
||||
setDifficultyFilter: (value: string) => void;
|
||||
clusterFilter: string;
|
||||
@@ -197,28 +197,23 @@ export const createKeywordsPageConfig = (
|
||||
},
|
||||
},
|
||||
{
|
||||
...intentColumn,
|
||||
sortable: false, // Backend doesn't support sorting by intent
|
||||
sortField: 'seed_keyword__intent',
|
||||
...countryColumn,
|
||||
sortable: false, // Backend doesn't support sorting by country
|
||||
sortField: 'seed_keyword__country',
|
||||
render: (value: string) => {
|
||||
// Map intent values to badge colors
|
||||
// Transactional and Commercial → success (green, like active)
|
||||
// Navigational → warning (amber/yellow, like pending)
|
||||
// Informational → info (blue)
|
||||
const getIntentColor = (intent: string) => {
|
||||
const lowerIntent = intent?.toLowerCase() || '';
|
||||
if (lowerIntent === 'transactional' || lowerIntent === 'commercial') {
|
||||
return 'success'; // Green, like active status
|
||||
} else if (lowerIntent === 'navigational') {
|
||||
return 'warning'; // Amber/yellow, like pending status
|
||||
}
|
||||
return 'info'; // Blue for informational or default
|
||||
const countryNames: Record<string, string> = {
|
||||
'US': 'United States',
|
||||
'CA': 'Canada',
|
||||
'GB': 'United Kingdom',
|
||||
'AE': 'United Arab Emirates',
|
||||
'AU': 'Australia',
|
||||
'IN': 'India',
|
||||
'PK': 'Pakistan',
|
||||
};
|
||||
|
||||
const properCase = value ? value.charAt(0).toUpperCase() + value.slice(1) : '-';
|
||||
const displayName = countryNames[value] || value || '-';
|
||||
return (
|
||||
<Badge color={getIntentColor(value)} size="xs" variant="soft">
|
||||
<span className="text-[11px] font-normal">{properCase}</span>
|
||||
<Badge color="info" size="xs" variant="soft">
|
||||
<span className="text-[11px] font-normal">{value || '-'}</span>
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
@@ -317,15 +312,18 @@ export const createKeywordsPageConfig = (
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Intent' },
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -541,18 +539,21 @@ export const createKeywordsPageConfig = (
|
||||
max: 100,
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Search Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select',
|
||||
value: handlers.formData.intent || 'informational',
|
||||
value: handlers.formData.country || 'US',
|
||||
onChange: (value: any) =>
|
||||
handlers.setFormData({ ...handlers.formData, intent: value }),
|
||||
handlers.setFormData({ ...handlers.formData, country: value }),
|
||||
required: true,
|
||||
options: [
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,9 +41,9 @@ export const difficultyColumn = {
|
||||
width: '120px',
|
||||
};
|
||||
|
||||
export const intentColumn = {
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
export const countryColumn = {
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
sortable: true,
|
||||
badge: true,
|
||||
width: '120px',
|
||||
|
||||
@@ -15,16 +15,19 @@ export const statusFilter = {
|
||||
],
|
||||
};
|
||||
|
||||
export const intentFilter = {
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
export const countryFilter = {
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Intent' },
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ interface DashboardStats {
|
||||
mapped: number;
|
||||
unmapped: number;
|
||||
byStatus: Record<string, number>;
|
||||
byIntent: Record<string, number>;
|
||||
byCountry: Record<string, number>;
|
||||
};
|
||||
clusters: {
|
||||
total: number;
|
||||
@@ -90,11 +90,11 @@ export default function PlannerDashboard() {
|
||||
const unmappedKeywords = keywords.filter(k => !k.cluster || k.cluster.length === 0);
|
||||
|
||||
const keywordsByStatus: Record<string, number> = {};
|
||||
const keywordsByIntent: Record<string, number> = {};
|
||||
const keywordsByCountry: Record<string, number> = {};
|
||||
keywords.forEach(k => {
|
||||
keywordsByStatus[k.status || 'unknown'] = (keywordsByStatus[k.status || 'unknown'] || 0) + 1;
|
||||
if (k.intent) {
|
||||
keywordsByIntent[k.intent] = (keywordsByIntent[k.intent] || 0) + 1;
|
||||
if (k.country) {
|
||||
keywordsByCountry[k.country] = (keywordsByCountry[k.country] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -135,7 +135,7 @@ export default function PlannerDashboard() {
|
||||
mapped: mappedKeywords.length,
|
||||
unmapped: unmappedKeywords.length,
|
||||
byStatus: keywordsByStatus,
|
||||
byIntent: keywordsByIntent
|
||||
byCountry: keywordsByCountry
|
||||
},
|
||||
clusters: {
|
||||
total: clusters.length,
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function KeywordOpportunities() {
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [intentFilter, setIntentFilter] = useState('');
|
||||
const [countryFilter, setCountryFilter] = useState('');
|
||||
const [difficultyFilter, setDifficultyFilter] = useState('');
|
||||
const [volumeMin, setVolumeMin] = useState<number | ''>('');
|
||||
const [volumeMax, setVolumeMax] = useState<number | ''>('');
|
||||
@@ -119,7 +119,7 @@ export default function KeywordOpportunities() {
|
||||
}
|
||||
|
||||
if (searchTerm) baseFilters.search = searchTerm;
|
||||
if (intentFilter) baseFilters.intent = intentFilter;
|
||||
if (countryFilter) baseFilters.country = countryFilter;
|
||||
|
||||
// Fetch ALL pages to get complete dataset
|
||||
let allResults: SeedKeyword[] = [];
|
||||
@@ -227,7 +227,7 @@ export default function KeywordOpportunities() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, intentFilter, difficultyFilter, volumeMin, volumeMax, sortBy, sortDirection]);
|
||||
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, countryFilter, difficultyFilter, volumeMin, volumeMax, sortBy, sortDirection]);
|
||||
|
||||
// Load data on mount and when filters change (excluding search - handled separately)
|
||||
useEffect(() => {
|
||||
@@ -504,28 +504,27 @@ export default function KeywordOpportunities() {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
sortable: true,
|
||||
sortField: 'intent',
|
||||
sortField: 'country',
|
||||
render: (value: string) => {
|
||||
const getIntentColor = (intent: string) => {
|
||||
const lowerIntent = intent?.toLowerCase() || '';
|
||||
if (lowerIntent === 'transactional' || lowerIntent === 'commercial') {
|
||||
return 'success';
|
||||
} else if (lowerIntent === 'navigational') {
|
||||
return 'warning';
|
||||
}
|
||||
return 'info';
|
||||
const countryNames: Record<string, string> = {
|
||||
'US': 'United States',
|
||||
'CA': 'Canada',
|
||||
'GB': 'United Kingdom',
|
||||
'AE': 'United Arab Emirates',
|
||||
'AU': 'Australia',
|
||||
'IN': 'India',
|
||||
'PK': 'Pakistan',
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge
|
||||
color={getIntentColor(value)}
|
||||
color="info"
|
||||
size="sm"
|
||||
variant={value?.toLowerCase() === 'informational' ? 'light' : undefined}
|
||||
variant="light"
|
||||
>
|
||||
{value}
|
||||
{value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
@@ -539,15 +538,18 @@ export default function KeywordOpportunities() {
|
||||
placeholder: 'Search keywords...',
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '', label: 'All Intent' },
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -612,7 +614,7 @@ export default function KeywordOpportunities() {
|
||||
filters={pageConfig.filters}
|
||||
filterValues={{
|
||||
search: searchTerm,
|
||||
intent: intentFilter,
|
||||
country: countryFilter,
|
||||
difficulty: difficultyFilter,
|
||||
}}
|
||||
onFilterChange={(key, value) => {
|
||||
@@ -620,8 +622,8 @@ export default function KeywordOpportunities() {
|
||||
|
||||
if (key === 'search') {
|
||||
setSearchTerm(stringValue);
|
||||
} else if (key === 'intent') {
|
||||
setIntentFilter(stringValue);
|
||||
} else if (key === 'country') {
|
||||
setCountryFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'difficulty') {
|
||||
setDifficultyFilter(stringValue);
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Keywords() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [clusterFilter, setClusterFilter] = useState('');
|
||||
const [intentFilter, setIntentFilter] = useState('');
|
||||
const [countryFilter, setCountryFilter] = useState('');
|
||||
const [difficultyFilter, setDifficultyFilter] = useState('');
|
||||
const [volumeMin, setVolumeMin] = useState<number | ''>('');
|
||||
const [volumeMax, setVolumeMax] = useState<number | ''>('');
|
||||
@@ -81,7 +81,7 @@ export default function Keywords() {
|
||||
keyword: '',
|
||||
volume: null,
|
||||
difficulty: null,
|
||||
intent: 'informational',
|
||||
country: 'US',
|
||||
cluster_id: null,
|
||||
status: 'new',
|
||||
});
|
||||
@@ -156,7 +156,7 @@ export default function Keywords() {
|
||||
...(searchTerm && { search: searchTerm }),
|
||||
...(statusFilter && { status: statusFilter }),
|
||||
...(clusterFilter && { cluster_id: clusterFilter }),
|
||||
...(intentFilter && { intent: intentFilter }),
|
||||
...(countryFilter && { country: countryFilter }),
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
page: currentPage,
|
||||
page_size: pageSize || 10, // Ensure we always send a page_size
|
||||
@@ -200,7 +200,7 @@ export default function Keywords() {
|
||||
setShowContent(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [currentPage, statusFilter, clusterFilter, intentFilter, difficultyFilter, volumeMin, volumeMax, sortBy, sortDirection, searchTerm, activeSite, activeSector, pageSize]);
|
||||
}, [currentPage, statusFilter, clusterFilter, countryFilter, difficultyFilter, volumeMin, volumeMax, sortBy, sortDirection, searchTerm, activeSite, activeSector, pageSize]);
|
||||
|
||||
// Listen for site and sector changes and refresh data
|
||||
useEffect(() => {
|
||||
@@ -324,8 +324,8 @@ export default function Keywords() {
|
||||
toast.error('Please select at least one keyword to cluster');
|
||||
return;
|
||||
}
|
||||
if (ids.length > 20) {
|
||||
toast.error('Maximum 20 keywords allowed for clustering');
|
||||
if (ids.length > 50) {
|
||||
toast.error('Maximum 50 keywords allowed for clustering');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ export default function Keywords() {
|
||||
keyword: '',
|
||||
volume: null,
|
||||
difficulty: null,
|
||||
intent: 'informational',
|
||||
country: 'US',
|
||||
cluster_id: null,
|
||||
status: 'new',
|
||||
});
|
||||
@@ -605,8 +605,8 @@ export default function Keywords() {
|
||||
setSearchTerm,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
intentFilter,
|
||||
setIntentFilter,
|
||||
countryFilter,
|
||||
setCountryFilter,
|
||||
difficultyFilter,
|
||||
setDifficultyFilter,
|
||||
clusterFilter,
|
||||
@@ -632,7 +632,7 @@ export default function Keywords() {
|
||||
formData,
|
||||
searchTerm,
|
||||
statusFilter,
|
||||
intentFilter,
|
||||
countryFilter,
|
||||
difficultyFilter,
|
||||
clusterFilter,
|
||||
volumeMin,
|
||||
@@ -776,7 +776,7 @@ export default function Keywords() {
|
||||
keyword: keyword.keyword,
|
||||
volume: keyword.volume,
|
||||
difficulty: keyword.difficulty,
|
||||
intent: keyword.intent,
|
||||
country: keyword.country,
|
||||
cluster_id: keyword.cluster_id,
|
||||
status: keyword.status,
|
||||
});
|
||||
@@ -807,7 +807,7 @@ export default function Keywords() {
|
||||
filterValues={{
|
||||
search: searchTerm,
|
||||
status: statusFilter,
|
||||
intent: intentFilter,
|
||||
country: countryFilter,
|
||||
difficulty: difficultyFilter,
|
||||
cluster_id: clusterFilter,
|
||||
volumeMin: volumeMin,
|
||||
@@ -823,8 +823,8 @@ export default function Keywords() {
|
||||
} else if (key === 'status') {
|
||||
setStatusFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'intent') {
|
||||
setIntentFilter(stringValue);
|
||||
} else if (key === 'country') {
|
||||
setCountryFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'difficulty') {
|
||||
setDifficultyFilter(stringValue);
|
||||
@@ -868,7 +868,7 @@ export default function Keywords() {
|
||||
search: searchTerm,
|
||||
status: statusFilter,
|
||||
cluster_id: clusterFilter,
|
||||
intent: intentFilter,
|
||||
country: countryFilter,
|
||||
difficulty: difficultyFilter,
|
||||
};
|
||||
await handleExport('csv', filterValues);
|
||||
@@ -903,7 +903,7 @@ export default function Keywords() {
|
||||
setSearchTerm('');
|
||||
setStatusFilter('');
|
||||
setClusterFilter('');
|
||||
setIntentFilter('');
|
||||
setCountryFilter('');
|
||||
setDifficultyFilter('');
|
||||
setVolumeMin('');
|
||||
setVolumeMax('');
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function SeedKeywords() {
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Sector</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Volume</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Difficulty</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Intent</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Country</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -109,7 +109,7 @@ export default function SeedKeywords() {
|
||||
{keyword.difficulty}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color="primary">{keyword.intent_display}</Badge>
|
||||
<Badge variant="light" color="info">{keyword.country_display}</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [intentFilter, setIntentFilter] = useState('');
|
||||
const [countryFilter, setCountryFilter] = useState('');
|
||||
const [difficultyFilter, setDifficultyFilter] = useState('');
|
||||
|
||||
// Check if user is admin/superuser (role is 'admin' or 'developer')
|
||||
@@ -153,7 +153,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
}
|
||||
|
||||
if (searchTerm) baseFilters.search = searchTerm;
|
||||
if (intentFilter) baseFilters.intent = intentFilter;
|
||||
if (countryFilter) baseFilters.country = countryFilter;
|
||||
|
||||
// Fetch ALL pages to get complete dataset
|
||||
let allResults: SeedKeyword[] = [];
|
||||
@@ -215,9 +215,9 @@ export default function IndustriesSectorsKeywords() {
|
||||
} else if (sortBy === 'difficulty') {
|
||||
aVal = a.difficulty;
|
||||
bVal = b.difficulty;
|
||||
} else if (sortBy === 'intent') {
|
||||
aVal = a.intent.toLowerCase();
|
||||
bVal = b.intent.toLowerCase();
|
||||
} else if (sortBy === 'country') {
|
||||
aVal = a.country.toLowerCase();
|
||||
bVal = b.country.toLowerCase();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
setTotalCount(0);
|
||||
setTotalPages(1);
|
||||
}
|
||||
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, intentFilter, difficultyFilter, sortBy, sortDirection, toast]);
|
||||
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, countryFilter, difficultyFilter, sortBy, sortDirection, toast]);
|
||||
|
||||
// Load data on mount and when filters change
|
||||
useEffect(() => {
|
||||
@@ -521,28 +521,18 @@ export default function IndustriesSectorsKeywords() {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
sortable: true,
|
||||
sortField: 'intent',
|
||||
sortField: 'country',
|
||||
render: (value: string) => {
|
||||
const getIntentColor = (intent: string) => {
|
||||
const lowerIntent = intent?.toLowerCase() || '';
|
||||
if (lowerIntent === 'transactional' || lowerIntent === 'commercial') {
|
||||
return 'success';
|
||||
} else if (lowerIntent === 'navigational') {
|
||||
return 'warning';
|
||||
}
|
||||
return 'info';
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge
|
||||
color={getIntentColor(value)}
|
||||
color="info"
|
||||
size="sm"
|
||||
variant={value?.toLowerCase() === 'informational' ? 'light' : undefined}
|
||||
variant="light"
|
||||
>
|
||||
{value}
|
||||
{value || '-'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
@@ -584,15 +574,18 @@ export default function IndustriesSectorsKeywords() {
|
||||
placeholder: 'Search keywords...',
|
||||
},
|
||||
{
|
||||
key: 'intent',
|
||||
label: 'Intent',
|
||||
key: 'country',
|
||||
label: 'Country',
|
||||
type: 'select' as const,
|
||||
options: [
|
||||
{ value: '', label: 'All Intent' },
|
||||
{ value: 'informational', label: 'Informational' },
|
||||
{ value: 'navigational', label: 'Navigational' },
|
||||
{ value: 'transactional', label: 'Transactional' },
|
||||
{ value: 'commercial', label: 'Commercial' },
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -691,7 +684,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
filters={pageConfig.filters}
|
||||
filterValues={{
|
||||
search: searchTerm,
|
||||
intent: intentFilter,
|
||||
country: countryFilter,
|
||||
difficulty: difficultyFilter,
|
||||
}}
|
||||
onFilterChange={(key, value) => {
|
||||
@@ -699,8 +692,8 @@ export default function IndustriesSectorsKeywords() {
|
||||
|
||||
if (key === 'search') {
|
||||
setSearchTerm(stringValue);
|
||||
} else if (key === 'intent') {
|
||||
setIntentFilter(stringValue);
|
||||
} else if (key === 'country') {
|
||||
setCountryFilter(stringValue);
|
||||
setCurrentPage(1);
|
||||
} else if (key === 'difficulty') {
|
||||
setDifficultyFilter(stringValue);
|
||||
@@ -762,7 +755,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
<div>
|
||||
<Label htmlFor="import-file">Upload CSV File</Label>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
Expected columns: keyword, volume, difficulty, intent, industry_name, sector_name
|
||||
Expected columns: keyword, volume, difficulty, country, industry_name, sector_name
|
||||
</p>
|
||||
<FileInput
|
||||
accept=".csv"
|
||||
|
||||
@@ -582,7 +582,7 @@ export interface KeywordFilters {
|
||||
search?: string;
|
||||
status?: string;
|
||||
cluster_id?: string;
|
||||
intent?: string;
|
||||
country?: string;
|
||||
difficulty_min?: number;
|
||||
difficulty_max?: number;
|
||||
volume_min?: number;
|
||||
@@ -608,7 +608,7 @@ export interface Keyword {
|
||||
keyword: string; // Read-only property from seed_keyword
|
||||
volume: number; // Read-only property from seed_keyword or volume_override
|
||||
difficulty: number; // Read-only property from seed_keyword or difficulty_override
|
||||
intent: string; // Read-only property from seed_keyword
|
||||
country: string; // Read-only property from seed_keyword
|
||||
volume_override?: number | null;
|
||||
difficulty_override?: number | null;
|
||||
cluster_id: number | null;
|
||||
@@ -623,7 +623,7 @@ export interface KeywordCreateData {
|
||||
keyword?: string; // For creating new custom keywords
|
||||
volume?: number | null; // For custom keywords
|
||||
difficulty?: number | null; // For custom keywords
|
||||
intent?: string; // For custom keywords
|
||||
country?: string; // For custom keywords
|
||||
seed_keyword_id?: number; // For linking existing seed keywords (optional)
|
||||
volume_override?: number | null;
|
||||
difficulty_override?: number | null;
|
||||
@@ -661,7 +661,7 @@ export async function fetchKeywords(filters: KeywordFilters = {}): Promise<Keywo
|
||||
if (filters.search) params.append('search', filters.search);
|
||||
if (filters.status) params.append('status', filters.status);
|
||||
if (filters.cluster_id) params.append('cluster_id', filters.cluster_id);
|
||||
if (filters.intent) params.append('seed_keyword__intent', filters.intent);
|
||||
if (filters.country) params.append('seed_keyword__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.volume_min !== undefined) params.append('volume_min', filters.volume_min.toString());
|
||||
@@ -695,12 +695,12 @@ export async function createKeyword(data: KeywordCreateData): Promise<Keyword> {
|
||||
requestData.custom_keyword = data.keyword;
|
||||
requestData.custom_volume = data.volume;
|
||||
requestData.custom_difficulty = data.difficulty;
|
||||
requestData.custom_intent = data.intent || 'informational';
|
||||
requestData.custom_country = data.country || 'US';
|
||||
// Remove the frontend-only fields
|
||||
delete requestData.keyword;
|
||||
delete requestData.volume;
|
||||
delete requestData.difficulty;
|
||||
delete requestData.intent;
|
||||
delete requestData.country;
|
||||
}
|
||||
|
||||
return fetchAPI('/v1/planner/keywords/', {
|
||||
@@ -2073,8 +2073,8 @@ export interface SeedKeyword {
|
||||
sector_slug: string;
|
||||
volume: number;
|
||||
difficulty: number;
|
||||
intent: string;
|
||||
intent_display: string;
|
||||
country: string;
|
||||
country_display: string;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -2090,7 +2090,7 @@ export interface SeedKeywordResponse {
|
||||
export async function fetchSeedKeywords(filters?: {
|
||||
industry?: number;
|
||||
sector?: number;
|
||||
intent?: string;
|
||||
country?: string;
|
||||
search?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
@@ -2105,7 +2105,7 @@ export async function fetchSeedKeywords(filters?: {
|
||||
params.append('sector', filters.sector.toString());
|
||||
params.append('sector_id', filters.sector.toString()); // Also send sector_id for get_queryset
|
||||
}
|
||||
if (filters?.intent) params.append('intent', filters.intent);
|
||||
if (filters?.country) params.append('country', filters.country);
|
||||
if (filters?.search) params.append('search', filters.search);
|
||||
if (filters?.page) params.append('page', filters.page.toString());
|
||||
if (filters?.page_size) params.append('page_size', filters.page_size.toString());
|
||||
|
||||
Reference in New Issue
Block a user