1
0

Initial commit - restaurant dashboard

هذا الالتزام موجود في:
RaghadMAlkous
2025-09-04 01:17:15 +03:00
الأصل 13891b47fd
التزام 7b2f8840cb
136 ملفات معدلة مع 16638 إضافات و6033 حذوفات

عرض الملف

@@ -1,104 +1,143 @@
import React, { useState, useEffect } from 'react';
import { useRestaurant } from '../../../contexts/RestaurantContext';
import StatisticsCard from './StatisticsCard';
import TopSellingProduct from './TopSellingProduct';
import SalesByLocation from './SalesByLocation';
import TablesManager from './TablesManager';
import {
Box,
useTheme,
useMediaQuery,
Skeleton,
Button,
Typography,
ButtonGroup
ButtonGroup,
TextField
} from '@mui/material';
import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined';
import authService from '../../../services/authService';
import dayjs from 'dayjs';
const AnalyticsPage = () => {
const [timeFrame, setTimeFrame] = useState('month');
const { restaurantId } = useRestaurant();
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [hasProducts, setHasProducts] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
const [chartData, setChartData] = useState([]);
const dailyData = [
{ date: '10:00', visitors: 1500, conversions: 300 },
{ date: '11:00', visitors: 1800, conversions: 320 },
];
const weeklyData = [
{ label: 'Week 1', visitors: 1500, conversions: 200 },
{ label: 'Week 2', visitors: 2300, conversions: 400 },
];
const monthlyData = [
{ label: 'Jan', visitors: 15000, conversions: 3000 },
{ label: 'Feb', visitors: 18000, conversions: 4000 },
];
const yearlyData = [
{ label: '2024', visitors: 25000, conversions: 7000 },
{ label: '2025', visitors: 38000, conversions: 12000 },
];
const getData = () => {
switch (timeFrame) {
case '24h':
return dailyData;
case '7d':
return weeklyData;
case '30d':
return monthlyData;
case '12m':
return yearlyData;
case 'all':
return [...yearlyData, ...monthlyData, ...weeklyData, ...dailyData];
default:
return dailyData;
}
const handleTimeFrameChange = (newTimeFrame) => {
setTimeFrame(newTimeFrame);
};
const topSellingProducts = [
{ product: 'Apple Watch', sales: 150, amount: 45000, price: 299, status: 'Published' },
{ product: 'Samsung Galaxy', sales: 90, amount: 36000, price: 400, status: 'Low Stock' },
{ product: 'Sony Headphones', sales: 60, amount: 18000, price: 299, status: 'Draft' },
];
const handleCustomDateChange = (event) => {
setCustomDate(event.target.value);
};
const fetchStatistics = async () => {
if (!restaurantId) return;
let period = 'daily';
let labels = [];
let mappedData = [];
switch (timeFrame) {
case '24h': // يومي
period = 'daily';
labels = Array.from({ length: 24 }, (_, i) => `${i.toString().padStart(2, '0')}:00`);
try {
const response = await authService.getReservationStatistics(restaurantId, period, customDate);
if (response.success && response.data.length > 0) {
mappedData = response.data.map(item => ({
label: item.label || item.date || '',
reservations_total: item.reservations_total || 0,
number_of_people_total: item.number_of_people_total || 0
}));
} else {
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
} catch (error) {
console.error(error);
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
break;
case '30d': // شهري
period = 'monthly';
labels = Array.from({ length: 30 }, (_, i) => dayjs().startOf('month').add(i, 'day').format('MM-DD'));
try {
const response = await authService.getReservationStatistics(restaurantId, period, customDate);
if (response.success && response.data.length > 0) {
mappedData = response.data.map(item => ({
label: item.label ? dayjs(item.label).format('MM-DD') : item.date ? dayjs(item.date).format('MM-DD') : '',
reservations_total: item.reservations_total || 0,
number_of_people_total: item.number_of_people_total || 0
}));
} else {
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
} catch (error) {
console.error(error);
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
break;
case '12m': // سنوي
default:
period = 'yearly';
labels = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
try {
const response = await authService.getReservationStatistics(restaurantId, period, customDate);
if (response.success && response.data.length > 0) {
mappedData = response.data.map(item => ({
label: item.label || item.date || '',
reservations_total: item.reservations_total || 0,
number_of_people_total: item.number_of_people_total || 0
}));
} else {
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
} catch (error) {
console.error(error);
mappedData = labels.map(label => ({
label,
reservations_total: 0,
number_of_people_total: 0
}));
}
break;
}
setChartData(mappedData);
};
const salesData = [
{ country: 'United Kingdom', amount: 17678, change: 12, sales: 340 },
{ country: 'Spain', amount: 5500, change: -5, sales: 100 },
{ country: 'Germany', amount: 24189, change: -25, sales: 540 },
];
useEffect(() => {
const checkProducts = async () => {
setIsLoading(true);
const productsExist = await new Promise((resolve) =>
setTimeout(() => resolve(true), 1500)
);
setHasProducts(productsExist);
setIsLoading(false);
};
checkProducts();
}, []);
useEffect(() => {
const handleResize = () => {
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [theme.breakpoints.values.md]);
fetchStatistics();
}, [timeFrame, restaurantId, customDate]);
return (
<Box
p={{ xs: 1, sm: 2 }}
maxWidth="100%"
overflowX="hidden"
>
<Box p={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
{/* Tables Manager */}
<Box mb={1.5}>
{restaurantId && <TablesManager restaurantId={restaurantId} />}
</Box>
{/* Header Buttons */}
<Box
display="flex"
@@ -106,7 +145,7 @@ const AnalyticsPage = () => {
justifyContent="space-between"
alignItems="center"
gap={2}
mb={3}
mb={1.5}
>
<ButtonGroup
size="small"
@@ -131,94 +170,73 @@ const AnalyticsPage = () => {
}
}}
>
{[
{ label: 'All Time', value: 'all' },
{ label: '12 Months', value: '12m' },
{ label: '30 Days', value: '30d' },
{ label: '7 Days', value: '7d' },
{ label: '24 Hour', value: '24h' },
].map(({ label, value }) => (
{[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
<Button
key={value}
variant={timeFrame === value ? 'contained' : 'text'}
onClick={() => setTimeFrame(value)}
onClick={() => handleTimeFrameChange(value)}
>
{label}
</Button>
))}
</ButtonGroup>
<Box display="flex" gap={2} flexWrap="wrap" justifyContent={{ xs: 'space-between', sm: 'flex-end' }} width={{ xs: '100%', sm: 'auto' }}>
<Button
variant="contained"
{/* Date Picker + Today Button */}
<Box
display="flex"
gap={1}
alignItems="center"
sx={{
backgroundColor: theme.palette.background.paper,
borderRadius: 2,
p: '4px 8px',
boxShadow: 'inset 0 0 0 1px #e0e0e0'
}}
>
<TextField
type="date"
value={customDate}
onChange={handleCustomDateChange}
size="small"
sx={{
textTransform: 'none',
color: '#667085',
backgroundColor: 'white',
boxShadow: 'none',
borderRadius: '8px',
height: '40px',
fontSize: '13px',
fontWeight: 500,
border: '1px solid #e0e0e0',
gap: 1,
minWidth: '120px'
width: isMobile ? '100%' : 150,
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
}}
>
<CalendarTodayOutlinedIcon sx={{ fontSize: 16 }} />
{!isSmallScreen && 'Select Dates'}
</Button>
<Button
variant="contained"
sx={{
textTransform: 'none',
color: 'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '8px',
height: '40px',
fontWeight: 600,
fontSize: '14px',
minWidth: '100px'
}}
>
KPIs Filter
</Button>
</Box>
</Box>
{/* Data Sections */}
<Box display="flex" flexDirection={{ xs: 'column', md: 'row' }} gap={2} mb={3}>
<Box flex={2}>
<TopSellingProduct data={topSellingProducts} />
</Box>
<Box flex={1}>
<SalesByLocation data={salesData} />
</Box>
</Box>
{/* Chart Section */}
<Box>
{isLoading ? (
<>
<Skeleton variant="rectangular" height={50} sx={{ mb: 2 }} />
<Skeleton variant="rectangular" height={200} sx={{ mb: 2 }} />
<Skeleton variant="rectangular" height={300} sx={{ mb: 2 }} />
</>
) : (
<StatisticsCard
title="Analytics Overview"
subtitle="Performance Metrics"
data={getData()}
dataKeys={[
{ key: 'visitors', name: 'Visitors', color: '#4CAF50' },
{ key: 'conversions', name: 'Conversions', color: '#9C27B0' },
]}
xDataKey={timeFrame === '24h' ? 'date' : 'label'}
timeFrame={timeFrame}
onTimeFrameChange={setTimeFrame}
/>
)}
<Button
variant="contained"
size="small"
onClick={() => setCustomDate(dayjs().format('YYYY-MM-DD'))}
sx={{
backgroundColor: 'rgba(255, 117, 34, 0.08)',
color: '#ff5722',
textTransform: 'none',
boxShadow: 'none',
fontSize: isMobile ? 12 : 13,
'&:hover': { backgroundColor: 'rgba(255, 117, 34, 0.15)' }
}}
>
Today
</Button>
</Box>
</Box>
{/* StatisticsCard */}
<Box>
<StatisticsCard
title="Analytics Overview"
subtitle="Reservation Statistics"
data={chartData}
dataKeys={[
{ key: 'reservations_total', name: 'Reservations', color: '#4CAF50' },
{ key: 'number_of_people_total', name: 'People', color: '#9C27B0' },
]}
xDataKey="label"
valueFormatter={(value) => value}
timeFrame={timeFrame}
onTimeFrameChange={handleTimeFrameChange}
/>
</Box>
</Box>
);