enhanced ui
This commit is contained in:
@@ -4,6 +4,8 @@ import PageMeta from "../../components/common/PageMeta";
|
||||
import ComponentCard from "../../components/common/ComponentCard";
|
||||
import { ProgressBar } from "../../components/ui/progress";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
import WorkflowPipeline, { WorkflowStep } from "../../components/dashboard/WorkflowPipeline";
|
||||
import EnhancedMetricCard from "../../components/dashboard/EnhancedMetricCard";
|
||||
|
||||
const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default })));
|
||||
import {
|
||||
@@ -523,174 +525,147 @@ export default function WriterDashboard() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Top Status Cards */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4 md:gap-6">
|
||||
<Link
|
||||
to="/writer/tasks"
|
||||
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">Total Tasks</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.tasks.total.toLocaleString()}
|
||||
</h4>
|
||||
{trends.tasks !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.tasks > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.tasks > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
<span>{Math.abs(trends.tasks)}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Hero Section - Key Metric */}
|
||||
<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">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">Content Creation Progress</p>
|
||||
<h3 className="mt-2 text-3xl font-bold text-gray-800 dark:text-white/90">
|
||||
{stats.content.published > 0 ? (
|
||||
<>
|
||||
{stats.content.published} Content Pieces Published
|
||||
</>
|
||||
) : stats.content.review > 0 ? (
|
||||
<>
|
||||
{stats.content.review} Pieces Ready to Publish
|
||||
</>
|
||||
) : stats.content.drafts > 0 ? (
|
||||
<>
|
||||
{stats.content.drafts} Drafts Ready for Review
|
||||
</>
|
||||
) : stats.tasks.total > 0 ? (
|
||||
<>
|
||||
{stats.tasks.total} Tasks Created
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Ready to Create Content
|
||||
</>
|
||||
)}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{stats.tasks.total} tasks • {stats.content.total} content pieces • {stats.images.generated} images generated
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-brand-500">{completionRate}%</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">Complete</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-success-500">{stats.productivity.publishRate}%</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">Published</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-warning-500">
|
||||
{stats.images.generated > 0 ? Math.round((stats.images.generated / stats.images.total) * 100) : 0}%
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.tasks.completed} completed • {stats.tasks.pending} pending
|
||||
</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">
|
||||
<FileTextIcon className="text-brand-500 size-6" />
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">Images</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/writer/content"
|
||||
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">Content Pieces</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.content.total.toLocaleString()}
|
||||
</h4>
|
||||
{trends.content !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.content > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.content > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
<span>{Math.abs(trends.content)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.content.published} published • {stats.content.drafts} drafts
|
||||
</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">
|
||||
<PencilIcon className="text-success-500 size-6" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/writer/images"
|
||||
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">Images 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.images.generated.toLocaleString()}
|
||||
</h4>
|
||||
{trends.images !== 0 && (
|
||||
<div className={`flex items-center gap-1 text-xs ${trends.images > 0 ? 'text-success-500' : 'text-error-500'}`}>
|
||||
{trends.images > 0 ? <ArrowUpIcon className="size-3" /> : <ArrowDownIcon className="size-3" />}
|
||||
<span>{Math.abs(trends.images)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.images.total} total • {stats.images.pending} 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">
|
||||
<BoxIcon className="text-warning-500 size-6" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/writer/published"
|
||||
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">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">Publish Rate</p>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
{stats.productivity.publishRate}%
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.content.published} of {stats.content.total} published
|
||||
</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">
|
||||
<BoltIcon className="text-purple-500 size-6" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Writer Workflow Steps */}
|
||||
<ComponentCard title="Writer Workflow Steps" desc="Track your content creation 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 justify-center w-10 h-10 rounded-full text-sm font-bold ${
|
||||
step.status === "completed"
|
||||
? "bg-success-500 text-white"
|
||||
: step.status === "in_progress"
|
||||
? "bg-warning-500 text-white"
|
||||
: "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>
|
||||
<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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ClockIcon 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="text-xs text-gray-600 dark:text-gray-400 mb-2">
|
||||
{step.count} {step.title.includes("Tasks") ? "tasks" : step.title.includes("Content") ? "pieces" : step.title.includes("Images") ? "images" : "items"}
|
||||
</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>
|
||||
))}
|
||||
</div>
|
||||
{/* Enhanced Metric Cards */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4 md:gap-6">
|
||||
<EnhancedMetricCard
|
||||
title="Total Tasks"
|
||||
value={stats.tasks.total}
|
||||
subtitle={`${stats.tasks.completed} completed • ${stats.tasks.pending} pending`}
|
||||
trend={trends.tasks}
|
||||
icon={<FileTextIcon className="size-6" />}
|
||||
accentColor="blue"
|
||||
href="/writer/tasks"
|
||||
details={[
|
||||
{ label: "Total Tasks", value: stats.tasks.total },
|
||||
{ label: "Completed", value: stats.tasks.completed },
|
||||
{ label: "Pending", value: stats.tasks.pending },
|
||||
{ label: "In Progress", value: stats.tasks.inProgress },
|
||||
{ label: "Avg Word Count", value: stats.tasks.avgWordCount },
|
||||
]}
|
||||
/>
|
||||
|
||||
<EnhancedMetricCard
|
||||
title="Content Pieces"
|
||||
value={stats.content.total}
|
||||
subtitle={`${stats.content.published} published • ${stats.content.drafts} drafts`}
|
||||
trend={trends.content}
|
||||
icon={<PencilIcon className="size-6" />}
|
||||
accentColor="green"
|
||||
href="/writer/content"
|
||||
details={[
|
||||
{ label: "Total Content", value: stats.content.total },
|
||||
{ label: "Published", value: stats.content.published },
|
||||
{ label: "In Review", value: stats.content.review },
|
||||
{ label: "Drafts", value: stats.content.drafts },
|
||||
{ label: "Avg Word Count", value: stats.content.avgWordCount.toLocaleString() },
|
||||
]}
|
||||
/>
|
||||
|
||||
<EnhancedMetricCard
|
||||
title="Images Generated"
|
||||
value={stats.images.generated}
|
||||
subtitle={`${stats.images.total} total • ${stats.images.pending} pending`}
|
||||
trend={trends.images}
|
||||
icon={<BoxIcon className="size-6" />}
|
||||
accentColor="orange"
|
||||
href="/writer/images"
|
||||
details={[
|
||||
{ label: "Generated", value: stats.images.generated },
|
||||
{ label: "Total Images", value: stats.images.total },
|
||||
{ label: "Pending", value: stats.images.pending },
|
||||
{ label: "Failed", value: stats.images.failed },
|
||||
]}
|
||||
/>
|
||||
|
||||
<EnhancedMetricCard
|
||||
title="Publish Rate"
|
||||
value={`${stats.productivity.publishRate}%`}
|
||||
subtitle={`${stats.content.published} of ${stats.content.total} published`}
|
||||
icon={<BoltIcon className="size-6" />}
|
||||
accentColor="purple"
|
||||
href="/writer/published"
|
||||
details={[
|
||||
{ label: "Publish Rate", value: `${stats.productivity.publishRate}%` },
|
||||
{ label: "Published", value: stats.content.published },
|
||||
{ label: "Total Content", value: stats.content.total },
|
||||
{ label: "This Week", value: stats.productivity.contentThisWeek },
|
||||
{ label: "This Month", value: stats.productivity.contentThisMonth },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Interactive Workflow Pipeline */}
|
||||
<ComponentCard title="Writer Workflow Pipeline" desc="Track your content creation 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">
|
||||
|
||||
Reference in New Issue
Block a user