mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
482 lines
20 KiB
TypeScript
482 lines
20 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Award, Briefcase, DollarSign, Globe, Target, TrendingUp, Zap } from 'lucide-react';
|
|
import { MainLayout } from '@/components/layout/MainLayout.tsx';
|
|
import PageHeader from '@/components/layout/PageHeader.tsx';
|
|
import Badge from '@/components/ui/Badge.tsx';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx';
|
|
import Spinner from '@/components/ui/Spinner.tsx';
|
|
import { Container, Flex, Grid, Stack } from '@/components/ui/layout';
|
|
import { useImpactMetrics, usePlatformStatistics } from '@/hooks/api/useAnalyticsAPI.ts';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import { useNavigation } from '@/hooks/useNavigation.tsx';
|
|
|
|
const ImpactMetrics = () => {
|
|
const { t } = useTranslation();
|
|
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
|
|
|
|
const { data: impactMetrics, isLoading: isLoadingImpact } = useImpactMetrics();
|
|
const { data: platformStats, isLoading: isLoadingPlatform } = usePlatformStatistics();
|
|
|
|
const isLoading = isLoadingImpact || isLoadingPlatform;
|
|
|
|
// Process impact data
|
|
const impact = useMemo(() => {
|
|
const data = impactMetrics || {};
|
|
const platform = platformStats || {};
|
|
|
|
return {
|
|
// Core impact metrics
|
|
totalCo2Saved: data.total_co2_saved_tonnes || 0,
|
|
totalEconomicValue: data.total_economic_value || 0,
|
|
activeMatchesCount: data.active_matches_count || 0,
|
|
totalOrganizations: platform.total_organizations || 0,
|
|
|
|
// Environmental breakdown
|
|
environmentalBreakdown: data.environmental_breakdown || {},
|
|
co2BySector: data.co2_by_sector || {},
|
|
co2ByResourceType: data.co2_by_resource_type || {},
|
|
|
|
// Economic metrics
|
|
economicBreakdown: data.economic_breakdown || {},
|
|
avgValuePerMatch: data.total_economic_value && data.active_matches_count
|
|
? data.total_economic_value / data.active_matches_count
|
|
: 0,
|
|
|
|
// Impact over time
|
|
monthlyImpact: data.monthly_impact || [],
|
|
yearlyProjections: data.yearly_projections || {},
|
|
|
|
// Resource-specific impacts
|
|
resourceImpacts: data.resource_impacts || [],
|
|
topImpactingMatches: data.top_impacting_matches || [],
|
|
};
|
|
}, [impactMetrics, platformStats]);
|
|
|
|
const formatCurrency = (value: number) => {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
}).format(value);
|
|
};
|
|
|
|
const formatNumber = (value: number) => {
|
|
return new Intl.NumberFormat('en-US').format(value);
|
|
};
|
|
|
|
// Simple visualization component for impact breakdown
|
|
const ImpactBreakdownChart = ({
|
|
data,
|
|
title,
|
|
color = 'hsl(var(--primary))'
|
|
}: {
|
|
data: Record<string, number>;
|
|
title: string;
|
|
color?: string;
|
|
}) => {
|
|
const entries = Object.entries(data);
|
|
const maxValue = Math.max(...entries.map(([_, value]) => value));
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-medium text-muted-foreground">{title}</h4>
|
|
<div className="space-y-2">
|
|
{entries.map(([key, value]) => (
|
|
<div key={key} className="flex items-center gap-3">
|
|
<span className="text-sm min-w-20 truncate capitalize">{key.replace('_', ' ')}</span>
|
|
<div className="flex-1 bg-muted rounded-full h-3">
|
|
<div
|
|
className="h-3 rounded-full transition-all duration-500"
|
|
style={{
|
|
width: `${(value / maxValue) * 100}%`,
|
|
backgroundColor: color,
|
|
}}
|
|
/>
|
|
</div>
|
|
<span className="text-sm font-medium min-w-16 text-right">
|
|
{formatNumber(value)}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Impact category icons
|
|
const getCategoryIcon = (category: string) => {
|
|
switch (category.toLowerCase()) {
|
|
case 'energy':
|
|
case 'electricity':
|
|
case 'heat':
|
|
return <Zap className="h-4 h-5 text-current text-yellow-500 w-4 w-5" />;
|
|
case 'transport':
|
|
case 'logistics':
|
|
return <Target className="h-4 h-5 text-blue-500 text-current w-4 w-5" />;
|
|
case 'industrial':
|
|
case 'manufacturing':
|
|
return <Briefcase className="h-4 h-5 text-current text-gray-600 w-4 w-5" />;
|
|
case 'buildings':
|
|
case 'construction':
|
|
return <Briefcase className="h-4 h-5 text-current text-green-600 w-4 w-5" />;
|
|
case 'waste':
|
|
case 'biowaste':
|
|
return <Globe className="h-4 h-5 text-brown-500 text-current w-4 w-5" />;
|
|
default:
|
|
return <Globe className="h-4 h-5 text-current text-primary w-4 w-5" />;
|
|
}
|
|
};
|
|
|
|
const getCategoryColor = (category: string) => {
|
|
switch (category.toLowerCase()) {
|
|
case 'energy':
|
|
case 'electricity':
|
|
case 'heat':
|
|
return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
|
case 'transport':
|
|
case 'logistics':
|
|
return 'text-blue-600 bg-blue-50 border-blue-200';
|
|
case 'industrial':
|
|
case 'manufacturing':
|
|
return 'text-gray-600 bg-gray-50 border-gray-200';
|
|
case 'buildings':
|
|
case 'construction':
|
|
return 'text-green-600 bg-green-50 border-green-200';
|
|
case 'waste':
|
|
case 'biowaste':
|
|
return 'text-amber-600 bg-amber-50 border-amber-200';
|
|
default:
|
|
return 'text-primary bg-primary/5 border-primary/20';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
|
|
<Container size="2xl" className="py-8 sm:py-12">
|
|
<PageHeader
|
|
title={t('impactMetrics.title')}
|
|
subtitle={t('impactMetrics.subtitle')}
|
|
onBack={handleBackNavigation}
|
|
/>
|
|
|
|
<Stack spacing="2xl">
|
|
{/* Key Impact Indicators */}
|
|
<Grid cols={{ md: 2, lg: 4 }} gap="md">
|
|
<Card className="border-green-200 bg-green-50/50">
|
|
<CardContent className="p-6">
|
|
<Flex align="center" gap="md" className="mb-4">
|
|
<div className="p-3 rounded-full bg-green-100">
|
|
<TrendingUp className="h-4 h-6 text-current text-green-600 w-4 w-6" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-green-700">
|
|
{t('impactMetrics.co2Saved')}
|
|
</p>
|
|
<p className="text-2xl font-bold text-green-800">
|
|
{formatNumber(impact.totalCo2Saved)}
|
|
</p>
|
|
</div>
|
|
</Flex>
|
|
<Badge variant="outline" className="border-green-300 text-green-700">
|
|
{t('impactMetrics.tonnesPerYear')}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-blue-200 bg-blue-50/50">
|
|
<CardContent className="p-6">
|
|
<Flex align="center" gap="md" className="mb-4">
|
|
<div className="p-3 rounded-full bg-blue-100">
|
|
<DollarSign className="h-4 h-6 text-blue-600 text-current w-4 w-6" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-blue-700">
|
|
{t('impactMetrics.economicValue')}
|
|
</p>
|
|
<p className="text-2xl font-bold text-blue-800">
|
|
{formatCurrency(impact.totalEconomicValue)}
|
|
</p>
|
|
</div>
|
|
</Flex>
|
|
<Badge variant="outline" className="border-blue-300 text-blue-700">
|
|
{t('impactMetrics.createdAnnually')}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-purple-200 bg-purple-50/50">
|
|
<CardContent className="p-6">
|
|
<Flex align="center" gap="md" className="mb-4">
|
|
<div className="p-3 rounded-full bg-purple-100">
|
|
<Target className="h-4 h-6 text-current text-purple-600 w-4 w-6" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-purple-700">
|
|
{t('impactMetrics.activeMatches')}
|
|
</p>
|
|
<p className="text-2xl font-bold text-purple-800">
|
|
{formatNumber(impact.activeMatchesCount)}
|
|
</p>
|
|
</div>
|
|
</Flex>
|
|
<Badge variant="outline" className="border-purple-300 text-purple-700">
|
|
{t('impactMetrics.operational')}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-amber-200 bg-amber-50/50">
|
|
<CardContent className="p-6">
|
|
<Flex align="center" gap="md" className="mb-4">
|
|
<div className="p-3 rounded-full bg-amber-100">
|
|
<Award className="h-4 h-6 text-amber-600 text-current w-4 w-6" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-amber-700">
|
|
{t('impactMetrics.avgValuePerMatch')}
|
|
</p>
|
|
<p className="text-2xl font-bold text-amber-800">
|
|
{formatCurrency(impact.avgValuePerMatch)}
|
|
</p>
|
|
</div>
|
|
</Flex>
|
|
<Badge variant="outline" className="border-amber-300 text-amber-700">
|
|
{t('impactMetrics.perMatch')}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
{/* Environmental Impact Breakdown */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<TrendingUp className="h-4 h-5 text-current w-4 w-5" />
|
|
{t('impactMetrics.environmentalBreakdown')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<Spinner className="h-6 w-6" />
|
|
</div>
|
|
) : Object.keys(impact.environmentalBreakdown).length > 0 ? (
|
|
<Grid cols={{ md: 2 }} gap="lg">
|
|
<div>
|
|
<ImpactBreakdownChart
|
|
data={impact.environmentalBreakdown}
|
|
title={t('impactMetrics.byCategory')}
|
|
color="hsl(142, 71%, 45%)"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<ImpactBreakdownChart
|
|
data={impact.co2BySector}
|
|
title={t('impactMetrics.bySector')}
|
|
color="hsl(221, 83%, 53%)"
|
|
/>
|
|
</div>
|
|
</Grid>
|
|
) : (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<TrendingUp className="h-12 h-4 mb-4 mx-auto opacity-50 text-current w-12 w-4" />
|
|
<p>{t('impactMetrics.noEnvironmentalData')}</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Economic Impact Breakdown */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<DollarSign className="h-4 h-5 text-current w-4 w-5" />
|
|
{t('impactMetrics.economicBreakdown')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<Spinner className="h-6 w-6" />
|
|
</div>
|
|
) : Object.keys(impact.economicBreakdown).length > 0 ? (
|
|
<ImpactBreakdownChart
|
|
data={impact.economicBreakdown}
|
|
title={t('impactMetrics.byEconomicCategory')}
|
|
color="hsl(217, 91%, 60%)"
|
|
/>
|
|
) : (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<DollarSign className="h-12 h-4 mb-4 mx-auto opacity-50 text-current w-12 w-4" />
|
|
<p>{t('impactMetrics.noEconomicData')}</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Top Impacting Matches */}
|
|
{impact.topImpactingMatches.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Award className="h-4 h-5 text-current w-4 w-5" />
|
|
{t('impactMetrics.topImpactingMatches')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{impact.topImpactingMatches.slice(0, 5).map((match: any, index: number) => (
|
|
<div key={index} className="flex items-center justify-between p-4 border rounded-lg">
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-primary/10 text-primary font-bold text-sm">
|
|
{index + 1}
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">{match.description || `Match ${match.id}`}</p>
|
|
<p className="text-sm text-muted-foreground">{match.resource_type}</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="font-semibold text-green-600">
|
|
{formatNumber(match.co2_impact || 0)} t CO₂
|
|
</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{formatCurrency(match.economic_impact || 0)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Impact Categories Overview */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('impactMetrics.impactCategoriesOverview')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{Object.entries(impact.environmentalBreakdown).map(([category, value]) => (
|
|
<div
|
|
key={category}
|
|
className={`p-4 rounded-lg border ${getCategoryColor(category)}`}
|
|
>
|
|
<Flex align="center" gap="sm" className="mb-3">
|
|
{getCategoryIcon(category)}
|
|
<span className="font-medium capitalize">{category.replace('_', ' ')}</span>
|
|
</Flex>
|
|
<div className="text-2xl font-bold mb-1">
|
|
{formatNumber(value)}
|
|
</div>
|
|
<p className="text-sm opacity-75">
|
|
{t('impactMetrics.tonnesCo2Reduced')}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Projections and Goals */}
|
|
<Grid cols={{ md: 2 }} gap="lg">
|
|
{/* Yearly Projections */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<TrendingUp className="h-4 h-5 text-current w-4 w-5" />
|
|
{t('impactMetrics.yearlyProjections')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{Object.keys(impact.yearlyProjections).length > 0 ? (
|
|
<div className="space-y-3">
|
|
{Object.entries(impact.yearlyProjections).map(([year, projection]: [string, any]) => (
|
|
<div key={year} className="flex justify-between items-center p-3 bg-muted/50 rounded">
|
|
<span className="font-medium">{year}</span>
|
|
<div className="text-right">
|
|
<div className="font-semibold text-green-600">
|
|
{formatNumber(projection.co2_projected || 0)} t CO₂
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">
|
|
{formatCurrency(projection.economic_projected || 0)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<TrendingUp className="h-12 h-4 mb-4 mx-auto opacity-50 text-current w-12 w-4" />
|
|
<p>{t('impactMetrics.noProjectionData')}</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Impact Achievements */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Award className="h-4 h-5 text-current w-4 w-5" />
|
|
{t('impactMetrics.achievements')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Stack spacing="md">
|
|
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<TrendingUp className="h-4 h-5 text-current text-green-600 w-4 w-5" />
|
|
<span className="font-medium text-green-800">
|
|
{t('impactMetrics.carbonReduction')}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-green-700">
|
|
{impact.totalCo2Saved >= 1000
|
|
? t('impactMetrics.majorContributor')
|
|
: t('impactMetrics.growingImpact')
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<DollarSign className="h-4 h-5 text-blue-600 text-current w-4 w-5" />
|
|
<span className="font-medium text-blue-800">
|
|
{t('impactMetrics.economicGrowth')}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-blue-700">
|
|
{impact.totalEconomicValue >= 1000000
|
|
? t('impactMetrics.significantEconomicValue')
|
|
: t('impactMetrics.economicBenefits')
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-4 bg-purple-50 border border-purple-200 rounded-lg">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<Target className="h-4 h-5 text-current text-purple-600 w-4 w-5" />
|
|
<span className="font-medium text-purple-800">
|
|
{t('impactMetrics.networkGrowth')}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-purple-700">
|
|
{t('impactMetrics.activeConnections')}
|
|
.replace('{{count}}', impact.activeMatchesCount.toString())
|
|
</p>
|
|
</div>
|
|
</Stack>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
</Stack>
|
|
</Container>
|
|
</MainLayout>
|
|
);
|
|
};
|
|
|
|
export default ImpactMetrics;
|