hMarket Trae ilk versiyon
This commit is contained in:
331
frontend/src/pages/Dashboard/DashboardPage.tsx
Normal file
331
frontend/src/pages/Dashboard/DashboardPage.tsx
Normal file
@@ -0,0 +1,331 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user