mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Some checks failed
CI/CD Pipeline / backend-lint (push) Failing after 31s
CI/CD Pipeline / backend-build (push) Has been skipped
CI/CD Pipeline / frontend-lint (push) Failing after 1m37s
CI/CD Pipeline / frontend-build (push) Has been skipped
CI/CD Pipeline / e2e-test (push) Has been skipped
- Replace all 'any' types with proper TypeScript interfaces - Fix React hooks setState in useEffect issues with lazy initialization - Remove unused variables and imports across all files - Fix React Compiler memoization dependency issues - Add comprehensive i18n translation keys for admin interfaces - Apply consistent prettier formatting throughout codebase - Clean up unused bulk editing functionality - Improve type safety and code quality across frontend Files changed: 39 - ImpactMetrics.tsx: Fixed any types and interfaces - AdminVerificationQueuePage.tsx: Added i18n keys, removed unused vars - LocalizationUIPage.tsx: Fixed memoization, added translations - LocalizationDataPage.tsx: Added type safety and translations - And 35+ other files with various lint fixes
205 lines
7.5 KiB
TypeScript
205 lines
7.5 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,
|
|
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;
|