fixes
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useEffect, useState, useMemo, lazy, Suspense } from "react";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import PageMeta from "../../components/common/PageMeta";
|
||||
import ComponentCard from "../../components/common/ComponentCard";
|
||||
import { ProgressBar } from "../../components/ui/progress";
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
|
||||
const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default })));
|
||||
import {
|
||||
ListIcon,
|
||||
GroupIcon,
|
||||
@@ -445,18 +446,18 @@ export default function PlannerDashboard() {
|
||||
|
||||
{/* 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"
|
||||
<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-lg transition-all 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>
|
||||
>
|
||||
<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>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<h4 className="font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
{stats.keywords.total.toLocaleString()}
|
||||
</h4>
|
||||
</h4>
|
||||
{trends.keywords !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.keywords > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.keywords > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
@@ -464,28 +465,28 @@ export default function PlannerDashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.keywords.mapped} mapped • {stats.keywords.unmapped} unmapped
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<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"
|
||||
<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-lg transition-all 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>
|
||||
>
|
||||
<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>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<h4 className="font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
{stats.clusters.total.toLocaleString()}
|
||||
</h4>
|
||||
</h4>
|
||||
{trends.clusters !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.clusters > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.clusters > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
@@ -493,28 +494,28 @@ export default function PlannerDashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.clusters.totalVolume.toLocaleString()} total volume • {stats.clusters.avgKeywords} avg keywords
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<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"
|
||||
<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-lg transition-all 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>
|
||||
>
|
||||
<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>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<h4 className="font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
{stats.ideas.total.toLocaleString()}
|
||||
</h4>
|
||||
</h4>
|
||||
{trends.ideas !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.ideas > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.ideas > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
@@ -522,48 +523,48 @@ export default function PlannerDashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.ideas.queued} queued • {stats.ideas.notQueued} pending
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<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"
|
||||
<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-lg transition-all 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">
|
||||
>
|
||||
<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">Mapping Progress</p>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
{keywordMappingPct}%
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.keywords.mapped} of {stats.keywords.total} keywords mapped
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</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}
|
||||
{/* 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-gradient-to-br from-gray-50 to-white p-4 dark:from-gray-900/50 dark:to-gray-800/50 dark:border-gray-800 hover:border-brand-300 hover:bg-gradient-to-br hover:from-brand-50 hover:to-white dark:hover:from-brand-500/10 dark:hover:to-gray-800/50 transition-all group"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className={`flex items-center justify-center w-10 h-10 rounded-full text-sm font-bold ${
|
||||
step.status === "completed"
|
||||
? "bg-success-500 text-white"
|
||||
@@ -572,150 +573,156 @@ export default function PlannerDashboard() {
|
||||
: "bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-400"
|
||||
}`}>
|
||||
{step.status === "completed" ? <CheckCircleIcon className="size-5" /> : step.number}
|
||||
</div>
|
||||
<h4 className="font-medium text-gray-800 dark:text-white/90">{step.title}</h4>
|
||||
</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 mb-2">
|
||||
<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 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>
|
||||
{step.count !== null && (
|
||||
</div>
|
||||
{step.count !== null && (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mb-2">
|
||||
{step.count} {step.title.includes("Keywords") ? "keywords" : step.title.includes("Clusters") ? "clusters" : step.title.includes("Ideas") ? "ideas" : "items"}
|
||||
</p>
|
||||
)}
|
||||
{step.status === "pending" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate(step.path);
|
||||
}}
|
||||
</p>
|
||||
)}
|
||||
{step.status === "pending" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate(step.path);
|
||||
}}
|
||||
className="mt-2 inline-flex items-center gap-1 text-xs font-medium text-brand-500 hover:text-brand-600 cursor-pointer group-hover:translate-x-1 transition-transform"
|
||||
>
|
||||
>
|
||||
Start Now <ArrowRightIcon className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</button>
|
||||
)}
|
||||
</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.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>
|
||||
|
||||
<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 ? (
|
||||
<Chart
|
||||
options={topClustersChart.options}
|
||||
series={topClustersChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<Chart
|
||||
options={keywordsStatusChart.options}
|
||||
series={keywordsStatusChart.series}
|
||||
type="donut"
|
||||
height={300}
|
||||
/>
|
||||
<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">
|
||||
<Chart
|
||||
options={ideasStatusChart.options}
|
||||
series={ideasStatusChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* Next Actions */}
|
||||
{/* Next Actions */}
|
||||
{nextActions.length > 0 && (
|
||||
<ComponentCard title="Next Actions" desc="Actionable items requiring attention">
|
||||
<div className="space-y-3">
|
||||
{nextActions.map((action, index) => (
|
||||
<div
|
||||
key={index}
|
||||
<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-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"
|
||||
>
|
||||
>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">{action.text}</span>
|
||||
<Link
|
||||
to={action.path}
|
||||
<Link
|
||||
to={action.path}
|
||||
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"
|
||||
>
|
||||
{action.action}
|
||||
<ArrowRightIcon className="size-4" />
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
>
|
||||
{action.action}
|
||||
<ArrowRightIcon className="size-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
))}
|
||||
</div>
|
||||
</ComponentCard>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useEffect, useState, useMemo, lazy, Suspense } from "react";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import PageMeta from "../../components/common/PageMeta";
|
||||
import ComponentCard from "../../components/common/ComponentCard";
|
||||
import { ProgressBar } from "../../components/ui/progress";
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
|
||||
const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default })));
|
||||
import {
|
||||
FileTextIcon,
|
||||
BoxIcon,
|
||||
@@ -748,12 +749,14 @@ export default function WriterDashboard() {
|
||||
{/* Content Status Chart */}
|
||||
{contentStatusChart && (
|
||||
<ComponentCard title="Content by Status" desc="Distribution across workflow stages" className="lg:col-span-2">
|
||||
<Chart
|
||||
options={contentStatusChart.options}
|
||||
series={contentStatusChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
<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={contentStatusChart.options}
|
||||
series={contentStatusChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
</Suspense>
|
||||
</ComponentCard>
|
||||
)}
|
||||
</div>
|
||||
@@ -762,24 +765,28 @@ export default function WriterDashboard() {
|
||||
{/* Tasks by Status */}
|
||||
{tasksStatusChart && (
|
||||
<ComponentCard title="Tasks by Status" desc="Task distribution across statuses">
|
||||
<Chart
|
||||
options={tasksStatusChart.options}
|
||||
series={tasksStatusChart.series}
|
||||
type="donut"
|
||||
height={300}
|
||||
/>
|
||||
<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={tasksStatusChart.options}
|
||||
series={tasksStatusChart.series}
|
||||
type="donut"
|
||||
height={300}
|
||||
/>
|
||||
</Suspense>
|
||||
</ComponentCard>
|
||||
)}
|
||||
|
||||
{/* Images by Type */}
|
||||
{imagesTypeChart ? (
|
||||
<ComponentCard title="Images by Type" desc="Image generation breakdown">
|
||||
<Chart
|
||||
options={imagesTypeChart.options}
|
||||
series={imagesTypeChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
<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={imagesTypeChart.options}
|
||||
series={imagesTypeChart.series}
|
||||
type="bar"
|
||||
height={300}
|
||||
/>
|
||||
</Suspense>
|
||||
</ComponentCard>
|
||||
) : (
|
||||
<ComponentCard title="Images Overview" desc="Image generation status">
|
||||
@@ -804,12 +811,14 @@ export default function WriterDashboard() {
|
||||
{/* Productivity Chart */}
|
||||
{productivityChart && (
|
||||
<ComponentCard title="Content Creation Trend" desc="Content created this week and month">
|
||||
<Chart
|
||||
options={productivityChart.options}
|
||||
series={productivityChart.series}
|
||||
type="area"
|
||||
height={200}
|
||||
/>
|
||||
<Suspense fallback={<div className="flex items-center justify-center h-[200px]"><div className="animate-spin rounded-full h-8 w-8 border-4 border-brand-500 border-t-transparent"></div></div>}>
|
||||
<Chart
|
||||
options={productivityChart.options}
|
||||
series={productivityChart.series}
|
||||
type="area"
|
||||
height={200}
|
||||
/>
|
||||
</Suspense>
|
||||
</ComponentCard>
|
||||
)}
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ export default defineConfig(({ mode }) => {
|
||||
'clsx',
|
||||
'tailwind-merge',
|
||||
'zustand',
|
||||
// Include apexcharts for proper module resolution
|
||||
'apexcharts',
|
||||
'react-apexcharts',
|
||||
],
|
||||
// Exclude heavy dependencies that are only used in specific pages
|
||||
// They will be lazy-loaded when needed
|
||||
@@ -35,8 +38,6 @@ export default defineConfig(({ mode }) => {
|
||||
'@fullcalendar/list',
|
||||
'@fullcalendar/react',
|
||||
'@fullcalendar/timegrid',
|
||||
'apexcharts',
|
||||
'react-apexcharts',
|
||||
'@react-jvectormap/core',
|
||||
'@react-jvectormap/world',
|
||||
'react-dnd',
|
||||
|
||||
Reference in New Issue
Block a user