Files
hMarket/frontend/src/pages/Dashboard/DashboardPage.tsx
2026-02-03 01:22:08 +03:00

331 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import {
Container,
Typography,
Box,
Card,
CardContent,
CardActions,
Button,
List,
ListItem,
ListItemText,
ListItemIcon,
Avatar,
LinearProgress,
IconButton,
} from '@mui/material';
import {
Add,
List as ListIcon,
ShoppingCart,
CheckCircle,
PendingActions,
Share,
TrendingUp,
Refresh,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { dashboardAPI, listsAPI } from '../../services/api';
import { useAuth } from '../../contexts/AuthContext';
import { formatDistanceToNow } from 'date-fns';
import { ListsResponse } from '../../types';
const DashboardPage: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuth();
// Fetch dashboard stats
const { data: statsData, isLoading: statsLoading, refetch: refetchStats } = useQuery({
queryKey: ['dashboard', 'stats'],
queryFn: () => dashboardAPI.getStats(),
});
// Fetch recent lists
const { data: listsData, isLoading: listsLoading } = useQuery<ListsResponse>({
queryKey: ['lists', 'recent'],
queryFn: () => listsAPI.getLists({ limit: 5, sortBy: 'updatedAt', sortOrder: 'desc' }).then(response => response.data),
});
// Fetch recent activity
const { data: activityData, isLoading: activityLoading } = useQuery({
queryKey: ['dashboard', 'activity'],
queryFn: () => dashboardAPI.getRecentActivity({ limit: 10 }),
});
const stats = statsData?.data?.data;
const recentLists = listsData?.data?.lists || [];
const recentActivity = activityData?.data?.data?.activities || [];
const completionRate = stats ? Math.round((stats.completedItems / stats.totalItems) * 100) || 0 : 0;
return (
<Container maxWidth="xl" sx={{ mt: 4, mb: 4 }}>
{/* Welcome Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h4" gutterBottom>
Tekrar hoş geldin, {user?.firstName}! 👋
</Typography>
<Typography variant="body1" color="text.secondary">
Bugün alışveriş listelerinizde neler oluyor, işte özet.
</Typography>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: '1fr 1fr 1fr 1fr' }, gap: 3 }}>
{/* Stats Cards */}
<Box>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<ListIcon sx={{ fontSize: 40, color: 'primary.main', mr: 2 }} />
<Box>
<Typography color="textSecondary" gutterBottom>
Toplam Liste
</Typography>
<Typography variant="h4">
{statsLoading ? '-' : stats?.totalLists || 0}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
<Box>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<ShoppingCart sx={{ fontSize: 40, color: 'info.main', mr: 2 }} />
<Box>
<Typography color="textSecondary" gutterBottom>
Toplam Ürün
</Typography>
<Typography variant="h4">
{statsLoading ? '-' : stats?.totalItems || 0}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
<Box>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<CheckCircle sx={{ fontSize: 40, color: 'success.main', mr: 2 }} />
<Box>
<Typography color="textSecondary" gutterBottom>
Tamamlanan
</Typography>
<Typography variant="h4">
{statsLoading ? '-' : stats?.completedItems || 0}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
<Box>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<PendingActions sx={{ fontSize: 40, color: 'warning.main', mr: 2 }} />
<Box>
<Typography color="textSecondary" gutterBottom>
Bekleyen
</Typography>
<Typography variant="h4">
{statsLoading ? '-' : stats?.pendingItems || 0}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 3, mt: 3 }}>
{/* Completion Rate */}
<Box>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h6">Alışveriş İlerlemesi</Typography>
<IconButton onClick={() => refetchStats()}>
<Refresh />
</IconButton>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<TrendingUp sx={{ mr: 1, color: 'success.main' }} />
<Typography variant="h4" color="success.main">
{completionRate}%
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ ml: 1 }}>
tamamlanma oranı
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={completionRate}
sx={{ height: 8, borderRadius: 4 }}
/>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{stats?.totalItems || 0} üründen {stats?.completedItems || 0} tanesi tamamlandı
</Typography>
</CardContent>
</Card>
</Box>
{/* Quick Actions */}
<Box>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Hızlı İşlemler
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Button
fullWidth
variant="contained"
startIcon={<Add />}
onClick={() => navigate('/lists?action=create')}
>
Yeni Liste Oluştur
</Button>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}>
<Box>
<Button
fullWidth
variant="outlined"
startIcon={<ListIcon />}
onClick={() => navigate('/lists')}
>
Tüm Listeler
</Button>
</Box>
<Box>
<Button
fullWidth
variant="outlined"
startIcon={<ShoppingCart />}
onClick={() => navigate('/products')}
>
Ürünlere Gözat
</Button>
</Box>
</Box>
</Box>
</CardContent>
</Card>
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 3, mt: 3 }}>
{/* Recent Lists */}
<Box>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Son Listeler
</Typography>
{listsLoading ? (
<Typography>Yükleniyor...</Typography>
) : recentLists.length === 0 ? (
<Typography color="text.secondary">
Henüz liste yok. İlk alışveriş listenizi oluşturun!
</Typography>
) : (
<List>
{recentLists.map((list: any) => (
<ListItem
key={list.id}
onClick={() => navigate(`/lists/${list.id}`)}
sx={{ px: 0, cursor: 'pointer' }}
>
<ListItemIcon>
<Avatar sx={{ bgcolor: list.color || 'primary.main', width: 32, height: 32 }}>
{list.isShared ? <Share /> : <ListIcon />}
</Avatar>
</ListItemIcon>
<ListItemText
primary={list.name}
secondary={
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{ fontSize: '0.875rem', color: 'rgba(0, 0, 0, 0.6)' }}>
{list._count?.items || 0} ürün
</span>
{list.isShared && (
<span style={{
fontSize: '0.75rem',
backgroundColor: '#1976d2',
color: 'white',
padding: '2px 8px',
borderRadius: '12px',
fontWeight: 500
}}>
Paylaşılan
</span>
)}
</span>
}
/>
</ListItem>
))}
</List>
)}
</CardContent>
<CardActions>
<Button size="small" onClick={() => navigate('/lists')}>
Tüm Listeleri Görüntüle
</Button>
</CardActions>
</Card>
</Box>
{/* Recent Activity */}
<Box>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Son Aktiviteler
</Typography>
{activityLoading ? (
<Typography>Yükleniyor...</Typography>
) : recentActivity.length === 0 ? (
<Typography color="text.secondary">
Son aktivite yok.
</Typography>
) : (
<List>
{recentActivity.slice(0, 5).map((activity: any) => (
<ListItem key={activity.id} sx={{ px: 0 }}>
<ListItemIcon>
<Avatar sx={{ width: 32, height: 32 }}>
{activity.user?.firstName?.[0] || '?'}
</Avatar>
</ListItemIcon>
<ListItemText
primary={activity.description}
secondary={formatDistanceToNow(new Date(activity.createdAt), { addSuffix: true })}
/>
</ListItem>
))}
</List>
)}
</CardContent>
<CardActions>
<Button size="small" onClick={() => navigate('/activity')}>
Tüm Aktiviteleri Görüntüle
</Button>
</CardActions>
</Card>
</Box>
</Box>
</Container>
);
};
export default DashboardPage;