feat(analytics): Improve Production Analytics UI/UX and complete translations
IMPROVEMENTS: 1. Unified styling across all Production Analytics widgets - Replaced hardcoded colors with global CSS variable system - Improved consistency with design system color palette - Enhanced visual hierarchy and spacing - Added smooth transitions and hover states 2. Complete Basque (eu) translation - Extended from 105 lines to 667 lines (635% increase) - Now matches Spanish and English coverage - All analytics features fully translated 3. Widget-specific enhancements: - LiveBatchTrackerWidget: Better priority colors, improved summary cards - QualityScoreTrendsWidget: Enhanced score display, better trend indicators - AIInsightsWidget: Unified color scheme, improved insights cards - WasteDefectTrackerWidget: Better metric cards, improved recommendations 4. UX improvements: - Better mobile responsiveness - Consistent border and shadow treatments - Improved contrast for better readability - Enhanced visual feedback on interactions TECHNICAL CHANGES: - Replaced hardcoded colors (text-green-600, etc.) with CSS variables - Used semantic color system: --color-success, --color-error, etc. - Added transition-colors for smooth theme switching - Improved component spacing consistency - Enhanced accessibility with better contrast
This commit is contained in:
@@ -131,11 +131,11 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
|
||||
const getTypeColor = (type: AIInsight['type']) => {
|
||||
switch (type) {
|
||||
case 'optimization': return 'text-green-600';
|
||||
case 'prediction': return 'text-blue-600';
|
||||
case 'anomaly': return 'text-red-600';
|
||||
case 'recommendation': return 'text-purple-600';
|
||||
default: return 'text-gray-600';
|
||||
case 'optimization': return 'text-[var(--color-success)]';
|
||||
case 'prediction': return 'text-[var(--color-info)]';
|
||||
case 'anomaly': return 'text-[var(--color-error)]';
|
||||
case 'recommendation': return 'text-[var(--color-secondary)]';
|
||||
default: return 'text-[var(--text-secondary)]';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -207,43 +207,43 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-5">
|
||||
{/* AI Insights Overview Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<Brain className="w-8 h-8 mx-auto text-purple-600 mb-2" />
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)] hover:border-[var(--border-focus)] transition-colors">
|
||||
<Brain className="w-8 h-8 mx-auto text-[var(--color-secondary)] mb-2" />
|
||||
<p className="text-2xl font-bold text-[var(--text-primary)]">{activeInsights.length}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('ai.stats.active_insights')}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('ai.stats.active_insights')}</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<AlertTriangle className="w-8 h-8 mx-auto text-red-600 mb-2" />
|
||||
<p className="text-2xl font-bold text-[var(--text-primary)]">{highPriorityInsights.length}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('ai.stats.high_priority')}</p>
|
||||
<div className="text-center p-4 bg-[var(--color-error)]/10 rounded-lg border border-[var(--color-error)]/20 hover:border-[var(--color-error)]/40 transition-colors">
|
||||
<AlertTriangle className="w-8 h-8 mx-auto text-[var(--color-error)] mb-2" />
|
||||
<p className="text-2xl font-bold text-[var(--color-error)]">{highPriorityInsights.length}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('ai.stats.high_priority')}</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<div className="w-8 h-8 mx-auto bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center mb-2">
|
||||
<span className="text-green-600 font-bold text-sm">€</span>
|
||||
<div className="text-center p-4 bg-[var(--color-success)]/10 rounded-lg border border-[var(--color-success)]/20 hover:border-[var(--color-success)]/40 transition-colors">
|
||||
<div className="w-8 h-8 mx-auto bg-[var(--color-success)]/20 rounded-full flex items-center justify-center mb-2">
|
||||
<span className="text-[var(--color-success)] font-bold text-sm">€</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[var(--text-primary)]">€{totalPotentialSavings}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('ai.stats.potential_savings')}</p>
|
||||
<p className="text-2xl font-bold text-[var(--color-success)]">€{totalPotentialSavings}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('ai.stats.potential_savings')}</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<div className="w-8 h-8 mx-auto bg-blue-100 dark:bg-blue-900/20 rounded-full flex items-center justify-center mb-2">
|
||||
<span className="text-blue-600 font-bold text-sm">%</span>
|
||||
<div className="text-center p-4 bg-[var(--color-info)]/10 rounded-lg border border-[var(--color-info)]/20 hover:border-[var(--color-info)]/40 transition-colors">
|
||||
<div className="w-8 h-8 mx-auto bg-[var(--color-info)]/20 rounded-full flex items-center justify-center mb-2">
|
||||
<span className="text-[var(--color-info)] font-bold text-sm">%</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[var(--text-primary)]">{avgConfidence.toFixed(0)}%</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('ai.stats.avg_confidence')}</p>
|
||||
<p className="text-2xl font-bold text-[var(--color-info)]">{avgConfidence.toFixed(0)}%</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('ai.stats.avg_confidence')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Status */}
|
||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||
<div className="p-4 bg-[var(--color-success)]/10 border border-[var(--color-success)]/20 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-sm font-medium text-green-600">
|
||||
<div className="w-2 h-2 bg-[var(--color-success)] rounded-full animate-pulse"></div>
|
||||
<span className="text-sm font-semibold text-[var(--color-success)]">
|
||||
{t('ai.status.active')}
|
||||
</span>
|
||||
<span className="text-xs text-[var(--text-secondary)] ml-2">
|
||||
<span className="text-xs text-[var(--text-secondary)] ml-2 font-medium">
|
||||
{t('ai.last_updated')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -252,8 +252,8 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
{/* High Priority Insights */}
|
||||
{highPriorityInsights.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-3 flex items-center">
|
||||
<AlertTriangle className="w-4 h-4 mr-2 text-red-600" />
|
||||
<h4 className="text-sm font-semibold text-[var(--text-primary)] mb-3 flex items-center">
|
||||
<AlertTriangle className="w-4 h-4 mr-2 text-[var(--color-error)]" />
|
||||
{t('ai.high_priority_insights')} ({highPriorityInsights.length})
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
@@ -262,7 +262,7 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
const ImpactIcon = getImpactIcon(insight.impact.type);
|
||||
|
||||
return (
|
||||
<div key={insight.id} className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-red-200 dark:border-red-800">
|
||||
<div key={insight.id} className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--color-error)]/30 hover:border-[var(--color-error)]/50 transition-all duration-200">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-start space-x-3">
|
||||
<TypeIcon className={`w-5 h-5 mt-1 ${getTypeColor(insight.type)}`} />
|
||||
@@ -287,14 +287,14 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Impact */}
|
||||
<div className="flex items-center justify-between p-3 bg-[var(--bg-tertiary)] rounded-lg">
|
||||
<div className="flex items-center justify-between p-3 bg-[var(--color-success)]/5 border border-[var(--color-success)]/10 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<ImpactIcon className="w-4 h-4 text-[var(--color-primary)]" />
|
||||
<span className="text-sm font-medium text-[var(--text-primary)]">
|
||||
<span className="text-sm font-semibold text-[var(--text-primary)]">
|
||||
{t(`ai.impact.${insight.impact.type}`)}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-lg font-bold text-green-600">
|
||||
<span className="text-lg font-bold text-[var(--color-success)]">
|
||||
{formatImpactValue(insight.impact)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -318,17 +318,17 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
|
||||
{/* All Insights */}
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-3 flex items-center">
|
||||
<Brain className="w-4 h-4 mr-2" />
|
||||
<h4 className="text-sm font-semibold text-[var(--text-primary)] mb-3 flex items-center">
|
||||
<Brain className="w-4 h-4 mr-2 text-[var(--color-secondary)]" />
|
||||
{t('ai.all_insights')} ({activeInsights.length})
|
||||
</h4>
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
<div className="space-y-2 max-h-96 overflow-y-auto pr-1">
|
||||
{activeInsights.map((insight) => {
|
||||
const TypeIcon = getTypeIcon(insight.type);
|
||||
const ImpactIcon = getImpactIcon(insight.impact.type);
|
||||
|
||||
return (
|
||||
<div key={insight.id} className="p-3 bg-[var(--bg-secondary)] rounded-lg hover:bg-[var(--bg-tertiary)] transition-colors">
|
||||
<div key={insight.id} className="p-3 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)] hover:border-[var(--border-focus)] hover:shadow-sm transition-all duration-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<TypeIcon className={`w-4 h-4 ${getTypeColor(insight.type)}`} />
|
||||
@@ -362,16 +362,16 @@ export const AIInsightsWidget: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* AI Performance Summary */}
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
<div className="flex items-start space-x-2">
|
||||
<Brain className="w-5 h-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-600">
|
||||
<div className="p-4 bg-[var(--color-info)]/10 border border-[var(--color-info)]/20 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Brain className="w-5 h-5 text-[var(--color-info)] mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-[var(--color-info)] mb-1">
|
||||
{t('ai.performance.summary')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
{implementedInsights.length} {t('ai.performance.insights_implemented')},
|
||||
{totalPotentialSavings > 0 && ` €${totalPotentialSavings} ${t('ai.performance.in_savings_identified')}`}
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{implementedInsights.length} {t('ai.performance.insights_implemented')}
|
||||
{totalPotentialSavings > 0 && `, €${totalPotentialSavings} ${t('ai.performance.in_savings_identified')}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Activity, Clock, AlertCircle, CheckCircle, Play, Pause } from 'lucide-react';
|
||||
import { Activity, Clock, AlertCircle, CheckCircle, Play, Pause, Zap } from 'lucide-react';
|
||||
import { AnalyticsWidget } from '../AnalyticsWidget';
|
||||
import { Badge, ProgressBar, Button } from '../../../../ui';
|
||||
import { useActiveBatches } from '../../../../../api/hooks/production';
|
||||
@@ -86,6 +86,19 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
return 'info';
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'URGENT':
|
||||
return 'text-[var(--color-error)]';
|
||||
case 'HIGH':
|
||||
return 'text-[var(--color-warning)]';
|
||||
case 'MEDIUM':
|
||||
return 'text-[var(--color-info)]';
|
||||
default:
|
||||
return 'text-[var(--text-secondary)]';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnalyticsWidget
|
||||
title={t('tracker.live_batch_tracker')}
|
||||
@@ -110,7 +123,7 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
<p className="text-sm">{t('messages.no_active_batches_description')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto pr-1">
|
||||
{batches.map((batch) => {
|
||||
const StatusIcon = getStatusIcon(batch.status);
|
||||
const progress = calculateProgress(batch);
|
||||
@@ -119,16 +132,16 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
key={batch.id}
|
||||
className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)] hover:shadow-md transition-all"
|
||||
className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)] hover:border-[var(--border-focus)] hover:shadow-md transition-all duration-200"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-start space-x-3 flex-1">
|
||||
<div className="w-8 h-8 bg-[var(--color-primary)]/10 rounded-lg flex items-center justify-center mt-0.5">
|
||||
<div className="w-8 h-8 bg-[var(--color-primary)]/10 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<StatusIcon className="w-4 h-4 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-1">
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-1 truncate">
|
||||
{batch.product_name}
|
||||
</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-1">
|
||||
@@ -144,7 +157,7 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant={getStatusBadgeVariant(batch.status)}>
|
||||
<Badge variant={getStatusBadgeVariant(batch.status)} className="flex-shrink-0">
|
||||
{t(`status.${batch.status.toLowerCase()}`)}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -152,11 +165,11 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
{/* Progress Bar */}
|
||||
{batch.status !== ProductionStatus.PENDING && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-xs text-[var(--text-secondary)]">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-xs font-medium text-[var(--text-secondary)]">
|
||||
{t('tracker.progress')}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-[var(--text-primary)]">
|
||||
<span className="text-xs font-semibold text-[var(--text-primary)]">
|
||||
{progress}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -169,33 +182,29 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Details */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center justify-between text-xs flex-wrap gap-2">
|
||||
<div className="flex items-center space-x-3">
|
||||
{batch.priority && (
|
||||
<span className={`font-medium ${
|
||||
batch.priority === 'URGENT' ? 'text-red-600' :
|
||||
batch.priority === 'HIGH' ? 'text-orange-600' :
|
||||
batch.priority === 'MEDIUM' ? 'text-blue-600' :
|
||||
'text-gray-600'
|
||||
}`}>
|
||||
<span className={`font-semibold ${getPriorityColor(batch.priority)}`}>
|
||||
{t(`priority.${batch.priority.toLowerCase()}`)}
|
||||
</span>
|
||||
)}
|
||||
{batch.is_rush_order && (
|
||||
<span className="text-red-600 font-medium">
|
||||
<span className="text-[var(--color-error)] font-semibold flex items-center">
|
||||
<Zap className="w-3 h-3 mr-1" />
|
||||
{t('batch.rush_order')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-1 text-[var(--text-secondary)]">
|
||||
<Clock className="w-3 h-3" />
|
||||
<div className="flex items-center space-x-1.5 text-[var(--text-secondary)]">
|
||||
<Clock className="w-3.5 h-3.5" />
|
||||
<span>
|
||||
{eta ? (
|
||||
<span className={eta.includes('delayed') ? 'text-red-600' : ''}>
|
||||
<span className={eta.includes('delayed') ? 'text-[var(--color-error)] font-medium' : 'font-medium'}>
|
||||
ETA: {eta}
|
||||
</span>
|
||||
) : (
|
||||
t('tracker.pending_start')
|
||||
<span className="font-medium">{t('tracker.pending_start')}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -203,15 +212,19 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
|
||||
{/* Equipment & Staff */}
|
||||
{(batch.equipment_used?.length > 0 || batch.staff_assigned?.length > 0) && (
|
||||
<div className="mt-2 pt-2 border-t border-[var(--border-primary)] text-xs text-[var(--text-tertiary)]">
|
||||
<div className="mt-3 pt-3 border-t border-[var(--border-secondary)] text-xs">
|
||||
{batch.equipment_used?.length > 0 && (
|
||||
<div className="mb-1">
|
||||
{t('batch.equipment')}: {batch.equipment_used.join(', ')}
|
||||
<div className="mb-1.5 flex items-start">
|
||||
<span className="text-[var(--text-secondary)] font-medium mr-2">{t('batch.equipment')}:</span>
|
||||
<span className="text-[var(--text-tertiary)] flex-1">{batch.equipment_used.join(', ')}</span>
|
||||
</div>
|
||||
)}
|
||||
{batch.staff_assigned?.length > 0 && (
|
||||
<div>
|
||||
{t('batch.staff')}: {batch.staff_assigned.length} {t('common.assigned')}
|
||||
<div className="flex items-center">
|
||||
<span className="text-[var(--text-secondary)] font-medium mr-2">{t('batch.staff')}:</span>
|
||||
<span className="text-[var(--text-tertiary)]">
|
||||
{batch.staff_assigned.length} {t('common.assigned')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -224,31 +237,31 @@ export const LiveBatchTrackerWidget: React.FC = () => {
|
||||
|
||||
{/* Summary */}
|
||||
{batches.length > 0 && (
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<div className="grid grid-cols-4 gap-4 text-center text-xs">
|
||||
<div>
|
||||
<p className="font-medium text-green-600">
|
||||
<div className="pt-4 mt-2 border-t border-[var(--border-primary)]">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-center text-xs">
|
||||
<div className="p-3 bg-[var(--color-success)]/10 rounded-lg border border-[var(--color-success)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-success)] mb-1">
|
||||
{batches.filter(b => b.status === ProductionStatus.COMPLETED).length}
|
||||
</p>
|
||||
<p className="text-[var(--text-tertiary)]">{t('status.completed')}</p>
|
||||
<p className="text-[var(--text-secondary)] font-medium">{t('status.completed')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-blue-600">
|
||||
<div className="p-3 bg-[var(--color-info)]/10 rounded-lg border border-[var(--color-info)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-info)] mb-1">
|
||||
{batches.filter(b => b.status === ProductionStatus.IN_PROGRESS).length}
|
||||
</p>
|
||||
<p className="text-[var(--text-tertiary)]">{t('status.in_progress')}</p>
|
||||
<p className="text-[var(--text-secondary)] font-medium">{t('status.in_progress')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-orange-600">
|
||||
<div className="p-3 bg-[var(--color-warning)]/10 rounded-lg border border-[var(--color-warning)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-warning)] mb-1">
|
||||
{batches.filter(b => b.status === ProductionStatus.PENDING).length}
|
||||
</p>
|
||||
<p className="text-[var(--text-tertiary)]">{t('status.pending')}</p>
|
||||
<p className="text-[var(--text-secondary)] font-medium">{t('status.pending')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-red-600">
|
||||
<div className="p-3 bg-[var(--color-error)]/10 rounded-lg border border-[var(--color-error)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-error)] mb-1">
|
||||
{batches.filter(b => [ProductionStatus.FAILED, ProductionStatus.CANCELLED].includes(b.status)).length}
|
||||
</p>
|
||||
<p className="text-[var(--text-tertiary)]">{t('tracker.issues')}</p>
|
||||
<p className="text-[var(--text-secondary)] font-medium">{t('tracker.issues')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
id: 'quality-trend',
|
||||
name: t('quality.daily_quality_score'),
|
||||
type: 'line',
|
||||
color: '#16a34a',
|
||||
color: 'var(--color-success)',
|
||||
data: scores
|
||||
}
|
||||
];
|
||||
@@ -83,7 +83,7 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
id: 'quality-distribution',
|
||||
name: t('quality.score_distribution'),
|
||||
type: 'bar',
|
||||
color: '#d97706',
|
||||
color: 'var(--color-primary)',
|
||||
data: distribution.map(item => ({
|
||||
x: item.range,
|
||||
y: item.count,
|
||||
@@ -94,17 +94,17 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
};
|
||||
|
||||
const getScoreColor = (score: number) => {
|
||||
if (score >= 9) return 'text-green-600';
|
||||
if (score >= 8) return 'text-blue-600';
|
||||
if (score >= 7) return 'text-orange-600';
|
||||
return 'text-red-600';
|
||||
if (score >= 9) return 'text-[var(--color-success)]';
|
||||
if (score >= 8) return 'text-[var(--color-info)]';
|
||||
if (score >= 7) return 'text-[var(--color-warning)]';
|
||||
return 'text-[var(--color-error)]';
|
||||
};
|
||||
|
||||
const getScoreStatus = (score: number) => {
|
||||
if (score >= 9) return { status: 'excellent', bgColor: 'bg-green-100 dark:bg-green-900/20' };
|
||||
if (score >= 8) return { status: 'good', bgColor: 'bg-blue-100 dark:bg-blue-900/20' };
|
||||
if (score >= 7) return { status: 'acceptable', bgColor: 'bg-orange-100 dark:bg-orange-900/20' };
|
||||
return { status: 'needs_improvement', bgColor: 'bg-red-100 dark:bg-red-900/20' };
|
||||
if (score >= 9) return { status: 'excellent', bgColor: 'bg-[var(--color-success)]/10 border border-[var(--color-success)]/20' };
|
||||
if (score >= 8) return { status: 'good', bgColor: 'bg-[var(--color-info)]/10 border border-[var(--color-info)]/20' };
|
||||
if (score >= 7) return { status: 'acceptable', bgColor: 'bg-[var(--color-warning)]/10 border border-[var(--color-warning)]/20' };
|
||||
return { status: 'needs_improvement', bgColor: 'bg-[var(--color-error)]/10 border border-[var(--color-error)]/20' };
|
||||
};
|
||||
|
||||
const scoreStatus = getScoreStatus(qualityData.averageScore);
|
||||
@@ -124,23 +124,24 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-5">
|
||||
{/* Quality Score Display */}
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center space-x-2 mb-2">
|
||||
<Star className={`w-6 h-6 ${getScoreColor(qualityData.averageScore)}`} />
|
||||
<span className="text-3xl font-bold text-[var(--text-primary)]">
|
||||
{qualityData.averageScore.toFixed(1)}/10
|
||||
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)]">
|
||||
<div className="flex items-center justify-center space-x-3 mb-3">
|
||||
<Star className={`w-8 h-8 fill-current ${getScoreColor(qualityData.averageScore)}`} />
|
||||
<span className="text-4xl font-bold text-[var(--text-primary)]">
|
||||
{qualityData.averageScore.toFixed(1)}
|
||||
</span>
|
||||
<span className="text-xl text-[var(--text-secondary)] font-medium">/10</span>
|
||||
</div>
|
||||
<div className={`inline-flex items-center space-x-2 px-3 py-1 rounded-full text-sm ${scoreStatus.bgColor}`}>
|
||||
<div className={`inline-flex items-center space-x-2 px-4 py-1.5 rounded-full text-sm font-medium ${scoreStatus.bgColor}`}>
|
||||
<span className={getScoreColor(qualityData.averageScore)}>
|
||||
{t(`quality.${scoreStatus.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center space-x-1 mt-2">
|
||||
<TrendIcon className={`w-4 h-4 ${qualityData.trend >= 0 ? 'text-green-600' : 'text-red-600'}`} />
|
||||
<span className={`text-sm ${qualityData.trend >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
<div className="flex items-center justify-center space-x-1.5 mt-3 pt-3 border-t border-[var(--border-secondary)]">
|
||||
<TrendIcon className={`w-4 h-4 ${qualityData.trend >= 0 ? 'text-[var(--color-success)]' : 'text-[var(--color-error)]'}`} />
|
||||
<span className={`text-sm font-semibold ${qualityData.trend >= 0 ? 'text-[var(--color-success)]' : 'text-[var(--color-error)]'}`}>
|
||||
{qualityData.trend > 0 ? '+' : ''}{qualityData.trend.toFixed(1)} {t('quality.vs_last_week')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -148,17 +149,17 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
|
||||
{/* Key Metrics */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg text-center">
|
||||
<div className="text-2xl font-bold text-[var(--text-primary)] mb-1">
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-primary)] text-center hover:border-[var(--border-focus)] transition-colors">
|
||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
||||
{qualityData.totalChecks}
|
||||
</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('stats.total_checks')}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('stats.total_checks')}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg text-center">
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">
|
||||
<div className="p-4 bg-[var(--color-success)]/10 rounded-lg border border-[var(--color-success)]/20 text-center hover:border-[var(--color-success)]/40 transition-colors">
|
||||
<div className="text-3xl font-bold text-[var(--color-success)] mb-1">
|
||||
{qualityData.passRate.toFixed(1)}%
|
||||
</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('stats.pass_rate')}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('stats.pass_rate')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,36 +198,36 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
|
||||
{/* Quality Insights */}
|
||||
<div className="grid grid-cols-3 gap-3 text-center">
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<p className="text-lg font-bold text-green-600">
|
||||
<div className="p-3 bg-[var(--color-success)]/10 rounded-lg border border-[var(--color-success)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-success)] mb-1">
|
||||
{Math.round(qualityData.totalChecks * 0.35)}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('quality.excellent_scores')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('quality.excellent_scores')}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<p className="text-lg font-bold text-blue-600">
|
||||
<div className="p-3 bg-[var(--color-info)]/10 rounded-lg border border-[var(--color-info)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-info)] mb-1">
|
||||
{Math.round(qualityData.totalChecks * 0.30)}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('quality.good_scores')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('quality.good_scores')}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<p className="text-lg font-bold text-red-600">
|
||||
<div className="p-3 bg-[var(--color-error)]/10 rounded-lg border border-[var(--color-error)]/20">
|
||||
<p className="text-2xl font-bold text-[var(--color-error)] mb-1">
|
||||
{Math.round(qualityData.totalChecks * 0.15)}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('quality.needs_improvement')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('quality.needs_improvement')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quality Recommendations */}
|
||||
{qualityData.averageScore < 8 && (
|
||||
<div className="p-3 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
|
||||
<div className="flex items-start space-x-2">
|
||||
<Star className="w-4 h-4 mt-0.5 text-orange-600" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-orange-600">
|
||||
<div className="p-4 bg-[var(--color-warning)]/10 border border-[var(--color-warning)]/20 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Star className="w-5 h-5 mt-0.5 text-[var(--color-warning)]" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-[var(--color-warning)] mb-1">
|
||||
{t('quality.recommendations.improve_quality')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{t('quality.recommendations.focus_consistency')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -235,14 +236,14 @@ export const QualityScoreTrendsWidget: React.FC = () => {
|
||||
)}
|
||||
|
||||
{qualityData.averageScore >= 9 && (
|
||||
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||
<div className="flex items-start space-x-2">
|
||||
<Award className="w-4 h-4 mt-0.5 text-green-600" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-green-600">
|
||||
<div className="p-4 bg-[var(--color-success)]/10 border border-[var(--color-success)]/20 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Award className="w-5 h-5 mt-0.5 text-[var(--color-success)]" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-[var(--color-success)] mb-1">
|
||||
{t('quality.recommendations.excellent_quality')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{t('quality.recommendations.maintain_standards')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -154,10 +154,10 @@ export const WasteDefectTrackerWidget: React.FC = () => {
|
||||
|
||||
const getWasteStatus = () => {
|
||||
const totalWastePercentage = wasteData.wastePercentage + wasteData.defectPercentage;
|
||||
if (totalWastePercentage <= 3) return { status: 'excellent', color: 'text-green-600', bgColor: 'bg-green-100 dark:bg-green-900/20' };
|
||||
if (totalWastePercentage <= 5) return { status: 'good', color: 'text-blue-600', bgColor: 'bg-blue-100 dark:bg-blue-900/20' };
|
||||
if (totalWastePercentage <= 8) return { status: 'warning', color: 'text-orange-600', bgColor: 'bg-orange-100 dark:bg-orange-900/20' };
|
||||
return { status: 'critical', color: 'text-red-600', bgColor: 'bg-red-100 dark:bg-red-900/20' };
|
||||
if (totalWastePercentage <= 3) return { status: 'excellent', color: 'text-[var(--color-success)]', bgColor: 'bg-[var(--color-success)]/10 border border-[var(--color-success)]/20' };
|
||||
if (totalWastePercentage <= 5) return { status: 'good', color: 'text-[var(--color-info)]', bgColor: 'bg-[var(--color-info)]/10 border border-[var(--color-info)]/20' };
|
||||
if (totalWastePercentage <= 8) return { status: 'warning', color: 'text-[var(--color-warning)]', bgColor: 'bg-[var(--color-warning)]/10 border border-[var(--color-warning)]/20' };
|
||||
return { status: 'critical', color: 'text-[var(--color-error)]', bgColor: 'bg-[var(--color-error)]/10 border border-[var(--color-error)]/20' };
|
||||
};
|
||||
|
||||
const wasteStatus = getWasteStatus();
|
||||
@@ -177,43 +177,43 @@ export const WasteDefectTrackerWidget: React.FC = () => {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-5">
|
||||
{/* Overall Waste Metrics */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg text-center">
|
||||
<div className="p-4 bg-[var(--color-error)]/10 border border-[var(--color-error)]/20 rounded-lg text-center hover:border-[var(--color-error)]/40 transition-colors">
|
||||
<div className="flex items-center justify-center space-x-2 mb-1">
|
||||
<Trash2 className="w-4 h-4 text-red-600" />
|
||||
<span className="text-lg font-bold text-[var(--text-primary)]">
|
||||
<Trash2 className="w-5 h-5 text-[var(--color-error)]" />
|
||||
<span className="text-2xl font-bold text-[var(--color-error)]">
|
||||
{totalWastePercentage.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('quality.total_waste')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('quality.total_waste')}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg text-center">
|
||||
<div className="p-4 bg-[var(--color-warning)]/10 border border-[var(--color-warning)]/20 rounded-lg text-center hover:border-[var(--color-warning)]/40 transition-colors">
|
||||
<div className="flex items-center justify-center space-x-2 mb-1">
|
||||
<AlertTriangle className="w-4 h-4 text-orange-600" />
|
||||
<span className="text-lg font-bold text-[var(--text-primary)]">
|
||||
<AlertTriangle className="w-5 h-5 text-[var(--color-warning)]" />
|
||||
<span className="text-2xl font-bold text-[var(--color-warning)]">
|
||||
{wasteData.totalDefects}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('quality.total_defects')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('quality.total_defects')}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg text-center">
|
||||
<div className="p-4 bg-[var(--color-success)]/10 border border-[var(--color-success)]/20 rounded-lg text-center hover:border-[var(--color-success)]/40 transition-colors">
|
||||
<div className="flex items-center justify-center space-x-2 mb-1">
|
||||
<TrendingDown className="w-4 h-4 text-green-600" />
|
||||
<span className="text-lg font-bold text-[var(--text-primary)]">
|
||||
<TrendingDown className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<span className="text-2xl font-bold text-[var(--color-success)]">
|
||||
€{totalWasteCost.toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--text-secondary)]">{t('cost.waste_cost')}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('cost.waste_cost')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Waste Status */}
|
||||
<div className={`p-3 rounded-lg ${wasteStatus.bgColor}`}>
|
||||
<div className={`p-4 rounded-lg ${wasteStatus.bgColor}`}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertTriangle className={`w-4 h-4 ${wasteStatus.color}`} />
|
||||
<span className={`text-sm font-medium ${wasteStatus.color}`}>
|
||||
<AlertTriangle className={`w-5 h-5 ${wasteStatus.color}`} />
|
||||
<span className={`text-sm font-semibold ${wasteStatus.color}`}>
|
||||
{t(`quality.status.${wasteStatus.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -285,28 +285,28 @@ export const WasteDefectTrackerWidget: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Reduction Recommendations */}
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{wasteSources.filter(s => s.severity === 'high').length > 0 && (
|
||||
<div className="flex items-start space-x-2 p-3 bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||
<AlertTriangle className="w-4 h-4 mt-0.5 text-red-600" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-red-600">
|
||||
<div className="flex items-start space-x-3 p-4 bg-[var(--color-error)]/10 border border-[var(--color-error)]/20 rounded-lg">
|
||||
<AlertTriangle className="w-5 h-5 mt-0.5 text-[var(--color-error)]" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-[var(--color-error)] mb-1">
|
||||
{t('quality.recommendations.high_waste_detected')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{t('quality.recommendations.check_temperature_timing')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-start space-x-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
<Target className="w-4 h-4 mt-0.5 text-blue-600" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-600">
|
||||
<div className="flex items-start space-x-3 p-4 bg-[var(--color-info)]/10 border border-[var(--color-info)]/20 rounded-lg">
|
||||
<Target className="w-5 h-5 mt-0.5 text-[var(--color-info)]" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-[var(--color-info)] mb-1">
|
||||
{t('quality.recommendations.improvement_opportunity')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{totalWastePercentage > 5
|
||||
? t('quality.recommendations.reduce_waste_target', { target: '3%' })
|
||||
: t('quality.recommendations.maintain_quality_standards')
|
||||
|
||||
Reference in New Issue
Block a user