155 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
}; |