import { MainLayout } from '@/components/layout/MainLayout.tsx'; import PageHeader from '@/components/layout/PageHeader.tsx'; import { ActivityCard } from '@/components/ui/ActivityCard.tsx'; import Badge from '@/components/ui/Badge.tsx'; import Button from '@/components/ui/Button.tsx'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx'; import { CenteredContent } from '@/components/ui/CenteredContent.tsx'; import DateRangePicker, { type DateRange } from '@/components/ui/DateRangePicker.tsx'; import { EmptyState } from '@/components/ui/EmptyState.tsx'; import { Container, Flex, Grid, Stack } from '@/components/ui/layout'; import { LoadingState } from '@/components/ui/LoadingState.tsx'; import MetricItem from '@/components/ui/MetricItem.tsx'; import { Heading, Price, Text } from '@/components/ui/Typography.tsx'; import { useAuth } from '@/contexts/AuthContext.tsx'; import { useDashboardStatistics, useImpactMetrics, useMatchingStatistics, usePlatformStatistics } from '@/hooks/api/useAnalyticsAPI.ts'; import { useUserOrganizations } from '@/hooks/api/useOrganizationsAPI.ts'; import { useProposals } from '@/hooks/api/useProposalsAPI.ts'; import { useTranslation } from '@/hooks/useI18n.tsx'; import { useNavigation } from '@/hooks/useNavigation.tsx'; import { useOrganizations } from '@/hooks/useOrganizations.ts'; import type { Proposal } from '@/types.ts'; import { BarChart3, Briefcase, DollarSign, Leaf, MapPin, Plus, Search, Target, TrendingUp, Users } from 'lucide-react'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; type ActivityFilter = 'all' | 'match' | 'proposal' | 'organization'; const DashboardPage = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { handleBackNavigation, handleFooterNavigate } = useNavigation(); const { user } = useAuth(); const [activityFilter, setActivityFilter] = useState('all'); const [dateRange, setDateRange] = useState({ startDate: null, endDate: null, preset: '30d', }); // Analytics data - Zod validates these, so if data exists, it's guaranteed to be valid const { data: dashboardStats, isLoading: isLoadingDashboard, error: dashboardError } = useDashboardStatistics(); const { data: platformStats, isLoading: isLoadingPlatform, error: platformError } = usePlatformStatistics(); const { data: matchingStats, isLoading: isLoadingMatching, error: matchingError } = useMatchingStatistics(); const { data: impactMetrics, isLoading: isLoadingImpact, error: impactError } = useImpactMetrics(); // User-specific data const { data: proposalsData } = useProposals(); const { organizations } = useOrganizations(); const { data: userOrganizations } = useUserOrganizations(); // Proposals data - validated by API const proposals: Proposal[] = proposalsData?.proposals ?? []; const pendingProposals = proposals.filter((p: Proposal) => p.status === 'pending'); // Calculate derived statistics - data is validated by Zod, so we can trust it const stats = useMemo(() => { // If data exists, it's validated by Zod - use it directly const dashboard = dashboardStats; const platform = platformStats; const matching = matchingStats; const impact = impactMetrics; // User-specific metrics const myOrganizationsCount = userOrganizations?.length ?? 0; const myProposalsCount = proposals.length; const myPendingProposalsCount = pendingProposals.length; return { // Platform-wide metrics - prefer dashboard stats, fallback to platform stats totalOrganizations: dashboard?.total_organizations ?? platform?.total_organizations ?? 0, totalSites: dashboard?.total_sites ?? platform?.total_sites ?? 0, totalResourceFlows: dashboard?.total_resource_flows ?? platform?.total_resource_flows ?? 0, totalMatches: dashboard?.total_matches ?? platform?.total_matches ?? 0, activeProposals: pendingProposals.length, recentActivity: dashboard?.recent_activity ?? [], // User-specific metrics myOrganizations: myOrganizationsCount, myProposals: myProposalsCount, myPendingProposals: myPendingProposalsCount, // Matching statistics - optional fields use nullish coalescing matchSuccessRate: matching?.match_success_rate ?? 0, avgMatchTime: matching?.avg_match_time_days ?? 0, topResourceTypes: matching?.top_resource_types ?? [], // Impact metrics totalCo2Saved: impact?.total_co2_saved_tonnes ?? 0, totalEconomicValue: impact?.total_economic_value ?? 0, activeMatches: impact?.active_matches_count ?? 0, }; }, [dashboardStats, platformStats, matchingStats, impactMetrics, proposalsData, userOrganizations, pendingProposals]); // Filter activities based on selected filter // Activity type is validated by Zod, so it's guaranteed to be a string const filteredActivities = useMemo(() => { if (activityFilter === 'all') { return stats.recentActivity; } return stats.recentActivity.filter((activity) => { const activityType = activity.type.toLowerCase(); switch (activityFilter) { case 'match': return activityType.includes('match'); case 'proposal': return activityType.includes('proposal'); case 'organization': return activityType.includes('organization') || activityType.includes('org'); default: return true; } }); }, [stats.recentActivity, activityFilter]); 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); }; const isLoading = isLoadingDashboard || isLoadingPlatform || isLoadingMatching || isLoadingImpact; const hasError = dashboardError || platformError || matchingError || impactError; return ( {/* Your Contribution - User-Specific Metrics */} {t('dashboard.yourContribution')} } label={t('dashboard.myOrganizations')} value={formatNumber(stats.myOrganizations)} /> } label={t('dashboard.myProposals')} value={formatNumber(stats.myProposals)} /> } label={t('dashboard.myPendingProposals')} value={formatNumber(stats.myPendingProposals)} /> {/* Platform Overview - Platform-Wide Metrics */} {t('dashboard.platformOverview')} } label={t('dashboard.organizations')} value={formatNumber(stats.totalOrganizations)} /> } label={t('dashboard.sites')} value={formatNumber(stats.totalSites)} /> } label={t('dashboard.resourceFlows')} value={formatNumber(stats.totalResourceFlows)} /> } label={t('dashboard.matches')} value={formatNumber(stats.totalMatches)} /> {/* Impact Metrics */} {(stats.totalCo2Saved > 0 || stats.totalEconomicValue > 0) && (
{t('dashboard.co2Saved')}
{formatNumber(stats.totalCo2Saved)} t {t('dashboard.perYear')}
{t('dashboard.economicValue')}
{t('dashboard.created')}
{t('dashboard.activeMatches')}
{formatNumber(stats.activeMatches)} {t('dashboard.operational')}
)} {/* Quick Actions */} {t('dashboard.quickActions')} {/* Recent Activity & Proposals */} {/* Recent Activity */} {t('dashboard.recentActivity')} {filteredActivities.length} {/* Activity Filter Buttons */}
{isLoading ? ( ) : filteredActivities.length > 0 ? ( {filteredActivities.slice(0, 5).map((activity) => ( ))} {filteredActivities.length > 5 && ( )} ) : ( } title={ activityFilter === 'all' ? t('dashboard.noRecentActivityTitle') : t('dashboard.noFilteredActivityTitle', { filter: activityFilter }) } description={ activityFilter === 'all' ? t('dashboard.noRecentActivityDesc') : t('dashboard.noFilteredActivityDesc', { filter: activityFilter }) } /> )}
{/* Active Proposals */} {t('dashboard.activeProposals')} {pendingProposals.length} {pendingProposals.length > 0 ? (
{pendingProposals.slice(0, 5).map((proposal: Proposal) => (
{ // Navigate to organization page or matching dashboard if (proposal.toOrgId) { navigate(`/organization/${proposal.toOrgId}`); } else if (proposal.fromOrgId) { navigate(`/organization/${proposal.fromOrgId}`); } else { navigate('/matching'); } }} >
{proposal.message || t('dashboard.proposalNoMessage')} {t('dashboard.proposalStatus')}:{' '} {t(`organizationPage.status.${proposal.status}`)}
{t(`organizationPage.status.${proposal.status}`)}
))} {pendingProposals.length > 5 && ( )}
) : ( } title={t('dashboard.noActiveProposalsTitle')} description={t('dashboard.noActiveProposalsDesc')} action={{ label: t('dashboard.exploreMap'), onClick: () => navigate('/map'), }} /> )}
{/* My Organizations Summary */} {t('dashboard.myOrganizations')} {organizations && organizations.length > 0 && ( )} {organizations && organizations.length > 0 ? ( {organizations.slice(0, 3).map((org: any) => (
navigate(`/organization/${org.ID}`)} > {org.Name} {org.sector} {org.subtype}
))}
) : ( } title={t('dashboard.noOrganizationsTitle')} description={t('dashboard.noOrganizationsDesc')} action={{ label: t('dashboard.createFirstOrganization'), onClick: () => navigate('/map'), }} /> )}
{/* Platform Health Indicators */} {stats.matchSuccessRate > 0 && ( {t('dashboard.platformHealth')} {Math.round(stats.matchSuccessRate * 100)}% {stats.avgMatchTime.toFixed(1)} {stats.topResourceTypes.length} )}
); }; export default DashboardPage;