130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
// ================================================================
|
|
// frontend/src/components/dashboard/InsightsGrid.tsx
|
|
// ================================================================
|
|
/**
|
|
* Insights Grid - Key metrics at a glance
|
|
*
|
|
* 2x2 grid of important metrics: savings, inventory, waste, deliveries.
|
|
* Mobile-first design with large touch targets.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Insights } from '../../api/hooks/newDashboard';
|
|
|
|
interface InsightsGridProps {
|
|
insights: Insights;
|
|
loading?: boolean;
|
|
}
|
|
|
|
const colorConfig = {
|
|
green: {
|
|
bgColor: 'var(--color-success-50)',
|
|
borderColor: 'var(--color-success-200)',
|
|
textColor: 'var(--color-success-800)',
|
|
detailColor: 'var(--color-success-600)',
|
|
},
|
|
amber: {
|
|
bgColor: 'var(--color-warning-50)',
|
|
borderColor: 'var(--color-warning-200)',
|
|
textColor: 'var(--color-warning-900)',
|
|
detailColor: 'var(--color-warning-600)',
|
|
},
|
|
red: {
|
|
bgColor: 'var(--color-error-50)',
|
|
borderColor: 'var(--color-error-200)',
|
|
textColor: 'var(--color-error-900)',
|
|
detailColor: 'var(--color-error-600)',
|
|
},
|
|
};
|
|
|
|
function InsightCard({
|
|
color,
|
|
i18n,
|
|
}: {
|
|
color: 'green' | 'amber' | 'red';
|
|
i18n: {
|
|
label: {
|
|
key: string;
|
|
params?: Record<string, any>;
|
|
};
|
|
value: {
|
|
key: string;
|
|
params?: Record<string, any>;
|
|
};
|
|
detail: {
|
|
key: string;
|
|
params?: Record<string, any>;
|
|
} | null;
|
|
};
|
|
}) {
|
|
const { t } = useTranslation('dashboard');
|
|
const config = colorConfig[color];
|
|
|
|
// Translate using i18n keys
|
|
const displayLabel = t(i18n.label.key, i18n.label.params);
|
|
const displayValue = t(i18n.value.key, i18n.value.params);
|
|
const displayDetail = i18n.detail ? t(i18n.detail.key, i18n.detail.params) : '';
|
|
|
|
return (
|
|
<div
|
|
className="border-2 rounded-xl p-4 md:p-6 transition-all duration-200 hover:shadow-lg cursor-pointer"
|
|
style={{
|
|
backgroundColor: config.bgColor,
|
|
borderColor: config.borderColor,
|
|
}}
|
|
>
|
|
{/* Label */}
|
|
<div className="text-sm md:text-base font-bold mb-2" style={{ color: 'var(--text-primary)' }}>{displayLabel}</div>
|
|
|
|
{/* Value */}
|
|
<div className="text-xl md:text-2xl font-bold mb-1" style={{ color: config.textColor }}>{displayValue}</div>
|
|
|
|
{/* Detail */}
|
|
<div className="text-sm font-medium" style={{ color: config.detailColor }}>{displayDetail}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function InsightsGrid({ insights, loading }: InsightsGridProps) {
|
|
if (loading) {
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
{[1, 2, 3, 4].map((i) => (
|
|
<div key={i} className="animate-pulse rounded-xl p-6" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
|
<div className="h-4 rounded w-1/2 mb-3" style={{ backgroundColor: 'var(--bg-quaternary)' }}></div>
|
|
<div className="h-8 rounded w-3/4 mb-2" style={{ backgroundColor: 'var(--bg-quaternary)' }}></div>
|
|
<div className="h-4 rounded w-2/3" style={{ backgroundColor: 'var(--bg-quaternary)' }}></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Guard against undefined values
|
|
if (!insights || !insights.savings || !insights.inventory || !insights.waste || !insights.deliveries) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<InsightCard
|
|
color={insights.savings.color}
|
|
i18n={insights.savings.i18n}
|
|
/>
|
|
<InsightCard
|
|
color={insights.inventory.color}
|
|
i18n={insights.inventory.i18n}
|
|
/>
|
|
<InsightCard
|
|
color={insights.waste.color}
|
|
i18n={insights.waste.i18n}
|
|
/>
|
|
<InsightCard
|
|
color={insights.deliveries.color}
|
|
i18n={insights.deliveries.i18n}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|