331 lines
11 KiB
TypeScript
331 lines
11 KiB
TypeScript
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; |