turash/bugulma/frontend/pages/OrganizationDashboardPage.tsx
2025-12-15 10:06:41 +01:00

288 lines
12 KiB
TypeScript

import { MainLayout } from '@/components/layout/MainLayout.tsx';
import PageHeader from '@/components/layout/PageHeader.tsx';
import OrganizationStatistics from '@/components/organization/OrganizationStatistics.tsx';
import ResourceFlowList from '@/components/resource-flow/ResourceFlowList.tsx';
import Button from '@/components/ui/Button.tsx';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx';
import DateRangePicker, { type DateRange } from '@/components/ui/DateRangePicker.tsx';
import MetricItem from '@/components/ui/MetricItem.tsx';
import Spinner from '@/components/ui/Spinner.tsx';
import { Container, Flex, Grid, Stack } from '@/components/ui/layout';
import { useOrganizationStatistics } from '@/hooks/api/useAnalyticsAPI.ts';
import { useOrganization } from '@/hooks/api/useOrganizationsAPI.ts';
import { useProposalsForOrganization } from '@/hooks/api/useProposalsAPI.ts';
import { useTranslation } from '@/hooks/useI18n.tsx';
import { useNavigation } from '@/hooks/useNavigation.tsx';
import {
Activity,
BarChart3,
Briefcase,
DollarSign,
Leaf,
MapPin,
Target,
TrendingUp,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { formatCurrency, formatNumber } from '../lib/fin';
const OrganizationDashboardPage = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { t } = useTranslation();
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
// Fetch organization data
const { data: organization, isLoading: isLoadingOrg } = useOrganization(id);
const { data: stats, isLoading: isLoadingStats } = useOrganizationStatistics(id);
const { data: proposalsData } = useProposalsForOrganization(id);
const [dateRange, setDateRange] = useState<DateRange>({
startDate: null,
endDate: null,
preset: '30d',
});
useEffect(() => {
if (!isLoadingOrg && !organization) {
navigate('/map');
}
}, [organization, isLoadingOrg, navigate]);
// use central fin utilities
// Calculate proposals summary
const proposalsSummary = useMemo(() => {
const incoming = Array.isArray(proposalsData?.incoming) ? proposalsData.incoming : [];
const outgoing = Array.isArray(proposalsData?.outgoing) ? proposalsData.outgoing : [];
const pendingIncoming = incoming.filter((p) => p?.status === 'pending');
const pendingOutgoing = outgoing.filter((p) => p?.status === 'pending');
return {
total: incoming.length + outgoing.length,
incoming: incoming.length,
outgoing: outgoing.length,
pending: pendingIncoming.length + pendingOutgoing.length,
pendingIncoming: pendingIncoming.length,
pendingOutgoing: pendingOutgoing.length,
};
}, [proposalsData]);
const isLoading = isLoadingOrg || isLoadingStats;
if (isLoading) {
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<div className="flex items-center justify-center py-20">
<Spinner className="h-8 w-8 text-primary" />
</div>
</Container>
</MainLayout>
);
}
if (!organization) {
return null;
}
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('organizationDashboard.title', `${organization.Name} - Dashboard`)}
subtitle={t(
'organizationDashboard.subtitle',
"Overview of your organization's performance and activity"
)}
onBack={() => navigate(`/organization/${id}`)}
/>
<DateRangePicker value={dateRange} onChange={setDateRange} />
</Flex>
<Stack spacing="2xl">
{/* Organization Statistics */}
<OrganizationStatistics organizationId={id!} />
{/* Key Metrics Overview */}
{stats && (
<Grid cols={{ md: 2, lg: 4 }} gap="md">
<MetricItem
icon={<MapPin className="h-4 h-5 text-current text-warning w-4 w-5" />}
label={t('organizationDashboard.totalSites')}
value={formatNumber(stats.total_sites)}
/>
<MetricItem
icon={<Target className="h-4 h-5 text-current text-success w-4 w-5" />}
label={t('organizationDashboard.resourceFlows')}
value={formatNumber(stats.total_resource_flows)}
/>
<MetricItem
icon={<Activity className="h-4 h-5 text-current text-primary w-4 w-5" />}
label={t('organizationDashboard.activeMatches')}
value={formatNumber(stats.active_matches)}
/>
<MetricItem
icon={<TrendingUp className="h-4 h-5 text-current text-purple-500 w-4 w-5" />}
label={t('organizationDashboard.totalMatches')}
value={formatNumber(stats.total_matches)}
/>
</Grid>
)}
{/* Impact Metrics */}
{stats && (stats.co2_savings_tonnes > 0 || stats.economic_value_eur > 0) && (
<Grid cols={{ md: 2 }} gap="md">
<Card>
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Leaf className="h-4 w-4 text-green-600" />
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('organizationDashboard.co2Saved')}
</CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
{formatNumber(stats.co2_savings_tonnes)} t
</div>
<p className="text-xs text-muted-foreground mt-1">
{t('organizationDashboard.totalSavings')}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<DollarSign className="h-4 text-current text-success w-4" />
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('organizationDashboard.economicValue')}
</CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-success">
{formatCurrency(stats.economic_value_eur)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{t('organizationDashboard.totalValue')}
</p>
</CardContent>
</Card>
</Grid>
)}
{/* Proposals Summary */}
{proposalsSummary.total > 0 && (
<Card>
<CardHeader>
<Flex align="center" justify="between">
<CardTitle>{t('organizationDashboard.proposals')}</CardTitle>
<Button
variant="outline"
size="sm"
onClick={() => navigate(`/organization/${id}`)}
>
{t('organizationDashboard.viewAll')}
</Button>
</Flex>
</CardHeader>
<CardContent>
<Grid cols={{ md: 3 }} gap="md">
<div className="text-center p-4 border rounded-lg">
<div className="text-2xl font-bold mb-1">{proposalsSummary.total}</div>
<p className="text-sm text-muted-foreground">
{t('organizationDashboard.totalProposals')}
</p>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="text-2xl font-bold text-warning mb-1">
{proposalsSummary.pending}
</div>
<p className="text-sm text-muted-foreground">
{t('organizationDashboard.pendingProposals')}
</p>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="text-2xl font-bold text-success mb-1">
{proposalsSummary.incoming}
</div>
<p className="text-sm text-muted-foreground">
{t('organizationDashboard.incomingProposals')}
</p>
</div>
</Grid>
</CardContent>
</Card>
)}
{/* Resource Flows */}
<Card>
<CardHeader>
<Flex align="center" justify="between">
<CardTitle>{t('organizationDashboard.resourceFlows')}</CardTitle>
<Button variant="outline" size="sm" onClick={() => navigate(`/organization/${id}`)}>
{t('organizationDashboard.viewDetails')}
</Button>
</Flex>
</CardHeader>
<CardContent>
<ResourceFlowList organizationId={id!} />
</CardContent>
</Card>
{/* Quick Actions */}
<Card>
<CardHeader>
<CardTitle>{t('organizationDashboard.quickActions')}</CardTitle>
</CardHeader>
<CardContent>
<Grid cols={{ sm: 2, md: 4 }} gap="md">
<Button
variant="outline"
onClick={() => navigate(`/organization/${id}`)}
className="h-20 flex flex-col items-center justify-center gap-2 hover:bg-primary/5"
>
<Briefcase className="h-4 h-6 text-current w-4 w-6" />
<span className="text-sm">{t('organizationDashboard.viewOrganization')}</span>
</Button>
<Button
variant="outline"
onClick={() => navigate('/resources')}
className="h-20 flex flex-col items-center justify-center gap-2 hover:bg-primary/5"
>
<Target className="h-4 h-6 text-current w-4 w-6" />
<span className="text-sm">{t('organizationDashboard.manageResources')}</span>
</Button>
<Button
variant="outline"
onClick={() => navigate('/matching')}
className="h-20 flex flex-col items-center justify-center gap-2 hover:bg-primary/5"
>
<BarChart3 className="h-4 h-6 text-current w-4 w-6" />
<span className="text-sm">{t('organizationDashboard.findMatches')}</span>
</Button>
<Button
variant="outline"
onClick={() => navigate('/map')}
className="h-20 flex flex-col items-center justify-center gap-2 hover:bg-primary/5"
>
<MapPin className="h-4 h-6 text-current w-4 w-6" />
<span className="text-sm">{t('organizationDashboard.exploreMap')}</span>
</Button>
</Grid>
</CardContent>
</Card>
</Stack>
</Container>
</MainLayout>
);
};
export default OrganizationDashboardPage;