Files
bakery-ia/frontend/src/components/dashboard/PerformanceChart.tsx
2025-11-30 09:12:40 +01:00

155 lines
4.1 KiB
TypeScript

/*
* Performance Chart Component
* Shows anonymized ranking of outlets based on selected metric
*/
import React from 'react';
import { Bar } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { Card, CardContent } from '../ui/Card';
import { useTranslation } from 'react-i18next';
// Register Chart.js components
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
interface PerformanceData {
rank: number;
tenant_id: string;
anonymized_name: string;
metric_value: number;
}
interface PerformanceChartProps {
data?: PerformanceData[];
metric: string;
period: number;
}
export const PerformanceChart: React.FC<PerformanceChartProps> = ({ data, metric, period }) => {
const { t } = useTranslation('dashboard');
// Prepare chart data
const chartData = {
labels: data?.map(item => item.anonymized_name) || [],
datasets: [
{
label: t(`enterprise.metric_labels.${metric}`) || metric,
data: data?.map(item => item.metric_value) || [],
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
},
],
};
const options = {
responsive: true,
plugins: {
legend: {
display: false,
},
title: {
display: true,
text: t('enterprise.outlet_performance_chart_title'),
},
tooltip: {
callbacks: {
label: function(context: any) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
if (metric === 'sales') {
label += `${context.parsed.y.toFixed(2)}`;
} else {
label += context.parsed.y;
}
}
return label;
}
}
}
},
scales: {
x: {
title: {
display: true,
text: t('enterprise.outlet'),
},
},
y: {
title: {
display: true,
text: t(`enterprise.metric_labels.${metric}`) || metric,
},
beginAtZero: true,
},
},
};
return (
<div className="space-y-4">
<div className="text-sm text-gray-600">
{t('enterprise.performance_based_on', {
metric: t(`enterprise.metrics.${metric}`) || metric,
period
})}
</div>
{data && data.length > 0 ? (
<div className="h-80">
<Bar data={chartData} options={options} />
</div>
) : (
<div className="h-80 flex items-center justify-center text-gray-500">
{t('enterprise.no_performance_data')}
</div>
)}
{/* Performance ranking table */}
<div className="mt-4">
<h4 className="font-medium mb-2">{t('enterprise.ranking')}</h4>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-2 text-left">{t('enterprise.rank')}</th>
<th className="px-3 py-2 text-left">{t('enterprise.outlet')}</th>
<th className="px-3 py-2 text-right">
{t(`enterprise.metric_labels.${metric}`) || metric}
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{data?.map((item, index) => (
<tr key={item.tenant_id} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<td className="px-3 py-2">{item.rank}</td>
<td className="px-3 py-2 font-medium">{item.anonymized_name}</td>
<td className="px-3 py-2 text-right">
{metric === 'sales' ? `${item.metric_value.toFixed(2)}` : item.metric_value}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};