localgreenchain/components/transport/CarbonFootprintCard.tsx
Claude 0cce5e2345
Add UI components for transport tracking, demand visualization, and analytics
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.
2025-11-22 18:34:51 +00:00

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>
);
}