New enterprise feature

This commit is contained in:
Urtzi Alfaro
2025-11-30 09:12:40 +01:00
parent f9d0eec6ec
commit 972db02f6d
176 changed files with 19741 additions and 1361 deletions

View File

@@ -0,0 +1,148 @@
/*
* Performance Chart Component for Enterprise Dashboard
* Shows anonymized performance ranking of child outlets
*/
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card';
import { Badge } from '../ui/Badge';
import { BarChart3, TrendingUp, TrendingDown, ArrowUp, ArrowDown } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface PerformanceDataPoint {
rank: number;
tenant_id: string;
anonymized_name: string; // "Outlet 1", "Outlet 2", etc.
metric_value: number;
original_name?: string; // Only for internal use, not displayed
}
interface PerformanceChartProps {
data: PerformanceDataPoint[];
metric: string;
period: number;
}
const PerformanceChart: React.FC<PerformanceChartProps> = ({
data = [],
metric,
period
}) => {
const { t } = useTranslation('dashboard');
// Get metric info
const getMetricInfo = () => {
switch (metric) {
case 'sales':
return {
icon: <TrendingUp className="w-4 h-4" />,
label: t('enterprise.metrics.sales'),
unit: '€',
format: (val: number) => val.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
};
case 'inventory_value':
return {
icon: <Package className="w-4 h-4" />,
label: t('enterprise.metrics.inventory_value'),
unit: '€',
format: (val: number) => val.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
};
case 'order_frequency':
return {
icon: <ShoppingCart className="w-4 h-4" />,
label: t('enterprise.metrics.order_frequency'),
unit: '',
format: (val: number) => Math.round(val).toString()
};
default:
return {
icon: <BarChart3 className="w-4 h-4" />,
label: metric,
unit: '',
format: (val: number) => val.toString()
};
}
};
const metricInfo = getMetricInfo();
// Calculate max value for bar scaling
const maxValue = data.length > 0 ? Math.max(...data.map(item => item.metric_value), 1) : 1;
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div className="flex items-center gap-2">
<BarChart3 className="w-5 h-5 text-blue-600" />
<CardTitle>{t('enterprise.outlet_performance')}</CardTitle>
</div>
<div className="text-sm text-gray-500">
{t('enterprise.performance_based_on_period', {
metric: t(`enterprise.metrics.${metric}`) || metric,
period
})}
</div>
</CardHeader>
<CardContent>
{data.length > 0 ? (
<div className="space-y-4">
{data.map((item, index) => {
const percentage = (item.metric_value / maxValue) * 100;
const isTopPerformer = index === 0;
return (
<div key={item.tenant_id} className="space-y-2">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium ${
isTopPerformer
? 'bg-yellow-100 text-yellow-800 border border-yellow-300'
: 'bg-gray-100 text-gray-700'
}`}>
{item.rank}
</div>
<span className="font-medium">{item.anonymized_name}</span>
</div>
<div className="flex items-center gap-2">
<span className="font-semibold">
{metricInfo.unit}{metricInfo.format(item.metric_value)}
</span>
{isTopPerformer && (
<Badge variant="secondary" className="bg-yellow-50 text-yellow-800">
{t('enterprise.top_performer')}
</Badge>
)}
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className={`h-3 rounded-full transition-all duration-500 ${
isTopPerformer
? 'bg-gradient-to-r from-blue-500 to-purple-500'
: 'bg-blue-400'
}`}
style={{ width: `${percentage}%` }}
></div>
</div>
</div>
);
})}
</div>
) : (
<div className="text-center py-8 text-gray-500">
<BarChart3 className="w-12 h-12 mx-auto mb-4 text-gray-300" />
<p>{t('enterprise.no_performance_data')}</p>
<p className="text-sm text-gray-400 mt-1">
{t('enterprise.performance_based_on_period', {
metric: t(`enterprise.metrics.${metric}`) || metric,
period
})}
</p>
</div>
)}
</CardContent>
</Card>
);
};
export default PerformanceChart;