Files
bakery-ia/frontend/src/components/dashboard/blocks/AIInsightsBlock.tsx

213 lines
8.9 KiB
TypeScript
Raw Normal View History

2025-12-13 23:57:54 +01:00
/**
* 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;
2025-12-15 21:14:22 +01:00
recommendation_actions?: Array<{
label: string;
action: string;
}>;
2025-12-13 23:57:54 +01:00
}
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>
2025-12-15 21:14:22 +01:00
{/* Recommendations */}
{insight.recommendation_actions && insight.recommendation_actions.length > 0 && (
<div className="mb-2 p-2 bg-[var(--color-primary-50)] border border-[var(--color-primary-100)] rounded">
<div className="flex items-start gap-1.5">
<Lightbulb className="w-4 h-4 text-[var(--color-primary-600)] flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-[var(--color-primary-700)] mb-1">
{t('dashboard:ai_insights.recommendations', 'Recomendaciones')}:
</p>
<ul className="space-y-1">
{insight.recommendation_actions.slice(0, 2).map((action, idx) => (
<li key={idx} className="text-xs text-[var(--color-primary-700)] flex items-start gap-1">
<span className="flex-shrink-0"></span>
<span className="flex-1">{action.label || action.action}</span>
</li>
))}
</ul>
</div>
</div>
</div>
)}
2025-12-13 23:57:54 +01:00
{/* Impact Value */}
{insight.impact_value && (
2025-12-15 21:14:22 +01:00
<div className="flex items-center gap-2 mt-2">
2025-12-13 23:57:54 +01:00
{insight.type === 'cost_optimization' && (
<span className="text-sm font-semibold text-[var(--color-success-600)]">
2025-12-15 21:14:22 +01:00
💰 {insight.impact_currency}{insight.impact_value} {t('dashboard:ai_insights.savings')}
2025-12-13 23:57:54 +01:00
</span>
)}
{insight.type === 'waste_reduction' && (
<span className="text-sm font-semibold text-[var(--color-success-600)]">
2025-12-15 21:14:22 +01:00
{insight.impact_value} {t('dashboard:ai_insights.reduction')}
</span>
)}
{!['cost_optimization', 'waste_reduction'].includes(insight.type) && (
<span className="text-sm font-semibold text-[var(--color-success-600)]">
💰 {insight.impact_currency}{insight.impact_value}
2025-12-13 23:57:54 +01:00
</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;