page adn app header mods
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchKeywords,
|
||||
@@ -346,6 +347,18 @@ export default function Keywords() {
|
||||
}
|
||||
}, [toast, activeSector, loadKeywords, progressModal, keywords]);
|
||||
|
||||
// Quick auto-cluster unclustered keywords (for Next Step button)
|
||||
const handleAutoCluster = useCallback(async () => {
|
||||
const unclusteredIds = keywords.filter(k => !k.cluster_id).map(k => k.id);
|
||||
if (unclusteredIds.length === 0) {
|
||||
toast.info('All keywords are already clustered');
|
||||
return;
|
||||
}
|
||||
// Limit to 50 keywords
|
||||
const idsToCluster = unclusteredIds.slice(0, 50);
|
||||
await handleBulkAction('auto_cluster', idsToCluster.map(String));
|
||||
}, [keywords, handleBulkAction, toast]);
|
||||
|
||||
// Reset reload flag when modal closes or opens
|
||||
useEffect(() => {
|
||||
if (!progressModal.isOpen) {
|
||||
@@ -481,63 +494,32 @@ export default function Keywords() {
|
||||
}, [pageConfig?.headerMetrics, keywords, totalCount, clusters]);
|
||||
|
||||
// Calculate workflow insights based on UX doc principles
|
||||
const workflowInsights = useMemo(() => {
|
||||
const insights = [];
|
||||
const workflowStats = useMemo(() => {
|
||||
const clusteredCount = keywords.filter(k => k.cluster_id).length;
|
||||
const unclusteredCount = totalCount - clusteredCount;
|
||||
const pipelineReadiness = totalCount > 0 ? Math.round((clusteredCount / totalCount) * 100) : 0;
|
||||
|
||||
return {
|
||||
total: totalCount,
|
||||
clustered: clusteredCount,
|
||||
unclustered: unclusteredCount,
|
||||
readiness: pipelineReadiness,
|
||||
};
|
||||
}, [keywords, totalCount]);
|
||||
|
||||
// Determine next step action
|
||||
const nextStep = useMemo(() => {
|
||||
if (totalCount === 0) {
|
||||
insights.push({
|
||||
type: 'info' as const,
|
||||
message: 'Import keywords to begin building your content strategy and unlock SEO opportunities',
|
||||
});
|
||||
return insights;
|
||||
return { label: 'Import Keywords', path: '/add-keywords', disabled: false };
|
||||
}
|
||||
|
||||
// Pipeline Readiness Score insight
|
||||
if (pipelineReadiness < 30) {
|
||||
insights.push({
|
||||
type: 'warning' as const,
|
||||
message: `Pipeline readiness at ${pipelineReadiness}% - Most keywords need clustering before content ideation can begin`,
|
||||
});
|
||||
} else if (pipelineReadiness < 60) {
|
||||
insights.push({
|
||||
type: 'info' as const,
|
||||
message: `Pipeline readiness at ${pipelineReadiness}% - Clustering progress is moderate, continue organizing keywords`,
|
||||
});
|
||||
} else if (pipelineReadiness >= 85) {
|
||||
insights.push({
|
||||
type: 'success' as const,
|
||||
message: `Excellent pipeline readiness (${pipelineReadiness}%) - Ready for content ideation phase`,
|
||||
});
|
||||
if (workflowStats.unclustered >= 5) {
|
||||
return { label: 'Auto-Cluster', action: 'cluster', disabled: false };
|
||||
}
|
||||
|
||||
// Clustering Potential (minimum batch size check)
|
||||
if (unclusteredCount >= 5) {
|
||||
insights.push({
|
||||
type: 'action' as const,
|
||||
message: `${unclusteredCount} keywords available for auto-clustering (minimum batch size met)`,
|
||||
});
|
||||
} else if (unclusteredCount > 0 && unclusteredCount < 5) {
|
||||
insights.push({
|
||||
type: 'info' as const,
|
||||
message: `${unclusteredCount} unclustered keywords - Need ${5 - unclusteredCount} more to run auto-cluster`,
|
||||
});
|
||||
if (workflowStats.clustered > 0) {
|
||||
return { label: 'Generate Ideas', path: '/planner/ideas', disabled: false };
|
||||
}
|
||||
|
||||
// Coverage Gaps - thin clusters that need more research
|
||||
const thinClusters = clusters.filter(c => (c.keywords_count || 0) === 1);
|
||||
if (thinClusters.length > 3) {
|
||||
const thinVolume = thinClusters.reduce((sum, c) => sum + (c.volume || 0), 0);
|
||||
insights.push({
|
||||
type: 'warning' as const,
|
||||
message: `${thinClusters.length} clusters have only 1 keyword each (${thinVolume.toLocaleString()} monthly volume) - Consider expanding research`,
|
||||
});
|
||||
}
|
||||
|
||||
return insights;
|
||||
}, [keywords, totalCount, clusters]);
|
||||
return { label: 'Add More Keywords', path: '/add-keywords', disabled: false };
|
||||
}, [totalCount, workflowStats]);
|
||||
|
||||
// Handle create/edit
|
||||
const handleSave = async () => {
|
||||
@@ -612,10 +594,37 @@ export default function Keywords() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Keywords"
|
||||
description="Your target search terms organized for content creation. Import, cluster, and transform into content ideas."
|
||||
description="Your target search terms organized for content creation"
|
||||
badge={{ icon: <ListIcon />, color: 'green' }}
|
||||
breadcrumb="Planner / Keywords"
|
||||
workflowInsights={workflowInsights}
|
||||
breadcrumb="Planner"
|
||||
actions={
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 hidden md:block">
|
||||
{workflowStats.clustered}/{workflowStats.total} clustered
|
||||
</span>
|
||||
{nextStep.path ? (
|
||||
<Link
|
||||
to={nextStep.path}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
{nextStep.label}
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
) : nextStep.action === 'cluster' ? (
|
||||
<button
|
||||
onClick={handleAutoCluster}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
{nextStep.label}
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
|
||||
Reference in New Issue
Block a user