mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
202 lines
8.0 KiB
TypeScript
202 lines
8.0 KiB
TypeScript
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 { 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 type { BackendOrganization } from '@/schemas/backend/organization';
|
|
import type { Proposal } from '@/types.ts';
|
|
import { Target } from 'lucide-react';
|
|
import { useCallback, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
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();
|
|
|
|
// Get user's organizations
|
|
const { data: userOrganizations, isLoading: isLoadingOrganizations } = useUserOrganizations();
|
|
|
|
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>
|
|
{isLoadingOrganizations ? (
|
|
<Spinner className="h-6 w-6 text-primary" />
|
|
) : (
|
|
<div className="text-2xl font-bold">
|
|
{Array.isArray(userOrganizations) ? userOrganizations.length : 0}
|
|
</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('/matching')}>
|
|
{t('userDashboard.viewAllProposals')}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('userDashboard.recentProposals')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
<Target className="h-12 w-12 mb-4 mx-auto opacity-50" />
|
|
<p className="text-lg font-medium mb-2">{t('userDashboard.noProposalsTitle')}</p>
|
|
<p className="text-sm mb-4">{t('userDashboard.noProposalsDesc')}</p>
|
|
<div className="flex gap-2 justify-center">
|
|
<Button variant="primary" onClick={() => navigate('/map')}>
|
|
{t('userDashboard.exploreMap')}
|
|
</Button>
|
|
<Button variant="outline" onClick={() => navigate('/matching')}>
|
|
{t('userDashboard.findMatches')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</Stack>
|
|
</Container>
|
|
</MainLayout>
|
|
);
|
|
};
|
|
|
|
export default UserDashboard;
|