turash/bugulma/frontend/pages/UserDashboard.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

170 lines
6.6 KiB
TypeScript

import { useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { MainLayout } from '@/components/layout/MainLayout.tsx';
import PageHeader from '@/components/layout/PageHeader.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 { Container, Flex, Grid, Stack } from '@/components/ui/layout';
import Spinner from '@/components/ui/Spinner.tsx';
import MyOrganizations from '@/components/user/MyOrganizations.tsx';
import { useAuth } from '@/contexts/AuthContext.tsx';
import { useProposals } from '@/hooks/api/useProposalsAPI.ts';
import { useTranslation } from '@/hooks/useI18n.tsx';
import { useNavigation } from '@/hooks/useNavigation.tsx';
import type { BackendOrganization } from '@/schemas/backend/organization';
import type { Proposal } from '@/types.ts';
const UserDashboard = () => {
const { t } = useTranslation();
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
const { user } = useAuth();
const navigate = useNavigate();
const [selectedOrg, setSelectedOrg] = useState<BackendOrganization | null>(null);
// Get all proposals for user's organizations
const { data: proposalsData, isLoading: isLoadingProposals } = useProposals();
const handleSelectOrganization = useCallback(
(org: BackendOrganization) => {
setSelectedOrg(org);
navigate(`/organization/${org.ID}`);
},
[navigate]
);
const handleAddOrganization = useCallback(() => {
navigate('/map');
}, [navigate]);
// Safely handle proposals data - ensure it's always an array
const proposals: Proposal[] = Array.isArray(proposalsData?.proposals)
? proposalsData.proposals
: [];
const pendingProposals = Array.isArray(proposals)
? proposals.filter((p) => p?.status === 'pending')
: [];
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<PageHeader
title={t('userDashboard.title')}
subtitle={t('userDashboard.subtitle', { name: user?.name || user?.email || '' })}
onBack={handleBackNavigation}
/>
<Stack spacing="2xl">
{/* Quick Stats */}
<Grid cols={{ md: 3 }} gap="md">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('userDashboard.myOrganizations')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{selectedOrg ? '1' : '—'}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('userDashboard.pendingProposals')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{pendingProposals.length}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('userDashboard.totalProposals')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{proposals.length}</div>
</CardContent>
</Card>
</Grid>
{/* My Organizations */}
<Card>
<CardHeader>
<Flex align="center" justify="between">
<CardTitle>{t('userDashboard.myOrganizations')}</CardTitle>
<Button variant="primary" size="sm" onClick={handleAddOrganization}>
{t('userDashboard.addOrganization')}
</Button>
</Flex>
</CardHeader>
<CardContent>
<MyOrganizations onSelectOrganization={handleSelectOrganization} />
</CardContent>
</Card>
{/* Recent Proposals */}
{isLoadingProposals ? (
<Card>
<CardContent className="py-8">
<div className="flex items-center justify-center">
<Spinner className="h-6 w-6 text-primary" />
</div>
</CardContent>
</Card>
) : proposals.length > 0 ? (
<Card>
<CardHeader>
<CardTitle>{t('userDashboard.recentProposals')}</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{proposals.slice(0, 5).map((proposal: Proposal) => (
<div
key={proposal.id}
className="p-4 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="font-medium">
{proposal.message || t('userDashboard.proposalNoMessage')}
</p>
<p className="text-sm text-muted-foreground mt-1">
{t('userDashboard.proposalStatus')}:{' '}
{t(`organizationPage.status.${proposal.status}`)}
</p>
</div>
<Badge
variant={
proposal.status === 'accepted'
? 'default'
: proposal.status === 'rejected'
? 'destructive'
: 'secondary'
}
>
{t(`organizationPage.status.${proposal.status}`)}
</Badge>
</div>
</div>
))}
</div>
{proposals.length > 5 && (
<div className="mt-4 text-center">
<Button variant="outline" onClick={() => navigate('/map')}>
{t('userDashboard.viewAllProposals')}
</Button>
</div>
)}
</CardContent>
</Card>
) : null}
</Stack>
</Container>
</MainLayout>
);
};
export default UserDashboard;