Transport components: - TransportTimeline: Visual timeline of transport events with status badges - JourneyMap: SVG-based map visualization of plant journey locations - CarbonFootprintCard: Carbon metrics display with comparison charts - QRCodeDisplay: QR code generation for traceability verification - TransportEventForm: Form for recording transport events Demand components: - DemandSignalCard: Regional demand signal with supply status indicators - PreferencesForm: Multi-section consumer preference input form - RecommendationList: Planting recommendations with risk assessment - SupplyGapChart: Supply vs demand visualization with gap indicators - SeasonalCalendar: Seasonal produce availability calendar view Analytics components: - EnvironmentalImpact: Comprehensive carbon and food miles analysis - FoodMilesTracker: Food miles tracking with daily charts and targets - SavingsCalculator: Environmental savings vs conventional agriculture All components follow existing patterns, use Tailwind CSS, and are fully typed.
191 lines
7.7 KiB
TypeScript
191 lines
7.7 KiB
TypeScript
import { EnvironmentalImpact, TransportMethod } from '../../lib/transport/types';
|
|
|
|
interface CarbonFootprintCardProps {
|
|
impact: EnvironmentalImpact;
|
|
showComparison?: boolean;
|
|
}
|
|
|
|
const METHOD_LABELS: Record<TransportMethod, string> = {
|
|
walking: 'Walking',
|
|
bicycle: 'Bicycle',
|
|
electric_vehicle: 'Electric Vehicle',
|
|
hybrid_vehicle: 'Hybrid Vehicle',
|
|
gasoline_vehicle: 'Gas Vehicle',
|
|
diesel_truck: 'Diesel Truck',
|
|
electric_truck: 'Electric Truck',
|
|
refrigerated_truck: 'Refrigerated Truck',
|
|
rail: 'Rail',
|
|
ship: 'Ship',
|
|
air: 'Air Freight',
|
|
drone: 'Drone',
|
|
local_delivery: 'Local Delivery',
|
|
customer_pickup: 'Customer Pickup',
|
|
};
|
|
|
|
const METHOD_COLORS: Record<string, string> = {
|
|
walking: 'bg-green-500',
|
|
bicycle: 'bg-green-400',
|
|
electric_vehicle: 'bg-emerald-400',
|
|
hybrid_vehicle: 'bg-lime-400',
|
|
electric_truck: 'bg-teal-400',
|
|
drone: 'bg-cyan-400',
|
|
rail: 'bg-blue-400',
|
|
ship: 'bg-blue-500',
|
|
local_delivery: 'bg-yellow-400',
|
|
customer_pickup: 'bg-orange-400',
|
|
gasoline_vehicle: 'bg-orange-500',
|
|
diesel_truck: 'bg-red-400',
|
|
refrigerated_truck: 'bg-red-500',
|
|
air: 'bg-red-600',
|
|
};
|
|
|
|
export default function CarbonFootprintCard({ impact, showComparison = true }: CarbonFootprintCardProps) {
|
|
// Calculate breakdown percentages
|
|
const totalCarbon = impact.totalCarbonKg;
|
|
const methodBreakdown = Object.entries(impact.breakdownByMethod)
|
|
.filter(([_, data]) => data.carbon > 0)
|
|
.sort((a, b) => b[1].carbon - a[1].carbon);
|
|
|
|
const getCarbonRating = (carbon: number): { label: string; color: string; icon: string } => {
|
|
if (carbon < 0.5) return { label: 'Excellent', color: 'text-green-600', icon: '🌟' };
|
|
if (carbon < 2) return { label: 'Good', color: 'text-lime-600', icon: '✓' };
|
|
if (carbon < 5) return { label: 'Moderate', color: 'text-yellow-600', icon: '⚡' };
|
|
if (carbon < 10) return { label: 'High', color: 'text-orange-600', icon: '⚠️' };
|
|
return { label: 'Very High', color: 'text-red-600', icon: '🚨' };
|
|
};
|
|
|
|
const rating = getCarbonRating(totalCarbon);
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
|
{/* Header */}
|
|
<div className="bg-gradient-to-r from-green-600 to-emerald-600 text-white p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h3 className="text-xl font-bold">Carbon Footprint</h3>
|
|
<p className="text-green-100 text-sm mt-1">Environmental impact analysis</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="text-3xl font-bold">{totalCarbon.toFixed(2)}</div>
|
|
<div className="text-green-100 text-sm">kg CO2e</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Rating badge */}
|
|
<div className="mt-4 inline-flex items-center px-3 py-1 bg-white bg-opacity-20 rounded-full">
|
|
<span className="mr-2">{rating.icon}</span>
|
|
<span className="font-semibold">{rating.label}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-6">
|
|
{/* Key metrics */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="p-4 bg-blue-50 rounded-lg">
|
|
<p className="text-sm text-gray-600">Total Food Miles</p>
|
|
<p className="text-2xl font-bold text-blue-600">{impact.totalFoodMiles.toFixed(1)}</p>
|
|
<p className="text-xs text-gray-500">kilometers</p>
|
|
</div>
|
|
<div className="p-4 bg-green-50 rounded-lg">
|
|
<p className="text-sm text-gray-600">Carbon Intensity</p>
|
|
<p className="text-2xl font-bold text-green-600">
|
|
{impact.carbonPerKgProduce.toFixed(3)}
|
|
</p>
|
|
<p className="text-xs text-gray-500">kg CO2 / kg produce</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Comparison with conventional */}
|
|
{showComparison && impact.comparisonToConventional && (
|
|
<div className="p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-lg border border-green-200">
|
|
<h4 className="font-semibold text-gray-900 mb-3 flex items-center">
|
|
<span className="mr-2">📊</span>
|
|
vs. Conventional Agriculture
|
|
</h4>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span className="text-gray-600">Carbon Saved</span>
|
|
<span className="font-semibold text-green-600">
|
|
{impact.comparisonToConventional.carbonSaved.toFixed(2)} kg
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className="bg-green-500 h-2 rounded-full"
|
|
style={{
|
|
width: `${Math.min(impact.comparisonToConventional.percentageReduction, 100)}%`,
|
|
}}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span className="text-gray-600">Miles Saved</span>
|
|
<span className="font-semibold text-blue-600">
|
|
{impact.comparisonToConventional.milesSaved.toFixed(1)} km
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2 border-t border-green-200 text-center">
|
|
<span className="text-2xl font-bold text-green-600">
|
|
{impact.comparisonToConventional.percentageReduction.toFixed(0)}%
|
|
</span>
|
|
<span className="text-sm text-gray-600 ml-2">reduction vs conventional</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Breakdown by transport method */}
|
|
{methodBreakdown.length > 0 && (
|
|
<div>
|
|
<h4 className="font-semibold text-gray-900 mb-3">Breakdown by Transport Method</h4>
|
|
<div className="space-y-2">
|
|
{methodBreakdown.map(([method, data]) => {
|
|
const percentage = totalCarbon > 0 ? (data.carbon / totalCarbon) * 100 : 0;
|
|
return (
|
|
<div key={method} className="flex items-center">
|
|
<div className="w-32 text-sm text-gray-600 truncate">
|
|
{METHOD_LABELS[method as TransportMethod] || method}
|
|
</div>
|
|
<div className="flex-1 mx-3">
|
|
<div className="w-full bg-gray-200 rounded-full h-4">
|
|
<div
|
|
className={`${METHOD_COLORS[method] || 'bg-gray-400'} h-4 rounded-full transition-all`}
|
|
style={{ width: `${percentage}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div className="w-24 text-right text-sm">
|
|
<span className="font-medium text-gray-900">{data.carbon.toFixed(3)} kg</span>
|
|
<span className="text-gray-500 ml-1">({percentage.toFixed(0)}%)</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tips */}
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
<h4 className="font-semibold text-yellow-800 mb-2">Tips to Reduce Impact</h4>
|
|
<ul className="text-sm text-yellow-700 space-y-1">
|
|
{totalCarbon > 5 && (
|
|
<li>• Consider electric or hybrid vehicles for transport</li>
|
|
)}
|
|
{impact.totalFoodMiles > 50 && (
|
|
<li>• Source produce from closer locations when possible</li>
|
|
)}
|
|
<li>• Consolidate shipments to reduce trips</li>
|
|
<li>• Use bicycle delivery for short distances</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|