New enterprise feature
This commit is contained in:
155
frontend/src/components/dashboard/PerformanceChart.tsx
Normal file
155
frontend/src/components/dashboard/PerformanceChart.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user