Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,308 @@
import { Link } from "react-router";
import PageMeta from "../../components/common/PageMeta";
import ComponentCard from "../../components/common/ComponentCard";
import { ProgressBar } from "../../components/ui/progress";
import { ListIcon, GroupIcon, BoltIcon, PieChartIcon, ArrowRightIcon, CheckCircleIcon, TimeIcon } from "../../icons";
export default function PlannerDashboard() {
// Mock data - will be replaced with API calls
const stats = {
keywords: 245,
clusters: 18,
ideas: 52,
mappedKeywords: 180,
clustersWithIdeas: 12,
queuedIdeas: 35,
};
const keywordMappingPct = stats.keywords > 0 ? Math.round((stats.mappedKeywords / stats.keywords) * 100) : 0;
const clustersIdeasPct = stats.clusters > 0 ? Math.round((stats.clustersWithIdeas / stats.clusters) * 100) : 0;
const ideasQueuedPct = stats.ideas > 0 ? Math.round((stats.queuedIdeas / stats.ideas) * 100) : 0;
const workflowSteps = [
{ number: 1, title: "Add Keywords", status: "completed", count: stats.keywords, path: "/planner/keywords" },
{ number: 2, title: "Select Sector", status: "completed", count: null, path: "/planner" },
{ number: 3, title: "Auto Cluster", status: "pending", count: stats.clusters, path: "/planner/clusters" },
{ number: 4, title: "Generate Ideas", status: "pending", count: stats.ideas, path: "/planner/ideas" },
];
const topClusters = [
{ name: "SEO Optimization", volume: 45800, keywords: 24 },
{ name: "Content Marketing", volume: 32100, keywords: 18 },
{ name: "Link Building", volume: 28700, keywords: 15 },
{ name: "Keyword Research", volume: 24100, keywords: 12 },
{ name: "Analytics", volume: 18900, keywords: 9 },
];
const ideasByStatus = [
{ status: "New", count: 20, color: "blue" },
{ status: "Scheduled", count: 15, color: "amber" },
{ status: "Published", count: 17, color: "green" },
];
const nextActions = [
{ text: "65 keywords unmapped", action: "Map Keywords", path: "/planner/keywords" },
{ text: "6 clusters without ideas", action: "Generate Ideas", path: "/planner/ideas" },
{ text: "17 ideas not queued to writer", action: "Queue to Writer", path: "/writer/tasks" },
];
return (
<>
<PageMeta title="Planner Dashboard - IGNY8" description="Content planning overview" />
<div className="space-y-5 sm:space-y-6">
{/* Top Status Cards */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4 md:gap-6">
<Link
to="/planner/keywords"
className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6 hover:shadow-md transition-shadow cursor-pointer group relative overflow-hidden"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-brand-500"></div>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm text-gray-500 dark:text-gray-400">Keywords Ready</p>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
{stats.keywords.toLocaleString()}
</h4>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Research, analyze, and manage keywords strategy
</p>
</div>
<div className="flex items-center justify-center w-12 h-12 bg-blue-50 rounded-xl dark:bg-blue-500/10 group-hover:bg-blue-100 dark:group-hover:bg-blue-500/20 transition-colors">
<ListIcon className="text-brand-500 size-6" />
</div>
</div>
</Link>
<Link
to="/planner/clusters"
className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6 hover:shadow-md transition-shadow cursor-pointer group relative overflow-hidden"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-success-500"></div>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm text-gray-500 dark:text-gray-400">Clusters Built</p>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
{stats.clusters.toLocaleString()}
</h4>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Organize keywords into strategic topical clusters
</p>
</div>
<div className="flex items-center justify-center w-12 h-12 bg-green-50 rounded-xl dark:bg-green-500/10 group-hover:bg-green-100 dark:group-hover:bg-green-500/20 transition-colors">
<GroupIcon className="text-success-500 size-6" />
</div>
</div>
</Link>
<Link
to="/planner/ideas"
className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6 hover:shadow-md transition-shadow cursor-pointer group relative overflow-hidden"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-warning-500"></div>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm text-gray-500 dark:text-gray-400">Ideas Generated</p>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
{stats.ideas.toLocaleString()}
</h4>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Generate creative content ideas based on semantic strategy
</p>
</div>
<div className="flex items-center justify-center w-12 h-12 bg-amber-50 rounded-xl dark:bg-amber-500/10 group-hover:bg-amber-100 dark:group-hover:bg-amber-500/20 transition-colors">
<BoltIcon className="text-warning-500 size-6" />
</div>
</div>
</Link>
<Link
to="/planner/keywords"
className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6 hover:shadow-md transition-shadow cursor-pointer group relative overflow-hidden"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-purple-500"></div>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm text-gray-500 dark:text-gray-400">Mapped Keywords</p>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
{stats.mappedKeywords.toLocaleString()}
</h4>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Keywords successfully mapped to content pages
</p>
</div>
<div className="flex items-center justify-center w-12 h-12 bg-purple-50 rounded-xl dark:bg-purple-500/10 group-hover:bg-purple-100 dark:group-hover:bg-purple-500/20 transition-colors">
<PieChartIcon className="text-purple-500 size-6" />
</div>
</div>
</Link>
</div>
{/* Planner Workflow Steps */}
<ComponentCard title="Planner Workflow Steps" desc="Track your planning progress">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{workflowSteps.map((step) => (
<Link
key={step.number}
to={step.path}
className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50 hover:border-brand-300 hover:bg-brand-50 dark:hover:bg-brand-500/10 transition-colors"
>
<div className="flex items-center gap-3 mb-3">
<div className="flex items-center justify-center w-8 h-8 bg-white border-2 border-gray-300 rounded-full text-sm font-semibold text-gray-600 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400">
{step.number}
</div>
<h4 className="font-medium text-gray-800 dark:text-white/90">{step.title}</h4>
</div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-1.5">
{step.status === "completed" ? (
<>
<CheckCircleIcon className="size-4 text-success-500" />
<span className="text-gray-600 dark:text-gray-300 font-medium">Completed</span>
</>
) : (
<>
<TimeIcon className="size-4 text-amber-500" />
<span className="text-gray-600 dark:text-gray-300 font-medium">Pending</span>
</>
)}
</div>
</div>
{step.count !== null && (
<p className="mt-2 text-xs text-gray-600 dark:text-gray-400">
{step.count} {step.title.includes("Keywords") ? "keywords" : step.title.includes("Clusters") ? "clusters" : "ideas"}{" "}
{step.status === "completed" ? "added" : ""}
</p>
)}
{step.status === "pending" && (
<Link
to={step.path}
className="mt-3 inline-block text-xs font-medium text-brand-500 hover:text-brand-600"
onClick={(e) => e.stopPropagation()}
>
Start Now
</Link>
)}
</Link>
))}
</div>
</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.mappedKeywords} of {stats.keywords} 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.clustersWithIdeas} of {stats.clusters} 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.queuedIdeas} of {stats.ideas} 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-1">
<div className="space-y-4">
{topClusters.map((cluster, index) => {
const maxVolume = topClusters[0].volume;
const percentage = Math.round((cluster.volume / maxVolume) * 100);
return (
<div key={index}>
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-800 dark:text-white/90">{cluster.name}</span>
<span className="text-sm font-semibold text-gray-600 dark:text-gray-400">
{cluster.volume.toLocaleString()}
</span>
</div>
<ProgressBar
value={percentage}
color={index % 2 === 0 ? "primary" : "success"}
size="sm"
/>
</div>
);
})}
</div>
</ComponentCard>
{/* Ideas by Status */}
<ComponentCard title="Ideas by Status" desc="Content ideas workflow status" className="lg:col-span-1">
<div className="space-y-4">
{ideasByStatus.map((item, index) => {
const total = ideasByStatus.reduce((sum, i) => sum + i.count, 0);
const percentage = Math.round((item.count / total) * 100);
return (
<div key={index}>
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-800 dark:text-white/90">{item.status}</span>
<span className="text-sm font-semibold text-gray-600 dark:text-gray-400">{item.count}</span>
</div>
<ProgressBar
value={percentage}
color={
item.color === "blue"
? "primary"
: item.color === "amber"
? "warning"
: "success"
}
size="sm"
/>
</div>
);
})}
</div>
</ComponentCard>
</div>
{/* Next Actions */}
<ComponentCard title="Next Actions" desc="Actionable items requiring attention">
<div className="space-y-3">
{nextActions.map((action, index) => (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800"
>
<span className="text-sm text-gray-700 dark:text-gray-300">{action.text}</span>
<Link
to={action.path}
className="inline-flex items-center gap-1 text-sm font-medium text-brand-500 hover:text-brand-600"
>
{action.action}
<ArrowRightIcon className="size-4" />
</Link>
</div>
))}
</div>
</ComponentCard>
</div>
</>
);
}