نسخ من RaghadAlkhous/RestaurantDash
Initial commit - restaurant dashboard
هذا الالتزام موجود في:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery, Skeleton } from '@mui/material';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import AnalyticsContect from './AnalyticsContect';
|
||||
@@ -15,11 +15,11 @@ const AnalyticsPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// محاكاة التحقق من المنتجات
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
setIsLoading(true);
|
||||
const productsExist = await checkIfProductsExist(); // استبدل بمنطقك
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
setIsLoading(false);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ const AnalyticsPage = () => {
|
||||
}, []);
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500)); // محاكاة تأخير
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -57,8 +57,6 @@ const AnalyticsPage = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
return (
|
||||
|
||||
|
||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||
|
||||
@@ -94,15 +92,9 @@ const AnalyticsPage = () => {
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
{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 }} />
|
||||
</>
|
||||
) : (
|
||||
|
||||
<AnalyticsContect />
|
||||
)}
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -22,9 +22,9 @@ const SalesByLocation = ({ data }) => {
|
||||
p: { xs: 1, sm: 2 },
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.1)',
|
||||
height: '95%', // إضافة هذه السطر
|
||||
display: 'flex', // إضافة
|
||||
flexDirection: 'column' // إضافة
|
||||
height: '95%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
@@ -56,7 +56,7 @@ const SalesByLocation = ({ data }) => {
|
||||
</Box>
|
||||
|
||||
<List dense sx={{
|
||||
flexGrow: 1, // إضافة هذه السطر
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Box,
|
||||
@@ -6,7 +6,11 @@ import {
|
||||
Paper,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery
|
||||
useMediaQuery,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AreaChart,
|
||||
@@ -18,37 +22,64 @@ import {
|
||||
ResponsiveContainer,
|
||||
CartesianGrid
|
||||
} from 'recharts';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`;
|
||||
if (value >= 1000) return `$${(value / 1000).toFixed(1)}K`;
|
||||
return `$${value}`;
|
||||
};
|
||||
// import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
|
||||
const StatisticsCard = ({
|
||||
title = "Statistics",
|
||||
subtitle = "Delivery Times",
|
||||
data = [],
|
||||
dataKeys = [
|
||||
{ key: 'revenue', name: 'Revenue', color: '#E46A11' }, // << هنا تغيير اللون
|
||||
{ key: 'sales', name: 'Sales', color: '#0182FC' } // << وهنا أيضاً
|
||||
{ key: 'revenue', name: 'Revenue', color: '#E46A11' },
|
||||
{ key: 'sales', name: 'Sales', color: '#0182FC' }
|
||||
],
|
||||
xDataKey = 'month',
|
||||
valueFormatter = formatCurrency,
|
||||
valueFormatter = (value) => value,
|
||||
timeFrame = 'month',
|
||||
onTimeFrameChange
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||
const [selectedDate, setSelectedDate] = useState('');
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
const handleTimeFrameChange = (newTimeFrame) => {
|
||||
if (onTimeFrameChange) onTimeFrameChange(newTimeFrame);
|
||||
const handleMenuOpen = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleDateChange = (event) => {
|
||||
setSelectedDate(event.target.value);
|
||||
if (onTimeFrameChange) onTimeFrameChange(timeFrame, event.target.value);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleTodayClick = () => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
setSelectedDate(today);
|
||||
if (onTimeFrameChange) onTimeFrameChange(timeFrame, today);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
// حساب العلامات الديناميكية للمحور الشاقولي
|
||||
const ticksArray = (() => {
|
||||
if (!data || data.length === 0) return [];
|
||||
const maxValue = Math.max(
|
||||
...data.flatMap(d => dataKeys.map(k => d[k.key] || 0))
|
||||
);
|
||||
const desiredTicks = 8; // عدد العلامات المطلوب
|
||||
const step = Math.ceil(maxValue / desiredTicks) || 1;
|
||||
const arr = [];
|
||||
for (let i = 0; i <= maxValue; i += step) {
|
||||
arr.push(i);
|
||||
}
|
||||
return arr;
|
||||
})();
|
||||
|
||||
return (
|
||||
<Box sx={{ borderRadius: 2, width: { sm: '100%', md: '167vh' }}}>
|
||||
<Box sx={{ borderRadius: 2, width: { sm: '100%', md: '167vh' } }}>
|
||||
<Paper sx={{
|
||||
p: { xs: 1.5, sm: 2 },
|
||||
mb: { xs: 2, sm: 2 },
|
||||
@@ -64,10 +95,34 @@ const StatisticsCard = ({
|
||||
right: { xs: 4, sm: 8 },
|
||||
color: '#667085'
|
||||
}}
|
||||
onClick={handleMenuOpen}
|
||||
>
|
||||
<MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} />
|
||||
{/* <MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} /> */}
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<MenuItem>
|
||||
<TextField
|
||||
type="date"
|
||||
value={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
size="small"
|
||||
sx={{ width: 150 }}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Button variant="outlined" size="small" onClick={handleTodayClick}>
|
||||
Today
|
||||
</Button>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Header */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
@@ -88,7 +143,7 @@ const StatisticsCard = ({
|
||||
</Box>
|
||||
|
||||
{/* Chart */}
|
||||
<ResponsiveContainer width="100%" height={isMobile ? 250 : isTablet ? 290 : 295}>
|
||||
<ResponsiveContainer width="100%" height={isMobile ? 350 : isTablet ? 390 : 395}>
|
||||
<AreaChart data={data} margin={{
|
||||
top: isMobile ? -30 : -45,
|
||||
right: isMobile ? 15 : 30,
|
||||
@@ -112,18 +167,20 @@ const StatisticsCard = ({
|
||||
tickMargin={isMobile ? 8 : 15}
|
||||
tick={{
|
||||
fontSize: isMobile ? 11 : 12,
|
||||
angle: -25, // تدوير النص لتفادي التزاحم
|
||||
angle: -25,
|
||||
textAnchor: 'end'
|
||||
}}
|
||||
interval={0} // عرض كل القيم على محور X
|
||||
interval={0}
|
||||
/>
|
||||
<YAxis
|
||||
ticks={ticksArray}
|
||||
tickFormatter={valueFormatter}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickMargin={isMobile ? 8 : 15}
|
||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
||||
/>
|
||||
|
||||
<Tooltip formatter={valueFormatter} />
|
||||
<Legend
|
||||
verticalAlign="top"
|
||||
@@ -138,9 +195,9 @@ const StatisticsCard = ({
|
||||
name={name}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={color} // لون الخط
|
||||
stroke={color}
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
fill={`url(#color-${key})`} // لون التعبئة بتدرج
|
||||
fill={`url(#color-${key})`}
|
||||
/>
|
||||
))}
|
||||
</AreaChart>
|
||||
|
||||
456
src/components/Home/Analytics&Reporting/TablesManager.js
Normal file
456
src/components/Home/Analytics&Reporting/TablesManager.js
Normal file
@@ -0,0 +1,456 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
Button,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Menu,
|
||||
Chip,
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import TuneIcon from "@mui/icons-material/Tune";
|
||||
import authService from "../../../services/authService";
|
||||
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
const handlePrev = () => currentPage > 1 && onChange(currentPage - 1);
|
||||
const handleNext = () => currentPage < pageCount && onChange(currentPage + 1);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handlePrev}
|
||||
disabled={currentPage <= 1}
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#FFECE0",
|
||||
// "&:hover": { backgroundColor: "#FFD6B5" },
|
||||
color: theme.palette.primary.main,
|
||||
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
||||
}}
|
||||
>
|
||||
<ArrowBackIosNewIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: "8px",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "#fff",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontWeight: 600,
|
||||
fontSize: 14,
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
{currentPage}
|
||||
</Box>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleNext}
|
||||
disabled={currentPage >= pageCount}
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#FFECE0",
|
||||
// "&:hover": { backgroundColor: "#FFD6B5" },
|
||||
color: theme.palette.primary.main,
|
||||
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIosIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const statusOptions = ["available", "unavailable"];
|
||||
|
||||
const TablesManager = ({ restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const itemsPerPage = 4;
|
||||
|
||||
const [rows, setRows] = useState([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [addingRow, setAddingRow] = useState(false);
|
||||
const [newRow, setNewRow] = useState({
|
||||
restaurant_id: restaurantId,
|
||||
table_number: "",
|
||||
capacity: "",
|
||||
status: "available",
|
||||
});
|
||||
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [rowToDelete, setRowToDelete] = useState(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
// فلتر
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
|
||||
const fetchTables = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await authService.getTablesByRestaurant(restaurantId);
|
||||
if (result.success) setRows(result.data);
|
||||
} catch (err) {
|
||||
console.error("❌ Error fetching tables:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (restaurantId) fetchTables();
|
||||
}, [restaurantId]);
|
||||
|
||||
const handleSaveRow = async () => {
|
||||
if (!newRow.table_number || !newRow.capacity) return;
|
||||
try {
|
||||
const result = await authService.addTable(newRow);
|
||||
if (result.success) {
|
||||
setRows((prev) => [result.data, ...prev]);
|
||||
setAddingRow(false);
|
||||
setNewRow({
|
||||
restaurant_id: restaurantId,
|
||||
table_number: "",
|
||||
capacity: "",
|
||||
status: "available",
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error adding table:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRow = (row) => {
|
||||
setRowToDelete(row);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!rowToDelete) return;
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const result = await authService.deleteTable(rowToDelete.id);
|
||||
if (result.success) setRows((prev) => prev.filter((r) => r.id !== rowToDelete.id));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterClick = (event) => setFilterAnchorEl(event.currentTarget);
|
||||
const handleFilterClose = () => setFilterAnchorEl(null);
|
||||
const handleFilterSelect = (value) => {
|
||||
setStatusFilter(value);
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
const filteredRows =
|
||||
(statusFilter === "all" ? rows : rows.filter((r) => r.status === statusFilter))
|
||||
.sort((a, b) => parseInt(a.table_number) - parseInt(b.table_number));
|
||||
|
||||
const pageCount = Math.ceil(filteredRows.length / itemsPerPage);
|
||||
const paginatedRows = filteredRows.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
const getStatusChipProps = (status) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case "available":
|
||||
return {
|
||||
label: "Available",
|
||||
sx: {
|
||||
backgroundColor: "#E7F4EE",
|
||||
color: "#0D894F",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
minWidth: 90,
|
||||
},
|
||||
};
|
||||
case "unavailable":
|
||||
return {
|
||||
label: "Unavailable",
|
||||
sx: {
|
||||
backgroundColor: "#fde8e8ff",
|
||||
color: "#e41111ff",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
minWidth: 100,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: status,
|
||||
sx: {
|
||||
backgroundColor: "#FDF1E8",
|
||||
color: "#E46A11",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
minWidth: 80,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: "100%", sm: "95.5%" },
|
||||
pt: { xs: 2, sm: 2 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 2 },
|
||||
// p: { xs: 1.5, sm: 2 },
|
||||
// mb: { xs: 2, sm: 2 },
|
||||
backgroundColor: "white",
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
justifyContent: "space-between",
|
||||
alignItems: { xs: "stretch", sm: "center" },
|
||||
mb: 2,
|
||||
gap: { xs: 1, sm: 0 },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: "18px", sm: "20px" },
|
||||
textAlign: { xs: "center", sm: "left" },
|
||||
}}
|
||||
>
|
||||
Tables
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
{/* فلتر */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
color: "#667085",
|
||||
borderColor: "#e0e0e0",
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: { xs: "120px", sm: "120px", md: "99px" },
|
||||
fontSize: { xs: "0", sm: "13px", md: "14px" },
|
||||
fontWeight: 600,
|
||||
minWidth: "unset",
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? "small" : "small"} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? "" : "Filters"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={addingRow ? "outlined" : "contained"}
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setAddingRow(!addingRow)}
|
||||
sx={{
|
||||
color: addingRow ? "primary.main" : "white",
|
||||
borderRadius: "8px",
|
||||
fontWeight: 600,
|
||||
fontSize: "14px",
|
||||
height: "40px",
|
||||
width: { xs: "100%", sm: "135px" },
|
||||
textTransform: "none",
|
||||
}}
|
||||
>
|
||||
{addingRow ? "Cancel" : "Add"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Menu الفلاتر */}
|
||||
<Menu anchorEl={filterAnchorEl} open={Boolean(filterAnchorEl)} onClose={handleFilterClose}>
|
||||
{["all", "available", "unavailable"].map((status) => (
|
||||
<MenuItem
|
||||
key={status}
|
||||
selected={statusFilter === status}
|
||||
onClick={() => handleFilterSelect(status)}
|
||||
sx={{
|
||||
color: statusFilter === status ? "#FF914D" : "#4F5867",
|
||||
fontWeight: statusFilter === status ? 700 : 500,
|
||||
}}
|
||||
>
|
||||
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* جدول */}
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: "none",
|
||||
border: "1px solid #e0e0e0",
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
height: {xs:"310px",md:"349px"},
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: "auto" }}
|
||||
aria-label="tables"
|
||||
size={isMobile ? "small" : "medium"}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: "#f5f5f5" }}>
|
||||
<TableRow>
|
||||
<TableCell sx={{ pl: { md: 7 } }}>Table Number</TableCell>
|
||||
<TableCell>Capacity</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading
|
||||
? Array.from({ length: itemsPerPage }).map((_, idx) => (
|
||||
<TableRow key={`skeleton-${idx}`}>
|
||||
<TableCell ><Skeleton variant="text" /></TableCell>
|
||||
<TableCell><Skeleton variant="text" /></TableCell>
|
||||
<TableCell><Skeleton variant="text" /></TableCell>
|
||||
<TableCell><Skeleton variant="text" /></TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: (
|
||||
<>
|
||||
{addingRow && (
|
||||
<TableRow>
|
||||
<TableCell >
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder="Table Number"
|
||||
value={newRow.table_number}
|
||||
onChange={(e) => setNewRow({ ...newRow, table_number: e.target.value })}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TextField
|
||||
size="small"
|
||||
type="number"
|
||||
placeholder="Capacity"
|
||||
value={newRow.capacity}
|
||||
onChange={(e) => setNewRow({ ...newRow, capacity: e.target.value })}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label="Available"
|
||||
sx={{
|
||||
backgroundColor: "#E7F4EE",
|
||||
color: "#0D894F",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
minWidth: 90,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton color="success" onClick={handleSaveRow}>
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
|
||||
{paginatedRows.map((row) => (
|
||||
<TableRow key={row.id} >
|
||||
<TableCell sx={{ pl: { md: 9 } ,color:'#689dffff'}}>{row.table_number}</TableCell>
|
||||
<TableCell>{row.capacity}</TableCell>
|
||||
<TableCell>
|
||||
<Chip {...getStatusChipProps(row.status)} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton color="error" onClick={() => handleDeleteRow(row)}>
|
||||
<DeleteOutlineIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Pagination Footer */}
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pt={2}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Showing {(currentPage - 1) * itemsPerPage + 1} -{" "}
|
||||
{Math.min(currentPage * itemsPerPage, filteredRows.length)} of {filteredRows.length}
|
||||
</Typography>
|
||||
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||
</Box>
|
||||
|
||||
{/* Dialog الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete table "{rowToDelete?.table_number}"?
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleConfirmDelete}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TablesManager;
|
||||
@@ -1,215 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
Pagination,
|
||||
Button,
|
||||
Avatar,
|
||||
TableContainer
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import { green } from '@mui/material/colors';
|
||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
|
||||
const TopSellingProduct = ({ data = [] }) => {
|
||||
const theme = useTheme();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 5;
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const pageCount = Math.ceil(data.length / itemsPerPage);
|
||||
const paginatedData = data.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper sx={{
|
||||
borderRadius: 2,
|
||||
width: '100%',
|
||||
maxWidth: { xs: '100%', md: '115vh' },
|
||||
height: { xs: 'auto', md: '100vh' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
boxShadow: theme.shadows[1]
|
||||
}}>
|
||||
{/* العنوان وزر الفلاتر */}
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p={{ xs: 1, sm: 2 }}
|
||||
sx={{ backgroundColor: theme.palette.background.paper }}
|
||||
>
|
||||
<Typography variant="h6" sx={{ fontSize: { xs: '1rem', sm: '1.25rem' } }}>
|
||||
Top Selling Product
|
||||
</Typography>
|
||||
<Button
|
||||
sx={{
|
||||
fontSize: { xs: '12px', sm: '16px' },
|
||||
fontWeight: 500,
|
||||
border: '1px solid #e0e0e0',
|
||||
color: '#667085',
|
||||
textTransform: 'none',
|
||||
p: { xs: '4px 8px', sm: '6px 16px' }
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* جدول البيانات */}
|
||||
<TableContainer
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
overflowX: 'auto',
|
||||
maxHeight: { xs: '60vh', md: 'calc(100vh - 160px)' },
|
||||
'&::-webkit-scrollbar': { height: 4 },
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
sx={{
|
||||
minWidth: 650,
|
||||
'& .MuiTableCell-root': {
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
py: { xs: 0.5, sm: 1.5 },
|
||||
px: { xs: 0.5, sm: 2 },
|
||||
},
|
||||
'& thead .MuiTableCell-root': {
|
||||
borderBottom: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '11px', sm: '14px' },
|
||||
px: { xs: 0.5, sm: 2 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#F0F1F3' }}>
|
||||
<TableRow sx={{ height: { xs: '6vh', sm: '10vh' } }}>
|
||||
<TableCell>Product</TableCell>
|
||||
{!isMobile && <TableCell>Sales</TableCell>}
|
||||
<TableCell>Amount</TableCell>
|
||||
{!isMobile && <TableCell>Price</TableCell>}
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{paginatedData.map((row, i) => (
|
||||
<TableRow key={i} sx={{ height: { xs: '8vh', sm: '13vh' } }}>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 1, sm: 2 } }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
bgcolor: green[50],
|
||||
width: { xs: 28, sm: 40 },
|
||||
height: { xs: 28, sm: 40 }
|
||||
}}
|
||||
variant="rounded"
|
||||
>
|
||||
<AssignmentIcon fontSize={isMobile ? 'small' : 'medium'} />
|
||||
</Avatar>
|
||||
<Box sx={{
|
||||
fontSize: { xs: '11px', sm: '14px' },
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: { xs: '80px', sm: '200px' }
|
||||
}}>
|
||||
{row.product}
|
||||
</Box>
|
||||
</Box>
|
||||
</TableCell>
|
||||
{!isMobile && <TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>{row.sales}</TableCell>}
|
||||
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>${row.amount.toLocaleString()}</TableCell>
|
||||
{!isMobile && <TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>${row.price}</TableCell>}
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={isMobile ? row.status.substring(0, 3) : row.status}
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
sx={{
|
||||
fontSize: { xs: '10px', sm: '14px' },
|
||||
fontWeight: 600,
|
||||
backgroundColor:
|
||||
row.status === 'Published' ? '#E7F4EE' :
|
||||
row.status === 'Low Stock' ? '#FDF1E8' :
|
||||
row.status === 'Out of Stock' ? '#FFCDD2' : '#E0E0E0',
|
||||
color:
|
||||
row.status === 'Published' ? '#0D894F' :
|
||||
row.status === 'Low Stock' ? '#E46A11' :
|
||||
row.status === 'Out of Stock' ? '#C62828' : '#424242',
|
||||
minWidth: { xs: '50px', sm: '100px' }
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* التذييل مع الترقيم */}
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p={{ xs: 1, sm: 2 }}
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
fontSize: { xs: '11px', sm: '14px' },
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, data.length)} of {data.length}
|
||||
</Typography>
|
||||
|
||||
<Pagination
|
||||
count={pageCount}
|
||||
page={currentPage}
|
||||
onChange={(event, value) => setCurrentPage(value)}
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
sx={{
|
||||
'& .MuiPaginationItem-root': {
|
||||
fontSize: { xs: '12px', sm: '14px' },
|
||||
minWidth: { xs: 24, sm: 32 },
|
||||
height: { xs: 24, sm: 32 },
|
||||
backgroundColor: '#FFECE0',
|
||||
color: theme.palette.primary.main,
|
||||
borderRadius: '8px',
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: '#FFD6B5',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopSellingProduct;
|
||||
المرجع في مشكلة جديدة
حظر مستخدم