113 lines
2.9 KiB
TypeScript
113 lines
2.9 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 { Insights } from '../../api/hooks/newDashboard';
|
||
|
|
|
||
|
|
interface InsightsGridProps {
|
||
|
|
insights: Insights;
|
||
|
|
loading?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const colorConfig = {
|
||
|
|
green: {
|
||
|
|
bg: 'bg-green-50',
|
||
|
|
border: 'border-green-200',
|
||
|
|
text: 'text-green-800',
|
||
|
|
detail: 'text-green-600',
|
||
|
|
},
|
||
|
|
amber: {
|
||
|
|
bg: 'bg-amber-50',
|
||
|
|
border: 'border-amber-200',
|
||
|
|
text: 'text-amber-900',
|
||
|
|
detail: 'text-amber-600',
|
||
|
|
},
|
||
|
|
red: {
|
||
|
|
bg: 'bg-red-50',
|
||
|
|
border: 'border-red-200',
|
||
|
|
text: 'text-red-900',
|
||
|
|
detail: 'text-red-600',
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
function InsightCard({
|
||
|
|
label,
|
||
|
|
value,
|
||
|
|
detail,
|
||
|
|
color,
|
||
|
|
}: {
|
||
|
|
label: string;
|
||
|
|
value: string;
|
||
|
|
detail: string;
|
||
|
|
color: 'green' | 'amber' | 'red';
|
||
|
|
}) {
|
||
|
|
const config = colorConfig[color];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`${config.bg} ${config.border} border-2 rounded-xl p-4 md:p-6 transition-all duration-200 hover:shadow-lg cursor-pointer`}
|
||
|
|
>
|
||
|
|
{/* Label */}
|
||
|
|
<div className="text-sm md:text-base font-bold text-gray-700 mb-2">{label}</div>
|
||
|
|
|
||
|
|
{/* Value */}
|
||
|
|
<div className={`text-xl md:text-2xl font-bold ${config.text} mb-1`}>{value}</div>
|
||
|
|
|
||
|
|
{/* Detail */}
|
||
|
|
<div className={`text-sm ${config.detail} font-medium`}>{detail}</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 bg-gray-100 rounded-xl p-6">
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-1/2 mb-3"></div>
|
||
|
|
<div className="h-8 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
|
|
<InsightCard
|
||
|
|
label={insights.savings.label}
|
||
|
|
value={insights.savings.value}
|
||
|
|
detail={insights.savings.detail}
|
||
|
|
color={insights.savings.color}
|
||
|
|
/>
|
||
|
|
<InsightCard
|
||
|
|
label={insights.inventory.label}
|
||
|
|
value={insights.inventory.value}
|
||
|
|
detail={insights.inventory.detail}
|
||
|
|
color={insights.inventory.color}
|
||
|
|
/>
|
||
|
|
<InsightCard
|
||
|
|
label={insights.waste.label}
|
||
|
|
value={insights.waste.value}
|
||
|
|
detail={insights.waste.detail}
|
||
|
|
color={insights.waste.color}
|
||
|
|
/>
|
||
|
|
<InsightCard
|
||
|
|
label={insights.deliveries.label}
|
||
|
|
value={insights.deliveries.value}
|
||
|
|
detail={insights.deliveries.detail}
|
||
|
|
color={insights.deliveries.color}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|