|
|
|
|
@@ -20,8 +20,6 @@ import {
|
|
|
|
|
Cluster,
|
|
|
|
|
API_BASE_URL,
|
|
|
|
|
autoClusterKeywords,
|
|
|
|
|
fetchSeedKeywords,
|
|
|
|
|
SeedKeyword,
|
|
|
|
|
} from '../../services/api';
|
|
|
|
|
import { useSiteStore } from '../../store/siteStore';
|
|
|
|
|
import { useSectorStore } from '../../store/sectorStore';
|
|
|
|
|
@@ -48,9 +46,7 @@ export default function Keywords() {
|
|
|
|
|
// Data state
|
|
|
|
|
const [keywords, setKeywords] = useState<Keyword[]>([]);
|
|
|
|
|
const [clusters, setClusters] = useState<Cluster[]>([]);
|
|
|
|
|
const [availableSeedKeywords, setAvailableSeedKeywords] = useState<SeedKeyword[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [loadingSeedKeywords, setLoadingSeedKeywords] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Filter state - match Keywords.tsx
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
|
@@ -82,9 +78,10 @@ export default function Keywords() {
|
|
|
|
|
const [isEditMode, setIsEditMode] = useState(false);
|
|
|
|
|
const [editingKeyword, setEditingKeyword] = useState<Keyword | null>(null);
|
|
|
|
|
const [formData, setFormData] = useState<KeywordCreateData>({
|
|
|
|
|
seed_keyword_id: 0,
|
|
|
|
|
volume_override: null,
|
|
|
|
|
difficulty_override: null,
|
|
|
|
|
keyword: '',
|
|
|
|
|
volume: null,
|
|
|
|
|
difficulty: null,
|
|
|
|
|
intent: 'informational',
|
|
|
|
|
cluster_id: null,
|
|
|
|
|
status: 'new',
|
|
|
|
|
});
|
|
|
|
|
@@ -131,43 +128,6 @@ export default function Keywords() {
|
|
|
|
|
}
|
|
|
|
|
}, [activeSite, loadSectorsForSite]);
|
|
|
|
|
|
|
|
|
|
// Load available SeedKeywords when site and sector are selected
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const loadAvailableSeedKeywords = async () => {
|
|
|
|
|
if (!activeSite || !activeSector || !activeSite.industry) {
|
|
|
|
|
setAvailableSeedKeywords([]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
setLoadingSeedKeywords(true);
|
|
|
|
|
// Fetch SeedKeywords for the site's industry and sector's industry_sector
|
|
|
|
|
const response = await fetchSeedKeywords({
|
|
|
|
|
industry: activeSite.industry,
|
|
|
|
|
sector: activeSector.industry_sector || undefined,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Filter out SeedKeywords that are already attached to this site/sector
|
|
|
|
|
const attachedSeedKeywordIds = new Set(
|
|
|
|
|
keywords.map(k => k.seed_keyword_id)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const available = (response.results || []).filter(
|
|
|
|
|
sk => !attachedSeedKeywordIds.has(sk.id)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setAvailableSeedKeywords(available);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('Failed to load available seed keywords:', error);
|
|
|
|
|
setAvailableSeedKeywords([]);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoadingSeedKeywords(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadAvailableSeedKeywords();
|
|
|
|
|
}, [activeSite, activeSector, keywords]);
|
|
|
|
|
|
|
|
|
|
// Load clusters for filter dropdown
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const loadClusters = async () => {
|
|
|
|
|
@@ -573,11 +533,12 @@ export default function Keywords() {
|
|
|
|
|
|
|
|
|
|
const resetForm = useCallback(() => {
|
|
|
|
|
setFormData({
|
|
|
|
|
seed_keyword_id: 0,
|
|
|
|
|
volume_override: null,
|
|
|
|
|
difficulty_override: null,
|
|
|
|
|
keyword: '',
|
|
|
|
|
volume: null,
|
|
|
|
|
difficulty: null,
|
|
|
|
|
intent: 'informational',
|
|
|
|
|
cluster_id: null,
|
|
|
|
|
status: 'pending',
|
|
|
|
|
status: 'new',
|
|
|
|
|
});
|
|
|
|
|
setIsEditMode(false);
|
|
|
|
|
setEditingKeyword(null);
|
|
|
|
|
@@ -637,7 +598,6 @@ export default function Keywords() {
|
|
|
|
|
return createKeywordsPageConfig({
|
|
|
|
|
clusters,
|
|
|
|
|
activeSector,
|
|
|
|
|
availableSeedKeywords,
|
|
|
|
|
formData,
|
|
|
|
|
setFormData,
|
|
|
|
|
// Filter state handlers
|
|
|
|
|
@@ -669,7 +629,6 @@ export default function Keywords() {
|
|
|
|
|
}, [
|
|
|
|
|
clusters,
|
|
|
|
|
activeSector,
|
|
|
|
|
availableSeedKeywords,
|
|
|
|
|
formData,
|
|
|
|
|
searchTerm,
|
|
|
|
|
statusFilter,
|
|
|
|
|
@@ -714,8 +673,18 @@ export default function Keywords() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!formData.seed_keyword_id) {
|
|
|
|
|
toast.error('Please select a seed keyword');
|
|
|
|
|
if (!formData.keyword?.trim()) {
|
|
|
|
|
toast.error('Please enter a keyword');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (formData.volume === null || formData.volume === undefined) {
|
|
|
|
|
toast.error('Please enter search volume');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (formData.difficulty === null || formData.difficulty === undefined) {
|
|
|
|
|
toast.error('Please enter difficulty score');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -727,14 +696,15 @@ export default function Keywords() {
|
|
|
|
|
sector_id: sectorId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
console.log('Creating keyword with data:', keywordData);
|
|
|
|
|
await createKeyword(keywordData);
|
|
|
|
|
toast.success('Keyword attached successfully');
|
|
|
|
|
toast.success('Keyword created successfully');
|
|
|
|
|
}
|
|
|
|
|
setIsModalOpen(false);
|
|
|
|
|
resetForm();
|
|
|
|
|
loadKeywords();
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
toast.error(`Failed to save: ${error.message}`);
|
|
|
|
|
toast.error(error.message || 'Unable to save keyword. Please try again.');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -743,9 +713,10 @@ export default function Keywords() {
|
|
|
|
|
setEditingKeyword(keyword);
|
|
|
|
|
setIsEditMode(true);
|
|
|
|
|
setFormData({
|
|
|
|
|
seed_keyword_id: keyword.seed_keyword_id,
|
|
|
|
|
volume_override: keyword.volume_override || null,
|
|
|
|
|
difficulty_override: keyword.difficulty_override || null,
|
|
|
|
|
keyword: keyword.keyword,
|
|
|
|
|
volume: keyword.volume,
|
|
|
|
|
difficulty: keyword.difficulty,
|
|
|
|
|
intent: keyword.intent,
|
|
|
|
|
cluster_id: keyword.cluster_id,
|
|
|
|
|
status: keyword.status,
|
|
|
|
|
});
|
|
|
|
|
@@ -918,7 +889,7 @@ export default function Keywords() {
|
|
|
|
|
|
|
|
|
|
{/* Create/Edit Modal */}
|
|
|
|
|
<FormModal
|
|
|
|
|
key={`keyword-form-${isEditMode ? editingKeyword?.id : 'new'}-${formData.seed_keyword_id}-${formData.status}`}
|
|
|
|
|
key={`keyword-form-${isEditMode ? editingKeyword?.id : 'new'}`}
|
|
|
|
|
isOpen={isModalOpen}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
setIsModalOpen(false);
|
|
|
|
|
|