Add comprehensive analytics system with: - Analytics data layer (aggregator, metrics, trends, cache) - 6 API endpoints (overview, plants, transport, farms, sustainability, export) - 6 chart components (LineChart, BarChart, PieChart, AreaChart, Gauge, Heatmap) - 5 dashboard widgets (KPICard, TrendIndicator, DataTable, DateRangePicker, FilterPanel) - 5 dashboard pages (overview, plants, transport, farms, sustainability) - Export functionality (CSV, JSON) Dependencies added: recharts, d3, date-fns Also includes minor fixes: - Fix EnvironmentalForm spread type error - Fix AgentOrchestrator Map iteration issues - Fix next.config.js image domains undefined error - Add downlevelIteration to tsconfig
105 lines
2.5 KiB
TypeScript
105 lines
2.5 KiB
TypeScript
/**
|
|
* Trend Indicator Component
|
|
* Shows trend direction with visual indicators
|
|
*/
|
|
|
|
import { TrendDirection } from '../../lib/analytics/types';
|
|
|
|
interface TrendIndicatorProps {
|
|
direction: TrendDirection;
|
|
value?: number;
|
|
showLabel?: boolean;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
}
|
|
|
|
const sizeClasses = {
|
|
sm: 'w-3 h-3',
|
|
md: 'w-4 h-4',
|
|
lg: 'w-5 h-5',
|
|
};
|
|
|
|
const textSizeClasses = {
|
|
sm: 'text-xs',
|
|
md: 'text-sm',
|
|
lg: 'text-base',
|
|
};
|
|
|
|
export default function TrendIndicator({
|
|
direction,
|
|
value,
|
|
showLabel = false,
|
|
size = 'md',
|
|
}: TrendIndicatorProps) {
|
|
const iconSize = sizeClasses[size];
|
|
const textSize = textSizeClasses[size];
|
|
|
|
const getColor = () => {
|
|
switch (direction) {
|
|
case 'up':
|
|
return 'text-green-500';
|
|
case 'down':
|
|
return 'text-red-500';
|
|
default:
|
|
return 'text-gray-400';
|
|
}
|
|
};
|
|
|
|
const getBgColor = () => {
|
|
switch (direction) {
|
|
case 'up':
|
|
return 'bg-green-100';
|
|
case 'down':
|
|
return 'bg-red-100';
|
|
default:
|
|
return 'bg-gray-100';
|
|
}
|
|
};
|
|
|
|
const getIcon = () => {
|
|
switch (direction) {
|
|
case 'up':
|
|
return (
|
|
<svg className={iconSize} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 11l5-5m0 0l5 5m-5-5v12" />
|
|
</svg>
|
|
);
|
|
case 'down':
|
|
return (
|
|
<svg className={iconSize} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 13l-5 5m0 0l-5-5m5 5V6" />
|
|
</svg>
|
|
);
|
|
default:
|
|
return (
|
|
<svg className={iconSize} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 12h14" />
|
|
</svg>
|
|
);
|
|
}
|
|
};
|
|
|
|
const getLabel = () => {
|
|
switch (direction) {
|
|
case 'up':
|
|
return 'Increasing';
|
|
case 'down':
|
|
return 'Decreasing';
|
|
default:
|
|
return 'Stable';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={`inline-flex items-center space-x-1.5 px-2 py-1 rounded-full ${getBgColor()}`}>
|
|
<span className={getColor()}>{getIcon()}</span>
|
|
{value !== undefined && (
|
|
<span className={`font-medium ${getColor()} ${textSize}`}>
|
|
{value > 0 ? '+' : ''}{value.toFixed(1)}%
|
|
</span>
|
|
)}
|
|
{showLabel && (
|
|
<span className={`${getColor()} ${textSize}`}>{getLabel()}</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|