182 lines
7.1 KiB
TypeScript
182 lines
7.1 KiB
TypeScript
|
|
/**
|
||
|
|
* AIInsightsBlock - AI Insights Dashboard Block
|
||
|
|
*
|
||
|
|
* Displays AI-generated insights for professional/enterprise tiers
|
||
|
|
* Shows top 2-3 insights with links to full AI Insights page
|
||
|
|
*/
|
||
|
|
|
||
|
|
import React from 'react';
|
||
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
import { Lightbulb, ArrowRight, BarChart2, TrendingUp, TrendingDown, Shield, AlertTriangle } from 'lucide-react';
|
||
|
|
|
||
|
|
interface AIInsight {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
description: string;
|
||
|
|
type: 'cost_optimization' | 'waste_reduction' | 'safety_stock' | 'demand_forecast' | 'risk_alert';
|
||
|
|
impact: 'high' | 'medium' | 'low';
|
||
|
|
impact_value?: string;
|
||
|
|
impact_currency?: string;
|
||
|
|
created_at: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface AIInsightsBlockProps {
|
||
|
|
insights: AIInsight[];
|
||
|
|
loading?: boolean;
|
||
|
|
onViewAll: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function AIInsightsBlock({ insights = [], loading = false, onViewAll }: AIInsightsBlockProps) {
|
||
|
|
const { t } = useTranslation(['dashboard', 'common']);
|
||
|
|
|
||
|
|
// Get icon based on insight type
|
||
|
|
const getInsightIcon = (type: string) => {
|
||
|
|
switch (type) {
|
||
|
|
case 'cost_optimization': return <TrendingUp className="w-5 h-5 text-[var(--color-success-600)]" />;
|
||
|
|
case 'waste_reduction': return <TrendingDown className="w-5 h-5 text-[var(--color-success-600)]" />;
|
||
|
|
case 'safety_stock': return <Shield className="w-5 h-5 text-[var(--color-info-600)]" />;
|
||
|
|
case 'demand_forecast': return <BarChart2 className="w-5 h-5 text-[var(--color-primary-600)]" />;
|
||
|
|
case 'risk_alert': return <AlertTriangle className="w-5 h-5 text-[var(--color-error-600)]" />;
|
||
|
|
default: return <Lightbulb className="w-5 h-5 text-[var(--color-primary-600)]" />;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Get impact color based on level
|
||
|
|
const getImpactColor = (impact: string) => {
|
||
|
|
switch (impact) {
|
||
|
|
case 'high': return 'bg-[var(--color-error-100)] text-[var(--color-error-700)]';
|
||
|
|
case 'medium': return 'bg-[var(--color-warning-100)] text-[var(--color-warning-700)]';
|
||
|
|
case 'low': return 'bg-[var(--color-info-100)] text-[var(--color-info-700)]';
|
||
|
|
default: return 'bg-[var(--bg-secondary)] text-[var(--text-secondary)]';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Get impact label
|
||
|
|
const getImpactLabel = (impact: string) => {
|
||
|
|
switch (impact) {
|
||
|
|
case 'high': return t('dashboard:ai_insights.impact_high');
|
||
|
|
case 'medium': return t('dashboard:ai_insights.impact_medium');
|
||
|
|
case 'low': return t('dashboard:ai_insights.impact_low');
|
||
|
|
default: return '';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className="rounded-xl shadow-lg p-6 border border-[var(--border-primary)] bg-[var(--bg-primary)] animate-pulse">
|
||
|
|
<div className="flex items-center gap-4 mb-4">
|
||
|
|
<div className="w-12 h-12 bg-[var(--bg-secondary)] rounded-full"></div>
|
||
|
|
<div className="flex-1 space-y-2">
|
||
|
|
<div className="h-5 bg-[var(--bg-secondary)] rounded w-1/3"></div>
|
||
|
|
<div className="h-4 bg-[var(--bg-secondary)] rounded w-1/4"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="space-y-4">
|
||
|
|
<div className="h-16 bg-[var(--bg-secondary)] rounded"></div>
|
||
|
|
<div className="h-16 bg-[var(--bg-secondary)] rounded"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show top 3 insights
|
||
|
|
const topInsights = insights.slice(0, 3);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="rounded-xl shadow-lg border border-[var(--border-primary)] bg-[var(--bg-primary)] overflow-hidden">
|
||
|
|
{/* Header */}
|
||
|
|
<div className="p-6 pb-4">
|
||
|
|
<div className="flex items-center gap-4">
|
||
|
|
{/* Icon */}
|
||
|
|
<div className="w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0 bg-[var(--color-primary-100)]">
|
||
|
|
<Lightbulb className="w-6 h-6 text-[var(--color-primary-600)]" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Title & Description */}
|
||
|
|
<div className="flex-1">
|
||
|
|
<h2 className="text-xl font-bold text-[var(--text-primary)]">
|
||
|
|
{t('dashboard:ai_insights.title')}
|
||
|
|
</h2>
|
||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
||
|
|
{t('dashboard:ai_insights.subtitle')}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* View All Button */}
|
||
|
|
<button
|
||
|
|
onClick={onViewAll}
|
||
|
|
className="flex items-center gap-2 px-3 py-2 rounded-lg border border-[var(--border-primary)] text-[var(--text-primary)] hover:bg-[var(--bg-secondary)] transition-colors text-sm font-medium"
|
||
|
|
>
|
||
|
|
<span>{t('dashboard:ai_insights.view_all')}</span>
|
||
|
|
<ArrowRight className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Insights List */}
|
||
|
|
{topInsights.length > 0 ? (
|
||
|
|
<div className="border-t border-[var(--border-primary)]">
|
||
|
|
{topInsights.map((insight, index) => (
|
||
|
|
<div
|
||
|
|
key={insight.id || index}
|
||
|
|
className={`p-4 ${index < topInsights.length - 1 ? 'border-b border-[var(--border-primary)]' : ''}`}
|
||
|
|
>
|
||
|
|
<div className="flex items-start gap-3">
|
||
|
|
{/* Icon */}
|
||
|
|
<div className="flex-shrink-0 mt-1">
|
||
|
|
{getInsightIcon(insight.type)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<div className="flex items-center gap-2 mb-1">
|
||
|
|
<h3 className="font-semibold text-[var(--text-primary)] text-sm">
|
||
|
|
{insight.title}
|
||
|
|
</h3>
|
||
|
|
{/* Impact Badge */}
|
||
|
|
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${getImpactColor(insight.impact)}`}>
|
||
|
|
{getImpactLabel(insight.impact)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p className="text-sm text-[var(--text-secondary)] mb-2">
|
||
|
|
{insight.description}
|
||
|
|
</p>
|
||
|
|
|
||
|
|
{/* Impact Value */}
|
||
|
|
{insight.impact_value && (
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
{insight.type === 'cost_optimization' && (
|
||
|
|
<span className="text-sm font-semibold text-[var(--color-success-600)]">
|
||
|
|
{insight.impact_currency}{insight.impact_value} {t('dashboard:ai_insights.savings')}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
{insight.type === 'waste_reduction' && (
|
||
|
|
<span className="text-sm font-semibold text-[var(--color-success-600)]">
|
||
|
|
{insight.impact_value} {t('dashboard:ai_insights.reduction')}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
/* Empty State */
|
||
|
|
<div className="px-6 pb-6">
|
||
|
|
<div className="flex items-center gap-3 p-4 rounded-lg bg-[var(--color-info-50)] border border-[var(--color-info-100)]">
|
||
|
|
<Lightbulb className="w-6 h-6 text-[var(--color-info-600)]" />
|
||
|
|
<p className="text-sm text-[var(--color-info-700)]">
|
||
|
|
{t('dashboard:ai_insights.no_insights')}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default AIInsightsBlock;
|