turash/bugulma/frontend/pages/DashboardPage.tsx
Damir Mukimov 18cdcb12fd
fix: continue linting fixes - remove unused variables, fix i18n strings
- Remove unused imports and variables from DashboardPage, HeritageBuildingPage, MatchesMapView
- Fix i18n literal strings in NetworkGraph component
- Continue systematic reduction of linting errors
2025-12-25 00:32:40 +01:00

218 lines
7.6 KiB
TypeScript

import {
ActiveProposalsSection,
ImpactMetricsSection,
MyOrganizationsSection,
PlatformHealthSection,
PlatformOverviewSection,
QuickActionsSection,
RecentActivitySection,
YourContributionSection,
} from '@/components/dashboard';
import { MainLayout } from '@/components/layout/MainLayout.tsx';
import PageHeader from '@/components/layout/PageHeader.tsx';
import DateRangePicker, { type DateRange } from '@/components/ui/DateRangePicker.tsx';
import { Container, Flex, Grid, Stack } from '@/components/ui/layout';
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 { 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<ActivityFilter>('all');
const [dateRange, setDateRange] = useState<DateRange>({
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,
} = useDashboardStatistics();
const {
data: platformStats,
isLoading: isLoadingPlatform,
} = usePlatformStatistics();
const {
data: matchingStats,
isLoading: isLoadingMatching,
} = useMatchingStatistics();
const {
data: impactMetrics,
isLoading: isLoadingImpact,
} = 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 = Array.isArray(userOrganizations) ? 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 ?? impact?.total_co2_savings_tonnes ?? 0,
totalEconomicValue: impact?.total_economic_value ?? impact?.total_economic_value_eur ?? 0,
activeMatches: impact?.active_matches_count ?? 0,
};
}, [
dashboardStats,
platformStats,
matchingStats,
impactMetrics,
proposalsData,
userOrganizations,
pendingProposals,
proposals.length,
]);
// 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]);
// Number formatting handled by shared utilities inside components
const isLoading = isLoadingDashboard || isLoadingPlatform || isLoadingMatching || isLoadingImpact;
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<Flex align="center" justify="between" className="mb-6">
<PageHeader
title={t('dashboard.title')}
subtitle={t('dashboard.subtitle', { name: user?.name || user?.email || 'User' })}
onBack={handleBackNavigation}
/>
<DateRangePicker value={dateRange} onChange={setDateRange} />
</Flex>
<Stack spacing="2xl">
<YourContributionSection
myOrganizations={stats.myOrganizations}
myProposals={stats.myProposals}
myPendingProposals={stats.myPendingProposals}
t={t}
/>
<PlatformOverviewSection
totalOrganizations={stats.totalOrganizations}
totalSites={stats.totalSites}
totalResourceFlows={stats.totalResourceFlows}
totalMatches={stats.totalMatches}
t={t}
/>
<ImpactMetricsSection
totalCo2Saved={stats.totalCo2Saved}
totalEconomicValue={stats.totalEconomicValue}
activeMatches={stats.activeMatches}
t={t}
/>
<QuickActionsSection onNavigate={(path) => navigate(path)} t={t} />
{/* Recent Activity & Proposals */}
<Grid cols={{ md: 2 }} gap="lg">
<RecentActivitySection
filteredActivities={filteredActivities}
activityFilter={activityFilter}
setActivityFilter={setActivityFilter}
isLoading={isLoading}
t={t}
/>
<ActiveProposalsSection
pendingProposals={pendingProposals}
onNavigate={(path) => navigate(path)}
t={t}
/>
</Grid>
<MyOrganizationsSection
organizations={organizations}
onNavigate={(path) => navigate(path)}
t={t}
/>
<PlatformHealthSection
matchSuccessRate={stats.matchSuccessRate}
avgMatchTime={stats.avgMatchTime}
topResourceTypes={stats.topResourceTypes}
t={t}
/>
</Stack>
</Container>
</MainLayout>
);
};
export default DashboardPage;