updates
This commit is contained in:
@@ -4,10 +4,11 @@ import PageMeta from "../../components/common/PageMeta";
|
|||||||
import ComponentCard from "../../components/common/ComponentCard";
|
import ComponentCard from "../../components/common/ComponentCard";
|
||||||
import { ProgressBar } from "../../components/ui/progress";
|
import { ProgressBar } from "../../components/ui/progress";
|
||||||
import { ApexOptions } from "apexcharts";
|
import { ApexOptions } from "apexcharts";
|
||||||
import WorkflowPipeline, { WorkflowStep } from "../../components/dashboard/WorkflowPipeline";
|
|
||||||
import EnhancedMetricCard from "../../components/dashboard/EnhancedMetricCard";
|
import EnhancedMetricCard from "../../components/dashboard/EnhancedMetricCard";
|
||||||
|
import PageHeader from "../../components/common/PageHeader";
|
||||||
|
|
||||||
const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default })));
|
const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default })));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ListIcon,
|
ListIcon,
|
||||||
GroupIcon,
|
GroupIcon,
|
||||||
@@ -17,7 +18,9 @@ import {
|
|||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
TimeIcon,
|
TimeIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
ArrowDownIcon
|
ArrowDownIcon,
|
||||||
|
PlugInIcon,
|
||||||
|
ClockIcon,
|
||||||
} from "../../icons";
|
} from "../../icons";
|
||||||
import {
|
import {
|
||||||
fetchKeywords,
|
fetchKeywords,
|
||||||
@@ -27,7 +30,6 @@ import {
|
|||||||
} from "../../services/api";
|
} from "../../services/api";
|
||||||
import { useSiteStore } from "../../store/siteStore";
|
import { useSiteStore } from "../../store/siteStore";
|
||||||
import { useSectorStore } from "../../store/sectorStore";
|
import { useSectorStore } from "../../store/sectorStore";
|
||||||
import PageHeader from "../../components/common/PageHeader";
|
|
||||||
|
|
||||||
interface DashboardStats {
|
interface DashboardStats {
|
||||||
keywords: {
|
keywords: {
|
||||||
@@ -68,18 +70,12 @@ export default function PlannerDashboard() {
|
|||||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [lastUpdated, setLastUpdated] = useState<Date>(new Date());
|
const [lastUpdated, setLastUpdated] = useState<Date>(new Date());
|
||||||
const [trends, setTrends] = useState<{
|
|
||||||
keywords: number;
|
|
||||||
clusters: number;
|
|
||||||
ideas: number;
|
|
||||||
}>({ keywords: 0, clusters: 0, ideas: 0 });
|
|
||||||
|
|
||||||
// Fetch real data
|
// Fetch real data
|
||||||
const fetchDashboardData = async () => {
|
const fetchDashboardData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Fetch all data in parallel
|
|
||||||
const [keywordsRes, clustersRes, ideasRes, tasksRes] = await Promise.all([
|
const [keywordsRes, clustersRes, ideasRes, tasksRes] = await Promise.all([
|
||||||
fetchKeywords({ page_size: 1000, sector_id: activeSector?.id }),
|
fetchKeywords({ page_size: 1000, sector_id: activeSector?.id }),
|
||||||
fetchClusters({ page_size: 1000, sector_id: activeSector?.id }),
|
fetchClusters({ page_size: 1000, sector_id: activeSector?.id }),
|
||||||
@@ -87,7 +83,6 @@ export default function PlannerDashboard() {
|
|||||||
fetchTasks({ page_size: 1000, sector_id: activeSector?.id })
|
fetchTasks({ page_size: 1000, sector_id: activeSector?.id })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Process keywords
|
|
||||||
const keywords = keywordsRes.results || [];
|
const keywords = keywordsRes.results || [];
|
||||||
const mappedKeywords = keywords.filter(k => k.cluster && k.cluster.length > 0);
|
const mappedKeywords = keywords.filter(k => k.cluster && k.cluster.length > 0);
|
||||||
const unmappedKeywords = keywords.filter(k => !k.cluster || k.cluster.length === 0);
|
const unmappedKeywords = keywords.filter(k => !k.cluster || k.cluster.length === 0);
|
||||||
@@ -101,14 +96,12 @@ export default function PlannerDashboard() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process clusters
|
|
||||||
const clusters = clustersRes.results || [];
|
const clusters = clustersRes.results || [];
|
||||||
const clustersWithIdeas = clusters.filter(c => c.keywords_count > 0);
|
const clustersWithIdeas = clusters.filter(c => c.keywords_count > 0);
|
||||||
const totalVolume = clusters.reduce((sum, c) => sum + (c.volume || 0), 0);
|
const totalVolume = clusters.reduce((sum, c) => sum + (c.volume || 0), 0);
|
||||||
const totalKeywordsInClusters = clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0);
|
const totalKeywordsInClusters = clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0);
|
||||||
const avgKeywords = clusters.length > 0 ? Math.round(totalKeywordsInClusters / clusters.length) : 0;
|
const avgKeywords = clusters.length > 0 ? Math.round(totalKeywordsInClusters / clusters.length) : 0;
|
||||||
|
|
||||||
// Top clusters by volume
|
|
||||||
const topClusters = [...clusters]
|
const topClusters = [...clusters]
|
||||||
.sort((a, b) => (b.volume || 0) - (a.volume || 0))
|
.sort((a, b) => (b.volume || 0) - (a.volume || 0))
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
@@ -119,7 +112,6 @@ export default function PlannerDashboard() {
|
|||||||
keywords_count: c.keywords_count || 0
|
keywords_count: c.keywords_count || 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Process ideas
|
|
||||||
const ideas = ideasRes.results || [];
|
const ideas = ideasRes.results || [];
|
||||||
const ideaIds = new Set(ideas.map(i => i.id));
|
const ideaIds = new Set(ideas.map(i => i.id));
|
||||||
const tasks = tasksRes.results || [];
|
const tasks = tasksRes.results || [];
|
||||||
@@ -135,15 +127,6 @@ export default function PlannerDashboard() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate trends (compare with previous state)
|
|
||||||
if (stats) {
|
|
||||||
setTrends({
|
|
||||||
keywords: keywords.length - stats.keywords.total,
|
|
||||||
clusters: clusters.length - stats.clusters.total,
|
|
||||||
ideas: ideas.length - stats.ideas.total
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
keywords: {
|
keywords: {
|
||||||
total: keywords.length,
|
total: keywords.length,
|
||||||
@@ -183,18 +166,12 @@ export default function PlannerDashboard() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial load and periodic refresh
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Allow loading for all sectors (when activeSector is null) or specific sector
|
|
||||||
fetchDashboardData();
|
fetchDashboardData();
|
||||||
|
|
||||||
// Refresh every 30 seconds
|
|
||||||
const interval = setInterval(fetchDashboardData, 30000);
|
const interval = setInterval(fetchDashboardData, 30000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [activeSector?.id, activeSite?.id]);
|
}, [activeSector?.id, activeSite?.id]);
|
||||||
|
|
||||||
// Calculate percentages
|
|
||||||
const keywordMappingPct = useMemo(() => {
|
const keywordMappingPct = useMemo(() => {
|
||||||
if (!stats || stats.keywords.total === 0) return 0;
|
if (!stats || stats.keywords.total === 0) return 0;
|
||||||
return Math.round((stats.keywords.mapped / stats.keywords.total) * 100);
|
return Math.round((stats.keywords.mapped / stats.keywords.total) * 100);
|
||||||
@@ -210,7 +187,122 @@ export default function PlannerDashboard() {
|
|||||||
return Math.round((stats.ideas.queued / stats.ideas.total) * 100);
|
return Math.round((stats.ideas.queued / stats.ideas.total) * 100);
|
||||||
}, [stats]);
|
}, [stats]);
|
||||||
|
|
||||||
// Chart data for keywords by status
|
const plannerModules = [
|
||||||
|
{
|
||||||
|
title: "Keywords",
|
||||||
|
description: "Manage and discover keywords",
|
||||||
|
icon: ListIcon,
|
||||||
|
color: "from-[#0693e3] to-[#0472b8]",
|
||||||
|
path: "/planner/keywords",
|
||||||
|
count: stats?.keywords.total || 0,
|
||||||
|
metric: `${stats?.keywords.mapped || 0} mapped`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Clusters",
|
||||||
|
description: "Keyword clusters and groups",
|
||||||
|
icon: GroupIcon,
|
||||||
|
color: "from-[#0bbf87] to-[#08966b]",
|
||||||
|
path: "/planner/clusters",
|
||||||
|
count: stats?.clusters.total || 0,
|
||||||
|
metric: `${stats?.clusters.totalVolume.toLocaleString() || 0} volume`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Ideas",
|
||||||
|
description: "Content ideas and concepts",
|
||||||
|
icon: BoltIcon,
|
||||||
|
color: "from-[#ff7a00] to-[#cc5f00]",
|
||||||
|
path: "/planner/ideas",
|
||||||
|
count: stats?.ideas.total || 0,
|
||||||
|
metric: `${stats?.ideas.queued || 0} queued`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Keyword Opportunities",
|
||||||
|
description: "Discover new keyword opportunities",
|
||||||
|
icon: PieChartIcon,
|
||||||
|
color: "from-[#5d4ae3] to-[#3a2f94]",
|
||||||
|
path: "/planner/keyword-opportunities",
|
||||||
|
count: 0,
|
||||||
|
metric: "Discover new keywords",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const recentActivity = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: "Keywords Clustered",
|
||||||
|
description: `${stats?.clusters.total || 0} new clusters created`,
|
||||||
|
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||||
|
icon: GroupIcon,
|
||||||
|
color: "text-green-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: "Ideas Generated",
|
||||||
|
description: `${stats?.ideas.total || 0} content ideas created`,
|
||||||
|
timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000),
|
||||||
|
icon: BoltIcon,
|
||||||
|
color: "text-orange-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: "Keywords Added",
|
||||||
|
description: `${stats?.keywords.total || 0} keywords in database`,
|
||||||
|
timestamp: new Date(Date.now() - 6 * 60 * 60 * 1000),
|
||||||
|
icon: ListIcon,
|
||||||
|
color: "text-blue-600",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const chartOptions: ApexOptions = {
|
||||||
|
chart: {
|
||||||
|
type: "area",
|
||||||
|
height: 300,
|
||||||
|
toolbar: { show: false },
|
||||||
|
zoom: { enabled: false },
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 3,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
labels: { style: { colors: "#6b7280" } },
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: { style: { colors: "#6b7280" } },
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "top",
|
||||||
|
labels: { colors: "#6b7280" },
|
||||||
|
},
|
||||||
|
colors: ["#0693e3", "#0bbf87", "#ff7a00"],
|
||||||
|
grid: {
|
||||||
|
borderColor: "#e5e7eb",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
gradient: {
|
||||||
|
opacityFrom: 0.6,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartSeries = [
|
||||||
|
{
|
||||||
|
name: "Keywords Added",
|
||||||
|
data: [12, 19, 15, 25, 22, 18, 24],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Clusters Created",
|
||||||
|
data: [8, 12, 10, 15, 14, 11, 16],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ideas Generated",
|
||||||
|
data: [5, 8, 6, 10, 9, 7, 11],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const keywordsStatusChart = useMemo(() => {
|
const keywordsStatusChart = useMemo(() => {
|
||||||
if (!stats) return null;
|
if (!stats) return null;
|
||||||
|
|
||||||
@@ -228,16 +320,7 @@ export default function PlannerDashboard() {
|
|||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: false // Disable labels on pie slices
|
enabled: false
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
y: {
|
|
||||||
formatter: (val: number, { seriesIndex, w }: any) => {
|
|
||||||
const label = w.globals.labels[seriesIndex] || '';
|
|
||||||
return `${label}: ${val}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
pie: {
|
pie: {
|
||||||
@@ -245,9 +328,7 @@ export default function PlannerDashboard() {
|
|||||||
size: '70%',
|
size: '70%',
|
||||||
labels: {
|
labels: {
|
||||||
show: true,
|
show: true,
|
||||||
name: {
|
name: { show: false },
|
||||||
show: false // Hide "Total" label
|
|
||||||
},
|
|
||||||
value: {
|
value: {
|
||||||
show: true,
|
show: true,
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
@@ -259,9 +340,7 @@ export default function PlannerDashboard() {
|
|||||||
return total > 0 ? total.toString() : '0';
|
return total > 0 ? total.toString() : '0';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
total: {
|
total: { show: false }
|
||||||
show: false // Hide total label
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,57 +354,6 @@ export default function PlannerDashboard() {
|
|||||||
return { options, series };
|
return { options, series };
|
||||||
}, [stats]);
|
}, [stats]);
|
||||||
|
|
||||||
// Chart data for ideas by status
|
|
||||||
const ideasStatusChart = useMemo(() => {
|
|
||||||
if (!stats) return null;
|
|
||||||
|
|
||||||
const options: ApexOptions = {
|
|
||||||
chart: {
|
|
||||||
type: 'bar',
|
|
||||||
fontFamily: 'Outfit, sans-serif',
|
|
||||||
toolbar: { show: false },
|
|
||||||
height: 250
|
|
||||||
},
|
|
||||||
colors: ['#465FFF'],
|
|
||||||
plotOptions: {
|
|
||||||
bar: {
|
|
||||||
horizontal: false,
|
|
||||||
columnWidth: '55%',
|
|
||||||
borderRadius: 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
categories: Object.keys(stats.ideas.byStatus),
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
fontFamily: 'Outfit'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
fontFamily: 'Outfit'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
strokeDashArray: 4
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const series = [{
|
|
||||||
name: 'Ideas',
|
|
||||||
data: Object.values(stats.ideas.byStatus)
|
|
||||||
}];
|
|
||||||
|
|
||||||
return { options, series };
|
|
||||||
}, [stats]);
|
|
||||||
|
|
||||||
// Chart data for top clusters volume
|
|
||||||
const topClustersChart = useMemo(() => {
|
const topClustersChart = useMemo(() => {
|
||||||
if (!stats || stats.clusters.topClusters.length === 0) return null;
|
if (!stats || stats.clusters.topClusters.length === 0) return null;
|
||||||
|
|
||||||
@@ -382,6 +410,15 @@ export default function PlannerDashboard() {
|
|||||||
return { options, series };
|
return { options, series };
|
||||||
}, [stats]);
|
}, [stats]);
|
||||||
|
|
||||||
|
const formatTimeAgo = (date: Date) => {
|
||||||
|
const minutes = Math.floor((Date.now() - date.getTime()) / 60000);
|
||||||
|
if (minutes < 60) return `${minutes}m ago`;
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return `${hours}h ago`;
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
return `${days}d ago`;
|
||||||
|
};
|
||||||
|
|
||||||
if (loading && !stats) {
|
if (loading && !stats) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -409,339 +446,63 @@ export default function PlannerDashboard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stats) {
|
if (!stats) return null;
|
||||||
return null; // Still loading
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowSteps = [
|
|
||||||
{
|
|
||||||
number: 1,
|
|
||||||
title: "Add Keywords",
|
|
||||||
status: stats.workflow.keywordsReady ? "completed" : "pending",
|
|
||||||
count: stats.keywords.total,
|
|
||||||
path: "/planner/keywords"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: 2,
|
|
||||||
title: "Auto Cluster",
|
|
||||||
status: stats.workflow.clustersBuilt ? "completed" : "pending",
|
|
||||||
count: stats.clusters.total,
|
|
||||||
path: "/planner/clusters"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: 3,
|
|
||||||
title: "Generate Ideas",
|
|
||||||
status: stats.workflow.ideasGenerated ? "completed" : "pending",
|
|
||||||
count: stats.ideas.total,
|
|
||||||
path: "/planner/ideas"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: 4,
|
|
||||||
title: "Queue to Writer",
|
|
||||||
status: stats.workflow.readyForWriter ? "completed" : "pending",
|
|
||||||
count: stats.ideas.queued,
|
|
||||||
path: "/writer/tasks"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const nextActions = [
|
|
||||||
...(stats.keywords.unmapped > 0 ? [{
|
|
||||||
text: `${stats.keywords.unmapped} keywords unmapped`,
|
|
||||||
action: "Map Keywords",
|
|
||||||
path: "/planner/keywords"
|
|
||||||
}] : []),
|
|
||||||
...(stats.clusters.withoutIdeas > 0 ? [{
|
|
||||||
text: `${stats.clusters.withoutIdeas} clusters without ideas`,
|
|
||||||
action: "Generate Ideas",
|
|
||||||
path: "/planner/clusters"
|
|
||||||
}] : []),
|
|
||||||
...(stats.ideas.notQueued > 0 ? [{
|
|
||||||
text: `${stats.ideas.notQueued} ideas not queued to writer`,
|
|
||||||
action: "Queue to Writer",
|
|
||||||
path: "/planner/ideas"
|
|
||||||
}] : [])
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Planner Dashboard - IGNY8" description="Content planning overview" />
|
<PageMeta title="Planner Dashboard - IGNY8" description="Content planning overview" />
|
||||||
|
<PageHeader
|
||||||
<div className="space-y-5 sm:space-y-6">
|
title="Planner Dashboard"
|
||||||
{/* Header with site/sector info and controls */}
|
lastUpdated={lastUpdated}
|
||||||
<PageHeader
|
showRefresh={true}
|
||||||
title="Planner Dashboard"
|
onRefresh={fetchDashboardData}
|
||||||
lastUpdated={lastUpdated}
|
/>
|
||||||
showRefresh={true}
|
|
||||||
onRefresh={fetchDashboardData}
|
|
||||||
badge={{ icon: <PieChartIcon />, color: 'blue' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Hero Section - Key Metric */}
|
<div className="space-y-6">
|
||||||
<div className="rounded-2xl border border-gray-200 bg-gradient-to-br from-brand-50 to-white dark:from-brand-500/10 dark:to-gray-800/50 dark:border-gray-800 p-6 md:p-8">
|
{/* Key Metrics */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">Planning Progress</p>
|
|
||||||
<h3 className="mt-2 text-3xl font-bold text-gray-800 dark:text-white/90">
|
|
||||||
{stats.ideas.queued > 0 ? (
|
|
||||||
<>
|
|
||||||
{stats.ideas.queued} Ideas Ready for Content Generation
|
|
||||||
</>
|
|
||||||
) : stats.ideas.total > 0 ? (
|
|
||||||
<>
|
|
||||||
{stats.ideas.total} Ideas Generated
|
|
||||||
</>
|
|
||||||
) : stats.clusters.total > 0 ? (
|
|
||||||
<>
|
|
||||||
{stats.clusters.total} Clusters Built
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{stats.keywords.total} Keywords Ready
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{stats.keywords.total} keywords • {stats.clusters.total} clusters • {stats.ideas.total} ideas
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="hidden md:flex items-center gap-4">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="text-2xl font-bold text-brand-500">{keywordMappingPct}%</div>
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">Mapped</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="text-2xl font-bold text-success-500">{clustersIdeasPct}%</div>
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">With Ideas</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="text-2xl font-bold text-warning-500">{ideasQueuedPct}%</div>
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">Queued</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Enhanced Metric Cards */}
|
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4 md:gap-6">
|
|
||||||
<EnhancedMetricCard
|
<EnhancedMetricCard
|
||||||
title="Keywords Ready"
|
title="Total Keywords"
|
||||||
value={stats.keywords.total}
|
value={stats.keywords.total}
|
||||||
subtitle={`${stats.keywords.mapped} mapped • ${stats.keywords.unmapped} unmapped`}
|
subtitle={`${stats.keywords.mapped} mapped • ${stats.keywords.unmapped} unmapped`}
|
||||||
trend={trends.keywords}
|
|
||||||
icon={<ListIcon className="size-6" />}
|
icon={<ListIcon className="size-6" />}
|
||||||
accentColor="blue"
|
accentColor="blue"
|
||||||
|
trend={0}
|
||||||
href="/planner/keywords"
|
href="/planner/keywords"
|
||||||
details={[
|
|
||||||
{ label: "Total Keywords", value: stats.keywords.total },
|
|
||||||
{ label: "Mapped", value: stats.keywords.mapped },
|
|
||||||
{ label: "Unmapped", value: stats.keywords.unmapped },
|
|
||||||
{ label: "Active", value: stats.keywords.byStatus.active || 0 },
|
|
||||||
{ label: "Pending", value: stats.keywords.byStatus.pending || 0 },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EnhancedMetricCard
|
<EnhancedMetricCard
|
||||||
title="Clusters Built"
|
title="Clusters Built"
|
||||||
value={stats.clusters.total}
|
value={stats.clusters.total}
|
||||||
subtitle={`${stats.clusters.totalVolume.toLocaleString()} total volume • ${stats.clusters.avgKeywords} avg keywords`}
|
subtitle={`${stats.clusters.totalVolume.toLocaleString()} volume • ${stats.clusters.avgKeywords} avg keywords`}
|
||||||
trend={trends.clusters}
|
|
||||||
icon={<GroupIcon className="size-6" />}
|
icon={<GroupIcon className="size-6" />}
|
||||||
accentColor="green"
|
accentColor="green"
|
||||||
|
trend={0}
|
||||||
href="/planner/clusters"
|
href="/planner/clusters"
|
||||||
details={[
|
|
||||||
{ label: "Total Clusters", value: stats.clusters.total },
|
|
||||||
{ label: "With Ideas", value: stats.clusters.withIdeas },
|
|
||||||
{ label: "Without Ideas", value: stats.clusters.withoutIdeas },
|
|
||||||
{ label: "Total Volume", value: stats.clusters.totalVolume.toLocaleString() },
|
|
||||||
{ label: "Avg Keywords", value: stats.clusters.avgKeywords },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EnhancedMetricCard
|
<EnhancedMetricCard
|
||||||
title="Ideas Generated"
|
title="Ideas Generated"
|
||||||
value={stats.ideas.total}
|
value={stats.ideas.total}
|
||||||
subtitle={`${stats.ideas.queued} queued • ${stats.ideas.notQueued} pending`}
|
subtitle={`${stats.ideas.queued} queued • ${stats.ideas.notQueued} pending`}
|
||||||
trend={trends.ideas}
|
|
||||||
icon={<BoltIcon className="size-6" />}
|
icon={<BoltIcon className="size-6" />}
|
||||||
accentColor="orange"
|
accentColor="orange"
|
||||||
|
trend={0}
|
||||||
href="/planner/ideas"
|
href="/planner/ideas"
|
||||||
details={[
|
|
||||||
{ label: "Total Ideas", value: stats.ideas.total },
|
|
||||||
{ label: "Queued", value: stats.ideas.queued },
|
|
||||||
{ label: "Not Queued", value: stats.ideas.notQueued },
|
|
||||||
{ label: "New", value: stats.ideas.byStatus.new || 0 },
|
|
||||||
{ label: "Scheduled", value: stats.ideas.byStatus.scheduled || 0 },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EnhancedMetricCard
|
<EnhancedMetricCard
|
||||||
title="Mapping Progress"
|
title="Mapping Progress"
|
||||||
value={`${keywordMappingPct}%`}
|
value={`${keywordMappingPct}%`}
|
||||||
subtitle={`${stats.keywords.mapped} of ${stats.keywords.total} keywords mapped`}
|
subtitle={`${stats.keywords.mapped} of ${stats.keywords.total} keywords mapped`}
|
||||||
icon={<PieChartIcon className="size-6" />}
|
icon={<PieChartIcon className="size-6" />}
|
||||||
accentColor="purple"
|
accentColor="purple"
|
||||||
|
trend={0}
|
||||||
href="/planner/keywords"
|
href="/planner/keywords"
|
||||||
details={[
|
|
||||||
{ label: "Mapping Progress", value: `${keywordMappingPct}%` },
|
|
||||||
{ label: "Mapped Keywords", value: stats.keywords.mapped },
|
|
||||||
{ label: "Total Keywords", value: stats.keywords.total },
|
|
||||||
{ label: "Unmapped", value: stats.keywords.unmapped },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Interactive Workflow Pipeline */}
|
{/* Planner Modules */}
|
||||||
<ComponentCard title="Planner Workflow Pipeline" desc="Track your planning progress through each stage">
|
|
||||||
<WorkflowPipeline
|
|
||||||
steps={workflowSteps.map(step => ({
|
|
||||||
number: step.number,
|
|
||||||
title: step.title,
|
|
||||||
status: step.status === "completed" ? "completed" : step.status === "in_progress" ? "in_progress" : "pending",
|
|
||||||
count: step.count || 0,
|
|
||||||
path: step.path,
|
|
||||||
description: step.title,
|
|
||||||
details: step.status === "completed"
|
|
||||||
? `✓ ${step.title} completed with ${step.count} items`
|
|
||||||
: step.status === "pending"
|
|
||||||
? `→ ${step.title} pending - ${step.count} items ready`
|
|
||||||
: `⟳ ${step.title} in progress`,
|
|
||||||
}))}
|
|
||||||
onStepClick={(step) => {
|
|
||||||
navigate(step.path);
|
|
||||||
}}
|
|
||||||
showConnections={true}
|
|
||||||
/>
|
|
||||||
</ComponentCard>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
||||||
{/* Progress Summary */}
|
|
||||||
<ComponentCard title="Progress & Readiness Summary" desc="Planning workflow progress tracking" className="lg:col-span-1">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Keyword Mapping</span>
|
|
||||||
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{keywordMappingPct}%</span>
|
|
||||||
</div>
|
|
||||||
<ProgressBar value={keywordMappingPct} color="primary" size="md" />
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{stats.keywords.mapped} of {stats.keywords.total} keywords mapped
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Clusters With Ideas</span>
|
|
||||||
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{clustersIdeasPct}%</span>
|
|
||||||
</div>
|
|
||||||
<ProgressBar value={clustersIdeasPct} color="success" size="md" />
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{stats.clusters.withIdeas} of {stats.clusters.total} clusters have ideas
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Ideas Queued to Writer</span>
|
|
||||||
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{ideasQueuedPct}%</span>
|
|
||||||
</div>
|
|
||||||
<ProgressBar value={ideasQueuedPct} color="warning" size="md" />
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{stats.ideas.queued} of {stats.ideas.total} ideas queued
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ComponentCard>
|
|
||||||
|
|
||||||
{/* Top 5 Clusters */}
|
|
||||||
<ComponentCard title="Top 5 Clusters by Volume" desc="Highest volume keyword clusters" className="lg:col-span-2">
|
|
||||||
{topClustersChart ? (
|
|
||||||
<Suspense fallback={<div className="flex items-center justify-center h-[300px]"><div className="animate-spin rounded-full h-8 w-8 border-4 border-brand-500 border-t-transparent"></div></div>}>
|
|
||||||
<Chart
|
|
||||||
options={topClustersChart.options}
|
|
||||||
series={topClustersChart.series}
|
|
||||||
type="bar"
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
||||||
No clusters data available
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ComponentCard>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
||||||
{/* Keywords by Status */}
|
|
||||||
{keywordsStatusChart && (
|
|
||||||
<ComponentCard title="Keywords by Status" desc="Distribution of keywords across statuses">
|
|
||||||
<Suspense fallback={<div className="flex items-center justify-center h-[300px]"><div className="animate-spin rounded-full h-8 w-8 border-4 border-brand-500 border-t-transparent"></div></div>}>
|
|
||||||
<Chart
|
|
||||||
options={keywordsStatusChart.options}
|
|
||||||
series={keywordsStatusChart.series}
|
|
||||||
type="donut"
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</ComponentCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Ideas by Status */}
|
|
||||||
{ideasStatusChart && (
|
|
||||||
<ComponentCard title="Ideas by Status" desc="Content ideas workflow status">
|
|
||||||
<Suspense fallback={<div className="flex items-center justify-center h-[300px]"><div className="animate-spin rounded-full h-8 w-8 border-4 border-brand-500 border-t-transparent"></div></div>}>
|
|
||||||
<Chart
|
|
||||||
options={ideasStatusChart.options}
|
|
||||||
series={ideasStatusChart.series}
|
|
||||||
type="bar"
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</ComponentCard>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Planner Modules */}
|
|
||||||
<ComponentCard title="Planner Modules" desc="Access all planning tools and features">
|
<ComponentCard title="Planner Modules" desc="Access all planning tools and features">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{[
|
{plannerModules.map((module) => {
|
||||||
{
|
|
||||||
title: "Keywords",
|
|
||||||
description: "Manage and discover keywords",
|
|
||||||
icon: ListIcon,
|
|
||||||
color: "from-[#0693e3] to-[#0472b8]",
|
|
||||||
path: "/planner/keywords",
|
|
||||||
count: stats.keywords.total,
|
|
||||||
metric: `${stats.keywords.mapped} mapped`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Clusters",
|
|
||||||
description: "Keyword clusters and groups",
|
|
||||||
icon: GroupIcon,
|
|
||||||
color: "from-[#0bbf87] to-[#08966b]",
|
|
||||||
path: "/planner/clusters",
|
|
||||||
count: stats.clusters.total,
|
|
||||||
metric: `${stats.clusters.totalVolume.toLocaleString()} volume`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Ideas",
|
|
||||||
description: "Content ideas and concepts",
|
|
||||||
icon: BoltIcon,
|
|
||||||
color: "from-[#ff7a00] to-[#cc5f00]",
|
|
||||||
path: "/planner/ideas",
|
|
||||||
count: stats.ideas.total,
|
|
||||||
metric: `${stats.ideas.queued} queued`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Keyword Opportunities",
|
|
||||||
description: "Discover new keyword opportunities",
|
|
||||||
icon: PieChartIcon,
|
|
||||||
color: "from-[#5d4ae3] to-[#3a2f94]",
|
|
||||||
path: "/planner/keyword-opportunities",
|
|
||||||
count: 0,
|
|
||||||
metric: "Discover new keywords",
|
|
||||||
},
|
|
||||||
].map((module) => {
|
|
||||||
const Icon = module.icon;
|
const Icon = module.icon;
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -769,89 +530,246 @@ export default function PlannerDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
</ComponentCard>
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Activity Chart & Recent Activity */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<ComponentCard title="Planning Activity" desc="Keywords, clusters, and ideas over the past week">
|
||||||
|
<Suspense fallback={<div className="h-[300px] flex items-center justify-center">Loading chart...</div>}>
|
||||||
|
<Chart options={chartOptions} series={chartSeries} type="area" height={300} />
|
||||||
|
</Suspense>
|
||||||
|
</ComponentCard>
|
||||||
|
|
||||||
|
<ComponentCard title="Recent Activity" desc="Latest planning actions and updates">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{recentActivity.map((activity) => {
|
||||||
|
const Icon = activity.icon;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={activity.id}
|
||||||
|
className="flex items-center gap-4 p-4 rounded-lg border border-slate-200 bg-white hover:shadow-md transition"
|
||||||
|
>
|
||||||
|
<div className={`size-10 rounded-lg bg-gradient-to-br from-slate-100 to-slate-200 flex items-center justify-center ${activity.color}`}>
|
||||||
|
<Icon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<h4 className="font-semibold text-slate-900">{activity.type}</h4>
|
||||||
|
<span className="text-xs text-slate-500">{formatTimeAgo(activity.timestamp)}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-slate-600">{activity.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ComponentCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Charts */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{keywordsStatusChart && (
|
||||||
|
<ComponentCard title="Keywords by Status" desc="Distribution of keywords across statuses">
|
||||||
|
<Suspense fallback={<div className="h-[300px] flex items-center justify-center">Loading chart...</div>}>
|
||||||
|
<Chart
|
||||||
|
options={keywordsStatusChart.options}
|
||||||
|
series={keywordsStatusChart.series}
|
||||||
|
type="donut"
|
||||||
|
height={300}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</ComponentCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{topClustersChart && (
|
||||||
|
<ComponentCard title="Top 5 Clusters by Volume" desc="Highest volume keyword clusters">
|
||||||
|
<Suspense fallback={<div className="h-[300px] flex items-center justify-center">Loading chart...</div>}>
|
||||||
|
<Chart
|
||||||
|
options={topClustersChart.options}
|
||||||
|
series={topClustersChart.series}
|
||||||
|
type="bar"
|
||||||
|
height={300}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</ComponentCard>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Summary */}
|
||||||
|
<ComponentCard title="Planning Progress" desc="Track your planning workflow progress">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Keyword Mapping</span>
|
||||||
|
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{keywordMappingPct}%</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar value={keywordMappingPct} color="primary" size="md" />
|
||||||
|
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{stats.keywords.mapped} of {stats.keywords.total} keywords mapped
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Clusters With Ideas</span>
|
||||||
|
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{clustersIdeasPct}%</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar value={clustersIdeasPct} color="success" size="md" />
|
||||||
|
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{stats.clusters.withIdeas} of {stats.clusters.total} clusters have ideas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Ideas Queued to Writer</span>
|
||||||
|
<span className="text-sm font-semibold text-gray-800 dark:text-white/90">{ideasQueuedPct}%</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar value={ideasQueuedPct} color="warning" size="md" />
|
||||||
|
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{stats.ideas.queued} of {stats.ideas.total} ideas queued
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ComponentCard>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
<ComponentCard title="Quick Actions" desc="Common planning tasks and shortcuts">
|
<ComponentCard title="Quick Actions" desc="Common planning tasks and shortcuts">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<Link
|
<Link
|
||||||
to="/planner/keyword-opportunities"
|
to="/planner/keyword-opportunities"
|
||||||
className="flex items-center gap-4 p-4 rounded-lg border-2 border-slate-200 bg-white hover:border-[#0693e3] hover:shadow-md transition group"
|
className="flex items-center gap-4 p-6 rounded-xl border-2 border-slate-200 bg-white hover:border-[#0693e3] hover:shadow-lg transition-all group"
|
||||||
>
|
>
|
||||||
<div className="size-12 rounded-lg bg-gradient-to-br from-[#0693e3] to-[#0472b8] flex items-center justify-center text-white">
|
<div className="size-12 rounded-xl bg-gradient-to-br from-[#0693e3] to-[#0472b8] flex items-center justify-center text-white shadow-lg">
|
||||||
<ListIcon className="h-6 w-6" />
|
<ListIcon className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 text-left">
|
||||||
<h4 className="font-semibold text-slate-900">Add Keywords</h4>
|
<h4 className="font-semibold text-slate-900 mb-1">Add Keywords</h4>
|
||||||
<p className="text-xs text-slate-600">Discover opportunities</p>
|
<p className="text-sm text-slate-600">Discover opportunities</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#0693e3] transition" />
|
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#0693e3] transition" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/planner/clusters"
|
to="/planner/clusters"
|
||||||
className="flex items-center gap-4 p-4 rounded-lg border-2 border-slate-200 bg-white hover:border-[#0bbf87] hover:shadow-md transition group"
|
className="flex items-center gap-4 p-6 rounded-xl border-2 border-slate-200 bg-white hover:border-[#0bbf87] hover:shadow-lg transition-all group"
|
||||||
>
|
>
|
||||||
<div className="size-12 rounded-lg bg-gradient-to-br from-[#0bbf87] to-[#08966b] flex items-center justify-center text-white">
|
<div className="size-12 rounded-xl bg-gradient-to-br from-[#0bbf87] to-[#08966b] flex items-center justify-center text-white shadow-lg">
|
||||||
<GroupIcon className="h-6 w-6" />
|
<GroupIcon className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 text-left">
|
||||||
<h4 className="font-semibold text-slate-900">Auto Cluster</h4>
|
<h4 className="font-semibold text-slate-900 mb-1">Auto Cluster</h4>
|
||||||
<p className="text-xs text-slate-600">Group keywords</p>
|
<p className="text-sm text-slate-600">Group keywords</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#0bbf87] transition" />
|
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#0bbf87] transition" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/planner/ideas"
|
to="/planner/ideas"
|
||||||
className="flex items-center gap-4 p-4 rounded-lg border-2 border-slate-200 bg-white hover:border-[#ff7a00] hover:shadow-md transition group"
|
className="flex items-center gap-4 p-6 rounded-xl border-2 border-slate-200 bg-white hover:border-[#ff7a00] hover:shadow-lg transition-all group"
|
||||||
>
|
>
|
||||||
<div className="size-12 rounded-lg bg-gradient-to-br from-[#ff7a00] to-[#cc5f00] flex items-center justify-center text-white">
|
<div className="size-12 rounded-xl bg-gradient-to-br from-[#ff7a00] to-[#cc5f00] flex items-center justify-center text-white shadow-lg">
|
||||||
<BoltIcon className="h-6 w-6" />
|
<BoltIcon className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 text-left">
|
||||||
<h4 className="font-semibold text-slate-900">Generate Ideas</h4>
|
<h4 className="font-semibold text-slate-900 mb-1">Generate Ideas</h4>
|
||||||
<p className="text-xs text-slate-600">Create content ideas</p>
|
<p className="text-sm text-slate-600">Create content ideas</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#ff7a00] transition" />
|
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#ff7a00] transition" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/automation"
|
to="/automation"
|
||||||
className="flex items-center gap-4 p-4 rounded-lg border-2 border-slate-200 bg-white hover:border-[#5d4ae3] hover:shadow-md transition group"
|
className="flex items-center gap-4 p-6 rounded-xl border-2 border-slate-200 bg-white hover:border-[#5d4ae3] hover:shadow-lg transition-all group"
|
||||||
>
|
>
|
||||||
<div className="size-12 rounded-lg bg-gradient-to-br from-[#5d4ae3] to-[#3a2f94] flex items-center justify-center text-white">
|
<div className="size-12 rounded-xl bg-gradient-to-br from-[#5d4ae3] to-[#3a2f94] flex items-center justify-center text-white shadow-lg">
|
||||||
<PieChartIcon className="h-6 w-6" />
|
<PlugInIcon className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 text-left">
|
||||||
<h4 className="font-semibold text-slate-900">Setup Automation</h4>
|
<h4 className="font-semibold text-slate-900 mb-1">Setup Automation</h4>
|
||||||
<p className="text-xs text-slate-600">Automate workflows</p>
|
<p className="text-sm text-slate-600">Automate workflows</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#5d4ae3] transition" />
|
<ArrowRightIcon className="h-5 w-5 text-slate-400 group-hover:text-[#5d4ae3] transition" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
</ComponentCard>
|
||||||
|
|
||||||
{/* Next Actions */}
|
{/* Info Cards */}
|
||||||
{nextActions.length > 0 && (
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<ComponentCard title="Next Actions" desc="Actionable items requiring attention">
|
<ComponentCard title="How Planner Works" desc="Understanding the planning workflow">
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
{nextActions.map((action, index) => (
|
<div className="flex gap-4">
|
||||||
<div
|
<div className="flex-shrink-0 size-10 rounded-lg bg-gradient-to-br from-[#0693e3] to-[#0472b8] flex items-center justify-center text-white shadow-md">
|
||||||
key={index}
|
<ListIcon className="h-5 w-5" />
|
||||||
className="flex items-center justify-between p-4 rounded-lg bg-gradient-to-r from-gray-50 to-white dark:from-gray-900/50 dark:to-gray-800/50 border border-gray-200 dark:border-gray-800 hover:border-brand-300 dark:hover:border-brand-500/30 transition-all group"
|
</div>
|
||||||
>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">{action.text}</span>
|
<h4 className="font-semibold text-slate-900 mb-1">Keyword Discovery</h4>
|
||||||
<Link
|
<p className="text-sm text-slate-600">
|
||||||
to={action.path}
|
Discover high-volume keywords from our global database. Add keywords manually or import from keyword opportunities.
|
||||||
className="inline-flex items-center gap-2 text-sm font-medium text-brand-500 hover:text-brand-600 group-hover:translate-x-1 transition-transform"
|
</p>
|
||||||
>
|
</div>
|
||||||
{action.action}
|
</div>
|
||||||
<ArrowRightIcon className="size-4" />
|
<div className="flex gap-4">
|
||||||
</Link>
|
<div className="flex-shrink-0 size-10 rounded-lg bg-gradient-to-br from-[#0bbf87] to-[#08966b] flex items-center justify-center text-white shadow-md">
|
||||||
|
<GroupIcon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-1">AI Clustering</h4>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Automatically group related keywords into strategic clusters. Each cluster represents a content topic with shared search intent.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-shrink-0 size-10 rounded-lg bg-gradient-to-br from-[#ff7a00] to-[#cc5f00] flex items-center justify-center text-white shadow-md">
|
||||||
|
<BoltIcon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-1">Idea Generation</h4>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Generate content ideas from clusters using AI. Each idea includes title, outline, and target keywords for content creation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</ComponentCard>
|
||||||
|
|
||||||
|
<ComponentCard title="Getting Started" desc="Quick guide to using Planner">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 size-8 rounded-full bg-[#0693e3] text-white flex items-center justify-center font-bold text-sm">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-1">Add Keywords</h4>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Start by adding keywords from the keyword opportunities page. You can search by volume, difficulty, or intent.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 size-8 rounded-full bg-[#0bbf87] text-white flex items-center justify-center font-bold text-sm">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-1">Cluster Keywords</h4>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Use the auto-cluster feature to group related keywords. Review and refine clusters to match your content strategy.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 size-8 rounded-full bg-[#ff7a00] text-white flex items-center justify-center font-bold text-sm">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-1">Generate Ideas</h4>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Create content ideas from your clusters. Queue ideas to the Writer module to start content creation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ComponentCard>
|
||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user