Files
igny8/frontend/src/templates/DashboardTemplate.tsx
2025-11-09 10:27:02 +00:00

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>
);
}