213 lines
7.1 KiB
TypeScript
213 lines
7.1 KiB
TypeScript
/**
|
|
* DashboardTemplate - Master template for module dashboard pages
|
|
*
|
|
* Usage:
|
|
* <DashboardTemplate
|
|
* title="Planner Dashboard"
|
|
* subtitle="Manage your content planning workflow"
|
|
* kpiCards={kpiConfig}
|
|
* workflowSteps={workflowConfig}
|
|
* charts={chartsConfig}
|
|
* recentActivity={activityConfig}
|
|
* />
|
|
*/
|
|
|
|
import React, { ReactNode } from 'react';
|
|
|
|
interface KPICard {
|
|
title: string;
|
|
value: string | number;
|
|
description?: string;
|
|
icon?: ReactNode;
|
|
trend?: {
|
|
value: number;
|
|
isPositive: boolean;
|
|
};
|
|
}
|
|
|
|
interface WorkflowStep {
|
|
step: number;
|
|
title: string;
|
|
status: 'completed' | 'pending' | 'in_progress';
|
|
description?: string;
|
|
count?: number;
|
|
actionLabel?: string;
|
|
onAction?: () => void;
|
|
}
|
|
|
|
interface ChartConfig {
|
|
title: string;
|
|
type: 'bar' | 'line' | 'pie' | 'progress';
|
|
data: any;
|
|
}
|
|
|
|
interface DashboardTemplateProps {
|
|
title: string;
|
|
subtitle?: string;
|
|
kpiCards?: KPICard[];
|
|
workflowSteps?: WorkflowStep[];
|
|
charts?: ChartConfig[];
|
|
recentActivity?: ReactNode;
|
|
quickActions?: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export default function DashboardTemplate({
|
|
title,
|
|
subtitle,
|
|
kpiCards = [],
|
|
workflowSteps = [],
|
|
charts = [],
|
|
recentActivity,
|
|
quickActions,
|
|
className = '',
|
|
}: DashboardTemplateProps) {
|
|
return (
|
|
<div className={className}>
|
|
{/* Page Header */}
|
|
<div className="mb-6">
|
|
<h2 className="text-xl font-semibold text-gray-800 dark:text-white/90 mb-1">{title}</h2>
|
|
{subtitle && (
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">{subtitle}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* KPI Cards Row */}
|
|
{kpiCards.length > 0 && (
|
|
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
|
{kpiCards.map((card, index) => (
|
|
<div
|
|
key={index}
|
|
className="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] sm:p-6"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
|
|
{card.title}
|
|
</p>
|
|
<p className="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
|
{card.value}
|
|
</p>
|
|
{card.trend && (
|
|
<div className="flex items-center mt-1">
|
|
<span
|
|
className={`text-xs font-medium ${
|
|
card.trend.isPositive ? 'text-success-500' : 'text-error-500'
|
|
}`}
|
|
>
|
|
{card.trend.isPositive ? '↑' : '↓'} {Math.abs(card.trend.value)}%
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{card.icon && (
|
|
<div className="flex h-14 w-14 items-center justify-center rounded-[10.5px] bg-brand-50 text-brand-500 dark:bg-brand-500/10">
|
|
{card.icon}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{card.description && (
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
|
{card.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Quick Actions */}
|
|
{quickActions && (
|
|
<div className="mb-6">
|
|
{quickActions}
|
|
</div>
|
|
)}
|
|
|
|
{/* Workflow Steps */}
|
|
{workflowSteps.length > 0 && (
|
|
<div className="mb-6">
|
|
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03] p-6">
|
|
<h3 className="text-base font-medium text-gray-800 dark:text-white/90 mb-4">
|
|
Workflow Steps - Track your planning progress
|
|
</h3>
|
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
{workflowSteps.map((step) => (
|
|
<div
|
|
key={step.step}
|
|
className="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-white/[0.03]"
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
{step.step}. {step.title}
|
|
</span>
|
|
<span
|
|
className={`text-xs font-medium px-2 py-1 rounded ${
|
|
step.status === 'completed'
|
|
? 'bg-success-50 text-success-600 dark:bg-success-500/15 dark:text-success-500'
|
|
: step.status === 'in_progress'
|
|
? 'bg-warning-50 text-warning-600 dark:bg-warning-500/15 dark:text-warning-500'
|
|
: 'bg-gray-100 text-gray-600 dark:bg-white/5 dark:text-gray-400'
|
|
}`}
|
|
>
|
|
{step.status === 'completed' ? 'Completed' : step.status === 'in_progress' ? 'In Progress' : 'Pending'}
|
|
</span>
|
|
</div>
|
|
{step.description && (
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
{step.description}
|
|
</p>
|
|
)}
|
|
{step.count !== undefined && (
|
|
<p className="text-sm font-medium text-gray-800 dark:text-white/90 mb-2">
|
|
{step.count} items
|
|
</p>
|
|
)}
|
|
{step.actionLabel && step.onAction && step.status !== 'completed' && (
|
|
<button
|
|
onClick={step.onAction}
|
|
className="text-sm text-brand-500 hover:text-brand-600 font-medium"
|
|
>
|
|
{step.actionLabel} →
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Charts Row */}
|
|
{charts.length > 0 && (
|
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2 mb-6">
|
|
{charts.map((chart, index) => (
|
|
<div
|
|
key={index}
|
|
className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03] p-6"
|
|
>
|
|
<h3 className="text-base font-medium text-gray-800 dark:text-white/90 mb-4">
|
|
{chart.title}
|
|
</h3>
|
|
{/* Chart component would be rendered here */}
|
|
<div className="h-64 flex items-center justify-center text-gray-400">
|
|
Chart: {chart.type}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Recent Activity */}
|
|
{recentActivity && (
|
|
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03] p-6">
|
|
<h3 className="text-base font-medium text-gray-800 dark:text-white/90 mb-4">
|
|
Recent Activity
|
|
</h3>
|
|
{recentActivity}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|