نسخ من 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;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
// src/components/AppBar/KitchPlusAppBar.jsx
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
@@ -7,31 +8,37 @@ import {
|
||||
IconButton,
|
||||
Divider,
|
||||
Avatar,
|
||||
Button,
|
||||
Autocomplete,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import NotificationsOutlinedIcon from '@mui/icons-material/NotificationsOutlined';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
|
||||
const top100Films = [
|
||||
{ title: 'The Shawshank Redemption' },
|
||||
{ title: 'The Godfather' },
|
||||
{ title: 'The Dark Knight' },
|
||||
{ title: 'Pulp Fiction' },
|
||||
];
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import { useRestaurant } from '../../contexts/RestaurantContext';
|
||||
import authService from '../../services/authService';
|
||||
import { UserContext } from '../../contexts/UserContext'; // ✅ استدعاء UserContext
|
||||
|
||||
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||
const { restaurantId } = useRestaurant();
|
||||
|
||||
const { user } = useContext(UserContext); // ✅ استخدم الـ Context بدلاً من localStorage
|
||||
const [restaurantLogo, setRestaurantLogo] = useState('/images/default-restaurant.png');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRestaurantLogo = async () => {
|
||||
if (!restaurantId) return;
|
||||
const res = await authService.getRestaurantById(restaurantId);
|
||||
if (res.success && res.data) {
|
||||
setRestaurantLogo(res.data.image_url);
|
||||
}
|
||||
};
|
||||
fetchRestaurantLogo();
|
||||
}, [restaurantId]);
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
@@ -54,156 +61,66 @@ const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{/* Left side with toggle button */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{/* Toggle button for mobile/tablet */}
|
||||
{/* Left side */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
mr: 2,
|
||||
}}
|
||||
>
|
||||
{(isMobile || isMediumScreen) && (
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={onDrawerToggle}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<IconButton color="inherit" edge="start" onClick={onDrawerToggle}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
{location.pathname === '/dashboard' ? (
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="div"
|
||||
sx={{
|
||||
fontWeight: '500',
|
||||
fontSize: { xs: '18px', sm: '20px', md: '24px' },
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: { xs: '200px', sm: 'none' }
|
||||
}}
|
||||
>
|
||||
Welcome to KitchPlus
|
||||
</Typography>
|
||||
) : (
|
||||
<Autocomplete
|
||||
sx={{
|
||||
width: { xs: '200px', sm: '200px', md: '250px' },
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
freeSolo
|
||||
id="free-solo-2-demo"
|
||||
disableClearable
|
||||
options={top100Films.map((option) => option.title)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder="Search"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
type: 'search',
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<SearchIcon sx={{ color: '#667085' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
backgroundColor: 'white',
|
||||
height: { xs: 36, sm: 38, md: 40 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="h6" sx={{ fontWeight: '500' }}>
|
||||
Welcome to KitchPlus
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Right side */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||
{location.pathname === '/dashboard' && !isSmallScreen && (
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'none', md: 'flex' }, // هذا السطر يخفي الزر عند xs و sm ويظهره من md وما فوق
|
||||
color: 'white',
|
||||
height: { xs: 32, sm: 36, md: 40 },
|
||||
mr: { xs: '6px', sm: '8px', md: '0px' },
|
||||
backgroundColor: '#61677F',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
||||
textTransform: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{isMediumScreen ? 'Switch Company' : 'Switch Company Profits'}
|
||||
</Button>
|
||||
)}
|
||||
{location.pathname === '/dashboard' && (
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
flexItem
|
||||
sx={{
|
||||
height: { xs: 30, sm: 36, md: 40 },
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<IconButton color="#667085" size={isSmallScreen ? 'small' : 'medium'}>
|
||||
<NotificationsOutlinedIcon fontSize={isSmallScreen ? 'small' : 'medium'} />
|
||||
{location.pathname === '/dashboard' && <Divider orientation="vertical" flexItem sx={{ height: 40 }} />}
|
||||
|
||||
<IconButton
|
||||
color="#667085"
|
||||
size={isSmallScreen ? 'small' : 'medium'}
|
||||
onClick={() => navigate('/restaurant')}
|
||||
>
|
||||
<HomeIcon fontSize={isSmallScreen ? 'medium' : 'medium'} />
|
||||
</IconButton>
|
||||
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
flexItem
|
||||
sx={{
|
||||
height: { xs: 30, sm: 36, md: 40 },
|
||||
alignSelf: 'center'
|
||||
}}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem sx={{ height: 40 }} />
|
||||
|
||||
<Avatar
|
||||
alt="Admin"
|
||||
src="/images/waitress3.png"
|
||||
sx={{
|
||||
width: { xs: 28, sm: 32, md: 40 },
|
||||
height: { xs: 28, sm: 32, md: 40 }
|
||||
}}
|
||||
alt="Restaurant Logo"
|
||||
src={restaurantLogo}
|
||||
sx={{ width: { xs: 28, sm: 32, md: 40 }, height: { xs: 28, sm: 32, md: 40 } }}
|
||||
/>
|
||||
|
||||
{!isSmallScreen && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
ml: { sm: 0.5, md: 1 },
|
||||
ml: 1,
|
||||
color: '#61677F',
|
||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
||||
fontSize: { xs: 12, sm: 13, md: 14 },
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: { xs: '100px', sm: '120px', md: 'none' }
|
||||
overflow: 'visible',
|
||||
textOverflow: 'clip',
|
||||
maxWidth: 200,
|
||||
}}
|
||||
>
|
||||
Admin@gmail.com
|
||||
{user?.email || 'Admin@gmail.com'}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
color="inherit"
|
||||
sx={{
|
||||
mt: 0.5,
|
||||
color: '#A6ACB8',
|
||||
padding: { xs: '4px', sm: '8px' }
|
||||
}}
|
||||
size={isSmallScreen ? 'small' : 'medium'}
|
||||
>
|
||||
<KeyboardArrowDownIcon fontSize={isSmallScreen ? 'small' : 'medium'} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default KitchPlusAppBar;
|
||||
export default KitchPlusAppBar;
|
||||
|
||||
74
src/components/Home/Cart/Cart.js
Normal file
74
src/components/Home/Cart/Cart.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import Orders from './contect/Orders';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Cart = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const admin = authService.getAdminData();
|
||||
// console.log('Admin Info:', admin);
|
||||
|
||||
const adminId = authService.getAdminId();
|
||||
// console.log('Admin ID:', adminId);
|
||||
}, []);
|
||||
|
||||
const admin = authService.getAdminData();
|
||||
const adminId = authService.getAdminId();
|
||||
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar onDrawerToggle={handleDrawerToggle} sidebarOpen={sidebarOpen} isMobile={isMobile} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 6,
|
||||
width: { xs: '90%', sm: '95%', md: '96%' },
|
||||
pt: { xs: 2, sm: 3 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 4 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
}}
|
||||
>
|
||||
<Orders adminId={adminId} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cart;
|
||||
223
src/components/Home/Cart/contect/CartDetails.js
Normal file
223
src/components/Home/Cart/contect/CartDetails.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Button,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import authService from '../../../../services/authService';
|
||||
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||
|
||||
const CartDetails = ({ cart, onClose, onUpdated, onDeleted }) => {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [totalPrice, setTotalPrice] = useState(cart?.attributes?.total_price || "");
|
||||
const [cartItems, setCartItems] = useState(cart.relationships?.cartItems || cart.relationships?.cart_items || []);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
const { showSnackbar } = useSnackbar();
|
||||
if (!cart) return null;
|
||||
|
||||
const handleSaveAll = async () => {
|
||||
setLoading(true);
|
||||
const payload = {
|
||||
data: {
|
||||
type: "cart",
|
||||
attributes: {
|
||||
totalPrice: Number(totalPrice),
|
||||
},
|
||||
relationships: {
|
||||
cartItems: cartItems.map(item => ({
|
||||
attributes: {
|
||||
quantity: Number(item.attributes.quantity),
|
||||
},
|
||||
relationships: {
|
||||
product: {
|
||||
data: {
|
||||
id: item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await authService.updateCart(cart.id, payload);
|
||||
if (result.success) {
|
||||
onUpdated(result.data);
|
||||
setEditMode(false);
|
||||
} else {
|
||||
// alert(result.message || 'Failed to update cart');
|
||||
showSnackbar(result.message || "Failed to update cart", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
// alert('An error occurred while updating the cart');
|
||||
showSnackbar("An error occurred while updating the cart", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteCart = async () => {
|
||||
setDeleteLoading(true);
|
||||
try {
|
||||
const result = await authService.deleteCart(cart.id);
|
||||
if (result.success) {
|
||||
onDeleted(cart.id);
|
||||
onClose();
|
||||
} else {
|
||||
// alert(result.message || 'Failed to delete cart');
|
||||
showSnackbar(result.message || "Failed to update cart", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
// alert('An error occurred while deleting the cart');
|
||||
showSnackbar("An error occurred while deleting the cart", "error");
|
||||
} finally {
|
||||
setDeleteLoading(false);
|
||||
setOpenDeleteDialog(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeQuantity = (itemId, newQuantity) => {
|
||||
setCartItems(prev =>
|
||||
prev.map(item =>
|
||||
item.id === itemId
|
||||
? { ...item, attributes: { ...item.attributes, quantity: newQuantity } }
|
||||
: item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper sx={{ p: 3, mt: 2, borderRadius: 2, border: '1px solid #e0e0e0' }}>
|
||||
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Cart #{cart.id} Details
|
||||
</Typography>
|
||||
|
||||
{editMode ? (
|
||||
<>
|
||||
<TextField
|
||||
label="Total Price"
|
||||
type="number"
|
||||
value={totalPrice}
|
||||
onChange={(e) => setTotalPrice(e.target.value)}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||
<List>
|
||||
{cartItems.map(item => (
|
||||
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||
<ListItemText
|
||||
primary={`Item ID: ${item.id} | Product: ${item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}`}
|
||||
secondary={
|
||||
<TextField
|
||||
type="number"
|
||||
size="small"
|
||||
label="Quantity"
|
||||
value={item.attributes.quantity}
|
||||
onChange={(e) => handleChangeQuantity(item.id, Number(e.target.value))}
|
||||
sx={{ width: '120px' }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||
<Button variant="contained" onClick={handleSaveAll} disabled={loading}>
|
||||
{loading ? "Saving..." : "Save All"}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={() => setEditMode(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Typography>Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}</Typography>
|
||||
<Typography>
|
||||
Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||
<List>
|
||||
{cartItems.map(item => (
|
||||
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||
<ListItemText
|
||||
primary={`Item ID: ${item.id}`}
|
||||
secondary={`Quantity: ${item.attributes.quantity} | Product: ${item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||
<Button variant="contained" onClick={() => setEditMode(true)}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setOpenDeleteDialog(true)}
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
Delete Cart
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={onClose}>
|
||||
Back to Orders
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this cart? This action cannot be undone.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} disabled={deleteLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDeleteCart}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
{deleteLoading ? <CircularProgress size={24} /> : 'Delete'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartDetails;
|
||||
170
src/components/Home/Cart/contect/CartView.js
Normal file
170
src/components/Home/Cart/contect/CartView.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { Box, Typography, Button, LinearProgress, IconButton } from "@mui/material";
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { CartContext } from "../../../../contexts/CartContextR";
|
||||
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||
|
||||
const CartView = ({ onClose, onCartCreated, adminId }) => {
|
||||
const { cart, clearCart, createNewCart } = useContext(CartContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { showSnackbar } = useSnackbar();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Admin ID from props in:CartView', adminId);
|
||||
}, [adminId]);
|
||||
|
||||
const totalPrice = cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0);
|
||||
|
||||
const handleSendCart = async () => {
|
||||
if (!cart.length) return;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// تحقق من أن adminId موجود ضمن قائمة صالحة (يمكنك تعديلها حسب بياناتك)
|
||||
const validAdminIds = [1, 2]; // IDs موجودة في DB
|
||||
if (!validAdminIds.includes(adminId)) {
|
||||
console.error("Invalid admin ID");
|
||||
showSnackbar("Selected admin is not valid.", "error");
|
||||
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// تحقق من أن جميع المنتجات موجودة في قاعدة البيانات
|
||||
const validProductIds = [1, 2, 3, 4]; // IDs المنتجات الموجودة
|
||||
for (let item of cart) {
|
||||
if (!validProductIds.includes(item.id)) {
|
||||
console.error(`Invalid product ID: ${item.id}`);
|
||||
// alert(`Product with ID ${item.id} does not exist.`);
|
||||
showSnackbar(`Product with ID ${item.id} does not exist.`, "error");
|
||||
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// تجهيز البيانات حسب شكل الـ backend
|
||||
const cartData = {
|
||||
data: {
|
||||
type: "cart",
|
||||
attributes: {
|
||||
totalPrice: cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0),
|
||||
},
|
||||
relationships: {
|
||||
admin: {
|
||||
data: { id: adminId },
|
||||
},
|
||||
cartItems: cart.map(item => ({
|
||||
attributes: { quantity: item.quantity },
|
||||
relationships: { product: { data: { id: item.id } } },
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newCart = await createNewCart(cartData);
|
||||
|
||||
if (newCart && newCart.success) {
|
||||
onCartCreated(newCart.data);
|
||||
clearCart();
|
||||
onClose();
|
||||
showSnackbar("Cart sent successfully!", "success");
|
||||
} else {
|
||||
console.error("Failed to create cart:", newCart.message);
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error sending cart:", error);
|
||||
showSnackbar("Failed to create cart.", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (loading) return <LinearProgress />;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: "100%", sm: "93.5%" },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: "white",
|
||||
borderRadius: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* السهم للعودة */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<IconButton onClick={onClose} size="small" sx={{ mr: 1 }}>
|
||||
<ArrowBackIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Typography variant="h5" sx={{ fontWeight: 600, fontSize: { xs: "18px", sm: "20px" } }}>
|
||||
Current Cart
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{cart.length === 0 ? (
|
||||
<Typography sx={{ mt: 2, textAlign: "center" }}>No items in cart.</Typography>
|
||||
) : (
|
||||
<>
|
||||
{cart.map(item => (
|
||||
<Box
|
||||
key={item.id}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0",
|
||||
backgroundColor: "#fafafa",
|
||||
flexWrap: "wrap",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography fontWeight={600}>{item.name}</Typography>
|
||||
<Typography variant="body2">Unit: {item.unit}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ textAlign: { xs: "left", sm: "right" } }}>
|
||||
<Typography variant="body2">Quantity: {item.quantity}</Typography>
|
||||
<Typography variant="body2">Total: ${item.totalPrice}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
Total Price: ${totalPrice.toFixed(2)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{cart.length > 0 && (
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 2, flexWrap: { xs: "wrap", sm: "nowrap" }, justifyContent: { xs: "center", sm: "flex-start" } }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSendCart}
|
||||
sx={{ color: 'white', borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '200px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '200px' } }}
|
||||
>
|
||||
Send Cart to Server
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={clearCart}
|
||||
sx={{ borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '150px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '150px' } }}
|
||||
>
|
||||
Clear Cart
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartView;
|
||||
389
src/components/Home/Cart/contect/Orders.js
Normal file
389
src/components/Home/Cart/contect/Orders.js
Normal file
@@ -0,0 +1,389 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
CircularProgress,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import authService from '../../../../services/authService';
|
||||
import { useRestaurant } from "../../../../contexts/RestaurantContext";
|
||||
import CartDetails from './CartDetails';
|
||||
import CartView from './CartView';
|
||||
|
||||
|
||||
|
||||
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
const handlePrev = () => { if (currentPage > 1) onChange(currentPage - 1); };
|
||||
const handleNext = () => { if (currentPage < pageCount) onChange(currentPage + 1); };
|
||||
const { restaurantId } = useRestaurant();
|
||||
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',
|
||||
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
|
||||
}}
|
||||
>
|
||||
{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 Orders = ({ adminId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [ordersData, setOrdersData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedCart, setSelectedCart] = useState(null);
|
||||
const [cartLoading, setCartLoading] = useState(false);
|
||||
const [showCartView, setShowCartView] = useState(false);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 6;
|
||||
|
||||
// ────────────── Fetch Orders ──────────────
|
||||
useEffect(() => {
|
||||
const fetchOrders = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await authService.getCart();
|
||||
if (result.success) {
|
||||
console.log(Array.isArray(result.data) ? result.data : []);
|
||||
setOrdersData(Array.isArray(result.data) ? result.data : []);
|
||||
} else {
|
||||
console.error(result.message);
|
||||
setOrdersData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch orders:', error);
|
||||
setOrdersData([]);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
fetchOrders();
|
||||
}, []);
|
||||
|
||||
// ────────────── Row Click ──────────────
|
||||
const handleRowClick = async (cartId) => {
|
||||
setCartLoading(true);
|
||||
try {
|
||||
const result = await authService.getCartById(cartId);
|
||||
if (result.success) setSelectedCart(result.data);
|
||||
else console.error(result.message);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch cart details:', error);
|
||||
}
|
||||
setCartLoading(false);
|
||||
};
|
||||
|
||||
const handleCartUpdated = (updatedCart) => {
|
||||
setOrdersData(prev =>
|
||||
prev.map(cart => cart.id === updatedCart.id ? updatedCart : cart)
|
||||
);
|
||||
setSelectedCart(null);
|
||||
};
|
||||
|
||||
const handleCloseCartDetails = () => { setSelectedCart(null); };
|
||||
|
||||
const handleAddOrder = (newOrder) => {
|
||||
setOrdersData(prev => [newOrder, ...prev]);
|
||||
};
|
||||
|
||||
const pageCount = Math.ceil(ordersData.length / itemsPerPage);
|
||||
const paginatedOrders = ordersData.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
if (cartLoading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCart) {
|
||||
return (
|
||||
<CartDetails
|
||||
cart={selectedCart}
|
||||
onClose={handleCloseCartDetails}
|
||||
onUpdated={handleCartUpdated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (showCartView) {
|
||||
return (
|
||||
<CartView
|
||||
onClose={() => setShowCartView(false)}
|
||||
onSend={handleAddOrder}
|
||||
onCartCreated={(newCart) => {
|
||||
setOrdersData(prev => [newCart, ...prev]);
|
||||
setShowCartView(false);
|
||||
}}
|
||||
adminId={adminId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ────────────── Main Orders Table ──────────────
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<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' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Cart
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
onClick={() => setShowCartView(true)}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '135px' },
|
||||
textTransform: 'none',
|
||||
minWidth: { xs: 'unset', sm: '135px' },
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Current Cart
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
height: '380px',
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: 'auto' }}
|
||||
aria-label="orders table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Cart ID</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Total Price</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Created At</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Items Count</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Action</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>
|
||||
<TableCell><Skeleton variant="text" /></TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: (
|
||||
<>
|
||||
{paginatedOrders.map(order => {
|
||||
const attributes = order?.attributes || {};
|
||||
const relationships = order?.relationships || {};
|
||||
const cartItems = relationships?.cartItems || relationships?.cart_items || [];
|
||||
return (
|
||||
<TableRow
|
||||
key={order?.id}
|
||||
hover
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.3s',
|
||||
'&:hover': {
|
||||
backgroundColor: '#ffe4d0ff !important',
|
||||
'& td': { color: '#000000ff' },
|
||||
},
|
||||
}}
|
||||
onClick={() => handleRowClick(order.id)}
|
||||
>
|
||||
<TableCell>{order?.id || '--'}</TableCell>
|
||||
<TableCell>{attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}</TableCell>
|
||||
<TableCell>{attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}</TableCell>
|
||||
<TableCell>{cartItems.length}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={ async (e) => {
|
||||
e.stopPropagation();
|
||||
const cart = order;
|
||||
if (!cart) return;
|
||||
|
||||
const orderData = {
|
||||
data: {
|
||||
type: "order",
|
||||
attributes: {
|
||||
totalPrice: parseFloat(cart.attributes.total_price) || 0,
|
||||
},
|
||||
relationships: {
|
||||
restaurant: {
|
||||
data: {
|
||||
id: restaurantId,
|
||||
},
|
||||
},
|
||||
orderItems: (cart.relationships.cart_items || []).map((item) => ({
|
||||
attributes: {
|
||||
quantity: item.attributes.quantity,
|
||||
},
|
||||
relationships: {
|
||||
product: {
|
||||
data: {
|
||||
id: item.relationships.supplier_product.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
authService.confirmOrder(orderData);
|
||||
|
||||
const result =await authService.confirmOrder(orderData);
|
||||
if (result.success) {
|
||||
setOrdersData(prev => prev.filter(c => c.id !== cart.id));
|
||||
} else {
|
||||
console.error(result.message);
|
||||
}
|
||||
}}>
|
||||
Confirm
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
{paginatedOrders.length < itemsPerPage &&
|
||||
Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
|
||||
<TableRow key={`empty-${idx}`} sx={{ height: 53 }}>
|
||||
<TableCell colSpan={4} sx={{ borderBottom: 'none' }} />
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pt={{ xs: 1, sm: 2 }}
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ fontSize: { xs: '11px', sm: '14px' }, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
|
||||
</Typography>
|
||||
|
||||
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Orders;
|
||||
@@ -8,27 +8,25 @@ const drawerWidth = 230;
|
||||
const Cashier = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [hasProducts, setHasProducts] = useState(false); // حالة لتتبع وجود المنتجات
|
||||
const [hasProducts, setHasProducts] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// محاكاة للتحقق من وجود المنتجات (استبدل هذا بمنطقك الفعلي)
|
||||
|
||||
useEffect(() => {
|
||||
// هنا يجب استبدال هذا بمنطق فعلي للتحقق من وجود المنتجات
|
||||
// مثلاً استدعاء API أو التحقق من state
|
||||
|
||||
const checkProducts = async () => {
|
||||
// محاكاة لاستدعاء API
|
||||
const productsExist = await checkIfProductsExist(); // استبدل هذه الدالة بمنطقك الفعلي
|
||||
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
};
|
||||
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
// دالة مساعدة لمحاكاة التحقق من المنتجات (استبدلها بمنطقك الفعلي)
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
// محاكاة - يمكن أن يكون هذا استدعاء لـ API أو تحقق من state
|
||||
// return true;
|
||||
return false; // غير هذه القيمة حسب منطقك
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -62,7 +60,7 @@ const Cashier = () => {
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor:'#F6F6F6',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Sidebar
|
||||
|
||||
136
src/components/Home/CreateNewRestaurant/AppBar.js
Normal file
136
src/components/Home/CreateNewRestaurant/AppBar.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Box,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const result = await authService.logout();
|
||||
|
||||
if (result.success) {
|
||||
localStorage.removeItem('token');
|
||||
navigate('/login');
|
||||
} else {
|
||||
console.error('Logout failed:', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
sx={{
|
||||
height: { xs: 56, sm: 64, md: 66 },
|
||||
backgroundColor: '#ffffff',
|
||||
color: 'black',
|
||||
boxShadow: 'none',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: theme.zIndex.appBar,
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
sx={{
|
||||
px: { xs: 2, sm: 3, md: '24px' },
|
||||
minHeight: { xs: '56px !important', sm: '64px !important' },
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{/* Left: Logo */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src="/image.png"
|
||||
alt="logo"
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
objectFit: 'contain',
|
||||
mr: 1.5,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: '1.25rem',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
>
|
||||
KITCH
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: '1.25rem',
|
||||
color: 'primary.main',
|
||||
ml: 0.5,
|
||||
}}
|
||||
>
|
||||
PLUS
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Right: Log Out Button */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||
<Box
|
||||
onClick={handleLogout}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: '5px',
|
||||
px: 2,
|
||||
py: 1,
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.2s',
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#fffcf9d5',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
color: 'divider',
|
||||
minWidth: 36,
|
||||
}}
|
||||
>
|
||||
<LogoutIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Log Out"
|
||||
primaryTypographyProps={{
|
||||
sx: {
|
||||
color: 'divider',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default KitchPlusAppBar;
|
||||
228
src/components/Home/CreateNewRestaurant/CreateRestaurant.js
Normal file
228
src/components/Home/CreateNewRestaurant/CreateRestaurant.js
Normal file
@@ -0,0 +1,228 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery, Snackbar, Alert } from '@mui/material';
|
||||
import AppBar from './AppBar';
|
||||
|
||||
import CloudKitchenProject from '../CreateYourRestaurant/contcet/CloudKitchenProject';
|
||||
import OperationalDetails from '../CreateYourRestaurant/contcet/OperationalDetails';
|
||||
import RequiredEquipments from '../CreateYourRestaurant/contcet/RequiredEquipments';
|
||||
import VisualIdentity from '../CreateYourRestaurant/contcet/VisualIdentity';
|
||||
import Budget from '../CreateYourRestaurant/contcet/Budget';
|
||||
import AdditionalNotes from '../CreateYourRestaurant/contcet/AdditionalNotes';
|
||||
import SideProfile from './SideProfile';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const { createNewRestaurant } = authService;
|
||||
const drawerWidth = 230;
|
||||
|
||||
const CreateRestaurant = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [formData, setFormData] = useState({});
|
||||
const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
|
||||
|
||||
const updateFormData = (newData) => {
|
||||
setFormData(prev => {
|
||||
const updated = { ...prev, ...newData };
|
||||
console.log('Updated formData:', updated);
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseSnackbar = (event, reason) => {
|
||||
if (reason === 'clickaway') return;
|
||||
setSnackbar(prev => ({ ...prev, open: false }));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const result = await createNewRestaurant(formData);
|
||||
if (result.success) {
|
||||
setSnackbar({ open: true, message: 'Restaurant created successfully!', severity: 'success' });
|
||||
} else {
|
||||
setSnackbar({ open: true, message: `Error: ${result.message}`, severity: 'error' });
|
||||
}
|
||||
console.log("Form Data being submitted:", formData);
|
||||
};
|
||||
|
||||
const steps = [
|
||||
<CloudKitchenProject
|
||||
key="step-0"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<OperationalDetails
|
||||
key="step-1"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<RequiredEquipments
|
||||
key="step-2"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<VisualIdentity
|
||||
key="step-3"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<Budget
|
||||
key="step-4"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<AdditionalNotes
|
||||
key="step-5"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<AppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
{/* Sidebar profile for desktop */}
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
ml: 3,
|
||||
mb: 2,
|
||||
width: { md: '30%' },
|
||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: '100%',
|
||||
pb: 15,
|
||||
pt: 3,
|
||||
}}
|
||||
>
|
||||
<SideProfile
|
||||
currentStepIndex={currentStep}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Step content */}
|
||||
<Box
|
||||
sx={{
|
||||
ml: { xs: 2, md: 3 },
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
pr: { sm: 2, md: 1 },
|
||||
pt: 3,
|
||||
mb: { sm: 20 },
|
||||
width: { md: '60%' },
|
||||
display: { xs: 'block', sm: 'block', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minHeight: '100%', pb: 18 }}>
|
||||
{steps[currentStep]}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
<Snackbar
|
||||
open={snackbar.open}
|
||||
autoHideDuration={4000}
|
||||
onClose={handleCloseSnackbar}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert onClose={handleCloseSnackbar} severity={snackbar.severity}
|
||||
sx={{
|
||||
width: { xs: '40%', sm: '60%', md: '100%' },
|
||||
color: 'white',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
backgroundColor: '#e57f3f',
|
||||
borderRadius: 6, mb: 6
|
||||
}}
|
||||
>
|
||||
{snackbar.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateRestaurant;
|
||||
@@ -3,12 +3,12 @@ import { Box, Typography, Stack, Button, useTheme } from '@mui/material';
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
|
||||
const steps = [
|
||||
{ title: 'Cloud Kitchen Hosting', icon: '/images/createProfile/BasicInf.png' },
|
||||
{ title: 'Restaurant Operations', icon: '/images/icons/rocket.png' },
|
||||
{ title: 'Infrastructure & Equipment', icon: '/images/createProfile/equipment.png' },
|
||||
{ title: 'Facilitaion & Cooperation', icon: '/images/createProfile/Facilitaion.png' },
|
||||
{ title: 'Expansion & Future Cooperation', icon: '/images/createProfile/Expansion.png' },
|
||||
|
||||
{ title: 'Cloud Kitchen Project', icon: '/images/createProfile/BasicInf.png' },
|
||||
{ title: 'OperationalDetails', icon: '/images/icons/rocket.png' },
|
||||
{ title: 'Required Equipments', icon: '/images/createProfile/equipment.png'},
|
||||
{ title: 'Visual Identity', icon: '/images/createProfile/Vector.png' },
|
||||
{ title: 'Budget & Expansion', icon: '/images/createProfile/Expansion.png' },
|
||||
{ title: 'Additional Notes & Concerns', icon: '/images/createProfile/Group.png' },
|
||||
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||
];
|
||||
|
||||
@@ -22,7 +22,7 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 3,
|
||||
py: 4,
|
||||
display: 'block' ,
|
||||
display: { xs: 'none', md: 'block' },
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
@@ -30,14 +30,14 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
>
|
||||
{/* العنوان الرئيسي */}
|
||||
<Typography fontSize="18px" fontWeight={600} mb={1.2}>
|
||||
Host Kitchen Flow
|
||||
Create Your Restaurant
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" mb={3}>
|
||||
Complete this process to register your restaurant on our amazing food platform.
|
||||
Complete this process to register your restaurant on our amazing food platform.
|
||||
</Typography>
|
||||
|
||||
{/* الخطوات */}
|
||||
<Stack spacing={5} sx={{ ml: 1, position: 'relative', pb: 15 }}>
|
||||
<Stack spacing={3} sx={{ ml: 1, position: 'relative', pb: 12 }}>
|
||||
{steps.map((step, index) => (
|
||||
<Box key={index} position="relative">
|
||||
{/* الخط الرأسي بين الدوائر */}
|
||||
@@ -45,10 +45,10 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: {sm:'50px',md:'40px'},
|
||||
left: '22px',
|
||||
top: '28px',
|
||||
left: '23px',
|
||||
width: '2px',
|
||||
height: {sm:'130%',md:'calc(100% - 0px)'},
|
||||
height: 'calc(100% - 5px)',
|
||||
backgroundColor: '#E0E0E0',
|
||||
zIndex: 0,
|
||||
}}
|
||||
@@ -87,7 +87,6 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* الدائرة الأمامية بالأيقونة */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
@@ -106,7 +105,7 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginLeft: step.title === 'Infrastructure & Equipment' ? 7 : 0,
|
||||
marginLeft: step.title === 'Required Equipments' ? 7 : 0,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
@@ -134,7 +133,6 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
right: 24,
|
||||
mt:10
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import { Box, useTheme, useMediaQuery, Snackbar, Alert } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import CloudKitchenProject from './contcet/CloudKitchenProject';
|
||||
@@ -10,7 +10,9 @@ import Budget from './contcet/Budget';
|
||||
import AdditionalSupport from './contcet/AdditionalSupport';
|
||||
import AdditionalNotes from './contcet/AdditionalNotes';
|
||||
import SideProfile from './SideProfile';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const { createNewRestaurant } = authService;
|
||||
const drawerWidth = 230;
|
||||
|
||||
const CreateRestaurant = () => {
|
||||
@@ -19,30 +21,98 @@ const CreateRestaurant = () => {
|
||||
const [hasProducts, setHasProducts] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// ⬇️ إدارة الخطوة الحالية
|
||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
||||
// إدارة بيانات النموذج لجميع الخطوات (مشاركة البيانات بين الخطوات)
|
||||
const [formData, setFormData] = useState({
|
||||
|
||||
});
|
||||
|
||||
// دالة لتحديث بيانات النموذج بشكل مرن (دمج الجديد مع القديم)
|
||||
const updateFormData = (newData) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
...newData,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
// دالة لإغلاق السناك بار
|
||||
const handleSnackbarClose = (event, reason) => {
|
||||
if (reason === 'clickaway') return;
|
||||
setSnackbarOpen(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await createNewRestaurant(formData);
|
||||
setSnackbarOpen(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const steps = [
|
||||
<CloudKitchenProject onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<OperationalDetails onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<RequiredEquipments onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<VisualIdentity onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<Budget onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<AdditionalSupport onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<AdditionalNotes onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<CloudKitchenProject
|
||||
key="step-0"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<OperationalDetails
|
||||
key="step-1"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<RequiredEquipments
|
||||
key="step-2"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<VisualIdentity
|
||||
key="step-3"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<Budget
|
||||
key="step-4"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>,
|
||||
<AdditionalNotes
|
||||
key="step-5"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
};
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return false;
|
||||
};
|
||||
// useEffect(() => {
|
||||
// const checkProducts = async () => {
|
||||
// const productsExist = await checkIfProductsExist();
|
||||
// setHasProducts(productsExist);
|
||||
// };
|
||||
// checkProducts();
|
||||
// }, []);
|
||||
|
||||
// const checkIfProductsExist = async () => {
|
||||
// return false;
|
||||
// };
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
@@ -71,12 +141,14 @@ const CreateRestaurant = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
@@ -84,82 +156,102 @@ const CreateRestaurant = () => {
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex', height: '100vh',
|
||||
|
||||
}}>
|
||||
<Box sx={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
ml: 3,
|
||||
mb: 2,
|
||||
width: { md: '30%' },
|
||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 15, pt: 3,
|
||||
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
ml: 3,
|
||||
mb: 2,
|
||||
width: { md: '30%' },
|
||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: '100%',
|
||||
pb: 15,
|
||||
pt: 3,
|
||||
}}
|
||||
>
|
||||
<SideProfile
|
||||
currentStepIndex={currentStep}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
ml: { xs: 2, md: 3 },
|
||||
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
pr: { sm: 2, md: 1 },
|
||||
|
||||
pt: 3,
|
||||
mb: { sm: 20 },
|
||||
width: { md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 18,
|
||||
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
ml: { xs: 2, md: 3 },
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
pr: { sm: 2, md: 1 },
|
||||
pt: 3,
|
||||
mb: { sm: 20 },
|
||||
width: { md: '60%' },
|
||||
display: { xs: 'block', sm: 'block', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: '100%',
|
||||
pb: 18,
|
||||
}}
|
||||
>
|
||||
{steps[currentStep]}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
<Snackbar
|
||||
open={snackbarOpen}
|
||||
autoHideDuration={4000}
|
||||
onClose={handleSnackbarClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert onClose={handleSnackbarClose} severity="success" sx={{ width: '100%' }}>
|
||||
عملية إنشاء المطعم تمت بنجاح
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default CreateRestaurant;
|
||||
export default CreateRestaurant;
|
||||
|
||||
@@ -8,7 +8,6 @@ const steps = [
|
||||
{ title: 'Required Equipments', icon: '/images/createProfile/equipment.png'},
|
||||
{ title: 'Visual Identity', icon: '/images/createProfile/Vector.png' },
|
||||
{ title: 'Budget & Expansion', icon: '/images/createProfile/Expansion.png' },
|
||||
{ title: 'Additional Support', icon: '/images/createProfile/hand.png' },
|
||||
{ title: 'Additional Notes & Concerns', icon: '/images/createProfile/Group.png' },
|
||||
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||
];
|
||||
|
||||
@@ -1,151 +1,171 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
|
||||
import ConfirmationDialog from './ConfirmationDialog';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
const AdditionalNotes = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const AdditionalNotes = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData, onSubmit }) => {
|
||||
const theme = useTheme();
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleOpenModal = () => setOpenModal(true);
|
||||
const handleCloseModal = () => setOpenModal(false);
|
||||
const handleConfirmNext = () => {
|
||||
handleCloseModal();
|
||||
onNext();
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: { xs: 4, md: 11 },
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography fontWeight={700} sx={{
|
||||
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
||||
}}>
|
||||
Additional Notes
|
||||
</Typography>
|
||||
const handleOpenModal = () => setOpenModal(true);
|
||||
const handleCloseModal = () => setOpenModal(false);
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
const handleNotesChange = (e) => {
|
||||
updateFormData({ need_help: e.target.value });
|
||||
};
|
||||
|
||||
{/*Notes / Concerns Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Notes / Concerns
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Write for us"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={7}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
const handleConfirmSubmit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (onSubmit) {
|
||||
await onSubmit();
|
||||
}
|
||||
setOpenModal(false);
|
||||
|
||||
// التوجيه فقط إذا كانت الصفحة هي /create-restaurant
|
||||
if (location.pathname === '/create-restaurant') {
|
||||
navigate('/restaurant');
|
||||
}
|
||||
// إذا كانت من رابط آخر مثل /create-kitchen لا نفعل شيئًا
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover
|
||||
}
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: { xs: 4, md: 11 },
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Additional Notes
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* ✅ Confirmation Modal */}
|
||||
<ConfirmationDialog
|
||||
open={openModal}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirmNext}
|
||||
title="Confirm Submission"
|
||||
description="Are you sure you want to proceed to the next step?"
|
||||
/>
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Notes / Concerns
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Write for us"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={7}
|
||||
value={formData.need_help || ''}
|
||||
onChange={handleNotesChange}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover
|
||||
}
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<ConfirmationDialog
|
||||
open={openModal}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirmSubmit}
|
||||
loading={loading}
|
||||
title="Confirm Submission"
|
||||
description="Are you sure you want to proceed to the next step?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdditionalNotes;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const Budget = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -12,6 +11,10 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
else navigate('/dashboard');
|
||||
};
|
||||
|
||||
const handleInputChange = (field) => (e) => {
|
||||
updateFormData({ [field]: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -44,20 +47,29 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Box>
|
||||
|
||||
{/* Estimated Budget Input */}
|
||||
<InputField label="Estimated Budget" placeholder="$4200" theme={theme} />
|
||||
<InputField
|
||||
label="Estimated Budget"
|
||||
placeholder="$4200"
|
||||
theme={theme}
|
||||
value={formData.estimated_budget || ''}
|
||||
onChange={handleInputChange('estimated_budget')}
|
||||
/>
|
||||
|
||||
{/* Expansion Plans Through Cloud Kitchen (No of Branches) Input */}
|
||||
<InputField label="Expansion Plans Through Cloud Kitchen (No of Branches)" placeholder="200" theme={theme} />
|
||||
|
||||
|
||||
<InputField
|
||||
label="Expansion Plans Through Cloud Kitchen (No of Branches)"
|
||||
placeholder="200"
|
||||
theme={theme}
|
||||
value={formData.expansion_branches || ''}
|
||||
onChange={handleInputChange('expansion_branches')}
|
||||
/>
|
||||
|
||||
{/* Next Button */}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -75,7 +87,6 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -88,7 +99,7 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -100,14 +111,12 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// مكون فرعي لتقليل التكرار في الحقول
|
||||
const InputField = ({ label, placeholder, theme }) => (
|
||||
const InputField = ({ label, placeholder, theme, value, onChange }) => (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||
{label}
|
||||
@@ -116,6 +125,8 @@ const InputField = ({ label, placeholder, theme }) => (
|
||||
placeholder={placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
|
||||
@@ -1,12 +1,85 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
FormHelperText
|
||||
} from '@mui/material';
|
||||
|
||||
const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
import authService from '../../../../services/authService'; // تأكد من المسار الصحيح
|
||||
|
||||
const CloudKitchenProject = ({
|
||||
currentStepIndex = 0,
|
||||
onNext,
|
||||
formData,
|
||||
updateFormData,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [cuisineTypes, setCuisineTypes] = useState([]);
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
//cuisineTypes
|
||||
useEffect(() => {
|
||||
const fetchCuisineTypes = async () => {
|
||||
const data = await authService.cuisineTypes();
|
||||
setCuisineTypes(data);
|
||||
};
|
||||
fetchCuisineTypes();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (formData.menu_status === undefined) {
|
||||
updateFormData({ menu_status: false });
|
||||
}
|
||||
if (formData.is_existing_brand === undefined) {
|
||||
updateFormData({ is_existing_brand: false });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
if (name === 'is_existing_brand' || name === 'menu_status') {
|
||||
updateFormData({ [name]: value === 'yes' });
|
||||
} else {
|
||||
updateFormData({ [name]: value });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const validate = () => {
|
||||
let tempErrors = {};
|
||||
if (!formData.name || formData.name.trim() === '') {
|
||||
tempErrors.name = 'Restaurant Name is required';
|
||||
}
|
||||
if (!formData.cuisine_type_id) {
|
||||
tempErrors.cuisine_type_id = 'Cuisine Type is required';
|
||||
}
|
||||
if (typeof formData.is_existing_brand !== 'boolean') {
|
||||
tempErrors.is_existing_brand = 'Existing Brand selection is required';
|
||||
}
|
||||
if (!formData.location || formData.location.trim() === '') {
|
||||
tempErrors.location = 'Location is required';
|
||||
}
|
||||
if (typeof formData.menu_status !== 'boolean') {
|
||||
tempErrors.menu_status = 'Menu Status selection is required';
|
||||
}
|
||||
setErrors(tempErrors);
|
||||
return Object.keys(tempErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (validate()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -47,92 +120,150 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{[
|
||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
||||
{ label: 'Cuisine Type', placeholder: 'Italian, Chinese, etc.' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
{/* Restaurant Name */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Restaurant Name
|
||||
</Typography>
|
||||
<TextField
|
||||
name="name"
|
||||
placeholder="Al-Baik Foods"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.name || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Cuisine Type */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Cuisine Type
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
name="cuisine_type_id"
|
||||
placeholder="Select Cuisine Type"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.cuisine_type_id || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.cuisine_type_id}
|
||||
helperText={errors.cuisine_type_id}
|
||||
sx={{
|
||||
'& .MuiSelect-select': { fontWeight: 500, fontSize: '15px' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{cuisineTypes.length > 0 ? (
|
||||
cuisineTypes.map((type) => (
|
||||
<MenuItem key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>No Cuisine Types Found</MenuItem>
|
||||
)}
|
||||
</TextField>
|
||||
</Box>
|
||||
|
||||
<Box sx={{}}>
|
||||
{/* Existing Brand */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Existing Brand
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
<FormControl error={!!errors.is_existing_brand}>
|
||||
<RadioGroup
|
||||
row
|
||||
name="is_existing_brand"
|
||||
value={formData.is_existing_brand ? 'yes' : 'no'}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{errors.is_existing_brand && (
|
||||
<FormHelperText>{errors.is_existing_brand}</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{/* Further Brand Details Input */}
|
||||
{/* Further Brand Details */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Further Brand Details
|
||||
</Typography>
|
||||
<TextField
|
||||
name="brand_details"
|
||||
placeholder="We need Pizza Machine"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
value={formData.brand_details || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.brand_details}
|
||||
helperText={errors.brand_details}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
@@ -158,122 +289,130 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{[
|
||||
{ label: 'Age Group', placeholder: '15 - 75' },
|
||||
{ label: 'Location', placeholder: 'Street 123, Jordan' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
{/* Age Group */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Age Group
|
||||
</Typography>
|
||||
<TextField
|
||||
name="age_group"
|
||||
placeholder="15 - 75"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.age_group || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.age_group}
|
||||
helperText={errors.age_group}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Location */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Location
|
||||
</Typography>
|
||||
<TextField
|
||||
name="location"
|
||||
placeholder="Street 123, Jordan"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.location || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.location}
|
||||
helperText={errors.location}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{}}>
|
||||
{/* Menu Status */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Menu Status
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Further Brand Details Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Need Help
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Write here..."
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormControl error={!!errors.menu_status}>
|
||||
<RadioGroup
|
||||
row
|
||||
name="menu_status"
|
||||
value={formData.menu_status ? 'yes' : 'no'}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{errors.menu_status && (
|
||||
<FormHelperText>{errors.menu_status}</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{/* زر Next */}
|
||||
<Box sx={{ pt: 2.8 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -290,33 +429,7 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
Box,
|
||||
Typography,
|
||||
useTheme,
|
||||
Divider
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
const ConfirmationDialog = ({ open, onClose, onConfirm, loading }) => {
|
||||
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Dialog
|
||||
@@ -24,7 +24,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
PaperProps={{
|
||||
sx: {
|
||||
width: '525px',
|
||||
height: '300px',
|
||||
height: '350px',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
borderRadius: '12px',
|
||||
@@ -69,7 +69,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
zIndex: 0,
|
||||
}} />
|
||||
|
||||
{/* الدائرة الأمامية مع الأيقونة */}
|
||||
|
||||
<Box sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
@@ -92,7 +92,6 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* العنوان بجانب الدائرة */}
|
||||
<DialogTitle sx={{ p: 0 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
@@ -115,7 +114,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.secondary
|
||||
}}>
|
||||
Congratulations! you have registered your restaurant successfully on our platform
|
||||
Congratulations! you well register your restaurant successfully on our platform
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
||||
@@ -128,7 +127,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* الزر الأول - Filled */}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
@@ -146,9 +145,34 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Done
|
||||
Censel
|
||||
</Button>
|
||||
|
||||
{/* الزر الأول - Filled */}
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onConfirm}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '16px',
|
||||
height: '48px',
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.dark,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<CircularProgress size={24} sx={{ color: 'white' }} />
|
||||
) : (
|
||||
'Submit'
|
||||
)}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,20 +1,71 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField, MenuItem } from '@mui/material';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import authService from '../../../../services/authService'; // ← عدّل المسار حسب مكانك
|
||||
|
||||
const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const OperationalDetails = ({
|
||||
currentStepIndex = 0,
|
||||
onNext,
|
||||
onBack,
|
||||
formData,
|
||||
updateFormData,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const countries = [
|
||||
{ code: 'US', name: 'United States' },
|
||||
{ code: 'GB', name: 'United Kingdom' },
|
||||
{ code: 'FR', name: 'France' },
|
||||
{ code: 'DE', name: 'Germany' },
|
||||
{ code: 'SA', name: 'Saudi Arabia' },
|
||||
{ code: 'EG', name: 'Egypt' },
|
||||
{ code: 'AE', name: 'United Arab Emirates' },
|
||||
// أضف المزيد حسب الحاجة
|
||||
];
|
||||
const [countries, setCountries] = useState([]);
|
||||
const [loadingCountries, setLoadingCountries] = useState(false);
|
||||
const [errors, setErrors] = useState({}); // <-- حالة الأخطاء
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCountries = async () => {
|
||||
setLoadingCountries(true);
|
||||
const result = await authService.getCountries();
|
||||
if (result.success) {
|
||||
setCountries(result.data);
|
||||
} else {
|
||||
console.error(result.message);
|
||||
}
|
||||
setLoadingCountries(false);
|
||||
};
|
||||
|
||||
fetchCountries();
|
||||
}, []);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
updateFormData({ [name]: value });
|
||||
};
|
||||
|
||||
// التحقق من صحة الحقول
|
||||
const validate = () => {
|
||||
let tempErrors = {};
|
||||
|
||||
// التحقق من أن الحقل غير فارغ
|
||||
if (!formData.staff_members || formData.staff_members.trim() === '') {
|
||||
tempErrors.staff_members = 'Staff Members is required';
|
||||
}
|
||||
// التحقق من أن القيمة رقمية
|
||||
else if (isNaN(formData.staff_members)) {
|
||||
tempErrors.staff_members = 'Staff Members must be a number';
|
||||
}
|
||||
|
||||
if (!formData.country_id || formData.country_id === '') {
|
||||
tempErrors.country_id = 'Country selection is required';
|
||||
}
|
||||
|
||||
setErrors(tempErrors);
|
||||
return Object.keys(tempErrors).length === 0;
|
||||
};
|
||||
|
||||
|
||||
const handleNext = () => {
|
||||
if (onNext) {
|
||||
if (validate()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
@@ -41,12 +92,13 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
md: '2.2rem',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Operational Details
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
@@ -54,19 +106,29 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
Enter your basic information to proceed to registration of your
|
||||
own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Staff MembersInput */}
|
||||
{/* Staff Members Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="black"
|
||||
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||
>
|
||||
Staff Members
|
||||
</Typography>
|
||||
<TextField
|
||||
name="staff_members"
|
||||
placeholder="200 (100 Chefs, 100 Cooks)"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.staff_members || ''}
|
||||
onChange={handleChange}
|
||||
error={!!errors.staff_members}
|
||||
helperText={errors.staff_members}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -75,35 +137,42 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Country Selector */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="black"
|
||||
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||
>
|
||||
Country
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
name="country_id"
|
||||
fullWidth
|
||||
defaultValue=""
|
||||
placeholder="Select your country"
|
||||
value={formData.country_id || ''}
|
||||
onChange={handleChange}
|
||||
disabled={loadingCountries}
|
||||
error={!!errors.country_id}
|
||||
helperText={errors.country_id}
|
||||
sx={{
|
||||
'& .MuiSelect-select': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
minHeight: '40px',
|
||||
minHeight: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
'& .MuiOutlinedInput-input': { padding: '10.5px 14px' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
@@ -111,37 +180,42 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
},
|
||||
}}
|
||||
|
||||
>
|
||||
<MenuItem value="" disabled hidden>
|
||||
Select your country
|
||||
{loadingCountries ? 'Loading...' : 'Select your country'}
|
||||
</MenuItem>
|
||||
{countries.map((country) => (
|
||||
<MenuItem key={country.code} value={country.code}>
|
||||
<MenuItem key={country.id} value={country.id}>
|
||||
{country.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Expansion Plan Cities MembersInput */}
|
||||
{/* Expansion Plan Cities Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Expansion Plan Cities
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="black"
|
||||
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||
>
|
||||
Expansion Plan Cities
|
||||
</Typography>
|
||||
<TextField
|
||||
name="expansion_plan_cities" // ← ✅ الاسم الصحيح المتوقع من الـ API
|
||||
placeholder="Layyah Lahore Islamabad"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.expansion_plan_cities || ''}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -150,23 +224,24 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Next Button */}
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Next Button */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -184,7 +259,7 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
{/* Back Button */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -197,7 +272,7 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -209,7 +284,6 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@@ -11,10 +11,56 @@ import {
|
||||
Radio
|
||||
} from '@mui/material';
|
||||
|
||||
const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData }) => {
|
||||
const theme = useTheme();
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const [selectedDays, setSelectedDays] = useState([]);
|
||||
// تعيين القيمة الافتراضية لـ needSpecializedEquipment إذا غير موجودة
|
||||
useEffect(() => {
|
||||
if (!formData.needSpecializedEquipment) {
|
||||
updateFormData({ needSpecializedEquipment: 'no' });
|
||||
}
|
||||
}, [formData.needSpecializedEquipment, updateFormData]);
|
||||
|
||||
// التعامل مع تغير القيمة النصية للمعدات العامة
|
||||
const handleEquipmentChange = (e) => {
|
||||
updateFormData({ equipment: e.target.value });
|
||||
};
|
||||
|
||||
|
||||
const handleNeedSpecializedChange = (e) => {
|
||||
updateFormData({ needSpecializedEquipment: e.target.value });
|
||||
};
|
||||
|
||||
// التعامل مع القيمة النصية للمعدات المتخصصة
|
||||
const handleSpecializedEquipmentChange = (e) => {
|
||||
updateFormData({ specialized_equipment: e.target.value });
|
||||
};
|
||||
|
||||
// دالة التحقق من صحة الحقول
|
||||
const validate = () => {
|
||||
let tempErrors = {};
|
||||
|
||||
// if (!formData.equipment || formData.equipment.trim() === '') {
|
||||
// tempErrors.equipment = 'Equipment is required';
|
||||
// }
|
||||
// if (!formData.needSpecializedEquipment || (formData.needSpecializedEquipment !== 'yes' && formData.needSpecializedEquipment !== 'no')) {
|
||||
// tempErrors.needSpecializedEquipment = 'Please select Yes or No';
|
||||
// }
|
||||
if (formData.needSpecializedEquipment === 'yes' && (!formData.specialized_equipment || formData.specialized_equipment.trim() === '')) {
|
||||
tempErrors.specialized_equipment = 'Please specify specialized equipments';
|
||||
}
|
||||
|
||||
setErrors(tempErrors);
|
||||
return Object.keys(tempErrors).length === 0;
|
||||
};
|
||||
|
||||
// دالة معالجة الضغط على زر Next مع تحقق الفالديشن
|
||||
const handleNext = () => {
|
||||
if (validate()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -56,15 +102,19 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Operational Hours Input */}
|
||||
{/* Equipment Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Equipment
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder=" Ovens Refrigerators Pizza Machines |"
|
||||
placeholder="Ovens Refrigerators Pizza Machines |"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.equipment || ''}
|
||||
onChange={handleEquipmentChange}
|
||||
error={!!errors.equipment}
|
||||
helperText={errors.equipment}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -84,22 +134,27 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{}}>
|
||||
{/* Need Specialized Equipment */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Need Specialized Equipment
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<RadioGroup
|
||||
row
|
||||
name="needSpecializedEquipment"
|
||||
value={formData.needSpecializedEquipment || 'no'}
|
||||
onChange={handleNeedSpecializedChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
@@ -125,53 +180,62 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{errors.needSpecializedEquipment && (
|
||||
<Typography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||
{errors.needSpecializedEquipment}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
{/* Specialized Equipments For Food Preparation Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Specialized Equipments For Food Preparation
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Knife Spoon Handle |"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
{/* Specialized Equipments For Food Preparation */}
|
||||
{formData.needSpecializedEquipment === 'yes' && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Specialized Equipments For Food Preparation
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Knife Spoon Handle |"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
value={formData.specialized_equipment || ''}
|
||||
onChange={handleSpecializedEquipmentChange}
|
||||
error={!!errors.specialized_equipment}
|
||||
helperText={errors.specialized_equipment
|
||||
}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Next Button */}
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -189,7 +253,6 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -202,7 +265,7 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -214,7 +277,6 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,61 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio
|
||||
Radio,
|
||||
Typography as MuiTypography
|
||||
} from '@mui/material';
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
|
||||
const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData }) => {
|
||||
const theme = useTheme();
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
// تأكد من وجود قيمة افتراضية
|
||||
useEffect(() => {
|
||||
if (!formData.visualIdentity) {
|
||||
updateFormData({ visualIdentity: 'no' });
|
||||
}
|
||||
}, [formData.visualIdentity, updateFormData]);
|
||||
|
||||
const handleVisualIdentityChange = (e) => {
|
||||
updateFormData({ visualIdentity: e.target.value });
|
||||
};
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
updateFormData({ logo: file });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
let tempErrors = {};
|
||||
|
||||
if (!formData.visualIdentity || (formData.visualIdentity !== 'yes' && formData.visualIdentity !== 'no')) {
|
||||
tempErrors.visualIdentity = 'Please select Yes or No';
|
||||
}
|
||||
|
||||
/*
|
||||
if (!formData.logo) {
|
||||
tempErrors.logo = 'Please upload a logo or color scheme image';
|
||||
}
|
||||
*/
|
||||
|
||||
setErrors(tempErrors);
|
||||
return Object.keys(tempErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (validate()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -54,23 +96,26 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box sx={{}}>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Visual Identity
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<RadioGroup
|
||||
row
|
||||
name="visualIdentity"
|
||||
value={formData.visualIdentity || ''}
|
||||
onChange={handleVisualIdentityChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
@@ -96,31 +141,34 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{errors.visualIdentity && (
|
||||
<MuiTypography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||
{errors.visualIdentity}
|
||||
</MuiTypography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* OR Separator */}
|
||||
{/* Upload Logo / Color Scheme */}
|
||||
<Box>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: '16px',
|
||||
color: 'black',
|
||||
mb:2 ,
|
||||
pl: 1, // padding left (اختياري)
|
||||
mb: 2,
|
||||
pl: 1,
|
||||
}}
|
||||
>
|
||||
Upload Logo / Color Scheme If Any
|
||||
</Typography>
|
||||
|
||||
{/* Upload Menu Picture */}
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
border: errors.logo_path ? '2px solid red' : '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '100px',
|
||||
height: '150px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@@ -137,19 +185,28 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Picture
|
||||
<input id="upload-image" type="file" accept="image/*" hidden />
|
||||
{formData.logo ? (formData.logo.name || 'Uploaded Image') : 'Upload Picture'}
|
||||
<input
|
||||
id="upload-image"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
hidden
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</Box>
|
||||
{errors.logo && (
|
||||
<MuiTypography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||
{errors.logo}
|
||||
</MuiTypography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Buttons: Back and Next */}
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -167,7 +224,6 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -180,7 +236,7 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -192,7 +248,6 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
244
src/components/Home/Dashboard/AnalyticsPage.js
Normal file
244
src/components/Home/Dashboard/AnalyticsPage.js
Normal file
@@ -0,0 +1,244 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import StatisticsCard from './StatisticsCard';
|
||||
// import TablesManager from './TablesManager';
|
||||
import {
|
||||
Box,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import authService from '../../../services/authService';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const AnalyticsPage = () => {
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [chartData, setChartData] = useState([]);
|
||||
|
||||
const handleTimeFrameChange = (newTimeFrame) => {
|
||||
setTimeFrame(newTimeFrame);
|
||||
};
|
||||
|
||||
const handleCustomDateChange = (event) => {
|
||||
setCustomDate(event.target.value);
|
||||
};
|
||||
|
||||
const fetchStatistics = async () => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
let period = 'yearly';
|
||||
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.getOrderStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label || item.date || '',
|
||||
orders_total: item.orders_total || 0,
|
||||
items_total: item.items_total || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_total: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_total: 0
|
||||
}));
|
||||
}
|
||||
break;
|
||||
|
||||
case '30d': // شهري
|
||||
period = 'monthly';
|
||||
labels = Array.from({ length: 30 }, (_, i) => dayjs().startOf('month').add(i, 'day').format('DD'));
|
||||
try {
|
||||
const response = await authService.getOrderStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label ? dayjs(item.label).format('DD') : item.date ? dayjs(item.date).format('DD') : '',
|
||||
orders_total: item.orders_total || 0,
|
||||
items_total: item.items_total || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_total: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_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.getOrderStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label || item.date || '',
|
||||
orders_total: item.orders_total || 0,
|
||||
items_total: item.items_total || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_total: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
orders_total: 0,
|
||||
items_total: 0
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
setChartData(mappedData);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatistics();
|
||||
}, [timeFrame, restaurantId, customDate]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
pr={{ xs: 1, sm: 2 }}
|
||||
maxWidth="100%"
|
||||
overflowX="hidden">
|
||||
{/* Header Buttons */}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{ xs: 'column', sm: 'row' }}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
mb={1.5}
|
||||
>
|
||||
<ButtonGroup
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||
'& .MuiButton-root': {
|
||||
textTransform: 'none',
|
||||
fontSize: '13px',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
color: '#555',
|
||||
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||
},
|
||||
'& .MuiButton-contained': {
|
||||
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||
color: '#ff5722',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{[{ 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={() => handleTimeFrameChange(value)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
|
||||
{/* 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={{
|
||||
width: isMobile ? '100%' : 150,
|
||||
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||
}}
|
||||
/>
|
||||
<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="Orders Statistics"
|
||||
data={chartData}
|
||||
dataKeys={[
|
||||
{ key: 'orders_total', name: 'Orders', color: '#E46A11' },
|
||||
{ key: 'items_total', name: 'Items', color: '#0182FC' },
|
||||
]}
|
||||
xDataKey="label"
|
||||
valueFormatter={(value) => value}
|
||||
timeFrame={timeFrame}
|
||||
onTimeFrameChange={handleTimeFrameChange}
|
||||
height={320}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsPage;
|
||||
82
src/components/Home/Dashboard/AnalyticsPageOccupancy.js
Normal file
82
src/components/Home/Dashboard/AnalyticsPageOccupancy.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import OrderStatusCard from './OrderStatusCard';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import authService from '../../../services/authService';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const AnalyticsPageOccupancy = () => {
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [occupancyRate, setOccupancyRate] = useState(0);
|
||||
|
||||
const fetchOccupancyRate = async () => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
let period = 'yearly';
|
||||
switch (timeFrame) {
|
||||
case '24h':
|
||||
period = 'daily';
|
||||
break;
|
||||
case '30d':
|
||||
period = 'monthly';
|
||||
break;
|
||||
case '12m':
|
||||
default:
|
||||
period = 'yearly';
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await authService.getOccupancyRate(
|
||||
restaurantId,
|
||||
period,
|
||||
customDate
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
// occupancy_rate جايه كنص مثل "11.83%" → نحولها لرقم
|
||||
const rateValue = parseFloat(response.data.replace('%', '').trim());
|
||||
setOccupancyRate(rateValue);
|
||||
} else {
|
||||
setOccupancyRate(0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setOccupancyRate(0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchOccupancyRate();
|
||||
}, [timeFrame, restaurantId, customDate]);
|
||||
|
||||
return (
|
||||
<Box pr={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
|
||||
{/* Occupancy Card */}
|
||||
<Box>
|
||||
<OrderStatusCard
|
||||
title="Occupancy Rate"
|
||||
period={
|
||||
timeFrame === '24h'
|
||||
? 'Today'
|
||||
: timeFrame === '30d'
|
||||
? 'This Month'
|
||||
: 'This Year'
|
||||
}
|
||||
progress={occupancyRate} // نسبة الإشغال
|
||||
description={`Current occupancy rate is ${occupancyRate}% for selected period.`}
|
||||
timeFrame={timeFrame}
|
||||
setTimeFrame={setTimeFrame}
|
||||
customDate={customDate}
|
||||
setCustomDate={setCustomDate}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsPageOccupancy;
|
||||
241
src/components/Home/Dashboard/AnalyticsPagebill.js
Normal file
241
src/components/Home/Dashboard/AnalyticsPagebill.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import StatisticsCard from './StatisticsCard';
|
||||
// import TablesManager from './TablesManager';
|
||||
import {
|
||||
Box,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import authService from '../../../services/authService';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const AnalyticsPagebill = () => {
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [chartData, setChartData] = useState([]);
|
||||
|
||||
const handleTimeFrameChange = (newTimeFrame) => {
|
||||
setTimeFrame(newTimeFrame);
|
||||
};
|
||||
|
||||
const handleCustomDateChange = (event) => {
|
||||
setCustomDate(event.target.value);
|
||||
};
|
||||
|
||||
const fetchStatistics = async () => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
let period = 'yearly';
|
||||
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.getBillStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label || item.date || '',
|
||||
bills_total: item.bills_total || 0,
|
||||
total_price: item.total_price || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 0
|
||||
}));
|
||||
}
|
||||
break;
|
||||
|
||||
case '30d': // شهري
|
||||
period = 'monthly';
|
||||
labels = Array.from({ length: 30 }, (_, i) => dayjs().startOf('month').add(i, 'day').format('DD'));
|
||||
try {
|
||||
const response = await authService.getBillStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label ? dayjs(item.label).format('DD') : item.date ? dayjs(item.date).format('DD') : '',
|
||||
bills_total: item.bills_total || 0,
|
||||
total_price: item.total_price || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 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.getBillStatistics(restaurantId, period, customDate);
|
||||
if (response.success && response.data.length > 0) {
|
||||
mappedData = response.data.map(item => ({
|
||||
label: item.label || item.date || '',
|
||||
bills_total: item.bills_total || 0,
|
||||
total_price: item.total_price || 0
|
||||
}));
|
||||
} else {
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 0
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
mappedData = labels.map(label => ({
|
||||
label,
|
||||
bills_total: 0,
|
||||
total_price: 0
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
setChartData(mappedData);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatistics();
|
||||
}, [timeFrame, restaurantId, customDate]);
|
||||
|
||||
return (
|
||||
<Box p={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
|
||||
{/* Header Buttons */}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{ xs: 'column', sm: 'row' }}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
mb={1}
|
||||
>
|
||||
<ButtonGroup
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||
'& .MuiButton-root': {
|
||||
textTransform: 'none',
|
||||
fontSize: '13px',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
color: '#555',
|
||||
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||
},
|
||||
'& .MuiButton-contained': {
|
||||
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||
color: '#ff5722',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{[{ 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={() => handleTimeFrameChange(value)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
|
||||
{/* 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={{
|
||||
width: isMobile ? '100%' : 150,
|
||||
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||
}}
|
||||
/>
|
||||
<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="Bills Statistics"
|
||||
data={chartData}
|
||||
dataKeys={[
|
||||
{ key: 'bills_total', name: 'Bills', color: '#11e4c1ff' },
|
||||
{ key: 'total_price', name: 'Total price', color: '#fcce01ff' },
|
||||
]}
|
||||
xDataKey="label"
|
||||
valueFormatter={(value) => value}
|
||||
timeFrame={timeFrame}
|
||||
onTimeFrameChange={handleTimeFrameChange}
|
||||
height={420}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsPagebill;
|
||||
@@ -11,23 +11,23 @@ const Dashboard = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [hasProducts, setHasProducts] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
// const [isLoading, setIsLoading] = useState(true);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// محاكاة التحقق من المنتجات
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
setIsLoading(true);
|
||||
const productsExist = await checkIfProductsExist(); // استبدل بمنطقك
|
||||
// setIsLoading(true);
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
setIsLoading(false);
|
||||
// setIsLoading(false);
|
||||
};
|
||||
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500)); // محاكاة تأخير
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -76,8 +76,8 @@ const Dashboard = () => {
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 20px)' : '100%',
|
||||
pt: { xs: 0.5, sm: 1 },
|
||||
mt: { xs: 1, sm: 2 },
|
||||
// pt: { xs: 0.5, sm: 1 },
|
||||
// mt: { xs: 1, sm: 2 },
|
||||
overflowY: 'auto',
|
||||
pl: { xs: 0, sm: 2 },
|
||||
pr: { xs: 1, sm: 2 },
|
||||
@@ -89,15 +89,18 @@ const Dashboard = () => {
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
{isLoading ? (
|
||||
{/* {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 }} />
|
||||
</>
|
||||
) : (
|
||||
hasProducts ? <DashbordContect /> : <NoProdectDash />
|
||||
)}
|
||||
hasProducts ?
|
||||
<DashbordContect />
|
||||
: <NoProdectDash />
|
||||
)} */}
|
||||
<DashbordContect />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -7,9 +7,9 @@ import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import OrderStatusCard from './OrderStatusCard';
|
||||
import StatisticsCard from './StatisticsCard';
|
||||
import RecentActivity from './RecentActivity';
|
||||
import AnalyticsPage from './AnalyticsPage';
|
||||
import AnalyticsPageOccupancy from './AnalyticsPageOccupancy';
|
||||
import AnalyticsPagebill from './AnalyticsPagebill';
|
||||
|
||||
const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) => (
|
||||
<Box
|
||||
@@ -52,111 +52,6 @@ const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) =
|
||||
</Box>
|
||||
);
|
||||
|
||||
const StatusCard = ({
|
||||
icon,
|
||||
statusText,
|
||||
statusColor,
|
||||
iconColor,
|
||||
outerColor,
|
||||
innerColor,
|
||||
height = {
|
||||
xs: 'calc(140px + 0.5vh)',
|
||||
sm: 'calc(150px + 0.5vh)',
|
||||
md: 'calc(162px + 0.5vh)'
|
||||
},
|
||||
width = {
|
||||
xs: 'min(90%, 600px)', // تقليل من 90% إلى 85% والحد الأقصى من 300px إلى 280px
|
||||
sm: 'clamp(220px, 23vw, 280px)', // تقليل جميع القيم
|
||||
md: 'clamp(220px, 19vw, 300px)'
|
||||
},
|
||||
transition = 'width 0.3s ease',
|
||||
iconSpacing = { xs: 6, sm: 8, md: 12 },
|
||||
extraButton,
|
||||
statusButtonWidth,
|
||||
title = 'Point Of Sale',
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<Card sx={{
|
||||
height,
|
||||
width,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
p: 0,
|
||||
pb: 2,
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'none',
|
||||
minWidth: { xs: 180, sm: 220 }
|
||||
}}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: iconSpacing }}>
|
||||
<IconCircle bgColor={innerColor} outerColor={outerColor}>
|
||||
{React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
|
||||
</IconCircle>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: statusColor,
|
||||
textTransform: 'none',
|
||||
boxShadow: 'none',
|
||||
borderRadius: '100px',
|
||||
width: statusButtonWidth || 'auto',
|
||||
fontSize: { xs: '0.7rem', sm: '0.8rem', md: '0.9rem' },
|
||||
px: { xs: 1, sm: 1.5 },
|
||||
py: 0.5
|
||||
}}
|
||||
>
|
||||
{statusText}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
mt={1.5}
|
||||
sx={{
|
||||
color: '#667085',
|
||||
fontSize: { xs: '14px', sm: '15px', md: '16px' },
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
||||
{extraButton ? extraButton : <Box />}
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#F7F7F7',
|
||||
width: { xs: '26px', sm: '28px', md: '30px' },
|
||||
height: { xs: '26px', sm: '28px', md: '30px' },
|
||||
minWidth: '0',
|
||||
padding: 0,
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: '#e0e0e0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ArrowOutwardIcon
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
width: { xs: '16px', sm: '18px', md: '20px' },
|
||||
height: { xs: '16px', sm: '18px', md: '20px' },
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const DashboardContect = () => {
|
||||
const theme = useTheme();
|
||||
@@ -172,7 +67,7 @@ const DashboardContect = () => {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||
mb: 3,
|
||||
mb: 1,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 3 },
|
||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||
@@ -192,7 +87,7 @@ const DashboardContect = () => {
|
||||
</Typography>
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{
|
||||
{/* <Box sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 1, sm: 2 },
|
||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||
@@ -260,11 +155,11 @@ const DashboardContect = () => {
|
||||
>
|
||||
Check Inventory
|
||||
</Button>
|
||||
</Box>
|
||||
</Box> */}
|
||||
</Box>
|
||||
|
||||
{/* Status Cards */}
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
|
||||
display: 'flex',
|
||||
@@ -333,7 +228,7 @@ const DashboardContect = () => {
|
||||
iconSpacing={{ xs: 3, sm: 5, md: 7 }}
|
||||
title="Delivery"
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
|
||||
{/* OrderStatusCard/ StatisticsCard */}
|
||||
<Box
|
||||
@@ -343,12 +238,8 @@ const DashboardContect = () => {
|
||||
flexWrap: 'nowrap',
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
gap: { xs: 1, sm: 1, md: 7 }, // gap مرن
|
||||
mb: 1,
|
||||
pl: 1,
|
||||
pr: 1,
|
||||
mt: 2,
|
||||
mr: { xs: 4, sm: 3, md: 0 },
|
||||
ml: { xs: 2, sm: 0 },
|
||||
mr: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
@@ -357,12 +248,13 @@ const DashboardContect = () => {
|
||||
sx={{
|
||||
flexGrow: 0,
|
||||
width: { xs: '100%', sm: '35%', md: '30%' },
|
||||
minWidth: 220,
|
||||
minWidth: 20,
|
||||
ml: { xs: 0, sm: -1 },
|
||||
flexShrink: 0,
|
||||
mt:8
|
||||
}}
|
||||
>
|
||||
<OrderStatusCard />
|
||||
<AnalyticsPageOccupancy />
|
||||
</Box>
|
||||
|
||||
{/* StatisticsCard */}
|
||||
@@ -370,11 +262,11 @@ const DashboardContect = () => {
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
minWidth: 0,
|
||||
ml: { xs: 0, sm: 2 },
|
||||
// ml: { xs: 0, sm: 2 },
|
||||
// backgroundColor:'red'
|
||||
}}
|
||||
>
|
||||
<StatisticsCard />
|
||||
<AnalyticsPage />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -382,11 +274,11 @@ const DashboardContect = () => {
|
||||
{/* RecentActivity */}
|
||||
<Box
|
||||
sx={{
|
||||
mr: { xs: 4, sm: 3 ,md:0 },
|
||||
mr: { xs: 4, sm: 3, md: 0 },
|
||||
ml: { xs: 2, sm: 0 },
|
||||
}}
|
||||
>
|
||||
<RecentActivity />
|
||||
<AnalyticsPagebill />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ const NoProdectDash = () => {
|
||||
fontSize: { xs: '14px', md: '18px' },
|
||||
mb: 2,
|
||||
color: '#5F6868',
|
||||
whiteSpace: 'pre-line' // هذا يسمح بكسر السطر عند المسافات
|
||||
whiteSpace: 'pre-line' // يسمح بكسر السطر عند المسافات
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ fontWeight: 600, fontSize: '18px' }}>Sorry!</Box>{' '}
|
||||
|
||||
@@ -1,260 +1,241 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Chip,
|
||||
useTheme,
|
||||
IconButton,
|
||||
useMediaQuery
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
useTheme,
|
||||
IconButton,
|
||||
useMediaQuery,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import {
|
||||
TrendingUp as TrendingUpIcon,
|
||||
TrendingDown as TrendingDownIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Cancel as CancelIcon,
|
||||
Pending as PendingIcon
|
||||
TrendingUp as TrendingUpIcon,
|
||||
TrendingDown as TrendingDownIcon,
|
||||
} from '@mui/icons-material';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import {
|
||||
CircularProgressbar,
|
||||
buildStyles
|
||||
CircularProgressbar,
|
||||
buildStyles
|
||||
} from 'react-circular-progressbar';
|
||||
import 'react-circular-progressbar/dist/styles.css';
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||
// import { Box, IconButton, Paper, Typography, useTheme, useMediaQuery } from '@mui/material';
|
||||
import {
|
||||
|
||||
ResponsiveContainer,
|
||||
|
||||
} from 'recharts';
|
||||
|
||||
import { ResponsiveContainer } from 'recharts';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const OrderStatusCard = ({
|
||||
title = 'Order Status',
|
||||
period = 'This Quarter',
|
||||
progress = 70.5,
|
||||
progressChange = '+10%',
|
||||
progressDirection = 'up', // or 'down'
|
||||
description = 'You succeeded in earning $240 today, it’s higher than yesterday',
|
||||
completed = '$20k',
|
||||
canceled = '$16k',
|
||||
pending = '$1.5k'
|
||||
title = 'Order Status',
|
||||
period = 'This Quarter',
|
||||
progress = 70.5,
|
||||
description = 'You succeeded in earning $240 today, it’s higher than yesterday',
|
||||
timeFrame,
|
||||
setTimeFrame,
|
||||
customDate,
|
||||
setCustomDate
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
const progressColor = progressDirection === 'up' ? theme.palette.success.main : theme.palette.error.main;
|
||||
const progressIcon = progressDirection === 'up' ? <TrendingUpIcon fontSize="small" /> : <TrendingDownIcon fontSize="small" />;
|
||||
const highlightedDescription = description.replace(
|
||||
/(\$\d+(?:\.\d+)?[kKmM]?)/g,
|
||||
'<span style="color:#000;font-weight:500;">$1</span>'
|
||||
);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||
const open = Boolean(anchorEl);
|
||||
const handleMenuOpen = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
width: { xs: '96%', sm: '94%', md: 360 },
|
||||
height: 'auto',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||
pl: { xs: 1, sm: 1 },
|
||||
pr: { xs: 1, sm: 1 },
|
||||
pb: { xs: 1, sm: 1 },
|
||||
pt: { xs: 0, sm: 0 },
|
||||
|
||||
position: 'relative' // إضافة هذه الخاصية
|
||||
}}
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
width: { xs: '96%', sm: '94%', md: 360 },
|
||||
height: '400',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||
p: 1,
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height={isMobile ? '75vh' : isTablet ? '59vh' : 385}>
|
||||
|
||||
{/* زر القائمة العلوية */}
|
||||
<IconButton
|
||||
aria-label="more-actions"
|
||||
onClick={handleMenuOpen}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
color: '#667085'
|
||||
}}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height={isMobile ? '75vh' : isTablet ? '59vh' : 385}>
|
||||
{/* زر الإجراءات في الزاوية العلوية اليمنى */}
|
||||
<IconButton
|
||||
aria-label="more-actions"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
color: '#667085'
|
||||
}}
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
|
||||
{/* القائمة المنسدلة */}
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleMenuClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<Box sx={{ p: 2, minWidth: 250 }}>
|
||||
{/* Header Buttons (Timeframe) */}
|
||||
<ButtonGroup
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||
mb: 2,
|
||||
width: '100%',
|
||||
'& .MuiButton-root': {
|
||||
textTransform: 'none',
|
||||
fontSize: '13px',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
color: '#555',
|
||||
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||
},
|
||||
'& .MuiButton-contained': {
|
||||
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||
color: '#ff5722',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{[{ 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); handleMenuClose(); }}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
|
||||
<CardContent>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
{/* 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={(e) => setCustomDate(e.target.value)}
|
||||
size="small"
|
||||
sx={{
|
||||
width: isMobile ? '100%' : 150,
|
||||
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
</Menu>
|
||||
|
||||
ml: 0 // هامش يسار لإفساح المجال للأيقونة
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontSize: { xs: '16px', sm: '18px' },
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: { xs: '12px', sm: '14px' },
|
||||
fontWeight: 500,
|
||||
color: '#667085'
|
||||
}}
|
||||
>
|
||||
{period}
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* المحتوى الأساسي للكارت */}
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', ml: 0 }}>
|
||||
<Typography variant="h6" sx={{ fontSize: { xs: '16px', sm: '18px' }, fontWeight: 500 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: { xs: '12px', sm: '14px' }, fontWeight: 500, color: '#667085', mb: 3 }}>
|
||||
{period}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Progress Circle */}
|
||||
<Box sx={{ height: 120, mb: 2, position: 'relative' }}>
|
||||
{/* تغيير النسبة تحت الدائرة */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '80%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
label={progressChange}
|
||||
size="small"
|
||||
icon={progressIcon}
|
||||
sx={{
|
||||
height: 22,
|
||||
fontSize: '0.75rem',
|
||||
color: progressDirection === 'up' ? '#087443' : '#B42318',
|
||||
backgroundColor: progressDirection === 'up' ? '#ECFDF3' : '#FEF3F2',
|
||||
'.MuiChip-icon': {
|
||||
color: progressDirection === 'up' ? '#087443' : '#B42318',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* دائرة التقدم */}
|
||||
<Box sx={{ height: 120, mb: 2, position: 'relative' }}>
|
||||
<Box sx={{ position: 'relative', width: '100%', maxWidth: 350, mx: 'auto' }}>
|
||||
<CircularProgressbar
|
||||
value={100}
|
||||
circleRatio={0.5}
|
||||
strokeWidth={5}
|
||||
styles={buildStyles({
|
||||
rotation: 0.75,
|
||||
strokeLinecap: 'round',
|
||||
trailColor: '#eee',
|
||||
pathColor: '#eee',
|
||||
})}
|
||||
/>
|
||||
<Box sx={{ position: 'absolute', top: -1.5, left: 0, width: '100%', height: '100%' }}>
|
||||
<CircularProgressbar
|
||||
value={progress}
|
||||
circleRatio={0.5}
|
||||
strokeWidth={6}
|
||||
styles={buildStyles({
|
||||
rotation: 0.75,
|
||||
strokeLinecap: 'round',
|
||||
trailColor: 'transparent',
|
||||
pathColor: theme.palette.primary.main,
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* دائرة التقدم */}
|
||||
<Box sx={{ position: 'relative', width: '100%', maxWidth: 250, mx: 'auto' }}>
|
||||
<CircularProgressbar
|
||||
value={100}
|
||||
circleRatio={0.5}
|
||||
strokeWidth={5}
|
||||
styles={buildStyles({
|
||||
rotation: 0.75,
|
||||
strokeLinecap: 'round',
|
||||
trailColor: '#eee',
|
||||
pathColor: '#eee',
|
||||
})}
|
||||
/>
|
||||
<Box sx={{ position: 'absolute', top: -1.5, left: 0, width: '100%', height: '100%' }}>
|
||||
<CircularProgressbar
|
||||
value={progress}
|
||||
circleRatio={0.5}
|
||||
strokeWidth={6}
|
||||
styles={buildStyles({
|
||||
rotation: 0.75,
|
||||
strokeLinecap: 'round',
|
||||
trailColor: 'transparent',
|
||||
pathColor: theme.palette.primary.main,
|
||||
textColor: 'transparent',
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
{/* النص داخل الدائرة */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '36%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: { xs: '20px', sm: '26px' },
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{`${progress}%`}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* النص داخل الدائرة */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '22%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: { xs: '18px', sm: '24px' },
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{`${progress}%`}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* الوصف */}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
mb: 1,
|
||||
mt: 5,
|
||||
color: '#667085',
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: highlightedDescription }}
|
||||
/>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
{/* الحقول الثلاثة (Completed / Canceled / Pending) */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: { xs: 1, sm: 2 },
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
ml: -1
|
||||
}}
|
||||
>
|
||||
{/* لكل قسم: */}
|
||||
{[{
|
||||
label: 'Completed',
|
||||
icon: <ArrowUpwardIcon color="success" fontSize="small" sx={{ mt: 0.2, ml: -1 }} />,
|
||||
value: completed
|
||||
}, {
|
||||
label: 'Canceled',
|
||||
icon: <ArrowDownwardIcon color="error" fontSize="small" sx={{ mt: 0.6, ml: -1 }} />,
|
||||
value: canceled
|
||||
}, {
|
||||
label: 'Pending',
|
||||
icon: <ArrowDownwardIcon color="warning" fontSize="small" sx={{ mt: 0.6, ml: -1 }} />,
|
||||
value: pending
|
||||
}].map(({ label, icon, value }, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
mb: { xs: 1, sm: 0 }
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: '#667085', fontSize: '12px', fontWeight: 500, mb: 1 }}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
{icon}
|
||||
<Typography sx={{ color: '#333843', fontSize: { xs: '14px', sm: '16px',md:'24px'}, fontWeight: 500 }}>
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
{/* الوصف */}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
mb: 1,
|
||||
mt: 10,
|
||||
color: '#667085',
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderStatusCard;
|
||||
|
||||
@@ -12,9 +12,6 @@ import {
|
||||
Button,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
// import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
||||
// import InventoryIcon from '@mui/icons-material/Inventory';
|
||||
// import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
|
||||
const RecentActivity = () => {
|
||||
// Sample data
|
||||
|
||||
@@ -1,60 +1,92 @@
|
||||
import React from 'react';
|
||||
import { Box, IconButton, Paper, Typography, useTheme, useMediaQuery } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
Box,
|
||||
IconButton,
|
||||
Paper,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
Area,
|
||||
AreaChart,
|
||||
CartesianGrid,
|
||||
defs,
|
||||
linearGradient,
|
||||
stop
|
||||
CartesianGrid
|
||||
} from 'recharts';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
// import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
|
||||
const data = [
|
||||
{ month: 'Jan', revenue: 4000, sales: 2400 },
|
||||
{ month: 'Feb', revenue: 3000, sales: 1398 },
|
||||
{ month: 'Mar', revenue: 2000, sales: 9800 },
|
||||
{ month: 'Apr', revenue: 2780, sales: 3908 },
|
||||
{ month: 'May', revenue: 1890, sales: 4800 },
|
||||
{ month: 'Jun', revenue: 2390, sales: 3800 },
|
||||
{ month: 'Jul', revenue: 3490, sales: 4300 },
|
||||
{ month: 'Aug', revenue: 5000, sales: 4000 },
|
||||
{ month: 'Sep', revenue: 4700, sales: 4200 },
|
||||
{ month: 'Oct', revenue: 5200, sales: 4600 },
|
||||
{ month: 'Nov', revenue: 4800, sales: 4400 },
|
||||
{ month: 'Dec', revenue: 5300, sales: 4800 },
|
||||
];
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
if (value >= 1000000) {
|
||||
return `$${(value / 1000000).toFixed(1)}M`;
|
||||
} else if (value >= 1000) {
|
||||
return `$${(value / 1000).toFixed(1)}K`;
|
||||
}
|
||||
return `$${value}`;
|
||||
};
|
||||
|
||||
const StatisticsCard = () => {
|
||||
const StatisticsCard = ({
|
||||
title = "Statistics",
|
||||
subtitle = "Delivery Times",
|
||||
data = [],
|
||||
dataKeys = [
|
||||
{ key: 'revenue', name: 'Revenue', color: '#E46A11' },
|
||||
{ key: 'sales', name: 'Sales', color: '#0182FC' }
|
||||
],
|
||||
xDataKey = 'month',
|
||||
valueFormatter = (value) => value,
|
||||
timeFrame = 'month',
|
||||
onTimeFrameChange,
|
||||
height // 👈 جديد
|
||||
}) => {
|
||||
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 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={{ width: '100%' }}>
|
||||
<Paper sx={{
|
||||
<Box sx={{ borderRadius: 2, width: '100%' }}>
|
||||
<Paper sx={{
|
||||
p: { xs: 1.5, sm: 2 },
|
||||
mb: { xs: 2, sm: 3 },
|
||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||
position: 'relative',
|
||||
borderRadius: 2 ,
|
||||
|
||||
mb: { xs: 2, sm: 2 },
|
||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: 2,
|
||||
position: 'relative'
|
||||
}}>
|
||||
<IconButton
|
||||
aria-label="more-actions"
|
||||
@@ -64,128 +96,115 @@ 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',
|
||||
flexDirection: 'column',
|
||||
mb: { xs: 1, sm: 2 },
|
||||
pr: { xs: 3, sm: 4 }
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
mb: 2,
|
||||
flexWrap: 'wrap',
|
||||
gap: 1
|
||||
}}>
|
||||
<Typography variant="h6" sx={{
|
||||
fontSize: { xs: '15px', sm: '18px' },
|
||||
fontWeight: 500
|
||||
}}>
|
||||
Statistics
|
||||
</Typography>
|
||||
<Typography sx={{
|
||||
fontSize: { xs: '11px', sm: '14px' },
|
||||
fontWeight: 500,
|
||||
color: '#667085'
|
||||
}}>
|
||||
Delivery Times
|
||||
</Typography>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontSize: { xs: '15px', sm: '18px' }, fontWeight: 500 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: { xs: '11px', sm: '14px' }, fontWeight: 500, color: '#667085' }}>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ResponsiveContainer width="100%" height={isMobile ? 250 : isTablet ? 290 : 295}>
|
||||
<AreaChart data={data} margin={{
|
||||
top: isMobile ? -30 : -45,
|
||||
right: isMobile ? 15 : 30,
|
||||
left: isMobile ? 10 : 20,
|
||||
bottom: isMobile ? 10 : 20
|
||||
{/* Chart */}
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
height={height || (isMobile ? 350 : isTablet ? 390 : 395)} // 👈 أولوية للـ prop
|
||||
>
|
||||
|
||||
<AreaChart data={data} margin={{
|
||||
top: isMobile ? -30 : -45,
|
||||
right: isMobile ? 15 : 30,
|
||||
left: isMobile ? 10 : 20,
|
||||
bottom: isMobile ? 10 : 20
|
||||
}}>
|
||||
<defs>
|
||||
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={theme.palette.primary.main} stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor={theme.palette.primary.main} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#81C2FF" stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor="#81C2FF" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
{dataKeys.map(({ key, color }) => (
|
||||
<linearGradient key={key} id={`color-${key}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={color} stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="0 0" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
dataKey={xDataKey}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickMargin={isMobile ? 8 : 15}
|
||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
||||
tick={{
|
||||
fontSize: isMobile ? 11 : 12,
|
||||
angle: -25,
|
||||
textAnchor: 'end'
|
||||
}}
|
||||
interval={0}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatCurrency}
|
||||
ticks={ticksArray}
|
||||
tickFormatter={valueFormatter}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickMargin={isMobile ? 8 : 15}
|
||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
||||
/>
|
||||
<Tooltip />
|
||||
|
||||
<Tooltip formatter={valueFormatter} />
|
||||
<Legend
|
||||
verticalAlign="top"
|
||||
height={isMobile ? 30 : 36}
|
||||
iconType="circle"
|
||||
iconSize={isMobile ? 8 : 10}
|
||||
wrapperStyle={{
|
||||
paddingBottom: isMobile ? '15px' : '20px'
|
||||
}}
|
||||
/>
|
||||
|
||||
<Area
|
||||
name="Revenue"
|
||||
type="monotone"
|
||||
dataKey="revenue"
|
||||
stroke='#E46A11'
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
fill="url(#colorRevenue)"
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
name="Sales"
|
||||
dataKey="sales"
|
||||
stroke="#0182FC"
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
fill="url(#colorSales)"
|
||||
/>
|
||||
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="revenue"
|
||||
stroke={theme.palette.primary.main}
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
dot={{
|
||||
r: isMobile ? 4 : 6,
|
||||
fill: theme.palette.primary.main,
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2
|
||||
}}
|
||||
activeDot={{
|
||||
r: isMobile ? 6 : 8,
|
||||
fill: theme.palette.primary.main,
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="sales"
|
||||
stroke="#0182FC"
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
dot={{
|
||||
r: isMobile ? 4 : 6,
|
||||
fill: "#0182FC",
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2
|
||||
}}
|
||||
activeDot={{
|
||||
r: isMobile ? 6 : 8,
|
||||
fill: "#0182FC",
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2
|
||||
}}
|
||||
wrapperStyle={{ paddingBottom: isMobile ? '15px' : '20px' }}
|
||||
/>
|
||||
{dataKeys.map(({ key, name, color }) => (
|
||||
<Area
|
||||
key={key}
|
||||
name={name}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={color}
|
||||
strokeWidth={isMobile ? 2 : 3}
|
||||
fill={`url(#color-${key})`}
|
||||
/>
|
||||
))}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Paper>
|
||||
@@ -193,4 +212,19 @@ const StatisticsCard = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticsCard;
|
||||
StatisticsCard.propTypes = {
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
data: PropTypes.arrayOf(PropTypes.object),
|
||||
dataKeys: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
})),
|
||||
xDataKey: PropTypes.string,
|
||||
valueFormatter: PropTypes.func,
|
||||
timeFrame: PropTypes.oneOf(['day', 'week', 'month', 'year']),
|
||||
onTimeFrameChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default StatisticsCard;
|
||||
|
||||
114
src/components/Home/Employ/Employ.js
Normal file
114
src/components/Home/Employ/Employ.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import Waiter from './contcet/waiter';
|
||||
import Cooker from './contcet/cooker';
|
||||
import Accountant from './contcet/Accountant';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Employ = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isMdUp = useMediaQuery(theme.breakpoints.up('md'));
|
||||
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
const { restaurantId } = useRestaurant();
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={() => setSidebarOpen(!sidebarOpen)}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
marginLeft: {
|
||||
xs: 0,
|
||||
sm: sidebarOpen ? `${drawerWidth}px` : 0,
|
||||
md: 0,
|
||||
},
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={() => setSidebarOpen(!sidebarOpen)}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||
pt: { xs: 2, md: 3 },
|
||||
pl: { xs: 1, sm: 3, md: 3 },
|
||||
pb: { xs: 1, md: 4 },
|
||||
pr: {
|
||||
xs: 2,
|
||||
md: 2,
|
||||
},
|
||||
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
transition: theme.transitions.create(['margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{/* المحتوى: Waiter و Cooker */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
width: { xs: '90%', sm: '95%', md: '100%' },
|
||||
mt: 0,
|
||||
}}
|
||||
>
|
||||
<Waiter restaurantId={restaurantId} />
|
||||
<Cooker restaurantId={restaurantId} />
|
||||
<Accountant restaurantId={restaurantId} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Employ;
|
||||
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
@@ -0,0 +1,526 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Button,
|
||||
Chip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import AddEmployModal from './AddEmployModal';
|
||||
import authService from '../../../../services/authService';
|
||||
import IOSSwitch from './IOSSwitch';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||
|
||||
const Accountant = ({ restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [switchStates, setSwitchStates] = useState({});
|
||||
const [shiftFilter, setShiftFilter] = useState('all');
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [Accountants, setAccountants] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||
const [AccountantDetails, setAccountantDetails] = useState(null);
|
||||
|
||||
|
||||
// حالات الحذف
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [selectedAccountant, setSelectedAccountant] = useState(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAccountants = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await authService.getAllAccountants(restaurantId);
|
||||
if (response.accountants) {
|
||||
setAccountants(response.accountants);
|
||||
|
||||
// تهيئة switchStates حسب قيمة active
|
||||
const initialStates = {};
|
||||
response.accountants.forEach(accountants => {
|
||||
initialStates[accountants.id] = accountants.active === 1; // أو Boolean(waiter.active)
|
||||
});
|
||||
setSwitchStates(initialStates);
|
||||
} else {
|
||||
setAccountants([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch waiters:", error);
|
||||
setAccountants([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAccountants();
|
||||
}, [restaurantId]);
|
||||
|
||||
const handleSwitchChange = async (id, email) => {
|
||||
try {
|
||||
const response = await authService.toggleAccountStatus(email, "Accountant");
|
||||
|
||||
if (response.success) {
|
||||
setSwitchStates((prev) => ({
|
||||
...prev,
|
||||
[id]: response.data,
|
||||
}));
|
||||
// alert(response.message);
|
||||
} else {
|
||||
alert("Failed: " + response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleOpenModal = () => setModalOpen(true);
|
||||
const handleCloseModal = () => setModalOpen(false);
|
||||
|
||||
const handleConfirm = async (formData) => {
|
||||
try {
|
||||
const AccountantData = {
|
||||
...formData,
|
||||
restaurant_id: restaurantId,
|
||||
};
|
||||
|
||||
const response = await authService.registerAccountant(AccountantData);
|
||||
|
||||
if (response.success) {
|
||||
alert('Accountant registered successfully!');
|
||||
setModalOpen(false);
|
||||
|
||||
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
|
||||
const newAccountant = response.Accountant || AccountantData;
|
||||
// تأكد أن الـ API يعيد الـ Accountant المضاف، أو استخدم البيانات المحلية
|
||||
setAccountants((prev) => [newAccountant, ...prev]);
|
||||
|
||||
} else {
|
||||
if (response.errors) {
|
||||
const errorsList = Object.entries(response.errors)
|
||||
.map(([field, msgs]) => {
|
||||
if (Array.isArray(msgs)) {
|
||||
return `${field}: ${msgs.join(', ')}`;
|
||||
} else {
|
||||
return `${field}: ${msgs}`;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
alert(`Failed to register Accountant due to validation errors:\n${errorsList}`);
|
||||
} else {
|
||||
alert('Failed to register Accountant: ' + response.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('An error occurred: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getShiftChipProps = (shiftType) => {
|
||||
switch (shiftType?.toLowerCase()) {
|
||||
case 'morning':
|
||||
return {
|
||||
label: 'Morning',
|
||||
sx: {
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: '#0D894F',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
case 'evening':
|
||||
return {
|
||||
label: 'Evening',
|
||||
sx: {
|
||||
backgroundColor: '#FDF1E8',
|
||||
color: '#E46A11',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: shiftType,
|
||||
sx: {
|
||||
backgroundColor: '#E0E0E0',
|
||||
color: '#424242',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const filteredAccountants =
|
||||
shiftFilter === 'all'
|
||||
? Accountants
|
||||
: Accountants.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||
|
||||
const displayedAccountants = showAll ? filteredAccountants : filteredAccountants.slice(0, 3);
|
||||
|
||||
const handleFilterClick = (event) => {
|
||||
setFilterAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleFilterClose = () => {
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleFilterSelect = (value) => {
|
||||
setShiftFilter(value);
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
// فتح مودال الحذف
|
||||
const handleDeleteClick = (Accountant) => {
|
||||
setSelectedAccountant(Accountant);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
// تأكيد الحذف
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (!selectedAccountant) return;
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const response = await authService.deleteAccountant(selectedAccountant.id);
|
||||
|
||||
// هنا تحقق من وجود message بدل success
|
||||
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||
// alert(response.message);
|
||||
setAccountants((prev) => prev.filter((w) => w.id !== selectedAccountant.id));
|
||||
} else {
|
||||
alert("Failed to delete Accountant: " + (response.message || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("An error occurred: " + error.message);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedAccountant(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenDetails = async (id) => {
|
||||
try {
|
||||
const response = await authService.getAccountantById(id);
|
||||
if (response.accountant) {
|
||||
setAccountantDetails(response.accountant);
|
||||
setDetailsModalOpen(true);
|
||||
} else {
|
||||
alert("Failed to fetch Accountant details");
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<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' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Accountant
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
}}
|
||||
>
|
||||
{Accountants.length > 3 && (
|
||||
<Button
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
textTransform: 'none',
|
||||
minWidth: 90,
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
{showAll ? 'See Less' : 'See All'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '135px' },
|
||||
textTransform: 'none',
|
||||
minWidth: { xs: 'unset', sm: '135px' },
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Add Accountant
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: { sm: 550, md: 650 },
|
||||
tableLayout: 'auto',
|
||||
}}
|
||||
aria-label="recent activity table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Email
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||
Shift Type
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Actions
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : displayedAccountants.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
No activities found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
displayedAccountants.map((Accountant) => (
|
||||
<TableRow key={Accountant.id}>
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "#296adaff",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 6 },
|
||||
wordBreak: "break-word",
|
||||
cursor: "pointer",
|
||||
"&:hover": { color: "#71a5ffff" },
|
||||
}}
|
||||
onClick={() => handleOpenDetails(Accountant.id)}
|
||||
>
|
||||
{Accountant.name}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#4F5867',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 3 },
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{Accountant.email}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||
<Chip {...getShiftChipProps(Accountant.shift_type)} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||
<IOSSwitch
|
||||
checked={!!switchStates[Accountant.id]}
|
||||
onChange={() => handleSwitchChange(Accountant.id, Accountant.email)}
|
||||
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||
/>
|
||||
<DeleteForeverIcon
|
||||
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||
onClick={() => handleDeleteClick(Accountant)}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<AddEmployModal
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirm}
|
||||
title="Add Accountant"
|
||||
buttonText="Add Accountant"
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={filterAnchorEl}
|
||||
open={Boolean(filterAnchorEl)}
|
||||
onClose={handleFilterClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: 'white',
|
||||
px: 1,
|
||||
position: 'relative',
|
||||
width: '120px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['all', 'morning', 'evening'].map((shift) => (
|
||||
<MenuItem
|
||||
key={shift}
|
||||
selected={shiftFilter === shift}
|
||||
onClick={() => handleFilterSelect(shift)}
|
||||
sx={{
|
||||
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0ff',
|
||||
transition: 'background-color 150ms ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* مودال تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{selectedAccountant?.name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleDeleteConfirm}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<EmployeeDetailsModal
|
||||
open={detailsModalOpen}
|
||||
onClose={() => setDetailsModalOpen(false)}
|
||||
employee={AccountantDetails}
|
||||
type="Accountant"
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Accountant;
|
||||
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
@@ -0,0 +1,330 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
TextField,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
useTheme,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import VisibilityOutlined from '@mui/icons-material/VisibilityOutlined';
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
|
||||
const AddUserModal = ({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title = 'Add User',
|
||||
buttonText = 'Add User',
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [formValues, setFormValues] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
shift_type: '',
|
||||
working_hours: '',
|
||||
monthly_salary: '',
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
const handleTogglePassword = () => setShowPassword((prev) => !prev);
|
||||
const handleToggleConfirmPassword = () => setShowConfirmPassword((prev) => !prev);
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
setFormValues((prev) => ({ ...prev, [key]: value }));
|
||||
setErrors((prev) => ({ ...prev, [key]: null })); // مسح الخطأ عند التعديل
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
let tempErrors = {};
|
||||
|
||||
if (!formValues.name.trim()) tempErrors.name = 'Name is required';
|
||||
|
||||
if (!formValues.email.trim()) tempErrors.email = 'Email is required';
|
||||
else {
|
||||
// Regex بسيط للتحقق من صحة الإيميل
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formValues.email))
|
||||
tempErrors.email = 'Invalid email format';
|
||||
}
|
||||
|
||||
if (!formValues.password) {
|
||||
tempErrors.password = 'Password is required';
|
||||
} else if (formValues.password.length < 6) {
|
||||
tempErrors.password = 'Password must be at least 6 characters long';
|
||||
}
|
||||
|
||||
if (!formValues.password_confirmation)
|
||||
tempErrors.password_confirmation = 'Confirm password is required';
|
||||
|
||||
if (
|
||||
formValues.password &&
|
||||
formValues.password_confirmation &&
|
||||
formValues.password !== formValues.password_confirmation
|
||||
)
|
||||
tempErrors.password_confirmation = 'Passwords do not match';
|
||||
|
||||
if (!formValues.shift_type) tempErrors.shift_type = 'Shift type is required';
|
||||
|
||||
if (!formValues.working_hours) tempErrors.working_hours = 'Working hours is required';
|
||||
|
||||
if (!formValues.monthly_salary) tempErrors.monthly_salary = 'Monthly salary is required';
|
||||
|
||||
setErrors(tempErrors);
|
||||
|
||||
return Object.keys(tempErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (validate()) {
|
||||
onConfirm(formValues);
|
||||
|
||||
// إعادة تعيين الحقول بعد النجاح
|
||||
setFormValues({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
shift_type: '',
|
||||
working_hours: '',
|
||||
monthly_salary: '',
|
||||
});
|
||||
|
||||
setErrors({}); // تصفير الأخطاء أيضًا
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 600, fontSize: '20px' }}>{title}</DialogTitle>
|
||||
<DialogContent
|
||||
dividers
|
||||
sx={{
|
||||
overflow: 'auto', // يسمح بالتمرير
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none', // يخفي الـ scrollbar على Webkit (Chrome, Edge, Safari)
|
||||
},
|
||||
scrollbarWidth: 'none', // يخفي scrollbar على Firefox
|
||||
msOverflowStyle: 'none', // يخفي scrollbar على IE 10+
|
||||
}}
|
||||
>
|
||||
{/* Name */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Name
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="John Doe"
|
||||
type="text"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formValues.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Email */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Email
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="example@example.com"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formValues.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Password */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Password
|
||||
</Typography>
|
||||
<TextField
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Enter password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
value={formValues.password}
|
||||
onChange={(e) => handleChange('password', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.password}
|
||||
helperText={errors.password}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleTogglePassword} edge="end">
|
||||
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Confirm Password */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Confirm Password
|
||||
</Typography>
|
||||
<TextField
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
placeholder="Confirm password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
value={formValues.password_confirmation}
|
||||
onChange={(e) => handleChange('password_confirmation', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.password_confirmation}
|
||||
helperText={errors.password_confirmation}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
||||
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Shift Type */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Shift Type
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formValues.shift_type}
|
||||
onChange={(e) => handleChange('shift_type', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.shift_type}
|
||||
helperText={errors.shift_type}
|
||||
placeholder="Select shift type"
|
||||
>
|
||||
<MenuItem value="morning">Morning</MenuItem>
|
||||
<MenuItem value="evening">Evening</MenuItem>
|
||||
</TextField>
|
||||
</Box>
|
||||
|
||||
{/* Working Hours */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Working Hours
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="12"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formValues.working_hours}
|
||||
onChange={(e) => handleChange('working_hours', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.working_hours}
|
||||
helperText={errors.working_hours}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Monthly Salary */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Monthly Salary
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="500"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formValues.monthly_salary}
|
||||
onChange={(e) => handleChange('monthly_salary', e.target.value)}
|
||||
sx={fieldStyle(theme)}
|
||||
error={!!errors.monthly_salary}
|
||||
helperText={errors.monthly_salary}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ pr: 3, pb: 2 }}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: '135px',
|
||||
textTransform: 'none',
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: '135px',
|
||||
textTransform: 'none',
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const fieldStyle = (theme) => ({
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& label': { fontWeight: 600, fontSize: '15px', color: 'black' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '8px',
|
||||
},
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: theme.palette.grey[400],
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
'& input::-ms-reveal, & input::-ms-clear': {
|
||||
display: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export default AddUserModal;
|
||||
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Typography,
|
||||
Button,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
|
||||
const EmployeeDetailsModal = ({ open, onClose, employee, type }) => {
|
||||
if (!employee) return null;
|
||||
|
||||
// دالة لتحويل الحالة الرقمية إلى نصية
|
||||
const getStatusText = (active) => (active ? "Active" : "Inactive");
|
||||
|
||||
// عنوان المودال حسب نوع الموظف
|
||||
const getTitle = () => {
|
||||
switch (type?.toLowerCase()) {
|
||||
case "waiter":
|
||||
return "Waiter Details";
|
||||
case "cooker":
|
||||
return "Cooker Details";
|
||||
case "accountant":
|
||||
return "Accountant Details";
|
||||
default:
|
||||
return "Employee Details";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{getTitle()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
{employee.name && <Typography><b>Name:</b> {employee.name}</Typography>}
|
||||
{employee.email && <Typography><b>Email:</b> {employee.email}</Typography>}
|
||||
{employee.shift_type && <Typography><b>Shift:</b> {employee.shift_type}</Typography>}
|
||||
{employee.working_hours !== undefined && (
|
||||
<Typography><b>Working Hours:</b> {employee.working_hours}</Typography>
|
||||
)}
|
||||
{employee.monthly_salary !== undefined && (
|
||||
<Typography><b>Monthly Salary:</b> {employee.monthly_salary}</Typography>
|
||||
)}
|
||||
{employee.active !== undefined && (
|
||||
<Typography><b>Status:</b> {getStatusText(employee.active)}</Typography>
|
||||
)}
|
||||
{employee.code && <Typography><b>Code:</b> {employee.code}</Typography>}
|
||||
{employee.expires_at && <Typography><b>Expires At:</b> {employee.expires_at}</Typography>}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outlined"
|
||||
sx={{ textTransform: "none", minWidth: { md: 120 } }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeDetailsModal;
|
||||
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Switch from '@mui/material/Switch';
|
||||
|
||||
const IOSSwitch = styled((props) => (
|
||||
<Switch focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} />
|
||||
))(({ theme }) => ({
|
||||
width: 42,
|
||||
height: 26,
|
||||
padding: 0,
|
||||
'& .MuiSwitch-switchBase': {
|
||||
padding: 0,
|
||||
margin: 2,
|
||||
transitionDuration: '300ms',
|
||||
'&.Mui-checked': {
|
||||
transform: 'translateX(16px)',
|
||||
color: '#fff',
|
||||
'& + .MuiSwitch-track': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
opacity: 1,
|
||||
border: 0,
|
||||
},
|
||||
'&.Mui-disabled + .MuiSwitch-track': {
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
'&.Mui-focusVisible .MuiSwitch-thumb': {
|
||||
color: theme.palette.primary.main,
|
||||
border: '6px solid #fff',
|
||||
},
|
||||
'&.Mui-disabled .MuiSwitch-thumb': {
|
||||
color:
|
||||
theme.palette.mode === 'light'
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[600],
|
||||
},
|
||||
'&.Mui-disabled + .MuiSwitch-track': {
|
||||
opacity: theme.palette.mode === 'light' ? 0.7 : 0.3,
|
||||
},
|
||||
},
|
||||
'& .MuiSwitch-thumb': {
|
||||
boxSizing: 'border-box',
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
},
|
||||
'& .MuiSwitch-track': {
|
||||
borderRadius: 26 / 2,
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'light' ? '#E9E9EA' : 'rgba(255,255,255,0.35)',
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(['background-color'], {
|
||||
duration: 500,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
export default IOSSwitch;
|
||||
526
src/components/Home/Employ/contcet/cooker.js
Normal file
526
src/components/Home/Employ/contcet/cooker.js
Normal file
@@ -0,0 +1,526 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Button,
|
||||
Chip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import AddEmployModal from './AddEmployModal';
|
||||
import authService from '../../../../services/authService';
|
||||
import IOSSwitch from './IOSSwitch';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||
|
||||
const Cooker = ({ restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [switchStates, setSwitchStates] = useState({});
|
||||
const [shiftFilter, setShiftFilter] = useState('all');
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [Cookers, setCookers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||
const [CookerDetails, setCookerDetails] = useState(null);
|
||||
|
||||
|
||||
// حالات الحذف
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [selectedCooker, setSelectedCooker] = useState(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// استدعاء API
|
||||
useEffect(() => {
|
||||
const fetchCookers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await authService.getAllCookers(restaurantId);
|
||||
if (response.cookers) {
|
||||
setCookers(response.cookers);
|
||||
|
||||
const initialStates = {};
|
||||
response.cookers.forEach(cookers => {
|
||||
initialStates[cookers.id] = cookers.active === 1; // أو Boolean(waiter.active)
|
||||
});
|
||||
setSwitchStates(initialStates);
|
||||
} else {
|
||||
setCookers([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch Cookers:", error);
|
||||
setCookers([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCookers();
|
||||
}, [restaurantId]);
|
||||
|
||||
const handleSwitchChange = async (id, email) => {
|
||||
try {
|
||||
const response = await authService.toggleAccountStatus(email, "Cooker");
|
||||
|
||||
if (response.success) {
|
||||
setSwitchStates((prev) => ({
|
||||
...prev,
|
||||
[id]: response.data,
|
||||
}));
|
||||
// alert(response.message);
|
||||
} else {
|
||||
alert("Failed: " + response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenModal = () => setModalOpen(true);
|
||||
const handleCloseModal = () => setModalOpen(false);
|
||||
|
||||
const handleConfirm = async (formData) => {
|
||||
try {
|
||||
const CookerData = {
|
||||
...formData,
|
||||
restaurant_id: restaurantId,
|
||||
};
|
||||
|
||||
const response = await authService.registerCooker(CookerData);
|
||||
|
||||
if (response.success) {
|
||||
alert('Cooker registered successfully!');
|
||||
setModalOpen(false);
|
||||
|
||||
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
|
||||
const newCooker = response.Cooker || CookerData;
|
||||
// تأكد أن الـ API يعيد الـ Cooker المضاف، أو استخدم البيانات المحلية
|
||||
setCookers((prev) => [newCooker, ...prev]);
|
||||
|
||||
} else {
|
||||
if (response.errors) {
|
||||
const errorsList = Object.entries(response.errors)
|
||||
.map(([field, msgs]) => {
|
||||
if (Array.isArray(msgs)) {
|
||||
return `${field}: ${msgs.join(', ')}`;
|
||||
} else {
|
||||
return `${field}: ${msgs}`;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
alert(`Failed to register Cooker due to validation errors:\n${errorsList}`);
|
||||
} else {
|
||||
alert('Failed to register Cooker: ' + response.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('An error occurred: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getShiftChipProps = (shiftType) => {
|
||||
switch (shiftType?.toLowerCase()) {
|
||||
case 'morning':
|
||||
return {
|
||||
label: 'Morning',
|
||||
sx: {
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: '#0D894F',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
case 'evening':
|
||||
return {
|
||||
label: 'Evening',
|
||||
sx: {
|
||||
backgroundColor: '#FDF1E8',
|
||||
color: '#E46A11',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: shiftType,
|
||||
sx: {
|
||||
backgroundColor: '#E0E0E0',
|
||||
color: '#424242',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const filteredCookers =
|
||||
shiftFilter === 'all'
|
||||
? Cookers
|
||||
: Cookers.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||
|
||||
const displayedCookers = showAll ? filteredCookers : filteredCookers.slice(0, 3);
|
||||
|
||||
const handleFilterClick = (event) => {
|
||||
setFilterAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleFilterClose = () => {
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleFilterSelect = (value) => {
|
||||
setShiftFilter(value);
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
// فتح مودال الحذف
|
||||
const handleDeleteClick = (Cooker) => {
|
||||
setSelectedCooker(Cooker);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
// تأكيد الحذف
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (!selectedCooker) return;
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const response = await authService.deleteCooker(selectedCooker.id);
|
||||
|
||||
// هنا تحقق من وجود message بدل success
|
||||
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||
// alert(response.message);
|
||||
setCookers((prev) => prev.filter((w) => w.id !== selectedCooker.id));
|
||||
} else {
|
||||
alert("Failed to delete Cooker: " + (response.message || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("An error occurred: " + error.message);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedCooker(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenDetails = async (id) => {
|
||||
try {
|
||||
const response = await authService.getCookerById(id);
|
||||
if (response.cooker) {
|
||||
setCookerDetails(response.cooker);
|
||||
setDetailsModalOpen(true);
|
||||
} else {
|
||||
alert("Failed to fetch Cooker details");
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<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' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Cooker
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
}}
|
||||
>
|
||||
{Cookers.length > 3 && (
|
||||
<Button
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
textTransform: 'none',
|
||||
minWidth: 90,
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
{showAll ? 'See Less' : 'See All'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '135px' },
|
||||
textTransform: 'none',
|
||||
minWidth: { xs: 'unset', sm: '135px' },
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Add Cooker
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: { sm: 550, md: 650 },
|
||||
tableLayout: 'auto',
|
||||
}}
|
||||
aria-label="recent activity table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Email
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||
Shift Type
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Actions
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : displayedCookers.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
No activities found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
displayedCookers.map((Cooker) => (
|
||||
<TableRow key={Cooker.id}>
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "#296adaff",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 6 },
|
||||
wordBreak: "break-word",
|
||||
cursor: "pointer",
|
||||
"&:hover": { color: "#71a5ffff" },
|
||||
}}
|
||||
onClick={() => handleOpenDetails(Cooker.id)}
|
||||
>
|
||||
{Cooker.name}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#4F5867',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 3 },
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{Cooker.email}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||
<Chip {...getShiftChipProps(Cooker.shift_type)} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||
<IOSSwitch
|
||||
checked={!!switchStates[Cooker.id]}
|
||||
onChange={() => handleSwitchChange(Cooker.id, Cooker.email)}
|
||||
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||
/>
|
||||
|
||||
<DeleteForeverIcon
|
||||
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||
onClick={() => handleDeleteClick(Cooker)}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<AddEmployModal
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirm}
|
||||
title="Add Cooker"
|
||||
buttonText="Add Cooker"
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={filterAnchorEl}
|
||||
open={Boolean(filterAnchorEl)}
|
||||
onClose={handleFilterClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: 'white',
|
||||
px: 1,
|
||||
position: 'relative',
|
||||
width: '120px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['all', 'morning', 'evening'].map((shift) => (
|
||||
<MenuItem
|
||||
key={shift}
|
||||
selected={shiftFilter === shift}
|
||||
onClick={() => handleFilterSelect(shift)}
|
||||
sx={{
|
||||
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0ff',
|
||||
transition: 'background-color 150ms ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* مودال تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{selectedCooker?.name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleDeleteConfirm}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<EmployeeDetailsModal
|
||||
open={detailsModalOpen}
|
||||
onClose={() => setDetailsModalOpen(false)}
|
||||
employee={CookerDetails}
|
||||
type="Cooker"
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cooker;
|
||||
519
src/components/Home/Employ/contcet/waiter.js
Normal file
519
src/components/Home/Employ/contcet/waiter.js
Normal file
@@ -0,0 +1,519 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Button,
|
||||
Chip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import AddEmployModal from './AddEmployModal';
|
||||
import authService from '../../../../services/authService';
|
||||
import IOSSwitch from './IOSSwitch';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||
|
||||
const Waiter = ({ restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [switchStates, setSwitchStates] = useState({});
|
||||
const [shiftFilter, setShiftFilter] = useState('all');
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [waiters, setWaiters] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||
const [waiterDetails, setWaiterDetails] = useState(null);
|
||||
|
||||
|
||||
// حالات الحذف
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [selectedWaiter, setSelectedWaiter] = useState(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// استدعاء API
|
||||
useEffect(() => {
|
||||
const fetchWaiters = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await authService.getAllWaiters(restaurantId);
|
||||
if (response.waiters) {
|
||||
setWaiters(response.waiters);
|
||||
|
||||
// تهيئة switchStates حسب قيمة active
|
||||
const initialStates = {};
|
||||
response.waiters.forEach(waiter => {
|
||||
initialStates[waiter.id] = waiter.active === 1; // أو Boolean(waiter.active)
|
||||
});
|
||||
setSwitchStates(initialStates);
|
||||
} else {
|
||||
setWaiters([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch waiters:", error);
|
||||
setWaiters([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchWaiters();
|
||||
}, [restaurantId]);
|
||||
|
||||
|
||||
const handleSwitchChange = async (id, email) => {
|
||||
try {
|
||||
const response = await authService.toggleAccountStatus(email, "Waiter");
|
||||
|
||||
if (response.success) {
|
||||
setSwitchStates((prev) => ({
|
||||
...prev,
|
||||
[id]: response.data,
|
||||
}));
|
||||
// alert(response.message);
|
||||
} else {
|
||||
alert("Failed: " + response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleOpenModal = () => setModalOpen(true);
|
||||
const handleCloseModal = () => setModalOpen(false);
|
||||
|
||||
const handleConfirm = async (formData) => {
|
||||
try {
|
||||
const waiterData = {
|
||||
...formData,
|
||||
restaurant_id: restaurantId,
|
||||
};
|
||||
|
||||
const response = await authService.registerWaiter(waiterData);
|
||||
|
||||
if (response.success) {
|
||||
alert('Waiter registered successfully!');
|
||||
setModalOpen(false);
|
||||
// إعادة التحميل بعد الإضافة
|
||||
const updated = await authService.getAllWaiters(restaurantId);
|
||||
if (updated.waiters) setWaiters(updated.waiters);
|
||||
} else {
|
||||
if (response.errors) {
|
||||
const errorsList = Object.entries(response.errors)
|
||||
.map(([field, msgs]) => `${field}: ${msgs.join(', ')}`)
|
||||
.join('\n');
|
||||
alert(`Failed to register waiter due to validation errors:\n${errorsList}`);
|
||||
} else {
|
||||
alert('Failed to register waiter: ' + response.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('An error occurred: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const getShiftChipProps = (shiftType) => {
|
||||
switch (shiftType?.toLowerCase()) {
|
||||
case 'morning':
|
||||
return {
|
||||
label: 'Morning',
|
||||
sx: {
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: '#0D894F',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
case 'evening':
|
||||
return {
|
||||
label: 'Evening',
|
||||
sx: {
|
||||
backgroundColor: '#FDF1E8',
|
||||
color: '#E46A11',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: shiftType,
|
||||
sx: {
|
||||
backgroundColor: '#E0E0E0',
|
||||
color: '#424242',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const filteredWaiters =
|
||||
shiftFilter === 'all'
|
||||
? waiters
|
||||
: waiters.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||
|
||||
const displayedWaiters = showAll ? filteredWaiters : filteredWaiters.slice(0, 3);
|
||||
|
||||
const handleFilterClick = (event) => {
|
||||
setFilterAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleFilterClose = () => {
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleFilterSelect = (value) => {
|
||||
setShiftFilter(value);
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
// فتح مودال الحذف
|
||||
const handleDeleteClick = (waiter) => {
|
||||
setSelectedWaiter(waiter);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
// تأكيد الحذف
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (!selectedWaiter) return;
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const response = await authService.deleteWaiter(selectedWaiter.id);
|
||||
|
||||
// هنا تحقق من وجود message بدل success
|
||||
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||
// alert(response.message);
|
||||
setWaiters((prev) => prev.filter((w) => w.id !== selectedWaiter.id));
|
||||
} else {
|
||||
alert("Failed to delete waiter: " + (response.message || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("An error occurred: " + error.message);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedWaiter(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenDetails = async (id) => {
|
||||
try {
|
||||
const response = await authService.getWaiterById(id);
|
||||
if (response.waiter) {
|
||||
setWaiterDetails(response.waiter);
|
||||
setDetailsModalOpen(true);
|
||||
} else {
|
||||
alert("Failed to fetch waiter details");
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<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' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Waiter
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
}}
|
||||
>
|
||||
{waiters.length > 3 && (
|
||||
<Button
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
textTransform: 'none',
|
||||
minWidth: 90,
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
{showAll ? 'See Less' : 'See All'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '135px' },
|
||||
textTransform: 'none',
|
||||
minWidth: { xs: 'unset', sm: '135px' },
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Add Waiter
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: { sm: 550, md: 650 },
|
||||
tableLayout: 'auto',
|
||||
}}
|
||||
aria-label="recent activity table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Email
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||
Shift Type
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Actions
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : displayedWaiters.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
No activities found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
displayedWaiters.map((waiter) => (
|
||||
<TableRow key={waiter.id}>
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "#296adaff",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 6 },
|
||||
wordBreak: "break-word",
|
||||
cursor: "pointer",
|
||||
"&:hover": { color: "#71a5ffff" },
|
||||
}}
|
||||
onClick={() => handleOpenDetails(waiter.id)}
|
||||
>
|
||||
{waiter.name}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#4F5867',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 3 },
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{waiter.email}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||
<Chip {...getShiftChipProps(waiter.shift_type)} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||
<IOSSwitch
|
||||
checked={!!switchStates[waiter.id]}
|
||||
onChange={() => handleSwitchChange(waiter.id, waiter.email)}
|
||||
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||
/>
|
||||
|
||||
<DeleteForeverIcon
|
||||
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||
onClick={() => handleDeleteClick(waiter)}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<AddEmployModal
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirm}
|
||||
title="Add Waiter"
|
||||
buttonText="Add Waiter"
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={filterAnchorEl}
|
||||
open={Boolean(filterAnchorEl)}
|
||||
onClose={handleFilterClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: 'white',
|
||||
px: 1,
|
||||
position: 'relative',
|
||||
width: '120px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['all', 'morning', 'evening'].map((shift) => (
|
||||
<MenuItem
|
||||
key={shift}
|
||||
selected={shiftFilter === shift}
|
||||
onClick={() => handleFilterSelect(shift)}
|
||||
sx={{
|
||||
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0ff',
|
||||
transition: 'background-color 150ms ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* مودال تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{selectedWaiter?.name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleDeleteConfirm}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<EmployeeDetailsModal
|
||||
open={detailsModalOpen}
|
||||
onClose={() => setDetailsModalOpen(false)}
|
||||
employee={waiterDetails} // بدل waiter
|
||||
type="waiter"
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Waiter;
|
||||
@@ -1,183 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import PostSubmission from './contcet/PostSubmission';
|
||||
import CloudKitchenHosting from './contcet/CloudKitchenHosting';
|
||||
import Infrastructure from './contcet/Infrastructure';
|
||||
import RstaurantOperations from './contcet/RstaurantOperations';
|
||||
import Facilitation from './contcet/Facilitation';
|
||||
import Expansion from './contcet/Expansion';
|
||||
|
||||
import SideProfile from './SideProfile';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const HostKitchen = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [hasProducts, setHasProducts] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// ⬇️ إدارة الخطوة الحالية
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
||||
const steps = [
|
||||
<CloudKitchenHosting onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<RstaurantOperations onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<Infrastructure onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<Facilitation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<Expansion onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
};
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
{/* <Box sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 20px)' : '100%',
|
||||
pt: { xs: 0.5, sm: 1 },
|
||||
mt: { xs: 1, sm: 2 },
|
||||
overflowY: 'auto',
|
||||
pl: { xs: 0, sm: 2 },
|
||||
pr: { xs: 1, sm: 2 },
|
||||
pb: { xs: 1, sm: 2 },
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
transition: theme.transitions.create(['margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
<PostSubmission />
|
||||
</Box> */}
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex', height: '100vh',
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
<Box sx={{
|
||||
height: '100vh',
|
||||
ml: 3,
|
||||
mb: 2,
|
||||
width: { xs: '30%', md: '30%' },
|
||||
display: { xs: 'none', sm: 'block', md: 'block' },
|
||||
// overflowY: 'auto',
|
||||
// scrollbarWidth: 'none',
|
||||
// '&::-webkit-scrollbar': {
|
||||
// display: 'none',
|
||||
// },
|
||||
}}>
|
||||
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 15, pt: 3,
|
||||
|
||||
}}>
|
||||
<SideProfile
|
||||
currentStepIndex={currentStep}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
ml: { xs: 1, md: 3 },
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
pr: { sm: 2, md: 1 },
|
||||
pt: 3,
|
||||
mb: { sm: 20 },
|
||||
width: { xs: '50%', md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
||||
// overflowY: 'auto',
|
||||
// scrollbarWidth: 'none',
|
||||
// '&::-webkit-scrollbar': {
|
||||
// display: 'none',
|
||||
// },
|
||||
}}>
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 18,
|
||||
|
||||
}}>
|
||||
{steps[currentStep]}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostKitchen;
|
||||
@@ -1,135 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
|
||||
const CloudKitchenHosting = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: 5,
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Cloud Kitchen Hosting
|
||||
</Typography>
|
||||
<Box sx={{ width: '80%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
||||
{ label: 'Restaurant Location', placeholder: 'Street 123, Jordan' },
|
||||
{ label: 'Availabel Space', placeholder: '300 sq. feet' },
|
||||
{ label: 'Number of Employees', placeholder: '200' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudKitchenHosting;
|
||||
@@ -1,233 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
} from '@mui/material';
|
||||
import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
|
||||
|
||||
const Expansion = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
const handleOpenModal = () => setOpenModal(true);
|
||||
const handleCloseModal = () => setOpenModal(false);
|
||||
const handleConfirmNext = () => {
|
||||
handleCloseModal();
|
||||
onNext();
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: 5,
|
||||
pb: 23.2,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Expansion & Future Cooperation
|
||||
</Typography>
|
||||
<Box sx={{ width: '80%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1.5, color: '#191635' }}
|
||||
>
|
||||
Hosting Multiple Cloud Kitchens
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Future Kitchen Plans', placeholder: '20' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 3 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1.5, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Partnership & Support
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* ✅ Confirmation Modal */}
|
||||
<ConfirmationDialog
|
||||
open={openModal}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirmNext}
|
||||
title="Confirm Submission"
|
||||
description="Are you sure you want to proceed to the next step?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Expansion;
|
||||
@@ -1,260 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
} from '@mui/material';
|
||||
|
||||
const Facilitation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: 5,
|
||||
pb: 5.8,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Facilitation & Cooperation
|
||||
</Typography>
|
||||
<Box sx={{ width: '80%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, color: '#191635' }}
|
||||
>
|
||||
Additional Services Provided
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Additional Services Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Additional Services
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={`1. Marketing\n2. Logistics\n3. Staff Training`}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={4}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
whiteSpace: 'pre-line', // لعرض الأسطر بشكل صحيح
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
<Box sx={{}}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, color: '#191635' }}
|
||||
>
|
||||
Ventilation and Cooling System
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Working Hours', placeholder: '8 hours' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
|
||||
<Box sx={{ pt: 1 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Facilitation;
|
||||
@@ -1,214 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
} from '@mui/material';
|
||||
|
||||
const Infrastructure = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: 5,
|
||||
pb: 11,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Infrastructure & Equipments
|
||||
</Typography>
|
||||
<Box sx={{ width: '80%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Current Equipment', placeholder: 'oven, refrgerators' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
|
||||
<Box sx={{}}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||
>
|
||||
Additional Facilities Needed
|
||||
</Typography>
|
||||
<RadioGroup row name="additionalFacilities">
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'rgba(150, 155, 167, 0.6)',
|
||||
transform: 'scale(0.85)',
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Description Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Description
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="We need Pizza Machine"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={5}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ pt: 2.8 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Infrastructure;
|
||||
@@ -1,230 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Card, CardContent, Stack, Typography, useMediaQuery } from '@mui/material';
|
||||
import AirportShuttleIcon from '@mui/icons-material/AirportShuttle';
|
||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import Inventory2Icon from '@mui/icons-material/Inventory2';
|
||||
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
|
||||
const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) => (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: { xs: 40, sm: 45, md: 50 },
|
||||
height: { xs: 43, sm: 48, md: 53 },
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: { xs: 32, sm: 36, md: 40 },
|
||||
height: { xs: 35, sm: 39, md: 43 },
|
||||
borderRadius: '50%',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
backgroundColor: outerColor,
|
||||
zIndex: 0,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: 24, sm: 27, md: 30 },
|
||||
height: { xs: 27, sm: 30, md: 33 },
|
||||
backgroundColor: bgColor,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const StatusCard = ({
|
||||
icon,
|
||||
statusText,
|
||||
statusColor,
|
||||
iconColor,
|
||||
outerColor,
|
||||
innerColor,
|
||||
height = {
|
||||
xs: 'calc(140px + 0.5vh)',
|
||||
sm: 'calc(150px + 0.5vh)',
|
||||
md: 'calc(162px + 0.5vh)'
|
||||
},
|
||||
width = {
|
||||
xs: 'min(90%, 600px)', // تقليل من 90% إلى 85% والحد الأقصى من 300px إلى 280px
|
||||
sm: 'clamp(220px, 23vw, 280px)', // تقليل جميع القيم
|
||||
md: 'clamp(220px, 19vw, 300px)'
|
||||
},
|
||||
transition = 'width 0.3s ease',
|
||||
iconSpacing = { xs: 6, sm: 8, md: 12 },
|
||||
extraButton,
|
||||
statusButtonWidth,
|
||||
title = 'Point Of Sale',
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<Card sx={{
|
||||
height,
|
||||
width,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
p: 0,
|
||||
pb: 2,
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'none',
|
||||
minWidth: { xs: 180, sm: 220 }
|
||||
}}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: iconSpacing }}>
|
||||
<IconCircle bgColor={innerColor} outerColor={outerColor}>
|
||||
{React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
|
||||
</IconCircle>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: statusColor,
|
||||
textTransform: 'none',
|
||||
boxShadow: 'none',
|
||||
borderRadius: '100px',
|
||||
width: statusButtonWidth || 'auto',
|
||||
fontSize: { xs: '0.7rem', sm: '0.8rem', md: '0.9rem' },
|
||||
px: { xs: 1, sm: 1.5 },
|
||||
py: 0.5
|
||||
}}
|
||||
>
|
||||
{statusText}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
mt={1.5}
|
||||
sx={{
|
||||
color: '#667085',
|
||||
fontSize: { xs: '14px', sm: '15px', md: '16px' },
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
||||
{extraButton ? extraButton : <Box />}
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#F7F7F7',
|
||||
width: { xs: '26px', sm: '28px', md: '30px' },
|
||||
height: { xs: '26px', sm: '28px', md: '30px' },
|
||||
minWidth: '0',
|
||||
padding: 0,
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: '#e0e0e0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ArrowOutwardIcon
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
width: { xs: '16px', sm: '18px', md: '20px' },
|
||||
height: { xs: '16px', sm: '18px', md: '20px' },
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const PostSubmission = () => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Header Section */}
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 3 },
|
||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||
gap: { xs: 2, sm: 0 }
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: '500',
|
||||
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
||||
color: '#121212'
|
||||
}}
|
||||
>
|
||||
Post Submission
|
||||
</Typography>
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 1, sm: 2 },
|
||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
justifyContent: { xs: 'space-between', sm: 'space-between', md: 'flex-end' }
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
boxShadow: 'none',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '48%', sm: '130px', md: '100px' },
|
||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset'
|
||||
}}
|
||||
>
|
||||
|
||||
{!isSmallScreen && 'Interested'}
|
||||
</Button>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostSubmission;
|
||||
@@ -1,135 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
|
||||
const CloudKitchenHosting = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: 5,
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Rstaurant Operations
|
||||
</Typography>
|
||||
<Box sx={{ width: '80%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Monthly Sales Value', placeholder: '$400' },
|
||||
{ label: 'Operating Size (Customers/Day)', placeholder: '20' },
|
||||
{ label: 'Peak Hours', placeholder:'Breakfast' },
|
||||
{ label: 'Preferred Cuisine to Host', placeholder: 'Fast Food' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudKitchenHosting;
|
||||
@@ -2,8 +2,8 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import ProductEntry from './ProductEntry';
|
||||
import TableView from './TableView';
|
||||
import ProductEntry from './contect/ProductEntry';
|
||||
import TableView from './contect/TableView';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
@@ -53,9 +53,9 @@ const Inventory = () => {
|
||||
status: newProduct.consumptionStatus ? 'Published' : 'Draft',
|
||||
expiration: newProduct.expirationDate
|
||||
? `${Math.ceil(
|
||||
(new Date(newProduct.expirationDate) - new Date()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
)} Days Left`
|
||||
(new Date(newProduct.expirationDate) - new Date()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
)} Days Left`
|
||||
: 'N/A',
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const initialProducts = [
|
||||
|
||||
const InventoryTablePage = () => {
|
||||
const [products, setProducts] = useState(initialProducts);
|
||||
|
||||
|
||||
const handleAddNewProduct = () => {
|
||||
alert('Navigate to Add Product page or open form here');
|
||||
};
|
||||
@@ -416,9 +416,9 @@ const ProductEntry = ({ onAdd, onShowTable }) => {
|
||||
color: theme.palette.primary.main,
|
||||
borderColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
borderColor: theme.palette.primary.dark,
|
||||
backgroundColor: '#FFECE0',
|
||||
color: theme.palette.primary.main,
|
||||
borderColor: '#FFECE0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
206
src/components/Home/Meal/Meal.js
Normal file
206
src/components/Home/Meal/Meal.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery, Typography } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import AccountSettings from './contect/AccountSettings';
|
||||
import MealsByCateg from './contect/MealsByCateg';
|
||||
import AllMeals from './contect/AllMeals';
|
||||
import ProductDetail from './contect/ProductDetail';
|
||||
import authService from '../../../services/authService';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Meal = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
const { restaurantId } = useRestaurant();
|
||||
|
||||
const [showAllPopular, setShowAllPopular] = useState(false);
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
|
||||
const [categoryName, setCategoryName] = useState('');
|
||||
const [selectedProduct, setSelectedProduct] = useState(null);
|
||||
const [meals, setMeals] = useState([]);
|
||||
const [loadingMeals, setLoadingMeals] = useState(false);
|
||||
|
||||
// جلب الفئات عند التحميل
|
||||
useEffect(() => {
|
||||
const fetchCategories = async () => {
|
||||
if (!restaurantId) return;
|
||||
const result = await authService.getCategoriesByRestaurant(restaurantId);
|
||||
if (result.success) setCategories(result.data);
|
||||
else setCategories([]);
|
||||
};
|
||||
fetchCategories();
|
||||
}, [restaurantId]);
|
||||
|
||||
// تحديث اسم الفئة عند تغيير selectedCategoryId أو categories
|
||||
useEffect(() => {
|
||||
if (selectedCategoryId && categories.length > 0) {
|
||||
const selected = categories.find(c => c.id === selectedCategoryId);
|
||||
setCategoryName(selected ? selected.name : '');
|
||||
} else {
|
||||
setCategoryName('');
|
||||
}
|
||||
}, [selectedCategoryId, categories]);
|
||||
|
||||
// جلب الوجبات عند اختيار الفئة
|
||||
useEffect(() => {
|
||||
const fetchMeals = async () => {
|
||||
if (selectedCategoryId && restaurantId) {
|
||||
setLoadingMeals(true);
|
||||
try {
|
||||
const result = await authService.getMealsByCategory(restaurantId, selectedCategoryId);
|
||||
if (result.success) {
|
||||
const selectedCategory = categories.find(c => c.id === selectedCategoryId);
|
||||
const mealsWithImages = result.data.map(meal => ({
|
||||
id: meal.id,
|
||||
name: meal.name,
|
||||
price: meal.price,
|
||||
unit: meal.unit || '/pcs',
|
||||
image: meal.photo || '/images/default-product.png',
|
||||
category: selectedCategory ? selectedCategory.name : '', // اسم الفئة
|
||||
category_id: meal.category_id,
|
||||
restaurant_id: meal.restaurant_id,
|
||||
description: meal.description || [],
|
||||
additions: meal.additions || []
|
||||
}));
|
||||
setMeals(mealsWithImages);
|
||||
} else setMeals([]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setMeals([]);
|
||||
} finally {
|
||||
setLoadingMeals(false);
|
||||
}
|
||||
} else setMeals([]);
|
||||
};
|
||||
fetchMeals();
|
||||
}, [selectedCategoryId, restaurantId, categories]);
|
||||
|
||||
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||
const toggleShowAllPopular = () => setShowAllPopular(prev => !prev);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '10%',
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||
pt: { xs: 2, sm: 3 },
|
||||
overflowY: 'auto',
|
||||
pl: { xs: 1, sm: 2 },
|
||||
pr: { xs: 1, sm: 2 },
|
||||
md: 10,
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
{selectedProduct ? (
|
||||
<ProductDetail
|
||||
product={selectedProduct}
|
||||
onBack={() => setSelectedProduct(null)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AccountSettings
|
||||
selectedCategory={selectedCategoryId}
|
||||
setSelectedCategory={setSelectedCategoryId}
|
||||
restaurantId={restaurantId}
|
||||
/>
|
||||
|
||||
{selectedCategoryId ? (
|
||||
<Box sx={{ mt: 3, md: 10, }}>
|
||||
|
||||
<MealsByCateg
|
||||
categoryId={selectedCategoryId}
|
||||
restaurantId={restaurantId}
|
||||
categoryName={categoryName}
|
||||
onBack={() => setSelectedCategoryId(null)}
|
||||
onProductClick={(meal) => {
|
||||
const category = categories.find(c => c.id === meal.category_id);
|
||||
setSelectedProduct({
|
||||
...meal,
|
||||
category: category ? category.name : '',
|
||||
category_id: meal.category_id,
|
||||
restaurant_id: restaurantId
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
) : !showAllPopular ? (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 3,
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', md: 'row' },
|
||||
alignItems: 'stretch',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexBasis: { xs: '100%', md: '100%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
pb: { xs: 4, md: 4 },
|
||||
pr: { xs: 1, md: 0 },
|
||||
mb: 5,
|
||||
}}
|
||||
>
|
||||
<AllMeals
|
||||
restaurantId={restaurantId}
|
||||
categories={categories}
|
||||
showAll={showAllPopular}
|
||||
onToggleShowAll={toggleShowAllPopular}
|
||||
onProductClick={(meal) => {
|
||||
const category = categories.find(c => c.id === meal.category_id);
|
||||
setSelectedProduct({
|
||||
...meal,
|
||||
category: category ? category.name : '',
|
||||
category_id: meal.category_id,
|
||||
restaurant_id: restaurantId
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Meal;
|
||||
330
src/components/Home/Meal/contect/AccountSettings.js
Normal file
330
src/components/Home/Meal/contect/AccountSettings.js
Normal file
@@ -0,0 +1,330 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Skeleton,
|
||||
} from '@mui/material';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import CategoryScrollList from './CategoryScrollList';
|
||||
import AddCategory from './AddCategory';
|
||||
import authService from '../../../../services/authService';
|
||||
|
||||
const AccountSettings = ({ selectedCategory, setSelectedCategory, restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [loading, setLoading] = useState(true); // <-- حالة التحميل
|
||||
const [selectedCategoryForEdit, setSelectedCategoryForEdit] = useState(null);
|
||||
const [contextMenu, setContextMenu] = useState(null);
|
||||
const [openAddModal, setOpenAddModal] = useState(false);
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
|
||||
const scrollRef = useRef();
|
||||
|
||||
// تحميل الفئات عند فتح الصفحة
|
||||
useEffect(() => {
|
||||
const fetchCategories = async () => {
|
||||
setLoading(true); // بدء التحميل
|
||||
const result = await authService.getCategoriesByRestaurant(restaurantId);
|
||||
if (result.success) setCategories(result.data);
|
||||
else console.error(result.message);
|
||||
setLoading(false); // انتهاء التحميل
|
||||
};
|
||||
if (restaurantId) fetchCategories();
|
||||
}, [restaurantId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCategory) localStorage.setItem('selectedCategoryId', selectedCategory);
|
||||
}, [selectedCategory]);
|
||||
|
||||
const scroll = (offset) => {
|
||||
if (scrollRef.current) scrollRef.current.scrollLeft += offset;
|
||||
};
|
||||
|
||||
const handleAddCategory = async (payload) => {
|
||||
try {
|
||||
const response = await authService.addCategory(payload);
|
||||
setCategories((prev) => [...prev, response.category]);
|
||||
setOpenAddModal(false);
|
||||
} catch (error) {
|
||||
console.error('خطأ أثناء الإضافة:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateCategory = async (payload) => {
|
||||
try {
|
||||
const response = await authService.updateCategory(selectedCategoryForEdit.id, payload);
|
||||
setCategories((prev) =>
|
||||
prev.map((cat) => (cat.id === selectedCategoryForEdit.id ? response.category : cat))
|
||||
);
|
||||
setOpenAddModal(false);
|
||||
} catch (error) {
|
||||
console.error('خطأ أثناء التعديل:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteCategory = async () => {
|
||||
try {
|
||||
await authService.deleteCategory(selectedCategoryForEdit.id);
|
||||
setCategories((prev) =>
|
||||
prev.filter((cat) => cat.id !== selectedCategoryForEdit.id)
|
||||
);
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedCategoryForEdit(null);
|
||||
|
||||
if (selectedCategory === selectedCategoryForEdit?.id) {
|
||||
setSelectedCategory(null);
|
||||
localStorage.removeItem('selectedCategoryId');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('خطأ أثناء الحذف:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!openAddModal) setSelectedCategoryForEdit(null);
|
||||
}, [openAddModal]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
maxWidth: { xs: '90%', md: '100%' },
|
||||
borderRadius: 2,
|
||||
maxHeight: { xs: '293px', sm: '30%', md: 140 },
|
||||
}}
|
||||
>
|
||||
{/* رأس القسم */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 0 },
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: '600',
|
||||
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
||||
color: '#121212',
|
||||
}}
|
||||
>
|
||||
Categories
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 1, sm: 2 },
|
||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
justifyContent: { xs: 'space-between', sm: 'flex-end' },
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#fff',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
boxShadow: 'none',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '130px', md: '150px' },
|
||||
fontSize: { xs: '12px', sm: '14px', md: '16px' },
|
||||
fontWeight: 700,
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
onClick={() => {
|
||||
setSelectedCategoryForEdit(null);
|
||||
setOpenAddModal(true);
|
||||
}}
|
||||
>
|
||||
Add Category
|
||||
</Button>
|
||||
|
||||
<IconButton
|
||||
onClick={() => scroll(-200)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
width: 40,
|
||||
height: 40,
|
||||
'&:hover': { backgroundColor: '#ffddbfff' },
|
||||
}}
|
||||
>
|
||||
<ArrowBackIosNewIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
onClick={() => scroll(200)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
width: 40,
|
||||
height: 40,
|
||||
'&:hover': { backgroundColor: '#ffddbfff' },
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIosIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* قائمة الفئات */}
|
||||
{/* قائمة الفئات */}
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '90%', sm: '95%', md: '100%' },
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
mt: 5,
|
||||
pr: { xs: 1, sm: 4 },
|
||||
flexDirection: 'row',
|
||||
overflowX: 'auto',
|
||||
gap: 2,
|
||||
}}
|
||||
ref={scrollRef}
|
||||
>
|
||||
{loading ? (
|
||||
// Skeleton Loader
|
||||
Array.from({ length: 5 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={index}
|
||||
variant="rectangular"
|
||||
width={120}
|
||||
height={60}
|
||||
sx={{ borderRadius: 2 }}
|
||||
/>
|
||||
))
|
||||
) : categories.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '60px',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
No categories available
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<CategoryScrollList
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
setSelectedCategory={setSelectedCategory}
|
||||
setSelectedCategoryForEdit={setSelectedCategoryForEdit}
|
||||
setContextMenu={setContextMenu}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
{/* مودال الإضافة / التعديل */}
|
||||
<Modal
|
||||
open={openAddModal}
|
||||
onClose={() => setOpenAddModal(false)}
|
||||
aria-labelledby="add-category-modal"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 2,
|
||||
boxShadow: 24,
|
||||
p: 2,
|
||||
maxWidth: '90vw',
|
||||
width: 400,
|
||||
}}
|
||||
>
|
||||
<AddCategory
|
||||
onAdd={selectedCategoryForEdit ? handleUpdateCategory : handleAddCategory}
|
||||
editingCategory={selectedCategoryForEdit}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
{/* قائمة السياق */}
|
||||
<Menu
|
||||
open={contextMenu !== null}
|
||||
onClose={() => setContextMenu(null)}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={
|
||||
contextMenu !== null
|
||||
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
||||
: undefined
|
||||
}
|
||||
sx={{ zIndex: 2000 }}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setOpenAddModal(true);
|
||||
setContextMenu(null);
|
||||
}}
|
||||
>
|
||||
Edit Category
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setConfirmDeleteOpen(true);
|
||||
setContextMenu(null);
|
||||
}}
|
||||
sx={{ color: 'red' }}
|
||||
>
|
||||
Delete Category
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>Are you sure you want to delete this category?</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleDeleteCategory} color="error" variant="contained">
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountSettings;
|
||||
102
src/components/Home/Meal/contect/AddCategory.js
Normal file
102
src/components/Home/Meal/contect/AddCategory.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||
import { useRestaurant } from '../../../../contexts/RestaurantContext';
|
||||
|
||||
const AddCategory = ({ onAdd, editingCategory }) => {
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [productName, setProductName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [price, setPrice] = useState('');
|
||||
const [discount, setDiscount] = useState('');
|
||||
const [productImage, setProductImage] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingCategory) {
|
||||
setProductName(editingCategory.name || '');
|
||||
setDescription(editingCategory.description || '');
|
||||
setPrice(editingCategory.price || '');
|
||||
setDiscount(editingCategory.discount || '');
|
||||
setProductImage(editingCategory.icon || null);
|
||||
}
|
||||
}, [editingCategory]);
|
||||
|
||||
const handleImageUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => setProductImage(reader.result);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!productName) {
|
||||
alert("Please enter category name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!restaurantId) {
|
||||
alert("Restaurant ID not set");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: productName,
|
||||
restaurant_id: restaurantId,
|
||||
description,
|
||||
price,
|
||||
discount,
|
||||
icon: productImage,
|
||||
};
|
||||
|
||||
// فقط أرسل البيانات للمكون الأب
|
||||
if (onAdd) onAdd(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={2} maxWidth={400} mx="auto">
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: 600, fontSize: '24px' }}>
|
||||
Category Name
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Enter category name"
|
||||
value={productName}
|
||||
onChange={(e) => setProductName(e.target.value)}
|
||||
margin="normal"
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#FF914D',
|
||||
boxShadow: '0 0 0 2px rgba(255,145,77,0.2)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 2,
|
||||
backgroundColor: '#FF914D',
|
||||
'&:hover': { backgroundColor: '#e57f3c' },
|
||||
borderRadius: 2,
|
||||
color: '#fff',
|
||||
textTransform: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: '16px',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{editingCategory ? 'Update Category' : 'Add Category'}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCategory;
|
||||
251
src/components/Home/Meal/contect/AddMeal.js
Normal file
251
src/components/Home/Meal/contect/AddMeal.js
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
TextField,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import authService from '../../../../services/authService';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
const MAX_FILE_SIZE_MB = 2;
|
||||
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||
|
||||
const AddMeal = ({ open, onClose, onAdd, selectedCategory, restaurantId }) => {
|
||||
const [mealName, setMealName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [additions, setAdditions] = useState(['']);
|
||||
const [price, setPrice] = useState('');
|
||||
const [mealImage, setMealImage] = useState(null);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setMealName('');
|
||||
setDescription('');
|
||||
setAdditions(['']);
|
||||
setPrice('');
|
||||
setMealImage(null);
|
||||
setErrors({});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleImageUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||
setErrors(prev => ({ ...prev, image: 'Allowed types: jpg, jpeg, png' }));
|
||||
return;
|
||||
}
|
||||
if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
|
||||
setErrors(prev => ({ ...prev, image: `Image must be smaller than ${MAX_FILE_SIZE_MB} MB` }));
|
||||
return;
|
||||
}
|
||||
|
||||
setMealImage(file);
|
||||
setErrors(prev => ({ ...prev, image: '' }));
|
||||
};
|
||||
|
||||
const handleAddAddition = () => setAdditions([...additions, '']);
|
||||
const handleRemoveAddition = (index) => setAdditions(additions.filter((_, i) => i !== index));
|
||||
const handleChangeAddition = (index, value) => {
|
||||
const newAdditions = [...additions];
|
||||
newAdditions[index] = value;
|
||||
setAdditions(newAdditions);
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const newErrors = {};
|
||||
if (!mealName.trim()) newErrors.name = 'Meal name is required';
|
||||
if (!description.trim()) newErrors.description = 'Description is required';
|
||||
if (!price || isNaN(price) || Number(price) <= 0) newErrors.price = 'Valid price is required';
|
||||
if (!mealImage) newErrors.image = 'Meal image is required';
|
||||
if (!selectedCategory) newErrors.category = 'Category must be selected';
|
||||
additions.forEach((add, i) => {
|
||||
if (!add.trim()) newErrors[`addition_${i}`] = 'Cannot be empty';
|
||||
});
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validate()) return;
|
||||
setIsSubmitting(true);
|
||||
|
||||
const mealData = {
|
||||
name: mealName.trim(),
|
||||
category_id: selectedCategory,
|
||||
price,
|
||||
restaurant_id: restaurantId,
|
||||
photo: mealImage,
|
||||
description: [description.trim()],
|
||||
additions: additions.filter(a => a.trim() !== ''),
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await authService.addMeal(mealData);
|
||||
|
||||
// عرض رسالة Snackbar أو alert
|
||||
alert(result.message);
|
||||
|
||||
// تمرير الوجبة الجديدة للمكون الأب لتحديث القائمة
|
||||
if (result.data && onAdd) {
|
||||
const newMeal = {
|
||||
id: result.data.id,
|
||||
name: result.data.name,
|
||||
price: result.data.price,
|
||||
description: result.data.description || [],
|
||||
additions: result.data.additions || [],
|
||||
unit: result.data.unit || "/pcs",
|
||||
photo: result.data.photo || "/images/default-product.png",
|
||||
category_id: result.data.category_id || selectedCategory,
|
||||
};
|
||||
onAdd(newMeal);
|
||||
}
|
||||
|
||||
// إغلاق المودال بعد الإضافة
|
||||
onClose();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Add meal error:', error);
|
||||
alert(error.message || "Something went wrong");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 600, fontSize: '20px' }}>Add Meal</DialogTitle>
|
||||
<DialogContent
|
||||
dividers
|
||||
sx={{
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none',
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
{/* Meal Image */}
|
||||
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Meal Image</Typography>
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '150px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: '#969BA7',
|
||||
backgroundColor: '#FAFAFA',
|
||||
fontWeight: 500,
|
||||
fontSize: '18px',
|
||||
textAlign: 'center',
|
||||
mb: 2,
|
||||
'&:hover': { borderColor: '#FF914D', backgroundColor: '#fffaf5' }
|
||||
}}
|
||||
>
|
||||
{mealImage ? (
|
||||
<Box sx={{ position: 'relative', width: '100%', height: '100%', borderRadius: 2, overflow: 'hidden' }}>
|
||||
<img src={mealImage instanceof File ? URL.createObjectURL(mealImage) : mealImage} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
<Button onClick={() => setMealImage(null)} sx={{ position: 'absolute', top: 4, right: 4, minWidth: 0, padding: '2px 6px', fontSize: 12 }}>Remove</Button>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
Upload Meal Picture
|
||||
<AddAPhotoOutlinedIcon sx={{ fontSize: 32, color: '#9e9e9e', mt: 1 }} />
|
||||
</>
|
||||
)}
|
||||
<input id="upload-image" type="file" accept="image/*" hidden onChange={handleImageUpload} />
|
||||
</Box>
|
||||
{errors.image && <Typography color="error" sx={{ mb: 1 }}>{errors.image}</Typography>}
|
||||
|
||||
{/* Meal Name */}
|
||||
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Meal Name</Typography>
|
||||
<TextField fullWidth placeholder="Meal Name" value={mealName} onChange={e => setMealName(e.target.value)} error={!!errors.name} helperText={errors.name} sx={{ mb: 2 }} />
|
||||
|
||||
{/* Description */}
|
||||
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Description</Typography>
|
||||
<TextField fullWidth placeholder="Description" value={description} onChange={e => setDescription(e.target.value)} error={!!errors.description} helperText={errors.description} sx={{ mb: 2 }} />
|
||||
|
||||
{/* Additions */}
|
||||
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Additions</Typography>
|
||||
{additions.map((add, i) => (
|
||||
<Box key={i} sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder={`Addition ${i + 1}`}
|
||||
value={add}
|
||||
onChange={e => handleChangeAddition(i, e.target.value)}
|
||||
error={!!errors[`addition_${i}`]}
|
||||
helperText={errors[`addition_${i}`]}
|
||||
/>
|
||||
<IconButton onClick={() => handleRemoveAddition(i)} color="error"><DeleteIcon /></IconButton>
|
||||
</Box>
|
||||
))}
|
||||
<Button variant="text" startIcon={<AddIcon />} onClick={handleAddAddition} sx={{ mb: 2 ,textTransform:'none'}}>Add Another</Button>
|
||||
|
||||
{/* Price */}
|
||||
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Price</Typography>
|
||||
<TextField fullWidth type="number" placeholder="Price" value={price} onChange={e => setPrice(e.target.value)} error={!!errors.price} helperText={errors.price} sx={{ mb: 2 }} />
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ pr: 3, pb: 2 }}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: '135px',
|
||||
textTransform: 'none',
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: '135px',
|
||||
textTransform: 'none',
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Add Meal
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddMeal;
|
||||
131
src/components/Home/Meal/contect/AllMeals.js
Normal file
131
src/components/Home/Meal/contect/AllMeals.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Typography, Skeleton, useTheme } from "@mui/material";
|
||||
import MealCard from "./MealCard";
|
||||
import authService from "../../../../services/authService";
|
||||
import SimplePagination from "../../SimplePagination";
|
||||
|
||||
const AllMeals = ({ restaurantId, categories = [], onProductClick }) => {
|
||||
const theme = useTheme();
|
||||
const [products, setProducts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
|
||||
const itemsPerPage = 6;
|
||||
|
||||
useEffect(() => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
const fetchMeals = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await authService.getAllMealsByRestaurant(restaurantId, page);
|
||||
|
||||
if (result.success) {
|
||||
const mealsWithImages = result.data.map(meal => {
|
||||
const category = categories.find(c => c.id === meal.category_id);
|
||||
return {
|
||||
id: meal.id,
|
||||
name: meal.name,
|
||||
price: meal.price,
|
||||
description: meal.description || [],
|
||||
additions: meal.additions || [],
|
||||
unit: meal.unit || "/pcs",
|
||||
photo: meal.photo || meal.photo_url || "/images/default-product.png",
|
||||
category_id: meal.category_id,
|
||||
category: category ? category.name : null,
|
||||
};
|
||||
});
|
||||
|
||||
setProducts(mealsWithImages);
|
||||
setPagination(result.pagination);
|
||||
} else {
|
||||
setProducts([]);
|
||||
setPagination(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Failed to fetch meals.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMeals();
|
||||
}, [restaurantId, page, categories]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mb: 0,
|
||||
mr: { xs: 2, sm: 2, md: 0 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: "10px",
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
overflowY: "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="h6" fontWeight={600}>All Meals</Typography>
|
||||
</Box>
|
||||
|
||||
{loading ? (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{Array.from({ length: itemsPerPage }).map((_, index) => (
|
||||
<Box key={index} sx={{ width: "calc(33.33% - 16px)" }}>
|
||||
<Skeleton variant="rectangular" width="100%" height={180} sx={{ borderRadius: 2 }} />
|
||||
<Skeleton variant="text" width="80%" sx={{ mt: 1 }} />
|
||||
<Skeleton variant="text" width="40%" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
) : error ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
|
||||
<Typography variant="body1" color="error">{error}</Typography>
|
||||
</Box>
|
||||
) : products.length === 0 ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "150px" }}>
|
||||
<Typography variant="body1" color="text.secondary">There is no meal available</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{products.map(product => (
|
||||
<Box key={product.id} onClick={() => onProductClick(product)} >
|
||||
<MealCard
|
||||
meal={product}
|
||||
onClick={() => onProductClick(product)}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Pagination Footer */}
|
||||
{pagination && !loading && (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2} sx={{ borderTop: "1px solid #f0f0f0", pt: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Showing {(page - 1) * itemsPerPage + 1} to {Math.min(page * itemsPerPage, products.length)} of {pagination.total} meals
|
||||
</Typography>
|
||||
|
||||
<SimplePagination
|
||||
currentPage={page}
|
||||
pageCount={pagination.last_page}
|
||||
onChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllMeals;
|
||||
76
src/components/Home/Meal/contect/CategoryScrollList.js
Normal file
76
src/components/Home/Meal/contect/CategoryScrollList.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
const CategoryScrollList = ({
|
||||
categories,
|
||||
selectedCategory,
|
||||
setSelectedCategory,
|
||||
setSelectedCategoryForEdit,
|
||||
setContextMenu,
|
||||
scrollRef,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={scrollRef}
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
overflowX: 'auto',
|
||||
scrollBehavior: 'smooth',
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 0 },
|
||||
mb: 3,
|
||||
mx: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
>
|
||||
{categories.map((cat) => {
|
||||
const isSelected = selectedCategory === cat.id;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={cat.id}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setSelectedCategoryForEdit(cat);
|
||||
setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
|
||||
}}
|
||||
sx={{
|
||||
p: 1,
|
||||
borderRadius: '12px',
|
||||
backgroundColor: isSelected ? theme.palette.primary.main : '#F9F9FC',
|
||||
border: isSelected ? `0px solid ${theme.palette.primary.main}` : '0px solid #ddd',
|
||||
textAlign: 'center',
|
||||
minWidth: '70px',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
transition: '0.3s',
|
||||
'&:hover': {
|
||||
backgroundColor: isSelected ? theme.palette.primary.dark : '#EDEDED',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
sx={{ color: isSelected ? '#fff' : '#121212' }}
|
||||
>
|
||||
{cat.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryScrollList;
|
||||
138
src/components/Home/Meal/contect/MealCard.js
Normal file
138
src/components/Home/Meal/contect/MealCard.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
CardMedia,
|
||||
IconButton,
|
||||
Chip,
|
||||
} from "@mui/material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
|
||||
const MealCard = ({ meal, onClick, onAdd }) => {
|
||||
const { name, price, unit, photo, image, description, additions } = meal;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
width: { xs: "160px", md: "160px" },
|
||||
height: 250,
|
||||
borderRadius: "20px",
|
||||
p: "15px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
backgroundColor: "#FCFCFC",
|
||||
boxShadow: "none",
|
||||
border: "1px solid #f0f0f0",
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.2s ease-in-out",
|
||||
"&:hover": { transform: "scale(1.02)" },
|
||||
}}
|
||||
>
|
||||
{/* صورة الوجبة */}
|
||||
<CardMedia
|
||||
component="img"
|
||||
image={photo || "/images/default-product.png"}
|
||||
alt={name}
|
||||
sx={{
|
||||
width: "120px",
|
||||
height: "120px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "12px",
|
||||
// mb: 1,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* تفاصيل الوجبة */}
|
||||
<CardContent sx={{ p: 0, width: "100%", textAlign: "center" }}>
|
||||
<Typography variant="body1" fontWeight={600} fontSize={16} noWrap>
|
||||
{name}
|
||||
</Typography>
|
||||
|
||||
{/* السعر */}
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={600}
|
||||
sx={{ color: "#FF914D", fontSize: "15px", mt: 0.5 }}
|
||||
>
|
||||
${price}{" "}
|
||||
<Typography
|
||||
component="span"
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
>
|
||||
{unit || "/pcs"}
|
||||
</Typography>
|
||||
</Typography>
|
||||
|
||||
{/* الوصف (أول عنصر فقط للتصغير) */}
|
||||
{description && description.length > 0 && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
sx={{ display: "block", mt: 0.5 }}
|
||||
>
|
||||
{description[0]}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* الإضافات (أول 2 فقط) */}
|
||||
{/* {additions && additions.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: 0.5,
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
{additions.slice(0, 2).map((add, i) => (
|
||||
<Chip
|
||||
key={i}
|
||||
label={add}
|
||||
size="small"
|
||||
sx={{
|
||||
fontSize: "10px",
|
||||
height: 18,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)} */}
|
||||
|
||||
{/* زر الإضافة */}
|
||||
{/* <IconButton
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onAdd) onAdd(meal);
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: "#FF8551",
|
||||
color: "#fff",
|
||||
mt: 1,
|
||||
width: 28,
|
||||
height: 28,
|
||||
"&:hover": { backgroundColor: "#ff7043" },
|
||||
}}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton> */}
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MealCard;
|
||||
202
src/components/Home/Meal/contect/MealsByCateg.js
Normal file
202
src/components/Home/Meal/contect/MealsByCateg.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Box, Typography, Skeleton, useTheme, Button } from "@mui/material";
|
||||
import MealCard from "./MealCard";
|
||||
import authService from "../../../../services/authService";
|
||||
import AddMeal from "./AddMeal";
|
||||
import SimplePagination from "../../SimplePagination";
|
||||
|
||||
const MealsByCateg = ({
|
||||
restaurantId,
|
||||
categoryId,
|
||||
onProductClick,
|
||||
onAddMeal,
|
||||
categoryName,
|
||||
onBack,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [products, setProducts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
|
||||
const itemsPerPage = 6;
|
||||
|
||||
// استخدام useCallback لمنع إنشاء الدالة عند كل إعادة render
|
||||
const fetchMeals = useCallback(async () => {
|
||||
if (!restaurantId || !categoryId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await authService.getMealsByCategory(
|
||||
restaurantId,
|
||||
categoryId,
|
||||
page
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
const mealsWithImages = result.data.map((meal) => ({
|
||||
id: meal.id,
|
||||
name: meal.name,
|
||||
price: meal.price,
|
||||
description: meal.description || [],
|
||||
additions: meal.additions || [],
|
||||
unit: meal.unit || "/pcs",
|
||||
photo: meal.photo || "/images/default-product.png",
|
||||
category_id: meal.category_id || categoryId,
|
||||
}));
|
||||
setProducts(mealsWithImages);
|
||||
setPagination(result.pagination);
|
||||
} else {
|
||||
setProducts([]);
|
||||
setPagination(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Failed to fetch meals.");
|
||||
setProducts([]);
|
||||
setPagination(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [restaurantId, categoryId, page]);
|
||||
|
||||
// جلب البيانات عند تغيير restaurantId أو categoryId أو page
|
||||
useEffect(() => {
|
||||
fetchMeals();
|
||||
}, [fetchMeals]);
|
||||
|
||||
// إضافة وجبة جديدة وتحديث القائمة فورًا
|
||||
// في MealsByCateg
|
||||
const handleAddProduct = async (meal) => {
|
||||
setOpenModal(false); // اغلاق مودال الإضافة
|
||||
setPage(1); // العودة للصفحة الأولى
|
||||
const result = await fetchMeals(); // جلب الوجبات بعد الإضافة
|
||||
if (onAddMeal) onAddMeal(meal); // تمرير الوجبة للمكون الأب
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
maxWidth: { xs: "90%", md: "100%" },
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: "10px",
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{/* العنوان + أزرار */}
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{categoryName || "Meals"}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Button
|
||||
onClick={() => setOpenModal(true)}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
color: "#fff",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: { xs: "100%", sm: "130px", md: "150px" },
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
Add Product
|
||||
</Button>
|
||||
<Button variant="text" color="primary" sx={{ ml: 1 }} onClick={onBack}>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* حالة التحميل أو الخطأ */}
|
||||
{loading ? (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{Array.from({ length: itemsPerPage }).map((_, index) => (
|
||||
<Box key={index} sx={{ flex: "1 1 calc(33.33% - 16px)" }}>
|
||||
<Skeleton variant="rectangular" width="100%" height={180} sx={{ borderRadius: 2 }} />
|
||||
<Skeleton variant="text" width="80%" sx={{ mt: 1 }} />
|
||||
<Skeleton variant="text" width="40%" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
) : error ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
|
||||
<Typography variant="body1" color="error">{error}</Typography>
|
||||
</Box>
|
||||
) : products.length === 0 ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "150px" }}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
There is no meal available
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{products.map((product) => (
|
||||
<Box
|
||||
key={product.id}
|
||||
onClick={() =>
|
||||
onProductClick({
|
||||
...product,
|
||||
category: categoryName,
|
||||
category_id: categoryId,
|
||||
})
|
||||
}
|
||||
>
|
||||
<MealCard
|
||||
meal={product}
|
||||
onClick={() =>
|
||||
onProductClick({
|
||||
...product,
|
||||
category: categoryName,
|
||||
category_id: categoryId,
|
||||
})
|
||||
}
|
||||
onAdd={onAddMeal}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Pagination Footer */}
|
||||
{pagination && !loading && pagination.last_page > 1 && (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2} sx={{ borderTop: "1px solid #f0f0f0", pt: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Showing {(page - 1) * itemsPerPage + 1} to{" "}
|
||||
{Math.min(page * itemsPerPage, pagination.total)} of {pagination.total} meals
|
||||
</Typography>
|
||||
|
||||
<SimplePagination
|
||||
currentPage={page}
|
||||
pageCount={pagination.last_page}
|
||||
onChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Add Meal Modal */}
|
||||
<AddMeal
|
||||
open={openModal}
|
||||
onClose={() => setOpenModal(false)}
|
||||
onAdd={handleAddProduct}
|
||||
selectedCategory={categoryId}
|
||||
restaurantId={restaurantId}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MealsByCateg;
|
||||
551
src/components/Home/Meal/contect/ProductDetail.js
Normal file
551
src/components/Home/Meal/contect/ProductDetail.js
Normal file
@@ -0,0 +1,551 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
TextField,
|
||||
Chip,
|
||||
LinearProgress,
|
||||
useTheme,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import authService from "../../../../services/authService";
|
||||
|
||||
const ProductDetail = ({ product, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
category_id: "",
|
||||
restaurant_id: "",
|
||||
description: [],
|
||||
additions: [],
|
||||
price: "",
|
||||
photoFile: null,
|
||||
});
|
||||
|
||||
const [newDescription, setNewDescription] = useState("");
|
||||
const [newAddition, setNewAddition] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [categories, setCategories] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (product) {
|
||||
setFormData({
|
||||
name: product.name || "",
|
||||
category_id: product.category_id || "",
|
||||
restaurant_id: product.restaurant_id || "",
|
||||
description: product.description || [],
|
||||
additions: product.additions || [],
|
||||
price: product.price || "",
|
||||
photoFile: null,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const result = await authService.getCategoriesByRestaurant(product?.restaurant_id);
|
||||
if (result.success) setCategories(result.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching categories:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (product?.restaurant_id) fetchCategories();
|
||||
}, [product]);
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.name.trim()) newErrors.name = "Name is required";
|
||||
if (!formData.category_id) newErrors.category_id = "Category is required";
|
||||
if (!formData.price || isNaN(formData.price) || formData.price <= 0) {
|
||||
newErrors.price = "Valid price is required";
|
||||
}
|
||||
if (formData.description.length === 0) newErrors.description = "At least one description is required";
|
||||
if (formData.additions.length === 0) newErrors.additions = "At least one addition is required";
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
if (errors[name]) setErrors((prev) => ({ ...prev, [name]: "" }));
|
||||
};
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
setErrors((prev) => ({ ...prev, photoFile: "Please select an image file" }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setErrors((prev) => ({ ...prev, photoFile: "File size should be less than 5MB" }));
|
||||
return;
|
||||
}
|
||||
|
||||
setFormData((prev) => ({ ...prev, photoFile: file }));
|
||||
setErrors((prev) => ({ ...prev, photoFile: "" }));
|
||||
};
|
||||
|
||||
const handleAddDescription = () => {
|
||||
if (newDescription.trim() !== "") {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
description: [...prev.description, newDescription.trim()],
|
||||
}));
|
||||
setNewDescription("");
|
||||
setErrors((prev) => ({ ...prev, description: "" }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveDescription = (idx) => {
|
||||
const newDesc = formData.description.filter((_, i) => i !== idx);
|
||||
setFormData((prev) => ({ ...prev, description: newDesc }));
|
||||
if (newDesc.length === 0) setErrors((prev) => ({ ...prev, description: "At least one description is required" }));
|
||||
};
|
||||
|
||||
const handleAddAddition = () => {
|
||||
if (newAddition.trim() !== "") {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
additions: [...prev.additions, newAddition.trim()],
|
||||
}));
|
||||
setNewAddition("");
|
||||
setErrors((prev) => ({ ...prev, additions: "" }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveAddition = (idx) => {
|
||||
const newAdds = formData.additions.filter((_, i) => i !== idx);
|
||||
setFormData((prev) => ({ ...prev, additions: newAdds }));
|
||||
if (newAdds.length === 0) setErrors((prev) => ({ ...prev, additions: "At least one addition is required" }));
|
||||
};
|
||||
|
||||
const handleEditDescription = (idx, value) => {
|
||||
const newDesc = [...formData.description];
|
||||
newDesc[idx] = value;
|
||||
setFormData((prev) => ({ ...prev, description: newDesc }));
|
||||
};
|
||||
|
||||
const handleEditAddition = (idx, value) => {
|
||||
const newAdds = [...formData.additions];
|
||||
newAdds[idx] = value;
|
||||
setFormData((prev) => ({ ...prev, additions: newAdds }));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
const updateData = {
|
||||
name: formData.name.trim(),
|
||||
category_id: formData.category_id,
|
||||
restaurant_id: formData.restaurant_id,
|
||||
description: formData.description,
|
||||
additions: formData.additions,
|
||||
price: formData.price,
|
||||
photo: formData.photoFile ? [formData.photoFile] : [],
|
||||
};
|
||||
|
||||
const res = await authService.updateMeal(product.id, updateData);
|
||||
|
||||
// ✅ حدّث state بآخر نسخة من السيرفر
|
||||
setFormData({
|
||||
name: res.meal.name || "",
|
||||
category_id: res.meal.category_id || "",
|
||||
restaurant_id: res.meal.restaurant_id || "",
|
||||
description: res.meal.description || [],
|
||||
additions: res.meal.additions || [],
|
||||
price: res.meal.price || "",
|
||||
photoFile: null,
|
||||
});
|
||||
|
||||
setIsEditing(false);
|
||||
alert("Meal updated successfully!");
|
||||
|
||||
// ✅ إغلاق المكون بعد النجاح
|
||||
if (onBack) {
|
||||
onBack();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error updating meal: " + (err.message || "Something went wrong"));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteMeal = async () => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await authService.deleteMeal(product.id, product.restaurant_id, product.category_id);
|
||||
onBack();
|
||||
// alert("Meal deleted successfully!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error deleting meal: " + (err.message || "Something went wrong"));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", backgroundColor: "#fff", borderRadius: "8px", p: 3, mx: "auto", gap: 3 }}>
|
||||
{/* صندوق الصورة */}
|
||||
<Box sx={{ width: "50%", display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
<Box sx={{ position: "relative", width: "100%", height: 450 }}>
|
||||
<IconButton
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 102,
|
||||
backgroundColor: "#fff",
|
||||
color: theme.palette.primary.main,
|
||||
"&:hover": { backgroundColor: "#f0f0f0" },
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<Box
|
||||
component="img"
|
||||
src={formData.photoFile ? URL.createObjectURL(formData.photoFile) : product.image || product.photo}
|
||||
alt={product.name}
|
||||
sx={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
objectFit: "cover",
|
||||
borderRadius: "8px",
|
||||
p: 5,
|
||||
border: errors.photoFile ? "2px solid red" : "none"
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{isEditing && (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
<Button
|
||||
component="label"
|
||||
variant="outlined"
|
||||
startIcon={<CloudUploadIcon />}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
borderColor: errors.photoFile ? 'red' : theme.palette.primary.main,
|
||||
color: errors.photoFile ? 'red' : theme.palette.primary.main
|
||||
}}
|
||||
>
|
||||
Change Image
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
accept="image/*"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</Button>
|
||||
{errors.photoFile && <FormHelperText error>{errors.photoFile}</FormHelperText>}
|
||||
{formData.photoFile && <Typography variant="caption" color="textSecondary">Selected: {formData.photoFile.name}</Typography>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* التفاصيل */}
|
||||
<Box sx={{ width: "50%", display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
{/* الاسم */}
|
||||
<Box>
|
||||
{isEditing ? (
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Meal Name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleInputChange}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
size="small"
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="h5" sx={{mt:1}} fontWeight={700}>{product.name}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* الفئة */}
|
||||
<Box>
|
||||
{isEditing ? (
|
||||
<FormControl fullWidth error={!!errors.category_id} size="small">
|
||||
<Select
|
||||
name="category_id"
|
||||
value={formData.category_id || ""}
|
||||
onChange={handleInputChange}
|
||||
displayEmpty
|
||||
>
|
||||
|
||||
{!formData.category_id && (
|
||||
<MenuItem value="">
|
||||
<em>Select Category</em>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{categories.map((category) => (
|
||||
<MenuItem key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.category_id && (
|
||||
<FormHelperText>{errors.category_id}</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
) : (
|
||||
<Typography variant="body2" sx={{ color: "#777" }}>
|
||||
{product.category ||
|
||||
categories.find((c) => c.id === (product.category_id || formData.category_id))?.name ||
|
||||
`Category #${product.category_id || formData.category_id}`}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
{/* السعر */}
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>Price</Typography>
|
||||
{isEditing ? (
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="0.00"
|
||||
name="price"
|
||||
type="number"
|
||||
value={formData.price}
|
||||
onChange={handleInputChange}
|
||||
error={!!errors.price}
|
||||
helperText={errors.price}
|
||||
inputProps={{ min: 0, step: 0.01 }}
|
||||
size="small"
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="h6" sx={{ color: theme.palette.primary.main, fontWeight: 700 }}>
|
||||
${Number(product.price).toFixed(2)} {product.unit || "/pcs"}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* الوصف */}
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>
|
||||
Description {isEditing && "*"}
|
||||
</Typography>
|
||||
{isEditing ? (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
{formData.description.map((desc, idx) => (
|
||||
<Box key={idx} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={desc}
|
||||
onChange={(e) => handleEditDescription(idx, e.target.value)}
|
||||
placeholder={`Description ${idx + 1}`}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
{errors.description && <FormHelperText error>{errors.description}</FormHelperText>}
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
|
||||
{formData.description.map((desc, idx) => <Typography key={idx} variant="body2">• {desc}</Typography>)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* الإضافات */}
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>
|
||||
Additions {isEditing && "*"}
|
||||
</Typography>
|
||||
{isEditing ? (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
{formData.additions.map((add, idx) => (
|
||||
<Box key={idx} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={add}
|
||||
onChange={(e) => handleEditAddition(idx, e.target.value)}
|
||||
placeholder={`Addition ${idx + 1}`}
|
||||
size="small"
|
||||
/>
|
||||
<IconButton color="error" onClick={() => handleRemoveAddition(idx)} size="small">
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))}
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Add new addition"
|
||||
value={newAddition}
|
||||
onChange={(e) => setNewAddition(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<IconButton color="primary" onClick={handleAddAddition} disabled={!newAddition.trim()}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
{errors.additions && <FormHelperText error>{errors.additions}</FormHelperText>}
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
|
||||
{formData.additions.map((add, idx) => <Chip key={idx} label={add} size="small" variant="outlined" />)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* شريط تقدم */}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={100}
|
||||
sx={{
|
||||
mt: 2,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#f0f0f0",
|
||||
"& .MuiLinearProgress-bar": { backgroundColor: theme.palette.primary.main },
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box sx={{backgroundColor:'#fff2e0ff' , height:100 , width:500 , borderRadius: '8px',alignItems:'center' }}>
|
||||
{/* أزرار التحكم */}
|
||||
<Box sx={{ display: "flex", gap: 2, p:4}}>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
flex: 1,
|
||||
textTransform: 'none',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: '8px',
|
||||
height: '45px',
|
||||
color: '#fff'
|
||||
}}
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? "Saving..." : "Save Changes"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
flex: 1,
|
||||
textTransform: 'none',
|
||||
borderColor: "gray",
|
||||
color: "gray",
|
||||
borderRadius: '8px',
|
||||
height: '45px',
|
||||
backgroundColor:'#fff'
|
||||
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
setFormData({
|
||||
name: product.name || "",
|
||||
category_id: product.category_id || "",
|
||||
restaurant_id: product.restaurant_id || "",
|
||||
description: product.description || [],
|
||||
additions: product.additions || [],
|
||||
price: product.price || "",
|
||||
photoFile: null,
|
||||
});
|
||||
setErrors({});
|
||||
}}
|
||||
disabled={isSaving}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
flex: 1,
|
||||
textTransform: 'none',
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
borderRadius: '8px',
|
||||
height: '45px',
|
||||
backgroundColor:'#fff'
|
||||
}}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
Edit Meal
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
flex: 1,
|
||||
textTransform: 'none',
|
||||
borderColor: "error.main",
|
||||
color: "error.main",
|
||||
borderRadius: '8px',
|
||||
height: '45px',
|
||||
backgroundColor:'#fff'
|
||||
}}
|
||||
onClick={() => setConfirmDeleteOpen(true)}
|
||||
>
|
||||
Delete Meal
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* مودال تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{product.name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleDeleteMeal}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail;
|
||||
74
src/components/Home/Order/Order.js
Normal file
74
src/components/Home/Order/Order.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import Orders from './contect/Orders';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Order = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const admin = authService.getAdminData();
|
||||
console.log('Admin Info:', admin);
|
||||
|
||||
const adminId = authService.getAdminId();
|
||||
console.log('Admin ID:', adminId);
|
||||
}, []);
|
||||
|
||||
const admin = authService.getAdminData();
|
||||
const adminId = authService.getAdminId();
|
||||
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar onDrawerToggle={handleDrawerToggle} sidebarOpen={sidebarOpen} isMobile={isMobile} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 6,
|
||||
width: { xs: '90%', sm: '95%', md: '96%' },
|
||||
pt: { xs: 2, sm: 3 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 4 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
}}
|
||||
>
|
||||
<Orders adminId={adminId} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Order;
|
||||
170
src/components/Home/Order/contect/CartView.js
Normal file
170
src/components/Home/Order/contect/CartView.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { Box, Typography, Button, LinearProgress, IconButton } from "@mui/material";
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { CartContext } from "../../../../contexts/CartContextR";
|
||||
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||
|
||||
const CartView = ({ onClose, onCartCreated, adminId }) => {
|
||||
const { cart, clearCart, createNewCart } = useContext(CartContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { showSnackbar } = useSnackbar();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Admin ID from props in:CartView', adminId);
|
||||
}, [adminId]);
|
||||
|
||||
const totalPrice = cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0);
|
||||
|
||||
const handleSendCart = async () => {
|
||||
if (!cart.length) return;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// تحقق من أن adminId موجود ضمن قائمة صالحة (يمكنك تعديلها حسب بياناتك)
|
||||
const validAdminIds = [1, 2]; // IDs موجودة في DB
|
||||
if (!validAdminIds.includes(adminId)) {
|
||||
console.error("Invalid admin ID");
|
||||
showSnackbar("Selected admin is not valid.", "error");
|
||||
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// تحقق من أن جميع المنتجات موجودة في قاعدة البيانات
|
||||
const validProductIds = [1, 2, 3, 4]; // IDs المنتجات الموجودة
|
||||
for (let item of cart) {
|
||||
if (!validProductIds.includes(item.id)) {
|
||||
console.error(`Invalid product ID: ${item.id}`);
|
||||
// alert(`Product with ID ${item.id} does not exist.`);
|
||||
showSnackbar(`Product with ID ${item.id} does not exist.`, "error");
|
||||
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// تجهيز البيانات حسب شكل الـ backend
|
||||
const cartData = {
|
||||
data: {
|
||||
type: "cart",
|
||||
attributes: {
|
||||
totalPrice: cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0),
|
||||
},
|
||||
relationships: {
|
||||
admin: {
|
||||
data: { id: adminId },
|
||||
},
|
||||
cartItems: cart.map(item => ({
|
||||
attributes: { quantity: item.quantity },
|
||||
relationships: { product: { data: { id: item.id } } },
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newCart = await createNewCart(cartData);
|
||||
|
||||
if (newCart && newCart.success) {
|
||||
onCartCreated(newCart.data);
|
||||
clearCart();
|
||||
onClose();
|
||||
showSnackbar("Cart sent successfully!", "success");
|
||||
} else {
|
||||
console.error("Failed to create cart:", newCart.message);
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error sending cart:", error);
|
||||
showSnackbar("Failed to create cart.", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (loading) return <LinearProgress />;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: "100%", sm: "93.5%" },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: "white",
|
||||
borderRadius: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* السهم للعودة */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<IconButton onClick={onClose} size="small" sx={{ mr: 1 }}>
|
||||
<ArrowBackIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Typography variant="h5" sx={{ fontWeight: 600, fontSize: { xs: "18px", sm: "20px" } }}>
|
||||
Current Cart
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{cart.length === 0 ? (
|
||||
<Typography sx={{ mt: 2, textAlign: "center" }}>No items in cart.</Typography>
|
||||
) : (
|
||||
<>
|
||||
{cart.map(item => (
|
||||
<Box
|
||||
key={item.id}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0",
|
||||
backgroundColor: "#fafafa",
|
||||
flexWrap: "wrap",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography fontWeight={600}>{item.name}</Typography>
|
||||
<Typography variant="body2">Unit: {item.unit}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ textAlign: { xs: "left", sm: "right" } }}>
|
||||
<Typography variant="body2">Quantity: {item.quantity}</Typography>
|
||||
<Typography variant="body2">Total: ${item.totalPrice}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
Total Price: ${totalPrice.toFixed(2)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{cart.length > 0 && (
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 2, flexWrap: { xs: "wrap", sm: "nowrap" }, justifyContent: { xs: "center", sm: "flex-start" } }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSendCart}
|
||||
sx={{ color: 'white', borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '200px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '200px' } }}
|
||||
>
|
||||
Send Cart to Server
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={clearCart}
|
||||
sx={{ borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '150px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '150px' } }}
|
||||
>
|
||||
Clear Cart
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartView;
|
||||
223
src/components/Home/Order/contect/OrderDetails.js
Normal file
223
src/components/Home/Order/contect/OrderDetails.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Button,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import authService from '../../../../services/authService';
|
||||
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||
|
||||
const CartDetails = ({ cart, onClose, onUpdated, onDeleted }) => {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [totalPrice, setTotalPrice] = useState(cart?.attributes?.total_price || "");
|
||||
const [cartItems, setCartItems] = useState(cart.relationships?.cartItems || cart.relationships?.cart_items || []);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
const { showSnackbar } = useSnackbar();
|
||||
if (!cart) return null;
|
||||
|
||||
const handleSaveAll = async () => {
|
||||
setLoading(true);
|
||||
const payload = {
|
||||
data: {
|
||||
type: "cart",
|
||||
attributes: {
|
||||
totalPrice: Number(totalPrice),
|
||||
},
|
||||
relationships: {
|
||||
cartItems: cartItems.map(item => ({
|
||||
attributes: {
|
||||
quantity: Number(item.attributes.quantity),
|
||||
},
|
||||
relationships: {
|
||||
product: {
|
||||
data: {
|
||||
id: item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await authService.updateCart(cart.id, payload);
|
||||
if (result.success) {
|
||||
onUpdated(result.data);
|
||||
setEditMode(false);
|
||||
} else {
|
||||
// alert(result.message || 'Failed to update cart');
|
||||
showSnackbar(result.message || "Failed to update cart", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
// alert('An error occurred while updating the cart');
|
||||
showSnackbar("An error occurred while updating the cart", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteCart = async () => {
|
||||
setDeleteLoading(true);
|
||||
try {
|
||||
const result = await authService.deleteCart(cart.id);
|
||||
if (result.success) {
|
||||
onDeleted(cart.id);
|
||||
onClose();
|
||||
} else {
|
||||
// alert(result.message || 'Failed to delete cart');
|
||||
showSnackbar(result.message || "Failed to update cart", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
// alert('An error occurred while deleting the cart');
|
||||
showSnackbar("An error occurred while deleting the cart", "error");
|
||||
} finally {
|
||||
setDeleteLoading(false);
|
||||
setOpenDeleteDialog(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeQuantity = (itemId, newQuantity) => {
|
||||
setCartItems(prev =>
|
||||
prev.map(item =>
|
||||
item.id === itemId
|
||||
? { ...item, attributes: { ...item.attributes, quantity: newQuantity } }
|
||||
: item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper sx={{ p: 3, mt: 2, borderRadius: 2, border: '1px solid #e0e0e0' }}>
|
||||
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Cart #{cart.id} Details
|
||||
</Typography>
|
||||
|
||||
{editMode ? (
|
||||
<>
|
||||
<TextField
|
||||
label="Total Price"
|
||||
type="number"
|
||||
value={totalPrice}
|
||||
onChange={(e) => setTotalPrice(e.target.value)}
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||
<List>
|
||||
{cartItems.map(item => (
|
||||
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||
<ListItemText
|
||||
primary={`Item ID: ${item.id} | Product: ${item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}`}
|
||||
secondary={
|
||||
<TextField
|
||||
type="number"
|
||||
size="small"
|
||||
label="Quantity"
|
||||
value={item.attributes.quantity}
|
||||
onChange={(e) => handleChangeQuantity(item.id, Number(e.target.value))}
|
||||
sx={{ width: '120px' }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||
<Button variant="contained" onClick={handleSaveAll} disabled={loading}>
|
||||
{loading ? "Saving..." : "Save All"}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={() => setEditMode(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Typography>Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}</Typography>
|
||||
<Typography>
|
||||
Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||
<List>
|
||||
{cartItems.map(item => (
|
||||
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||
<ListItemText
|
||||
primary={`Item ID: ${item.id}`}
|
||||
secondary={`Quantity: ${item.attributes.quantity} | Product: ${item.relationships?.supplier_product?.id ||
|
||||
item.relationships?.product?.data?.id
|
||||
}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||
<Button variant="contained" onClick={() => setEditMode(true)}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setOpenDeleteDialog(true)}
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
Delete Cart
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={onClose}>
|
||||
Back to Orders
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this cart? This action cannot be undone.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} disabled={deleteLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDeleteCart}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
{deleteLoading ? <CircularProgress size={24} /> : 'Delete'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartDetails;
|
||||
326
src/components/Home/Order/contect/Orders.js
Normal file
326
src/components/Home/Order/contect/Orders.js
Normal file
@@ -0,0 +1,326 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
CircularProgress,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import authService from '../../../../services/authService';
|
||||
import CartDetails from './OrderDetails';
|
||||
import CartView from './CartView';
|
||||
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
const handlePrev = () => { if (currentPage > 1) onChange(currentPage - 1); };
|
||||
const handleNext = () => { if (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',
|
||||
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
|
||||
}}
|
||||
>
|
||||
{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 Orders = ({ adminId }) => {
|
||||
useEffect(() => {
|
||||
console.log('Admin ID from props:', adminId);
|
||||
}, [adminId]);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [ordersData, setOrdersData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedCart, setSelectedCart] = useState(null);
|
||||
const [cartLoading, setCartLoading] = useState(false);
|
||||
const [showCartView, setShowCartView] = useState(false); // ← التحكم بعرض CartView هنا
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 6;
|
||||
const [orders, setOrders] = useState([]);
|
||||
|
||||
const handleAddOrder = (newOrder) => {
|
||||
setOrders((prevOrders) => [...prevOrders, newOrder]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrders = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await authService.getCart();
|
||||
if (result.success) {
|
||||
setOrdersData(Array.isArray(result.data) ? result.data : []);
|
||||
} else {
|
||||
console.error(result.message);
|
||||
setOrdersData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch orders:', error);
|
||||
setOrdersData([]);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
fetchOrders();
|
||||
}, []);
|
||||
|
||||
const handleRowClick = async (cartId) => {
|
||||
setCartLoading(true);
|
||||
try {
|
||||
const result = await authService.getCartById(cartId);
|
||||
if (result.success) setSelectedCart(result.data);
|
||||
else console.error(result.message);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch cart details:', error);
|
||||
}
|
||||
setCartLoading(false);
|
||||
};
|
||||
|
||||
const handleCartUpdated = (updatedCart) => {
|
||||
setOrdersData(prev =>
|
||||
prev.map(cart => cart.id === updatedCart.id ? updatedCart : cart)
|
||||
);
|
||||
setSelectedCart(null);
|
||||
};
|
||||
|
||||
const handleCloseCartDetails = () => { setSelectedCart(null); };
|
||||
|
||||
const pageCount = Math.ceil(ordersData.length / itemsPerPage);
|
||||
const paginatedOrders = ordersData.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
if (cartLoading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCart) {
|
||||
return (
|
||||
<CartDetails
|
||||
cart={selectedCart}
|
||||
onClose={handleCloseCartDetails}
|
||||
onUpdated={handleCartUpdated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// عرض CartView بدل الطلبات إذا تم الضغط على الزر
|
||||
// داخل Orders.js
|
||||
if (showCartView) {
|
||||
return (
|
||||
<CartView
|
||||
onClose={() => setShowCartView(false)}
|
||||
onSend={handleAddOrder}
|
||||
onCartCreated={(newCart) => {
|
||||
setOrdersData(prev => [newCart, ...prev]); // ضف الكارت الجديد مباشرة
|
||||
setShowCartView(false);
|
||||
}}
|
||||
adminId={adminId} // ← أرسل الـ adminId هنا
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<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' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Order
|
||||
</Typography>
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
{/* جدول الطلبات */}
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
height: '380px',
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: 'auto' }}
|
||||
aria-label="orders table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Cart ID</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Total Price</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Created At</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Items Count</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>
|
||||
))
|
||||
: (
|
||||
<>
|
||||
{paginatedOrders.map(order => {
|
||||
const attributes = order?.attributes || {};
|
||||
const relationships = order?.relationships || {};
|
||||
const cartItems = relationships?.cartItems || relationships?.cart_items || [];
|
||||
return (
|
||||
<TableRow
|
||||
key={order?.id}
|
||||
hover
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.3s',
|
||||
'&:hover': {
|
||||
backgroundColor: '#ffe4d0ff !important',
|
||||
'& td': { color: '#000000ff' },
|
||||
},
|
||||
}}
|
||||
onClick={() => handleRowClick(order.id)}
|
||||
>
|
||||
<TableCell>{order?.id || '--'}</TableCell>
|
||||
<TableCell>{attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}</TableCell>
|
||||
<TableCell>{attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}</TableCell>
|
||||
<TableCell>{cartItems.length}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
{paginatedOrders.length < itemsPerPage &&
|
||||
Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
|
||||
<TableRow key={`empty-${idx}`} sx={{ height: 53 }}>
|
||||
<TableCell colSpan={4} sx={{ borderBottom: 'none' }} />
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pt={{ xs: 1, sm: 2 }}
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ fontSize: { xs: '11px', sm: '14px' }, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
|
||||
</Typography>
|
||||
|
||||
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Orders;
|
||||
136
src/components/Home/ResList/AppBar.js
Normal file
136
src/components/Home/ResList/AppBar.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Box,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
// دالة تسجيل الخروج المعدلة
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const result = await authService.logout();
|
||||
|
||||
if (result.success) {
|
||||
localStorage.removeItem('token');
|
||||
navigate('/login');
|
||||
} else {
|
||||
console.error('Logout failed:', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
sx={{
|
||||
height: { xs: 56, sm: 64, md: 66 },
|
||||
backgroundColor: '#ffffff',
|
||||
color: 'black',
|
||||
boxShadow: 'none',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: theme.zIndex.appBar,
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
sx={{
|
||||
px: { xs: 2, sm: 3, md: '24px' },
|
||||
minHeight: { xs: '56px !important', sm: '64px !important' },
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{/* Left: Logo */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src="/image.png"
|
||||
alt="logo"
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
objectFit: 'contain',
|
||||
mr: 1.5,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: '1.25rem',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
>
|
||||
KITCH
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: '1.25rem',
|
||||
color: 'primary.main',
|
||||
ml: 0.5,
|
||||
}}
|
||||
>
|
||||
PLUS
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Right: Log Out Button */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||
<Box
|
||||
onClick={handleLogout}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: '5px',
|
||||
px: 2,
|
||||
py: 1,
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.2s',
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#fffcf9d5',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
color: 'divider',
|
||||
minWidth: 36,
|
||||
}}
|
||||
>
|
||||
<LogoutIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Log Out"
|
||||
primaryTypographyProps={{
|
||||
sx: {
|
||||
color: 'divider',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default KitchPlusAppBar;
|
||||
60
src/components/Home/ResList/RestaurantDetailsModal.js
Normal file
60
src/components/Home/ResList/RestaurantDetailsModal.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Typography,
|
||||
Button,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
|
||||
const RestaurantDetailsModal = ({ open, onClose, restaurant }) => {
|
||||
if (!restaurant) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{restaurant.name}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
{restaurant.cuisine_type && (
|
||||
<Typography><b>Cuisine Type:</b> {restaurant.cuisine_type.name}</Typography>
|
||||
)}
|
||||
<Typography><b>Location:</b> {restaurant.location}</Typography>
|
||||
<Typography><b>Brand Details:</b> {restaurant.brand_details || 'N/A'}</Typography>
|
||||
<Typography><b>Age Group:</b> {restaurant.age_group}</Typography>
|
||||
<Typography><b>Menu Status:</b> {restaurant.menu_status ? 'Active' : 'Inactive'}</Typography>
|
||||
<Typography><b>Need Help:</b> {restaurant.need_help || 'N/A'}</Typography>
|
||||
{restaurant.operational_details && (
|
||||
<>
|
||||
<Typography><b>Staff Members:</b> {restaurant.operational_details.staff_members}</Typography>
|
||||
<Typography><b>Equipment:</b> {restaurant.operational_details.equipment}</Typography>
|
||||
<Typography><b>Specialized Equipment:</b> {restaurant.operational_details.specialized_equipment}</Typography>
|
||||
<Typography><b>Expansion Cities:</b> {restaurant.operational_details.expansion_plan_cities}</Typography>
|
||||
</>
|
||||
)}
|
||||
{restaurant.budget_expansion && (
|
||||
<>
|
||||
<Typography><b>Estimated Budget:</b> {restaurant.budget_expansion.estimated_budget}</Typography>
|
||||
<Typography><b>Expansion Branches:</b> {restaurant.budget_expansion.expansion_branches}</Typography>
|
||||
</>
|
||||
)}
|
||||
{restaurant.created_by && (
|
||||
<Typography><b>Created By:</b> {restaurant.created_by.name}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outlined"
|
||||
sx={{ textTransform: "none", minWidth: 120 }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestaurantDetailsModal;
|
||||
76
src/components/Home/ResList/RestaurantProfile.js
Normal file
76
src/components/Home/ResList/RestaurantProfile.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from './AppBar';
|
||||
import RestaurantSelection from './RestaurantSelection';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const RestaurantProfile = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [restaurants, setRestaurants] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRestaurants = async () => {
|
||||
setLoading(true);
|
||||
const result = await authService.getRestaurants();
|
||||
if (result.success) {
|
||||
const mappedRestaurants = result.data.map((res) => ({
|
||||
id: res.id,
|
||||
name: res.name,
|
||||
location: res.location || '',
|
||||
image: res.image_url,
|
||||
address: res.address || '',
|
||||
}));
|
||||
setRestaurants(mappedRestaurants);
|
||||
} else {
|
||||
alert(result.message || 'خطأ في جلب المطاعم');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
fetchRestaurants();
|
||||
}, []);
|
||||
|
||||
const handleDrawerToggle = () => {};
|
||||
const handleCreateRestaurant = () => {
|
||||
alert('Redirect to restaurant creation page or open a form!');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={false}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
|
||||
<RestaurantSelection
|
||||
restaurants={restaurants}
|
||||
onCreateRestaurant={handleCreateRestaurant}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestaurantProfile;
|
||||
186
src/components/Home/ResList/RestaurantSelection.js
Normal file
186
src/components/Home/ResList/RestaurantSelection.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Grid,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
IconButton,
|
||||
Skeleton
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import AddToPhotosIcon from '@mui/icons-material/AddToPhotos';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import RestaurantDetailsModal from './RestaurantDetailsModal';
|
||||
import authService from '../../../services/authService';
|
||||
const RestaurantSelection = ({ restaurants = [], loading = false }) => {
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const navigate = useNavigate(); // استخدم navigate للتوجيه
|
||||
const { setRestaurantId } = useRestaurant();
|
||||
|
||||
const [selectedRestaurant, setSelectedRestaurant] = useState(null);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const handleSelectRestaurant = (id) => {
|
||||
setRestaurantId(id);
|
||||
navigate('/dashboard');
|
||||
};
|
||||
|
||||
const fetchRestaurantDetails = async (id) => {
|
||||
try {
|
||||
const result = await authService.getRestaurantById(id);
|
||||
if (result.success) {
|
||||
setSelectedRestaurant(result.data);
|
||||
setModalOpen(true);
|
||||
} else {
|
||||
alert(result.message || 'Failed to fetch restaurant details');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching restaurant details:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ دالة التوجيه عند الضغط على زر الإنشاء
|
||||
const handleCreateClick = () => {
|
||||
navigate('/create-restaurant'); // توجه مباشرة إلى صفحة الإنشاء
|
||||
};
|
||||
return (
|
||||
<Box sx={{ py: 8, backgroundColor: '#FAFAFA', minHeight: '100vh' }}>
|
||||
{/* زر إنشاء مطعم */}
|
||||
<Grid container spacing={1} justifyContent={isSmall ? 'center' : 'flex-start'} pl={isSmall ? 0 : 2}>
|
||||
<Box
|
||||
onClick={handleCreateClick}
|
||||
sx={{
|
||||
p: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
borderRadius: 3,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxWidth: 250,
|
||||
maxHeight: 186,
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.3s',
|
||||
backgroundColor: '#fff',
|
||||
ml: { xs: 0, sm: 8, md: 28 },
|
||||
'&:hover': { transform: 'translateY(-5px)' },
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: 52, height: 52, borderRadius: '50%', backgroundColor: '#F6F6F6', display: 'flex', alignItems: 'center', justifyContent: 'center', mb: 1 }}>
|
||||
<AddToPhotosIcon sx={{ fontSize: 30, color: theme.palette.primary.main }} />
|
||||
</Box>
|
||||
|
||||
<Typography fontWeight="600" variant="subtitle1" fontSize="1.25rem">
|
||||
Create New Restaurant
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Start from scratch and build your restaurant profile
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* قائمة المطاعم */}
|
||||
<Box sx={{ mt: 5, ml: { xs: 0, sm: 10, md: 30 } }}>
|
||||
<Typography variant="h6" fontWeight="bold" mb={3} textAlign={isSmall ? 'center' : 'flex-start'}>
|
||||
Existing Restaurants
|
||||
</Typography>
|
||||
|
||||
{restaurants.length === 0 && !loading ? (
|
||||
<Typography textAlign={isSmall ? 'center' : 'flex-start'} color="text.secondary" fontSize="1rem">
|
||||
No restaurants found. Create one to get started.
|
||||
</Typography>
|
||||
) : (
|
||||
<Grid container spacing={4} justifyContent={isSmall ? 'center' : 'flex-start'}>
|
||||
{(loading ? Array.from(new Array(3)) : restaurants).map((restaurant, index) => (
|
||||
<Grid key={restaurant?.id || index}>
|
||||
{loading ? (
|
||||
<Skeleton variant="rectangular" width={230} height={162} sx={{ borderRadius: 3 }} />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: 3,
|
||||
p: 3,
|
||||
width: '230px',
|
||||
height: '90%',
|
||||
maxHeight: '162px',
|
||||
transition: 'transform 0.2s',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { transform: 'translateY(-3px)' },
|
||||
mx: 'auto',
|
||||
}}
|
||||
onClick={() => handleSelectRestaurant(restaurant.id)}
|
||||
>
|
||||
{/* أيقونة فتح المودال */}
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
fetchRestaurantDetails(restaurant.id);
|
||||
}}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255,255,255,1)',
|
||||
transform: 'scale(1.1)',
|
||||
transition: 'transform 0.2s'
|
||||
},
|
||||
zIndex: 2,
|
||||
padding: 0.5
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<InfoIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
{/* صورة المطعم */}
|
||||
<Box
|
||||
sx={{ width: 110, height: 100, overflow: 'hidden', border: '2px solid #e0e0e0', mb: 1.5, mx: 'auto' }}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={restaurant.image || '/images/default-restaurant.png'}
|
||||
alt={restaurant.name}
|
||||
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* باقي الكارت */}
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Typography fontWeight="600" textAlign="center" fontSize="1.25rem" mb={0.5}>
|
||||
{restaurant.name}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" fontSize="0.9rem" textAlign="center" mb={1}>
|
||||
{restaurant.location || ' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* مودال المطعم */}
|
||||
<RestaurantDetailsModal
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
restaurant={selectedRestaurant}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestaurantSelection;
|
||||
@@ -1,165 +1,159 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import LoginRestaurant from './contcet/LoginRestaurant';
|
||||
import React, { useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import BasicInformation from './contcet/BasicInformation';
|
||||
import ContactInformation from './contcet/ContactInformation';
|
||||
import BusinessHours from './contcet/BusinessHours';
|
||||
import UploadMenu from './contcet/UploadMenu';
|
||||
import Equipment from './contcet/Equipment';
|
||||
import TypeOfRestaurant from './contcet/TypeOfRestaurant';
|
||||
import OperationalCapacity from './contcet/OperationalCapacity';
|
||||
import UploadPhotos from './contcet/UploadPhotos';
|
||||
import SideProfile from './SideProfile';
|
||||
|
||||
const drawerWidth = 230;
|
||||
import AccountProfile from '../Settings/AccountProfile';
|
||||
import authService from '../../../services/authService';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import ConfirmationDialog from './contcet/ConfirmationDialog';
|
||||
|
||||
const RestaurantProfile = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [hasProducts, setHasProducts] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// ⬇️ إدارة الخطوة الحالية
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [formData, setFormData] = useState({});
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [showSetting, setShowSetting] = useState(false);
|
||||
|
||||
const updateFormData = (newData) =>
|
||||
setFormData((prev) => ({ ...prev, ...newData }));
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
const payload = {
|
||||
restaurant_name: formData.restaurant_name || '',
|
||||
restaurant_type: formData.restaurant_type || '',
|
||||
Address: formData.Address || '',
|
||||
City: formData.City || '',
|
||||
Postal_code: formData.Postal_code || '',
|
||||
Phone: formData.Phone || '',
|
||||
Email: formData.Email || '',
|
||||
Operation_hour: formData.Operation_hour || '',
|
||||
closed_days: formData.closed_days || [],
|
||||
equipment: {
|
||||
...formData.equipment // ✅ أخذ جميع المعدات من Equipment مباشرة
|
||||
},
|
||||
Maximum_orders_per_day: formData.Maximum_orders_per_day || 0,
|
||||
Number_of_Cheff: formData.Number_of_Cheff || 0,
|
||||
Number_of_Waiters: formData.Number_of_Waiters || 0,
|
||||
Number_of_Cookers: formData.Number_of_Cookers || 0,
|
||||
host_type: formData.ownRestaurantType || '',
|
||||
collaboration_type: formData.collaborateRestaurantType || ''
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await authService.createRestaurantProfile(restaurantId, payload);
|
||||
if (res.success) {
|
||||
setModalOpen(true);
|
||||
|
||||
// بعد 10 ثواني يغلق المودال ويظهر الـ AccountProfile
|
||||
setTimeout(() => {
|
||||
setModalOpen(false);
|
||||
setShowSetting(true);
|
||||
}, 10000); // ✅ صار 10 ثواني بدل 5
|
||||
} else {
|
||||
console.error("Profile creation failed:", res.message, res.errors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const steps = [
|
||||
<BasicInformation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<ContactInformation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<BusinessHours onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<UploadMenu onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<Equipment onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<OperationalCapacity onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<TypeOfRestaurant onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<UploadPhotos onBack={() => setCurrentStep(currentStep - 1)} />,
|
||||
<BasicInformation
|
||||
key="step-0"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep((p) => p + 1)}
|
||||
/>,
|
||||
<ContactInformation
|
||||
key="step-1"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep((p) => p + 1)}
|
||||
onBack={() => setCurrentStep((p) => p - 1)}
|
||||
/>,
|
||||
<BusinessHours
|
||||
key="step-2"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep((p) => p + 1)}
|
||||
onBack={() => setCurrentStep((p) => p - 1)}
|
||||
/>,
|
||||
<Equipment
|
||||
key="step-3"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep((p) => p + 1)}
|
||||
onBack={() => setCurrentStep((p) => p - 1)}
|
||||
/>,
|
||||
<OperationalCapacity
|
||||
key="step-4"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onNext={() => setCurrentStep((p) => p + 1)}
|
||||
onBack={() => setCurrentStep((p) => p - 1)}
|
||||
/>,
|
||||
<TypeOfRestaurant
|
||||
key="step-5"
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onBack={() => setCurrentStep((p) => p - 1)}
|
||||
onRegister={handleSubmit}
|
||||
/>
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
};
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex', height: '100vh',
|
||||
|
||||
}}>
|
||||
<Box sx={{
|
||||
height: '100vh',
|
||||
ml: 3,
|
||||
mb: 2,
|
||||
width: { md: '30%' },
|
||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 15, pt: 3,
|
||||
|
||||
}}>
|
||||
<SideProfile
|
||||
currentStepIndex={currentStep}
|
||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
ml: { xs: 2, md: 3 },
|
||||
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
pr: { sm: 2, md: 1 },
|
||||
|
||||
pt: 3,
|
||||
mb: { sm: 20 },
|
||||
width: { md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
<Box sx={{
|
||||
minHeight: '100%', pb: 18,
|
||||
|
||||
}}>
|
||||
{steps[currentStep]}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', height: '100%', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
|
||||
{/* إظهار SideProfile فقط إذا لم يتم التحويل إلى AccountProfile */}
|
||||
{!showSetting && (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: { md: '30%' },
|
||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' }
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minHeight: '100%', pb: 15, pt: 1 }}>
|
||||
<SideProfile
|
||||
currentStepIndex={currentStep}
|
||||
onBack={() => setCurrentStep((prev) => Math.max(prev - 1, 0))}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
ml: { xs: 2, md: 3 },
|
||||
flexGrow: 1,
|
||||
height: '100%',
|
||||
pr: { sm: 2, md: 1 },
|
||||
pt: 1,
|
||||
mb: { sm: 20 },
|
||||
width: { md: '60%' },
|
||||
display: 'block',
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' }
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minHeight: '100%', pb: 18 }}>
|
||||
{showSetting ? <AccountProfile /> : steps[currentStep]}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* مودال بدون أزرار */}
|
||||
<ConfirmationDialog open={modalOpen} onClose={() => {}} hideButtons={true} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,11 +6,11 @@ const steps = [
|
||||
{ title: 'Basic Information', icon: '/images/createProfile/BasicInf.png' },
|
||||
{ title: 'Contact Information', icon: '/images/createProfile/ContactInf.png' },
|
||||
{ title: 'Business Hours', icon: '/images/createProfile/BusinessHours.png' },
|
||||
{ title: 'Menu Upload', icon: '/images/createProfile/MenuUpload.png' },
|
||||
// { title: 'Menu Upload', icon: '/images/createProfile/MenuUpload.png' },
|
||||
{ title: 'Available Equipment', icon: '/images/createProfile/equipment.png' },
|
||||
{ title: 'Operational Capacity', icon: '/images/icons/rocket.png' },
|
||||
{ title: 'Type of Restaurant', icon: '/images/createProfile/TypeOfRestaurant.png' },
|
||||
{ title: 'Upload Photos', icon: '/images/createProfile/UploadPhotos.png' },
|
||||
// { title: 'Upload Photos', icon: '/images/createProfile/UploadPhotos.png' },
|
||||
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||
];
|
||||
|
||||
|
||||
124
src/components/Home/RestaurantProfile/all.js
Normal file
124
src/components/Home/RestaurantProfile/all.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { CircularProgress, Box, useTheme, useMediaQuery, Skeleton } from "@mui/material";
|
||||
import RestaurantProfile from "./RestaurantProfile";
|
||||
import Setting from "../Settings/AccountProfile";
|
||||
import authService from "../../../services/authService";
|
||||
import { useRestaurant } from "../../../contexts/RestaurantContext";
|
||||
import KitchPlusAppBar from "../AppBar";
|
||||
import Sidebar from "../SideHome"
|
||||
const drawerWidth = 230;
|
||||
|
||||
const RestaurantWrapper = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const { restaurantId } = useRestaurant();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [hasProfile, setHasProfile] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
const fetchProfile = useCallback(async () => {
|
||||
if (!restaurantId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const profile = await authService.getRestaurantProfile(restaurantId);
|
||||
setHasProfile(profile && Object.keys(profile).length > 0);
|
||||
} catch (error) {
|
||||
console.error("Error checking restaurant profile:", error);
|
||||
setHasProfile(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [restaurantId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile();
|
||||
}, [fetchProfile]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () =>
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: "100vh",
|
||||
backgroundColor: "#F6F6F6",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* البار الجانبي */}
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
{/* المحتوى الرئيسي */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginLeft: {
|
||||
xs: 0,
|
||||
sm: sidebarOpen ? `${drawerWidth}px` : 0,
|
||||
md: 0,
|
||||
},
|
||||
transition: theme.transitions.create(["width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{/* البار العلوي */}
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
||||
{/* المحتوى حسب وجود البروفايل */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
scrollbarWidth: "none",
|
||||
"&::-webkit-scrollbar": { display: "none" },
|
||||
pt: { xs: 2, sm: 3, md: 3 },
|
||||
pl: { xs: 2, sm: 3, md: 3 },
|
||||
pr: { xs: 2, sm: 3, md: 3 },
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
// Skeleton placeholder
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Skeleton variant="rectangular" height={40} width="40%" />
|
||||
<Skeleton variant="rectangular" height={40} width="60%" />
|
||||
<Skeleton variant="rectangular" height={200} width="100%" />
|
||||
<Skeleton variant="rectangular" height={40} width="80%" />
|
||||
</Box>
|
||||
) : hasProfile ? (
|
||||
<Setting />
|
||||
) : (
|
||||
<RestaurantProfile refreshProfile={fetchProfile} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestaurantWrapper;
|
||||
@@ -1,17 +1,49 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
|
||||
const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const BasicInformation = ({ formData, updateFormData, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
updateFormData({ [field]: value });
|
||||
// إزالة رسالة الخطأ عند التعديل
|
||||
setErrors(prev => ({ ...prev, [field]: '' }));
|
||||
};
|
||||
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.restaurant_name || formData.restaurant_name.trim() === '') newErrors.restaurant_name = 'Restaurant Name is required';
|
||||
if (!formData.restaurant_type || formData.restaurant_type.trim() === '') newErrors.restaurant_type = 'Restaurant Type is required';
|
||||
if (!formData.Address || formData.Address.trim() === '') newErrors.Address = 'Address is required';
|
||||
if (!formData.City || formData.City.trim() === '') newErrors.City = 'City is required';
|
||||
if (!formData.Postal_code || formData.Postal_code.trim() === '') newErrors.Postal_code = 'Postal Code is required';
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
if (validateFields()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods', field: 'restaurant_name' },
|
||||
{ label: 'Restaurant Type', placeholder: 'Fast Food', field: 'restaurant_type' },
|
||||
{ label: 'Address', placeholder: 'Ward # 13', field: 'Address' },
|
||||
{ label: 'City', placeholder: 'Jordan', field: 'City' },
|
||||
{ label: 'Postal Code', placeholder: '31200', field: 'Postal_code' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: {xs:4,md: 4.5},
|
||||
|
||||
pt: { xs: 4, md: 4.5 },
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
@@ -20,19 +52,16 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Stack spacing={1.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
||||
}}
|
||||
>
|
||||
Basic Information
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
@@ -44,23 +73,21 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
||||
{[
|
||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
||||
{ label: 'Restaurant Type', placeholder: 'Fast Food' },
|
||||
{ label: 'Address', placeholder: 'Ward # 13' },
|
||||
{ label: 'City', placeholder: 'Jordan' },
|
||||
{ label: 'Postal Code', placeholder: '31200' },
|
||||
].map((field, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{fields.map((fieldObj, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column'}}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{field.label}
|
||||
{fieldObj.label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={field.placeholder}
|
||||
placeholder={fieldObj.placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData[fieldObj.field] || ''}
|
||||
onChange={(e) => handleChange(fieldObj.field, e.target.value)}
|
||||
error={!!errors[fieldObj.field]}
|
||||
helperText={errors[fieldObj.field] || ' '}
|
||||
sx={{
|
||||
pb:-1,
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
@@ -80,12 +107,11 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Box>
|
||||
))}
|
||||
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNextClick}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -103,7 +129,6 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -116,7 +141,7 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -128,7 +153,6 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@@ -11,28 +11,62 @@ import {
|
||||
FormControlLabel
|
||||
} from '@mui/material';
|
||||
|
||||
const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const BusinessHours = ({ formData, updateFormData, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [selectedDays, setSelectedDays] = useState([]);
|
||||
|
||||
const handleCheckboxChange = (day) => {
|
||||
setSelectedDays((prev) =>
|
||||
prev.includes(day)
|
||||
? prev.filter((d) => d !== day)
|
||||
: [...prev, day]
|
||||
);
|
||||
};
|
||||
const [selectedDays, setSelectedDays] = useState(formData.closed_days || []);
|
||||
const [operationHours, setOperationHours] = useState(formData.Operation_hour || '');
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
useEffect(() => {
|
||||
updateFormData({
|
||||
Operation_hour: operationHours, // الاسم الآن مطابق للـ API
|
||||
closed_days: selectedDays
|
||||
});
|
||||
}, [operationHours, selectedDays]);
|
||||
|
||||
const handleCheckboxChange = (day) => {
|
||||
const updatedDays = selectedDays.includes(day)
|
||||
? selectedDays.filter(d => d !== day)
|
||||
: [...selectedDays, day];
|
||||
setSelectedDays(updatedDays);
|
||||
};
|
||||
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
if (!operationHours || operationHours.trim() === '') {
|
||||
newErrors.operationHours = 'Operational hours are required';
|
||||
} else {
|
||||
// Regex للتحقق من الصيغة hh:mm AM/PM - hh:mm AM/PM
|
||||
const regex = /^([0]?[1-9]|1[0-2]):([0-5][0-9])\s?(AM|PM)\s?-\s?([0]?[1-9]|1[0-2]):([0-5][0-9])\s?(AM|PM)$/i;
|
||||
if (!regex.test(operationHours.trim())) {
|
||||
newErrors.operationHours = 'Invalid format (e.g., "9:00 AM - 11:00 PM")';
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDays.length === 7) {
|
||||
newErrors.closedDays = 'Restaurant cannot be closed all week';
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
if (validateFields()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: {xs:4,md:4.5},
|
||||
pt: { xs: 4, md: 4.5 },
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
@@ -41,27 +75,13 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack spacing={1.5}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Business Hours
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your business hours to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -72,9 +92,13 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Operational Hours
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="16 hours"
|
||||
placeholder="9:00 AM - 11:00 PM"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={operationHours}
|
||||
onChange={(e) => setOperationHours(e.target.value)}
|
||||
error={!!errors.operationHours}
|
||||
helperText={errors.operationHours || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -96,22 +120,11 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
|
||||
{/* Checkboxes for Days of the Week */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="black"
|
||||
sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}
|
||||
>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}>
|
||||
Closed Days
|
||||
</Typography>
|
||||
<FormGroup
|
||||
row
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{days.map((day) => {
|
||||
<FormGroup row sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{days.map(day => {
|
||||
const isChecked = selectedDays.includes(day);
|
||||
return (
|
||||
<FormControlLabel
|
||||
@@ -123,82 +136,69 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
sx={{
|
||||
transform: 'scale(1.5)',
|
||||
color: '#F0EDED',
|
||||
'&.Mui-checked': {
|
||||
color: '#FF914D',
|
||||
},
|
||||
'&.Mui-checked': { color: '#FF914D' },
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography
|
||||
sx={{
|
||||
color: isChecked ? '#e57f3f' : '#969BA7',
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: isChecked ? '#e57f3f' : '#969BA7', fontWeight: 500 }}>
|
||||
{day}
|
||||
</Typography>
|
||||
}
|
||||
sx={{
|
||||
m: 0,
|
||||
width: { xs: '45%', sm: '30%', md: '22%' },
|
||||
}}
|
||||
sx={{ m: 0, width: { xs: '45%', sm: '30%', md: '22%' } }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FormGroup>
|
||||
{errors.closedDays && (
|
||||
<Typography color="error" fontSize="0.875rem" sx={{ mt: 0.5 }}>
|
||||
{errors.closedDays}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Next Button */}
|
||||
|
||||
{/* Next & Back Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleNextClick}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.light, borderColor: theme.palette.primary.main },
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -4,22 +4,19 @@ import {
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
useTheme,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
|
||||
const ConfirmationDialog = ({ open, onClose }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="sm"
|
||||
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
@@ -33,148 +30,80 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
|
||||
p: 3,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 2,
|
||||
mb: 3,
|
||||
ml:-5,
|
||||
}}
|
||||
>
|
||||
{/* الدوائر */}
|
||||
<Box sx={{
|
||||
position: 'relative',
|
||||
width: 100,
|
||||
height: 100,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mb: 3
|
||||
}}>
|
||||
<Box sx={{
|
||||
position: 'relative',
|
||||
width: 100,
|
||||
height: 100,
|
||||
position: 'absolute',
|
||||
backgroundColor: '#F5F8FF',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
zIndex: 0,
|
||||
}} />
|
||||
<Box sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#E9EFFF',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1,
|
||||
}}>
|
||||
{/* الدائرة الخلفية */}
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
backgroundColor: '#F5F8FF',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
zIndex: 0,
|
||||
}} />
|
||||
|
||||
{/* الدائرة الأمامية مع الأيقونة */}
|
||||
<Box sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#E9EFFF',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1,
|
||||
}}>
|
||||
<img
|
||||
src='/images/createProfile/Successful.png'
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
alt="Success icon"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* العنوان بجانب الدائرة */}
|
||||
<DialogTitle sx={{ p: 0 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="div"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '28px',
|
||||
lineHeight: '1.2'
|
||||
<img
|
||||
src='/images/createProfile/Successful.png'
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
>
|
||||
Register successful!
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
alt="Success icon"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogContent sx={{ p: 0 }}>
|
||||
<DialogContentText sx={{
|
||||
textAlign: 'center',
|
||||
mb: 0,
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.secondary
|
||||
}}>
|
||||
Congratulations! you have registered your restaurant successfully on our platform
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions
|
||||
sx={{
|
||||
p: 0,
|
||||
px: 3,
|
||||
pt: 2,
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* الزر الأول - Filled */}
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '16px',
|
||||
height: '48px',
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.dark,
|
||||
},
|
||||
}}
|
||||
>
|
||||
View Profile
|
||||
</Button>
|
||||
|
||||
{/* الزر الثاني - Outlined + أيقونة Edit */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onClose}
|
||||
startIcon={<EditIcon sx={{ color: '#00081D' }} />}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '16px',
|
||||
height: '48px',
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
borderColor: '#E6E7EA',
|
||||
color: '#00081D',
|
||||
'&:hover': {
|
||||
backgroundColor: '#FAFAFA',
|
||||
borderColor: '#E6E7EA',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DialogActions>
|
||||
<DialogTitle sx={{ p: 0 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="div"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '28px',
|
||||
lineHeight: '1.2',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
Register successful!
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ p: 0, mt: 2 }}>
|
||||
<DialogContentText sx={{
|
||||
textAlign: 'center',
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.secondary
|
||||
}}>
|
||||
Congratulations! You have registered your restaurant successfully on our platform
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmationDialog;
|
||||
export default ConfirmationDialog;
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
|
||||
const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const ContactInformation = ({ formData, updateFormData, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleNext = () => {
|
||||
if (onNext) {
|
||||
const handleChange = (field, value) => {
|
||||
updateFormData({ [field]: value });
|
||||
setErrors(prev => ({ ...prev, [field]: '' })); // إزالة الخطأ عند التعديل
|
||||
};
|
||||
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.Phone || formData.Phone.trim() === '') {
|
||||
newErrors.Phone = 'Phone number is required';
|
||||
} else if (!/^\+?\d{7,15}$/.test(formData.Phone.trim())) {
|
||||
newErrors.Phone = 'Invalid phone number';
|
||||
}
|
||||
|
||||
if (!formData.Email || formData.Email.trim() === '') {
|
||||
newErrors.Email = 'Email is required';
|
||||
} else if (!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(formData.Email.trim())) {
|
||||
newErrors.Email = 'Invalid Email address';
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
if (validateFields()) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
@@ -25,26 +49,13 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack spacing={1.5}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Contact Information
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your contact information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -58,6 +69,10 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
placeholder="03055376864"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.Phone || ''}
|
||||
onChange={(e) => handleChange('Phone', e.target.value)}
|
||||
error={!!errors.Phone}
|
||||
helperText={errors.Phone || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -84,9 +99,13 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="example@example.com"
|
||||
type="email"
|
||||
type="Email"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={formData.Email || ''}
|
||||
onChange={(e) => handleChange('Email', e.target.value)}
|
||||
error={!!errors.Email}
|
||||
helperText={errors.Email || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
@@ -105,56 +124,49 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Next Button */}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
{/* Next & Back Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleNextClick}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.light, borderColor: theme.palette.primary.main },
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,66 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField,
|
||||
Modal,
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const Equipment = ({ formData, updateFormData, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleNext = () => {
|
||||
if (onNext) onNext();
|
||||
else navigate('/dashboard');
|
||||
const [equipment, setEquipment] = useState(formData.equipment || {
|
||||
oven: '',
|
||||
grill: '',
|
||||
freezer: '',
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [newEquipment, setNewEquipment] = useState({ name: '', quantity: '' });
|
||||
|
||||
useEffect(() => {
|
||||
updateFormData({ equipment });
|
||||
}, [equipment]);
|
||||
|
||||
const handleInputChange = (key, value) => {
|
||||
setEquipment(prev => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
Object.entries(equipment).forEach(([key, value]) => {
|
||||
if (!value || isNaN(value) || Number(value) < 0) {
|
||||
newErrors[key] = `${key} must be a positive number`;
|
||||
}
|
||||
});
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
if (validateFields()) {
|
||||
if (onNext) onNext();
|
||||
else navigate('/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddEquipment = () => {
|
||||
if (!newEquipment.name.trim() || !newEquipment.quantity || isNaN(newEquipment.quantity)) {
|
||||
return;
|
||||
}
|
||||
setEquipment(prev => ({
|
||||
...prev,
|
||||
[newEquipment.name.toLowerCase()]: newEquipment.quantity,
|
||||
}));
|
||||
setNewEquipment({ name: '', quantity: '' });
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -27,30 +78,29 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Stack spacing={1}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Equipment
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500}>
|
||||
Enter your kitchen equipment details to complete your restaurant registration.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Oven Input */}
|
||||
<InputField label="Oven" placeholder="5" theme={theme} />
|
||||
|
||||
{/* Grill Input */}
|
||||
<InputField label="Grill" placeholder="20" theme={theme} />
|
||||
|
||||
{/* Refrigerators Input */}
|
||||
<InputField label="Refrigerators" placeholder="200" theme={theme} />
|
||||
{/* Dynamic Equipment Fields */}
|
||||
{Object.keys(equipment).map((key) => (
|
||||
<InputField
|
||||
key={key}
|
||||
label={key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
placeholder="0"
|
||||
theme={theme}
|
||||
value={equipment[key]}
|
||||
onChange={(val) => handleInputChange(key, val)}
|
||||
error={errors[key]}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Add More Equipment Button */}
|
||||
<Box>
|
||||
@@ -65,23 +115,20 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
}
|
||||
'&:hover': { backgroundColor: 'transparent' }
|
||||
}}
|
||||
onClick={() => console.log('Add More Equipment')}
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Add More
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Next Button */}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
onClick={handleNextClick}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
@@ -91,15 +138,12 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -112,7 +156,7 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
@@ -124,14 +168,61 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
|
||||
{/* Modal for Adding Equipment */}
|
||||
<Modal open={modalOpen} onClose={() => setModalOpen(false)}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 400,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Add New Equipment
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Equipment Name"
|
||||
value={newEquipment.name}
|
||||
onChange={(e) => setNewEquipment({ ...newEquipment, name: e.target.value })}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Quantity"
|
||||
type="number"
|
||||
value={newEquipment.quantity}
|
||||
onChange={(e) => setNewEquipment({ ...newEquipment, quantity: e.target.value })}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleAddEquipment}
|
||||
sx={{
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// مكون فرعي لتقليل التكرار في الحقول
|
||||
const InputField = ({ label, placeholder, theme }) => (
|
||||
const InputField = ({ label, placeholder, theme, value, onChange, error }) => (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||
{label}
|
||||
@@ -140,6 +231,10 @@ const InputField = ({ label, placeholder, theme }) => (
|
||||
placeholder={placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
error={!!error}
|
||||
helperText={error || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TextField, Button, Typography, Stack, Box, IconButton, InputAdornment } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import { VisibilityOutlined } from '@mui/icons-material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
const LoginForm = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const handleTogglePassword = () => {
|
||||
setShowPassword((prev) => !prev);
|
||||
};
|
||||
const handleLogin = () => {
|
||||
// بعد تنفيذ عملية تسجيل الدخول بنجاح، يتم التوجيه
|
||||
navigate('/dashboard');
|
||||
};
|
||||
return (
|
||||
|
||||
|
||||
<Box
|
||||
|
||||
display="flex"
|
||||
sx={{
|
||||
width: '140%',
|
||||
height: '90vh',
|
||||
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Login Content */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
flex={1}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
paddingLeft={0}
|
||||
paddingRight={0}
|
||||
sx={{
|
||||
// backgroundColor: '#FFFFFF',
|
||||
minHeight: '10vh',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
position: 'sticky',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: '#FFFFFF',
|
||||
width: '100%',
|
||||
maxWidth: 500,
|
||||
paddingTop: 8, // زيادة المسافة من الأعلى داخل الكرت
|
||||
paddingX: 10, // الجوانب
|
||||
paddingBottom: 8, // الأسفل
|
||||
borderRadius: '16px',
|
||||
// boxShadow: '0px 4px 20px rgba(0,0,0,0.1)',
|
||||
}}
|
||||
>
|
||||
|
||||
<Stack spacing={1.5}>
|
||||
|
||||
<Typography
|
||||
variant="h4"
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your username and password to access your account securely. Welcome back to our service!
|
||||
</Typography>
|
||||
|
||||
{/* Email Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Email
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Password Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Password
|
||||
</Typography>
|
||||
<TextField
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Enter your password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
},
|
||||
'& input::-ms-reveal, & input::-ms-clear': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleTogglePassword} edge="end">
|
||||
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Login Button */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleLogin}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover
|
||||
}
|
||||
, fontFamily: 'PlusJakartaSans'
|
||||
}}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Divider */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, py: 2 }}>
|
||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||
<Typography variant="body1" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' , fontFamily: 'PlusJakartaSans' }}>
|
||||
Or
|
||||
</Typography>
|
||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||
</Box>
|
||||
|
||||
{/* Google Button */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: '16px',
|
||||
borderRadius: '50px',
|
||||
height: '50px',
|
||||
textTransform: 'none',
|
||||
gap: 1,
|
||||
borderColor: '#E6E6E6',
|
||||
color: 'black',
|
||||
'&:hover': {
|
||||
borderColor: 'black',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' , fontFamily: 'PlusJakartaSans' }}>
|
||||
<img
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg"
|
||||
alt="Google"
|
||||
style={{ width: 25, height: 25 }}
|
||||
/>
|
||||
</Box>
|
||||
Login with Google
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Register Link */}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontSize: '16px',
|
||||
textAlign: 'center',
|
||||
color: '#969BA7',
|
||||
pt: 3
|
||||
}}
|
||||
>
|
||||
Don’t have an account?{' '}
|
||||
<Link
|
||||
to="/register"
|
||||
style={{
|
||||
color: '#2261FF',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
Register
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
@@ -1,16 +1,83 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||
|
||||
const OperationalCapacity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const OperationalCapacity = ({ formData, updateFormData, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [capacity, setCapacity] = useState({
|
||||
Maximum_orders_per_day: formData.Maximum_orders_per_day || '',
|
||||
Number_of_Cheff: formData.Number_of_Cheff || '',
|
||||
Number_of_Waiters: formData.Number_of_Waiters || '',
|
||||
Number_of_Cookers: formData.Number_of_Cookers || '',
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
if (/^\d*$/.test(value)) { // السماح فقط بالأرقام
|
||||
setCapacity(prev => ({ ...prev, [field]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
Object.entries(capacity).forEach(([key, value]) => {
|
||||
if (!value || isNaN(Number(value)) || Number(value) < 0) {
|
||||
newErrors[key] = 'Please enter a positive number';
|
||||
}
|
||||
});
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
if (validateFields()) {
|
||||
updateFormData(capacity); // تحديث الأب عند الضغط على Next فقط
|
||||
if (onNext) onNext();
|
||||
}
|
||||
};
|
||||
|
||||
const InputField = ({ label, value, onChange, placeholder, error }) => (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
placeholder={placeholder}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
error={!!error}
|
||||
helperText={error || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md:740 },
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: {xs:4,md:12},
|
||||
pt: { xs: 4, md: 12 },
|
||||
pb: 4,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
@@ -19,192 +86,94 @@ const OperationalCapacity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack spacing={1.5}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Operational Capacity
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Maximum Orders Per Day Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Maximum Orders Per Day
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="5000"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<InputField
|
||||
label="Maximum Orders Per Day"
|
||||
value={capacity.Maximum_orders_per_day}
|
||||
onChange={(val) => handleChange('Maximum_orders_per_day', val)}
|
||||
placeholder="5000"
|
||||
error={errors.Maximum_orders_per_day}
|
||||
/>
|
||||
|
||||
{/* Number of Cheff Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Number of Cheff
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="20"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<InputField
|
||||
label="Number of Chefs"
|
||||
value={capacity.Number_of_Cheff}
|
||||
onChange={(val) => handleChange('Number_of_Cheff', val)}
|
||||
placeholder="20"
|
||||
error={errors.Number_of_Cheff}
|
||||
/>
|
||||
|
||||
{/* Number of Waiters Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Number of Waiters
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="200"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<InputField
|
||||
label="Number of Waiters"
|
||||
value={capacity.Number_of_Waiters}
|
||||
onChange={(val) => handleChange('Number_of_Waiters', val)}
|
||||
placeholder="200"
|
||||
error={errors.Number_of_Waiters}
|
||||
/>
|
||||
|
||||
{/* Number of Kitchen Assistant Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Number of Kitchen Assistant
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="200"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<InputField
|
||||
label="Number of Kitchen Assistants"
|
||||
value={capacity.Number_of_Cookers}
|
||||
onChange={(val) => handleChange('Number_of_Cookers', val)}
|
||||
placeholder="200"
|
||||
error={errors.Number_of_Cookers}
|
||||
/>
|
||||
|
||||
{/* Buttons */}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleNextClick}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,165 +1,157 @@
|
||||
// TypeOfRestaurant.jsx
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Stack, Button, useTheme, FormHelperText, TextField, Select, MenuItem } from '@mui/material';
|
||||
|
||||
const TypeOfRestaurant = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const TypeOfRestaurant = ({ formData, updateFormData, onBack, onRegister }) => {
|
||||
const theme = useTheme();
|
||||
const [typeData, setTypeData] = useState({
|
||||
ownRestaurantType: formData.ownRestaurantType || '',
|
||||
collaborateRestaurantType: formData.collaborateRestaurantType || ''
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 700 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: { xs: 4, md: 19.5 },
|
||||
pb: { xs: 20, sm: 20, md: 0 },
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
{/* العنوان */}
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Type of Restaurant
|
||||
</Typography>
|
||||
const collaborationOptions = [
|
||||
{ value: 'fine_dining', label: 'Fine Dining' },
|
||||
{ value: 'catering_services', label: 'Catering Services' },
|
||||
{ value: 'fast_food', label: 'Fast Food' },
|
||||
{ value: 'cloud_kitchen', label: 'Cloud Kitchen' }
|
||||
];
|
||||
|
||||
{/* الوصف */}
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your restaurant type you want to collaborate to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
useEffect(() => {
|
||||
updateFormData(typeData);
|
||||
}, [typeData]);
|
||||
|
||||
{/* الحقول */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Open to Host Restaurant/Cousin type*
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Fine Dining"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
const handleChange = (field, value) => setTypeData(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Select Restaurant type Willing to Collaborate With
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Catering Services"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
const validateFields = () => {
|
||||
const newErrors = {};
|
||||
if (!typeData.ownRestaurantType) newErrors.ownRestaurantType = 'This field is required';
|
||||
if (!typeData.collaborateRestaurantType) newErrors.collaborateRestaurantType = 'This field is required';
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
const handleRegister = async () => {
|
||||
if (!validateFields()) return;
|
||||
|
||||
</Stack>
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// استدعاء الدالة المرسلة من الأب لتسجيل المطعم
|
||||
await onRegister();
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ height: { xs: '90%', sm: '100%', md: 700 }, backgroundColor: '#FFFFFF', px: 4, pt: { xs: 4, md: 19.5 }, pb: { xs: 20, sm: 20, md: 0 }, display: 'block', borderRadius: 2, boxShadow: '0px 1px 4px rgba(0,0,0,0.05)', position: 'relative', width: { xs: '85%', sm: '90%' } }}>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Type of Restaurant
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your restaurant type you want to collaborate to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Open to Host Restaurant/Cousin type*
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Enter your restaurant type"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={typeData.ownRestaurantType}
|
||||
onChange={(e) => handleChange('ownRestaurantType', e.target.value)}
|
||||
error={!!errors.ownRestaurantType}
|
||||
helperText={errors.ownRestaurantType || ' '}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
'&.Mui-focused fieldset': { borderColor: theme.palette.primary.main }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 2 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Select Restaurant type Willing to Collaborate With*
|
||||
</Typography>
|
||||
<Select
|
||||
value={typeData.collaborateRestaurantType}
|
||||
onChange={(e) => handleChange('collaborateRestaurantType', e.target.value)}
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{
|
||||
borderRadius: '10px',
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: theme.palette.primary.main },
|
||||
'& .MuiSelect-select': { fontWeight: 500, fontSize: '15px', padding: '12px' }
|
||||
}}
|
||||
>
|
||||
<MenuItem value="" disabled>Select an option</MenuItem>
|
||||
{collaborationOptions.map(opt => (
|
||||
<MenuItem key={opt.value} value={opt.value}>{opt.label}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error>{errors.collaborateRestaurantType || ' '}</FormHelperText>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleRegister}
|
||||
disabled={isSubmitting}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: theme.palette.primary.hover }
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? 'Registering...' : 'Register'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypeOfRestaurant;
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
|
||||
const UploadMenu = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: {xs:4,md:4.5},
|
||||
pb: 10,
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
mx: 'auto'
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Menu
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography
|
||||
fontSize="16px"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Item Name Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Item Name
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Burgers"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Price Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Price
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="$120"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Description Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||
Description
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Write here..."
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
alignItems: 'start',
|
||||
},
|
||||
'& textarea::placeholder': {
|
||||
color: '#969BA7',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* OR Separator */}
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
|
||||
<Box sx={{ flex: 1, height: '0px', backgroundColor: '#E0E0E0' }} />
|
||||
<Typography sx={{ px: 2, fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||
OR
|
||||
</Typography>
|
||||
<Box sx={{ flex: 1, height: '0px', backgroundColor: '#E0E0E0' }} />
|
||||
</Box>
|
||||
|
||||
{/* Upload Menu Picture */}
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '150px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: '#969BA7',
|
||||
backgroundColor: '#FAFAFA',
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
textAlign: 'center',
|
||||
transition: 'border-color 0.3s',
|
||||
'&:hover': {
|
||||
borderColor: '#FF914D',
|
||||
backgroundColor: '#fffaf5',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Menu Picture
|
||||
<input id="upload-image" type="file" accept="image/*" hidden />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Buttons: Back and Next */}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onNext}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
{/* زر Back تحت زر Next */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadMenu;
|
||||
@@ -1,183 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
|
||||
|
||||
const UploadPhotos = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
const handleOpenModal = () => setOpenModal(true);
|
||||
const handleCloseModal = () => setOpenModal(false);
|
||||
const handleConfirmNext = () => {
|
||||
handleCloseModal();
|
||||
onNext();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: '90%', sm: '100%', md: 740 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
px: 4,
|
||||
pt: {xs:4,md:11},
|
||||
pb: 10,
|
||||
display: 'block',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||
position: 'relative',
|
||||
width: { xs: '85%', sm: '90%' },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2.5}>
|
||||
<Typography fontWeight={700} sx={{
|
||||
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
||||
}}>
|
||||
Upload Photos
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '70%' }}>
|
||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Kitchen Interior’s */}
|
||||
<Box sx={{ pt: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Kitchen Interior’s
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', mt: 1 }}>
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image-interior"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '120px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: '#969BA7',
|
||||
backgroundColor: '#FAFAFA',
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
textAlign: 'center',
|
||||
transition: 'border-color 0.3s',
|
||||
'&:hover': {
|
||||
borderColor: '#FF914D',
|
||||
backgroundColor: '#fffaf5',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Interior Picture
|
||||
<input id="upload-image-interior" type="file" accept="image/*" hidden />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Kitchen Equipment’s */}
|
||||
<Box sx={{ pt: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Kitchen Equipment’s
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', mt: 1 }}>
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image-equipment"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '120px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: '#969BA7',
|
||||
backgroundColor: '#FAFAFA',
|
||||
fontWeight: 500,
|
||||
fontSize: '15px',
|
||||
textAlign: 'center',
|
||||
transition: 'border-color 0.3s',
|
||||
'&:hover': {
|
||||
borderColor: '#FF914D',
|
||||
backgroundColor: '#fffaf5',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Equipment Picture
|
||||
<input id="upload-image-equipment" type="file" accept="image/*" hidden />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover
|
||||
}
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||
borderColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderColor: theme.palette.primary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* ✅ Confirmation Modal */}
|
||||
<ConfirmationDialog
|
||||
open={openModal}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirmNext}
|
||||
title="Confirm Submission"
|
||||
description="Are you sure you want to proceed to the next step?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadPhotos;
|
||||
403
src/components/Home/Settings/AccountProfile.js
Normal file
403
src/components/Home/Settings/AccountProfile.js
Normal file
@@ -0,0 +1,403 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Select,
|
||||
MenuItem,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
OutlinedInput,
|
||||
styled,
|
||||
useTheme,
|
||||
LinearProgress
|
||||
} from '@mui/material';
|
||||
import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
|
||||
import authService from '../../../services/authService';
|
||||
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
const AccountProfile = () => {
|
||||
const theme = useTheme();
|
||||
const { restaurantId } = useRestaurant();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [profileImage, setProfileImage] = useState(null);
|
||||
const [form, setForm] = useState({
|
||||
restaurant_name: '',
|
||||
restaurant_type: '',
|
||||
Address: '',
|
||||
City: '',
|
||||
Postal_code: '',
|
||||
Phone: '',
|
||||
Email: '',
|
||||
Operation_hour: '',
|
||||
host_type: '',
|
||||
collaboration_type: '',
|
||||
closed_days: [],
|
||||
equipment: [], // array of {name, quantity}
|
||||
Maximum_orders_per_day: 0,
|
||||
Number_of_Cheff: 0,
|
||||
Number_of_Waiters: 0,
|
||||
Number_of_Cookers: 0,
|
||||
});
|
||||
|
||||
const daysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
||||
|
||||
const Input = styled('input')({ display: 'none' });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProfile = async () => {
|
||||
if (!restaurantId) return;
|
||||
const data = await authService.getRestaurantProfile(restaurantId);
|
||||
console.log("PROFILE DATA ===>", data); // 👈 يوضح الأسماء الحقيقية
|
||||
if (data) {
|
||||
setForm({
|
||||
restaurant_name: data.restaurant_name,
|
||||
restaurant_type: data.restaurant_type,
|
||||
Address: data.Address,
|
||||
City: data.City,
|
||||
Postal_code: data.Postal_code,
|
||||
Phone: data.Phone,
|
||||
Email: data.Email,
|
||||
Operation_hour: data.Operation_hour,
|
||||
host_type: data.host_type,
|
||||
collaboration_type: data.collaboration_type,
|
||||
closed_days: data.closed_days || [],
|
||||
equipment: Object.entries(data.equipment || {}).map(([name, quantity]) => ({ name, quantity })),
|
||||
Maximum_orders_per_day: data.Maximum_orders_per_day ?? data.maximum_orders_per_day ?? 0,
|
||||
Number_of_Cheff: data.Number_of_Cheff ?? data.number_of_cheff ?? 0,
|
||||
Number_of_Waiters: data.Number_of_Waiters ?? data.number_of_waiters ?? 0,
|
||||
Number_of_Cookers: data.Number_of_Cookers ?? data.number_of_cookers ?? 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
fetchProfile();
|
||||
}, [restaurantId]);
|
||||
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
setProfileImage(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => setForm(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
const handleEquipmentChange = (index, key, value) => {
|
||||
const newEquipment = [...form.equipment];
|
||||
newEquipment[index][key] = value;
|
||||
setForm(prev => ({ ...prev, equipment: newEquipment }));
|
||||
};
|
||||
|
||||
const addEquipment = () => {
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
equipment: [...prev.equipment, { name: '', quantity: 0 }]
|
||||
}));
|
||||
};
|
||||
|
||||
const removeEquipment = (index) => {
|
||||
const newEquipment = [...form.equipment];
|
||||
newEquipment.splice(index, 1);
|
||||
setForm(prev => ({ ...prev, equipment: newEquipment }));
|
||||
};
|
||||
|
||||
const renderField = (label, value, fieldKey, options = null) => (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
pb: 1, // padding bottom
|
||||
borderBottom: '1px solid #ccc' // الخط الرمادي تحت كل حقل
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '18px',
|
||||
color: 'black'
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
|
||||
{editMode ? (
|
||||
options ? (
|
||||
<Select
|
||||
multiple
|
||||
value={form[fieldKey]}
|
||||
onChange={(e) => handleChange(fieldKey, e.target.value)}
|
||||
input={<OutlinedInput label={label} />}
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
>
|
||||
{options.map(day => (
|
||||
<MenuItem key={day} value={day}>
|
||||
<Checkbox checked={form[fieldKey].includes(day)} />
|
||||
<ListItemText primary={day.charAt(0).toUpperCase() + day.slice(1)} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<TextField
|
||||
value={value}
|
||||
onChange={(e) => handleChange(fieldKey, e.target.value)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '16px', color: 'black' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: '16px',
|
||||
color: 'gray'
|
||||
}}
|
||||
>
|
||||
{Array.isArray(value) ? value.join(', ') : value}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!restaurantId) return;
|
||||
|
||||
const equipmentObj = {};
|
||||
form.equipment.forEach(eq => {
|
||||
if (eq.name) equipmentObj[eq.name] = eq.quantity;
|
||||
});
|
||||
|
||||
const payload = {
|
||||
...form,
|
||||
equipment: equipmentObj
|
||||
};
|
||||
|
||||
if (profileImage) payload.logo = profileImage;
|
||||
|
||||
const result = await authService.updateRestaurantProfile(restaurantId, payload);
|
||||
|
||||
if (result.success) {
|
||||
alert("Profile updated successfully!");
|
||||
setEditMode(false);
|
||||
|
||||
const updated = await authService.getRestaurantProfile(restaurantId);
|
||||
if (updated) setForm({
|
||||
...updated,
|
||||
equipment: Object.entries(updated.equipment || {}).map(([name, quantity]) => ({ name, quantity })),
|
||||
});
|
||||
} else {
|
||||
alert(`Update failed: ${result.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// داخل AccountProfile
|
||||
|
||||
const collaborationOptions = [
|
||||
{ value: 'fine_dining', label: 'Fine Dining' },
|
||||
{ value: 'catering_services', label: 'Catering Services' },
|
||||
{ value: 'fast_food', label: 'Fast Food' },
|
||||
{ value: 'cloud_kitchen', label: 'Cloud Kitchen' }
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ p: { xs: 2, sm: 3 }, backgroundColor: '#FFFFFF', borderRadius: 2, maxWidth: '100%' }}>
|
||||
<Typography variant="h4" sx={{ mb: 3, fontWeight: 'bold' }}>Restaurant Profile</Typography>
|
||||
|
||||
{/* صورة البروفايل */}
|
||||
{/* <Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||
<label htmlFor="icon-button-file">
|
||||
<Input accept="image/*" id="icon-button-file" type="file" onChange={handleImageUpload} />
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: 110, height: 110, border: '2px dashed #4C535F', borderRadius: 2,
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
||||
color: '#4C535F', cursor: 'pointer', textAlign: 'center', backgroundColor: '#f6f6f6'
|
||||
}}
|
||||
>
|
||||
<AddAPhotoOutlinedIcon sx={{ fontSize: 28, mb: 1 }} />
|
||||
<Typography sx={{ fontSize: '13px', fontWeight: 500 }}>Upload your photo</Typography>
|
||||
</Box>
|
||||
</label>
|
||||
</Box> */}
|
||||
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={100}
|
||||
sx={{
|
||||
mt: 2,
|
||||
mb: 3,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#f0f0f0",
|
||||
"& .MuiLinearProgress-bar": { backgroundColor: theme.palette.primary.main },
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* الحقول */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 ,p:3, display: 'flex', flexWrap: 'wrap', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)'}}>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Restaurant Name', form.restaurant_name, 'restaurant_name')}
|
||||
|
||||
{renderField('Restaurant Type', form.restaurant_type, 'restaurant_type')}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Address', form.Address, 'Address')}
|
||||
{renderField('City', form.City, 'City')}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Postal Code', form.Postal_code, 'Postal_code')}
|
||||
{renderField('Phone', form.Phone, 'Phone')}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Email', form.Email, 'Email')}
|
||||
{renderField('Operation Hours', form.Operation_hour, 'Operation_hour')}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Host Type', form.host_type, 'host_type')}
|
||||
<Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>Collaboration Type</Typography>
|
||||
{editMode ? (
|
||||
<Select
|
||||
value={form.collaboration_type || ''}
|
||||
onChange={(e) => handleChange('collaboration_type', e.target.value)}
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">
|
||||
{/* <em>None</em> اختياري */}
|
||||
</MenuItem>
|
||||
{collaborationOptions.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<Typography variant="body1" sx={{ fontWeight: 500, fontSize: '15px' }}>
|
||||
{form.collaboration_type ? collaborationOptions.find(opt => opt.value === form.collaboration_type)?.label : 'None'}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Closed Days', form.closed_days, 'closed_days', daysOfWeek)}
|
||||
</Box>
|
||||
|
||||
{/* القدرة التشغيلية */}
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Maximum Orders per Day', form.Maximum_orders_per_day, 'Maximum_orders_per_day')}
|
||||
{renderField('Number of Chefs', form.Number_of_Cheff, 'Number_of_Cheff')}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{renderField('Number of Waiters', form.Number_of_Waiters, 'Number_of_Waiters')}
|
||||
{renderField('Number of Cookers', form.Number_of_Cookers, 'Number_of_Cookers')}
|
||||
</Box>
|
||||
|
||||
{/* المعدات */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>Equipment Name</Typography>
|
||||
{form.equipment.map((eq, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<TextField
|
||||
|
||||
value={eq.name}
|
||||
onChange={(e) => handleEquipmentChange(index, 'name', e.target.value)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!editMode}
|
||||
sx={{ '& input': { fontWeight: 500, fontSize: '16px' } }}
|
||||
/>
|
||||
<TextField
|
||||
label="Quantity"
|
||||
type="number"
|
||||
value={eq.quantity}
|
||||
onChange={(e) => handleEquipmentChange(index, 'quantity', Number(e.target.value))}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!editMode}
|
||||
sx={{ '& input': { fontWeight: 500, fontSize: '16px' } ,borderRadius:20}}
|
||||
/>
|
||||
{editMode && (
|
||||
<Button color="error" onClick={() => removeEquipment(index)}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
{editMode && (
|
||||
<Button variant="outlined" onClick={addEquipment} startIcon={<AddIcon/>} sx={{ mt: 1, width: 'fit-content' , textTransform:'none' }}>
|
||||
Add Equipment
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* أزرار التحكم */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, flexWrap: 'wrap', mt: 3 }}>
|
||||
{editMode ? (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setEditMode(false)}
|
||||
sx={{ px: 4, py: 1.5, borderRadius: 2, textTransform: 'none', fontWeight: 'bold' }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
sx={{
|
||||
px: 4, py: 1.5, borderRadius: 2,
|
||||
textTransform: 'none', fontWeight: 'bold',
|
||||
color: '#FFF', backgroundColor: theme.palette.primary.main
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setEditMode(true)}
|
||||
sx={{
|
||||
px: 4, py: 1.5, borderRadius: 2,
|
||||
textTransform: 'none', fontWeight: 'bold',
|
||||
color: '#FFF', backgroundColor: theme.palette.primary.main
|
||||
}}
|
||||
>
|
||||
Edit Profile
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountProfile;
|
||||
@@ -1,217 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
styled,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
|
||||
|
||||
const AccountSettings = () => {
|
||||
const theme = useTheme();
|
||||
const [profileImage, setProfileImage] = useState(null);
|
||||
const [fullName, setFullName] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const [bio, setBio] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
setProfileImage(URL.createObjectURL(event.target.files[0]));
|
||||
}
|
||||
};
|
||||
|
||||
const Input = styled('input')({
|
||||
display: 'none',
|
||||
});
|
||||
|
||||
const customInputStyle = {
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
},
|
||||
};
|
||||
|
||||
const renderField = (label, value, onChange, type = 'text') => (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: { xs: '100%', sm: '45%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={`Enter your ${label.toLowerCase()}`}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={customInputStyle}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
// margin: 'auto',
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
maxWidth: { xs: '90%', md: '100%' },
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* العنوان الرئيسي */}
|
||||
<Typography variant="h4" component="h1" sx={{ mb: 3, fontWeight: 'bold' }}>
|
||||
Account Setting
|
||||
</Typography>
|
||||
|
||||
{/* صورة الملف الشخصي */}
|
||||
<Typography variant="h6" component="h2" sx={{ mb: 2, fontWeight: 'bold' }}>
|
||||
Your Profile Picture
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||
<label htmlFor="icon-button-file">
|
||||
<Input
|
||||
accept="image/*"
|
||||
id="icon-button-file"
|
||||
type="file"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: { xs: 100, sm: 110 },
|
||||
height: { xs: 100, sm: 110 },
|
||||
border: '2px dashed #4C535F',
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#4C535F',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#f6f6f6',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AddAPhotoOutlinedIcon sx={{ fontSize: 28, mb: 1 }} />
|
||||
<Typography sx={{ fontSize: '13px', fontWeight: 500 }}>
|
||||
Upload your photo
|
||||
</Typography>
|
||||
</Box>
|
||||
</label>
|
||||
</Box>
|
||||
|
||||
{/* الحقول النصية */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold', mb: 2 }}>
|
||||
Personal Information
|
||||
</Typography>
|
||||
|
||||
{/* السطر الأول: Full Name + Email */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
{renderField('Full Name', fullName, (e) => setFullName(e.target.value))}
|
||||
{renderField('Email', email, (e) => setEmail(e.target.value), 'email')}
|
||||
</Box>
|
||||
|
||||
{/* السطر الثاني: Username + Phone Number */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
{renderField('Username', username, (e) => setUsername(e.target.value))}
|
||||
{renderField('Phone Number', phone, (e) => setPhone(e.target.value), 'tel')}
|
||||
</Box>
|
||||
|
||||
{/* الحقل Bio منفصل */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Bio
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Write something about your restaurant"
|
||||
value={bio}
|
||||
onChange={(e) => setBio(e.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
sx={customInputStyle}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* الأزرار */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, flexWrap: 'wrap' }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
borderRadius: 2,
|
||||
textTransform: 'none',
|
||||
fontWeight: 'bold',
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
borderRadius: 2,
|
||||
textTransform: 'none',
|
||||
fontWeight: 'bold',
|
||||
color: '#FFFFFF',
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
transition: 'border-color 0.3s',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.hover
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountSettings;
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
import AccountSettings from './AccountSettings';
|
||||
import AccountSettings from './AccountProfile';
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
|
||||
@@ -10,39 +10,28 @@ import {
|
||||
useTheme,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import {
|
||||
DashboardRounded as DashboardIcon,
|
||||
PointOfSale as CashierIcon,
|
||||
AllInbox as SupplierIcon,
|
||||
Store as InventoryIcon,
|
||||
School as TrainingIcon,
|
||||
AutoGraph as AnalyticsIcon,
|
||||
Restaurant as RestaurantIcon,
|
||||
CountertopsTwoTone as HostKitchenIcon,
|
||||
Queue as CreateKitchenIcon,
|
||||
Settings as SettingsIcon,
|
||||
Logout as LogoutIcon
|
||||
} from '@mui/icons-material';
|
||||
import DashboardIcon from '@mui/icons-material/DashboardRounded';
|
||||
import AnalyticsIcon from '@mui/icons-material/AutoGraph';
|
||||
import RestaurantIcon from '@mui/icons-material/Restaurant';
|
||||
import Employ from '@mui/icons-material/CountertopsTwoTone';
|
||||
import CreateKitchenIcon from '@mui/icons-material/Queue';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import CategoryIcon from '@mui/icons-material/Category';
|
||||
import authService from '../../services/authService';
|
||||
|
||||
import authService from '../../services/authService'; // تأكد من المسار الصحيح
|
||||
|
||||
const menuItems = [
|
||||
{ text: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
{ text: 'Cashier', icon: <CashierIcon />, path: '/cashier' },
|
||||
{ text: 'Supplier', icon: <SupplierIcon />, path: '/supplier' },
|
||||
{ text: 'Inventory', icon: <InventoryIcon />, path: '/inventory' },
|
||||
{ text: 'Training', icon: <TrainingIcon />, path: '/training' },
|
||||
{ text: 'Analytics & Reporting', icon: <AnalyticsIcon />, path: '/analytics' },
|
||||
{ text: 'Order & Occupancy', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
{ text: 'Tabels & Reservations', icon: <AnalyticsIcon />, path: '/analytics' },
|
||||
{ text: 'Restaurant Profile', icon: <RestaurantIcon />, path: '/profile' },
|
||||
// { text: 'Host Kitchen', icon: <HostKitchenIcon />, path: '/host-kitchen' },
|
||||
{ text: 'Employ', icon: <Employ />, path: '/employ' },
|
||||
{ text: 'Create Kitchen', icon: <CreateKitchenIcon />, path: '/create-kitchen' },
|
||||
{ text: 'Catigores & Meals', icon: <CategoryIcon />, path: '/categori-meal' },
|
||||
];
|
||||
|
||||
const bottomItems = [
|
||||
{ text: 'Settings', icon: <SettingsIcon />, path: '/settings' },
|
||||
{ text: 'Log Out', icon: <LogoutIcon />, path: '/login' },
|
||||
];
|
||||
|
||||
const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
@@ -175,7 +164,6 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<List>{renderListItems(menuItems)}</List>
|
||||
|
||||
71
src/components/Home/SimplePagination.js
Normal file
71
src/components/Home/SimplePagination.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// SimplePagination.jsx
|
||||
import React from 'react';
|
||||
import { Box, IconButton, useTheme } from '@mui/material';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage > 1) onChange(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (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>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimplePagination;
|
||||
@@ -1,67 +1,122 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, useTheme, useMediaQuery } from "@mui/material";
|
||||
import KitchPlusAppBar from "../AppBar";
|
||||
import Sidebar from "../SideHome";
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery, Typography } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
|
||||
import AccountSettings from "./contect/AccountSettings";
|
||||
import AllProducts from "./contect/AllProducts";
|
||||
import PopularProductSection from "./contect/PopularProductSection";
|
||||
import ProductDetail from "./contect/ProductDetail";
|
||||
|
||||
import api from "../../../services/authService";
|
||||
|
||||
const IMAGE_URL = "http://127.0.0.1:8000/storage/";
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Supplier = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [hasProducts, setHasProducts] = useState(false); // حالة لتتبع وجود المنتجات
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [products, setProducts] = useState([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
const [selectedCategoryName, setSelectedCategoryName] = useState(null);
|
||||
|
||||
const [selectedProduct, setSelectedProduct] = useState(null);
|
||||
|
||||
const [showAllPopular, setShowAllPopular] = useState(false);
|
||||
const [showAllTopItems, setShowAllTopItems] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkProducts = async () => {
|
||||
|
||||
const productsExist = await checkIfProductsExist();
|
||||
setHasProducts(productsExist);
|
||||
};
|
||||
|
||||
checkProducts();
|
||||
}, []);
|
||||
|
||||
|
||||
const checkIfProductsExist = async () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||
setSidebarOpen(true);
|
||||
} else {
|
||||
setSidebarOpen(false);
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
};
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const res = await api.getAllCategory();
|
||||
if (res.success) {
|
||||
const formatted = res.data.map((category) => ({
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
icon: category.image
|
||||
? IMAGE_URL + category.image
|
||||
: "/images/default-product.png",
|
||||
}));
|
||||
setCategories(formatted);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error fetching categories:", err);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
useEffect(() => {
|
||||
const fetchProducts = async () => {
|
||||
if (!selectedCategory) return;
|
||||
|
||||
try {
|
||||
const res = await api.getProductsByCategory(selectedCategory, 1);
|
||||
if (res.success) {
|
||||
const formatted = res.data.map((product) => ({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
category: product.supplier_category_id,
|
||||
price: product.price,
|
||||
unit: "Kg",
|
||||
image: product.image
|
||||
? IMAGE_URL + product.image
|
||||
: "/images/default-product.png",
|
||||
}));
|
||||
setProducts(formatted);
|
||||
|
||||
const categoryName = categories.find(
|
||||
(cat) => cat.id === selectedCategory
|
||||
)?.name;
|
||||
setSelectedCategoryName(categoryName || "");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching products:", err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProducts();
|
||||
}, [selectedCategory, categories]);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
const toggleShowAllPopular = () => {
|
||||
setShowAllPopular((prev) => !prev);
|
||||
};
|
||||
|
||||
const toggleShowAllTopItems = () => {
|
||||
setShowAllTopItems((prev) => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
backgroundColor: '#F6F6F6',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: "100vh",
|
||||
backgroundColor: "#F6F6F6",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
@@ -69,34 +124,107 @@ const Supplier = () => {
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: {
|
||||
xs: '100%',
|
||||
sm: '100%',
|
||||
md: '100%'
|
||||
},
|
||||
marginLeft: {
|
||||
xs: 0,
|
||||
sm: sidebarOpen ? `${drawerWidth}px` : 0,
|
||||
md: 0
|
||||
},
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "10%",
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(["width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar
|
||||
onDrawerToggle={handleDrawerToggle}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
<Typography>Supplier</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? "calc(100% - 40px)" : "100%",
|
||||
pt: { xs: 2, sm: 3 },
|
||||
overflowY: "auto",
|
||||
pl: { xs: 1, sm: 2 },
|
||||
pr: { xs: 1, sm: 2 },
|
||||
scrollbarWidth: "none",
|
||||
"&::-webkit-scrollbar": { display: "none" },
|
||||
}}
|
||||
>
|
||||
{selectedProduct ? (
|
||||
<ProductDetail
|
||||
product={selectedProduct}
|
||||
onBack={() => setSelectedProduct(null)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AccountSettings
|
||||
selectedCategory={selectedCategory}
|
||||
setSelectedCategory={setSelectedCategory}
|
||||
categories={categories}
|
||||
/>
|
||||
|
||||
{selectedCategory ? (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<AllProducts
|
||||
products={products}
|
||||
showAll={true}
|
||||
onToggleShowAll={() => setSelectedCategory(null)}
|
||||
onProductClick={setSelectedProduct}
|
||||
categoryName={selectedCategoryName}
|
||||
/>
|
||||
</Box>
|
||||
) : !showAllPopular && !showAllTopItems ? (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 3,
|
||||
display: "flex",
|
||||
flexDirection: { xs: "column", md: "row" },
|
||||
alignItems: "stretch",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexBasis: { xs: "100%", md: "100%" },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
pb: { xs: 4, md: 4 },
|
||||
pr: { xs: 1, md: 0 },
|
||||
mb: 10,
|
||||
}}
|
||||
>
|
||||
<PopularProductSection
|
||||
products={products}
|
||||
showAll={showAllPopular}
|
||||
onToggleShowAll={toggleShowAllPopular}
|
||||
onProductClick={setSelectedProduct}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : showAllPopular ? (
|
||||
<Box sx={{ mt: 3, width: "100%" }}>
|
||||
<PopularProductSection
|
||||
products={products}
|
||||
showAll={showAllPopular}
|
||||
onToggleShowAll={toggleShowAllPopular}
|
||||
onProductClick={setSelectedProduct}
|
||||
/>
|
||||
</Box>
|
||||
) : showAllTopItems ? (
|
||||
<Box sx={{ mt: 3, width: "100%" }}>
|
||||
</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Supplier;
|
||||
export default Supplier;
|
||||
|
||||
247
src/components/Home/Supplier/Supplieree.js
Normal file
247
src/components/Home/Supplier/Supplieree.js
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||
import KitchPlusAppBar from '../AppBar';
|
||||
import Sidebar from '../SideHome';
|
||||
|
||||
// بدل هذه المكونات عند عرض التفاصيل
|
||||
import AccountSettings from './contect/AccountSettings';
|
||||
import AllProducts from './contect/AllProducts';
|
||||
import PopularProductSection from './contect/PopularProductSection';
|
||||
import TopItems from './contect/TopItems';
|
||||
import OrderList from './contect/OrderList';
|
||||
import ProductDetail from './contect/ProductDetail'; // 👈 مكون التفاصيل
|
||||
//import auth servise
|
||||
import api from '../../../services/authService';
|
||||
const IMAGE_URL = 'http://127.0.0.1:8000/storage/' ;
|
||||
|
||||
|
||||
const categoriesInit0 = await api.getAllCategory();
|
||||
const categoriesInit = categoriesInit0.data.map(category => ({
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
icon: category.image || "/images/default-product.png",
|
||||
}));
|
||||
|
||||
|
||||
const productList0 = await api.getAllProduct();
|
||||
const productList = productList0.data.map(product => ({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
category: product.supplier_category_id,
|
||||
price: product.price,
|
||||
unit: "Kg",
|
||||
image: IMAGE_URL + product.image || "/images/default-product.png",
|
||||
}));
|
||||
|
||||
// const productList = [
|
||||
// { id: 1, name: 'Cabbage 1', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 2, name: 'vegetables 2', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 3, name: 'Brocoly 3', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 4, name: 'Onion 4', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 5, name: 'Bread 5', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
|
||||
// { id: 6, name: 'Meat 6', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 7, name: 'Cabbage', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 8, name: 'vegetables', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 9, name: 'Brocoly', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 10, name: 'Onion', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 11, name: 'Bread', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
|
||||
// { id: 12, name: 'Meat', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 13, name: 'Cabbage', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 14, name: 'vegetables', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 15, name: 'Brocoly', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 16, name: 'Onion', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// { id: 17, name: 'Bread', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
|
||||
// { id: 18, name: 'Meat', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
|
||||
// ];
|
||||
|
||||
const item = [
|
||||
{ name: 'Cabbage', price: '15.10', unit: '/kg', image: '/images/cabbage.png' },
|
||||
{ name: 'Kale vegetables', price: '8.34', unit: '/kg', image: '/images/kale.png' },
|
||||
{ name: 'Brocoly', price: '5.60', unit: '/kg', image: '/images/broccoli.png' },
|
||||
{ name: 'Celery', price: '4.80', unit: '/kg', image: '/images/celery.png' },
|
||||
{ name: 'Onion', price: '6.45', unit: '/kg', image: '/images/onion.png' },
|
||||
{ name: 'Garlic', price: '9.90', unit: '/kg', image: '/images/garlic.png' },
|
||||
];
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
const Supplier = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
// حالات العرض
|
||||
const [showAllPopular, setShowAllPopular] = useState(false);
|
||||
const [showAllTopItems, setShowAllTopItems] = useState(false);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
|
||||
// ✅ المنتج المختار
|
||||
const [selectedProduct, setSelectedProduct] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||
};
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
const toggleShowAllPopular = () => {
|
||||
setShowAllPopular(prev => !prev);
|
||||
};
|
||||
|
||||
const toggleShowAllTopItems = () => {
|
||||
setShowAllTopItems(prev => !prev);
|
||||
};
|
||||
|
||||
// اسم الفئة
|
||||
const selectedCategoryName = selectedCategory
|
||||
? categoriesInit.find(cat => cat.id === selectedCategory)?.name
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
isMobile={isMobile}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '10%',
|
||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||
transition: theme.transitions.create(['width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<KitchPlusAppBar onDrawerToggle={handleDrawerToggle} sidebarOpen={sidebarOpen} isMobile={isMobile} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||
pt: { xs: 2, sm: 3 },
|
||||
overflowY: 'auto',
|
||||
pl: { xs: 1, sm: 2 },
|
||||
pr: { xs: 1, sm: 2 },
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
{/* ✅ إذا تم اختيار منتج نظهر تفاصيله فقط */}
|
||||
{selectedProduct ? (
|
||||
<ProductDetail product={selectedProduct} onBack={() => setSelectedProduct(null)} />
|
||||
) : (
|
||||
<>
|
||||
<AccountSettings
|
||||
selectedCategory={selectedCategory}
|
||||
setSelectedCategory={setSelectedCategory}
|
||||
/>
|
||||
|
||||
{selectedCategory ? (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<AllProducts
|
||||
products={productList.filter(
|
||||
(p) => p.category.toLowerCase() === selectedCategoryName?.toLowerCase()
|
||||
)}
|
||||
showAll={true}
|
||||
onToggleShowAll={() => setSelectedCategory(null)}
|
||||
onProductClick={setSelectedProduct}
|
||||
categoryName={selectedCategoryName} // ✅ بدل القيمة الثابتة
|
||||
/>
|
||||
|
||||
</Box>
|
||||
) : !showAllPopular && !showAllTopItems ? (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 3,
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', md: 'row' },
|
||||
alignItems: 'stretch',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexBasis: { xs: '100%', md: '100%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
pb: { xs: 4, md: 4 },
|
||||
pr: { xs: 1, md: 0 },
|
||||
mb: 10,
|
||||
}}
|
||||
>
|
||||
<PopularProductSection
|
||||
products={productList}
|
||||
showAll={showAllPopular}
|
||||
onToggleShowAll={toggleShowAllPopular}
|
||||
onProductClick={setSelectedProduct}
|
||||
/>
|
||||
{/* <Box sx={{ mt: 3 }}>
|
||||
<TopItems
|
||||
item={item}
|
||||
showAll={showAllTopItems}
|
||||
onToggleShowAll={toggleShowAllTopItems}
|
||||
onProductClick={setSelectedProduct}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
|
||||
{/* <Box
|
||||
sx={{
|
||||
flexBasis: { xs: '100%', md: '40%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
pb: 10,
|
||||
pr: { xs: 3, md: 0 },
|
||||
}}
|
||||
>
|
||||
<Box sx={{ height: '100%' }}>
|
||||
<OrderList />
|
||||
</Box>
|
||||
</Box> */}
|
||||
</Box>
|
||||
) : showAllPopular ? (
|
||||
<Box sx={{ mt: 3, width: '100%' }}>
|
||||
<PopularProductSection
|
||||
products={productList}
|
||||
showAll={showAllPopular}
|
||||
onToggleShowAll={toggleShowAllPopular}
|
||||
onProductClick={setSelectedProduct}
|
||||
/>
|
||||
</Box>
|
||||
) : showAllTopItems ? (
|
||||
<Box sx={{ mt: 3, width: '100%' }}>
|
||||
{/* <TopItems
|
||||
item={item}
|
||||
showAll={showAllTopItems}
|
||||
onToggleShowAll={toggleShowAllTopItems}
|
||||
onProductClick={setSelectedProduct}
|
||||
/> */}
|
||||
</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Supplier;
|
||||
307
src/components/Home/Supplier/contect/AccountSettings.js
Normal file
307
src/components/Home/Supplier/contect/AccountSettings.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
useTheme,
|
||||
IconButton,
|
||||
} from '@mui/material';
|
||||
import AddCategory from './AddCategory'; // مكون لإضافة/تعديل الفئة (يفترض موجود)
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import CategoryScrollList from './CategoryScrollList'; // مكون لعرض الفئات بشكل أفقي مع تمرير
|
||||
//import auth servise
|
||||
import api from '../../../../services/authService';
|
||||
|
||||
const IMAGE_URL = 'http://127.0.0.1:8000/storage/' ;
|
||||
|
||||
const categoriesInit0 = await api.getAllCategory();
|
||||
const categoriesInit = categoriesInit0.data.map(category => (
|
||||
{
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
icon: IMAGE_URL + category.icon || "/images/default-product.png",
|
||||
}));
|
||||
|
||||
|
||||
const AccountSettings = ({ selectedCategory, setSelectedCategory }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const [categories, setCategories] = useState(categoriesInit);
|
||||
const [selectedCategoryForEdit, setSelectedCategoryForEdit] = useState(null);
|
||||
const [contextMenu, setContextMenu] = useState(null);
|
||||
const [openAddModal, setOpenAddModal] = useState(false);
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
|
||||
const scrollRef = useRef();
|
||||
|
||||
const scroll = (offset) => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollLeft += offset;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddCategory = (newCategory) => {
|
||||
setCategories((prev) => [...prev, { ...newCategory, id: Date.now() }]);
|
||||
setOpenAddModal(false);
|
||||
};
|
||||
|
||||
const handleUpdateCategory = (updatedCategory) => {
|
||||
setCategories((prev) =>
|
||||
prev.map((cat) =>
|
||||
cat.id === selectedCategoryForEdit.id ? { ...cat, ...updatedCategory } : cat
|
||||
)
|
||||
);
|
||||
setOpenAddModal(false);
|
||||
};
|
||||
|
||||
const handleDeleteCategory = () => {
|
||||
setCategories((prev) => prev.filter((cat) => cat.id !== selectedCategoryForEdit.id));
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedCategoryForEdit(null);
|
||||
|
||||
if (selectedCategory === selectedCategoryForEdit?.id) {
|
||||
setSelectedCategory(null); // أبطل اختيار الفئة
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!openAddModal) {
|
||||
setSelectedCategoryForEdit(null);
|
||||
}
|
||||
}, [openAddModal]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
maxWidth: { xs: '90%', md: '100%' },
|
||||
borderRadius: 2,
|
||||
maxHeight: { xs: '293px', sm: '30%', md: 160 },
|
||||
}}
|
||||
>
|
||||
{/* رأس القسم */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 0 },
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: '600',
|
||||
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
||||
color: '#121212',
|
||||
}}
|
||||
>
|
||||
Categories
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 1, sm: 2 },
|
||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
justifyContent: { xs: 'space-between', sm: 'flex-end' },
|
||||
}}
|
||||
>
|
||||
{/* <Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#fff',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
boxShadow: 'none',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '130px', md: '150px' },
|
||||
fontSize: { xs: '12px', sm: '14px', md: '16px' },
|
||||
fontWeight: 700,
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
onClick={() => {
|
||||
setSelectedCategoryForEdit(null);
|
||||
setOpenAddModal(true);
|
||||
}}
|
||||
>
|
||||
Add Category
|
||||
</Button> */}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '120px', md: '99px' },
|
||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
<IconButton
|
||||
onClick={() => scroll(-200)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
width: 40,
|
||||
height: 40,
|
||||
'&:hover': { backgroundColor: '#e96b00' },
|
||||
}}
|
||||
>
|
||||
<ArrowBackIosNewIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
onClick={() => scroll(200)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
width: 40,
|
||||
height: 40,
|
||||
'&:hover': { backgroundColor: '#e96b00' },
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIosIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* قائمة التمرير للفئات */}
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '90%', sm: '95%', md: '100%' },
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 4 },
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<CategoryScrollList
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
setSelectedCategory={setSelectedCategory} // مرر الدالة مباشرة
|
||||
setSelectedCategoryForEdit={setSelectedCategoryForEdit}
|
||||
setContextMenu={setContextMenu}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
{/* مودال الإضافة / التعديل */}
|
||||
<Modal
|
||||
open={openAddModal}
|
||||
onClose={() => setOpenAddModal(false)}
|
||||
aria-labelledby="add-category-modal"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 2,
|
||||
boxShadow: 24,
|
||||
p: 2,
|
||||
maxWidth: '90vw',
|
||||
width: 400,
|
||||
}}
|
||||
>
|
||||
<AddCategory
|
||||
onAdd={selectedCategoryForEdit ? handleUpdateCategory : handleAddCategory}
|
||||
editingCategory={selectedCategoryForEdit}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
{/* قائمة السياق (الزر الأيمن) */}
|
||||
<Menu
|
||||
open={contextMenu !== null}
|
||||
onClose={() => setContextMenu(null)}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={
|
||||
contextMenu !== null
|
||||
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
||||
: undefined
|
||||
}
|
||||
sx={{ zIndex: 2000 }}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setOpenAddModal(true);
|
||||
setContextMenu(null);
|
||||
}}
|
||||
>
|
||||
Edit Category
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setConfirmDeleteOpen(true);
|
||||
setContextMenu(null);
|
||||
}}
|
||||
sx={{ color: 'red' }}
|
||||
>
|
||||
Delete Category
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>Are you sure you want to delete this category?</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleDeleteCategory} color="error" variant="contained">
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountSettings;
|
||||
186
src/components/Home/Supplier/contect/AddCategory.js
Normal file
186
src/components/Home/Supplier/contect/AddCategory.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Button, TextField, Typography, IconButton } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
|
||||
|
||||
const AddCategory = ({ onAdd, editingCategory }) => {
|
||||
const [productName, setProductName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [price, setPrice] = useState('');
|
||||
const [discount, setDiscount] = useState('');
|
||||
const [productImage, setProductImage] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingCategory) {
|
||||
setProductName(editingCategory.name || '');
|
||||
setDescription(editingCategory.description || '');
|
||||
setPrice(editingCategory.price || '');
|
||||
setDiscount(editingCategory.discount || '');
|
||||
setProductImage(editingCategory.icon || null);
|
||||
}
|
||||
}, [editingCategory]);
|
||||
|
||||
const handleImageUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setProductImage(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveImage = () => {
|
||||
setProductImage(null);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
const newCategory = {
|
||||
name: productName,
|
||||
description,
|
||||
price,
|
||||
discount,
|
||||
icon: productImage,
|
||||
};
|
||||
|
||||
if (onAdd) {
|
||||
onAdd(newCategory);
|
||||
}
|
||||
|
||||
// إعادة تعيين الحقول فقط عند الإضافة الجديدة
|
||||
if (!editingCategory) {
|
||||
setProductName('');
|
||||
setDescription('');
|
||||
setPrice('');
|
||||
setDiscount('');
|
||||
setProductImage(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={2} maxWidth={400} mx="auto">
|
||||
{/* ✅ صورة التصنيف */}
|
||||
{productImage ? (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: 150,
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={productImage}
|
||||
alt="Preview"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={handleRemoveImage}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
right: 4,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
},
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="upload-image"
|
||||
sx={{
|
||||
border: '2px dashed #ccc',
|
||||
borderRadius: '10px',
|
||||
height: '150px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: '#969BA7',
|
||||
backgroundColor: '#FAFAFA',
|
||||
fontWeight: 500,
|
||||
fontSize: '18px',
|
||||
textAlign: 'center',
|
||||
mb: 2,
|
||||
transition: 'border-color 0.3s',
|
||||
'&:hover': {
|
||||
borderColor: '#FF914D',
|
||||
backgroundColor: '#fffaf5',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload Menu Picture
|
||||
<AddAPhotoOutlinedIcon sx={{ fontSize: 32, color: '#9e9e9e', mt: 1 }} />
|
||||
<input
|
||||
id="upload-image"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
hidden
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.1, mt: 4 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Categories name
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Enter category name"
|
||||
value={productName}
|
||||
onChange={(e) => setProductName(e.target.value)}
|
||||
margin="normal"
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#FF914D',
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#FF914D',
|
||||
boxShadow: '0 0 0 2px rgba(255,145,77,0.15)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 2,
|
||||
backgroundColor: '#FF914D',
|
||||
'&:hover': { backgroundColor: '#e57f3c' },
|
||||
borderRadius: 2,
|
||||
color: '#fff',
|
||||
textTransform: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: '16px'
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{editingCategory ? 'Update Category' : 'Add Category'}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCategory;
|
||||
93
src/components/Home/Supplier/contect/AllProducts.js
Normal file
93
src/components/Home/Supplier/contect/AllProducts.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import React from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import ProductCard from "./ProductCard";
|
||||
|
||||
const AllProducts = ({ products, showAll, onToggleShowAll, onProductClick, categoryName }) => {
|
||||
const visibleProducts = showAll ? products : products.slice(0, 6);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mb: showAll ? { xs: 10, sm: 10 } : 0,
|
||||
mr: showAll ? { xs: 2, sm: 3, md: 0 } : { xs: 2, sm: 2, md: 0 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
backgroundColor: "#FFFFFF",
|
||||
maxWidth: showAll ? { xs: "90%", sm: "100%" } : undefined,
|
||||
borderRadius: "10px",
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
overflowY: showAll ? "auto" : "hidden",
|
||||
}}
|
||||
>
|
||||
{/* العنوان */}
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{categoryName ? categoryName : "All Products"}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="primary"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={onToggleShowAll}
|
||||
>
|
||||
{showAll ? "Show Less" : "See All"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* عرض المنتجات أو رسالة لا يوجد منتجات */}
|
||||
{visibleProducts.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "150px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{categoryName
|
||||
? `There is no product in ${categoryName}`
|
||||
: "There is no product"}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{visibleProducts.map((product, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
// flex: "1 1 calc(33.333% - 16px)", // 3 منتجات بالصف في الديسكتوب
|
||||
// minWidth: "250px", // أصغر حجم للمنتج
|
||||
}}
|
||||
onClick={() => onProductClick(product)}
|
||||
>
|
||||
<ProductCard
|
||||
name={product.name}
|
||||
price={product.price}
|
||||
unit={product.unit}
|
||||
image={product.image}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllProducts;
|
||||
88
src/components/Home/Supplier/contect/CategoryScrollList.js
Normal file
88
src/components/Home/Supplier/contect/CategoryScrollList.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { Box, Avatar, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
const CategoryScrollList = ({
|
||||
categories,
|
||||
selectedCategory,
|
||||
setSelectedCategory,
|
||||
setSelectedCategoryForEdit,
|
||||
setContextMenu,
|
||||
scrollRef,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={scrollRef}
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
overflowX: 'auto',
|
||||
scrollBehavior: 'smooth',
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 0 },
|
||||
mb: 3,
|
||||
mx: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
>
|
||||
|
||||
{categories.map((cat) => (
|
||||
<Box
|
||||
key={cat.id}
|
||||
sx={{
|
||||
p: 1,
|
||||
|
||||
borderRadius: '12px',
|
||||
backgroundColor: selectedCategory === cat.id
|
||||
? `${theme.palette.primary.main}`
|
||||
: '#F9F9FC',
|
||||
border: selectedCategory === cat.id
|
||||
? `0px solid ${theme.palette.primary.main}`
|
||||
: '0px solid #ddd',
|
||||
textAlign: 'center',
|
||||
minWidth: '70px',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setSelectedCategoryForEdit(cat);
|
||||
setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={cat.icon}
|
||||
sx={{
|
||||
width: 60,
|
||||
height: 50,
|
||||
mx: 'auto',
|
||||
mb: 1,
|
||||
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
sx={{ color: selectedCategory === cat.id ? '#fff' : '#121212' }}
|
||||
>
|
||||
{cat.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryScrollList;
|
||||
359
src/components/Home/Supplier/contect/OrderList.js
Normal file
359
src/components/Home/Supplier/contect/OrderList.js
Normal file
@@ -0,0 +1,359 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
IconButton,
|
||||
TableContainer,
|
||||
Button,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage > 1) onChange(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (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',
|
||||
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
|
||||
}}
|
||||
>
|
||||
{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 OrderList = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const initialOrders = [
|
||||
{ restaurant: 'المطعم الاول المطعم الاول ', time: '5:00 Pm', status: 'Pending' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'In-Progress' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Pending' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Pending' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
|
||||
{ restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [statusFilter, setStatusFilter] = useState('all'); // 'all' يعني بدون فلتر
|
||||
|
||||
const itemsPerPage = 5;
|
||||
|
||||
// افتح قائمة الفلتر
|
||||
const handleFilterClick = (event) => {
|
||||
setFilterAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
// اغلق قائمة الفلتر
|
||||
const handleFilterClose = () => {
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
// عند اختيار فلتر جديد
|
||||
const handleFilterSelect = (filter) => {
|
||||
setStatusFilter(filter);
|
||||
setCurrentPage(1); // العودة للصفحة الأولى بعد تغيير الفلتر
|
||||
handleFilterClose();
|
||||
};
|
||||
|
||||
// تصفية البيانات حسب الفلتر
|
||||
const filteredOrders =
|
||||
statusFilter === 'all'
|
||||
? initialOrders
|
||||
: initialOrders.filter(
|
||||
(order) => order.status.toLowerCase() === statusFilter.toLowerCase()
|
||||
);
|
||||
|
||||
const pageCount = Math.ceil(filteredOrders.length / itemsPerPage);
|
||||
|
||||
const paginatedData = filteredOrders.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'Pending':
|
||||
return { bg: '#FFF3E0', color: '#E65100' };
|
||||
case 'Delivered':
|
||||
return { bg: '#E8F5E9', color: '#2E7D32' };
|
||||
case 'Completed':
|
||||
return { bg: '#F3E5F5', color: '#6A1B9A' };
|
||||
case 'In-Progress':
|
||||
return { bg: '#F1F8E9', color: '#9E9D24' };
|
||||
default:
|
||||
return { bg: '#E0E0E0', color: '#424242' };
|
||||
}
|
||||
};
|
||||
|
||||
const rowHeight = 73; // ارتفاع الصف الواحد تقريبي
|
||||
const emptyRowsCount = itemsPerPage - paginatedData.length;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
backgroundColor: '#FFFFFF',
|
||||
width: { xs: '100%', md: 400 },
|
||||
borderRadius: '10px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
px: 2,
|
||||
py: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ fontSize: { xs: '1rem', sm: '1.25rem' } }}>
|
||||
Order List
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
fontSize: { xs: '12px', sm: '14px' },
|
||||
fontWeight: 600,
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
{/* قائمة الفلاتر */}
|
||||
<Menu
|
||||
anchorEl={filterAnchorEl}
|
||||
open={Boolean(filterAnchorEl)}
|
||||
onClose={handleFilterClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: 'white',
|
||||
px: 1,
|
||||
position: 'relative',
|
||||
width: '140px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['all', 'Pending', 'Delivered', 'Completed', 'In-Progress'].map((status) => (
|
||||
<MenuItem
|
||||
key={status}
|
||||
selected={statusFilter.toLowerCase() === status.toLowerCase()}
|
||||
onClick={() => handleFilterSelect(status)}
|
||||
sx={{
|
||||
color: statusFilter.toLowerCase() === status.toLowerCase() ? '#FF914D' : '#4F5867',
|
||||
backgroundColor:
|
||||
statusFilter.toLowerCase() === status.toLowerCase() ? '#fffcf9d5' : 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0ff',
|
||||
transition: 'background-color 150ms ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
{/* Table */}
|
||||
<TableContainer
|
||||
sx={{
|
||||
maxHeight: 430,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
'&::-webkit-scrollbar': { height: 4 },
|
||||
}}
|
||||
>
|
||||
<Table size={isMobile ? 'small' : 'medium'}>
|
||||
<TableHead sx={{ backgroundColor: '#F9FAFB' }}>
|
||||
<TableRow>
|
||||
<TableCell sx={{ pl: 2 }}>Restaurant</TableCell>
|
||||
<TableCell sx={{ pl: 2 }}>Time</TableCell>
|
||||
<TableCell sx={{ pl: 4 }}>Status</TableCell>
|
||||
<TableCell sx={{ pl: 2 }}>Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{paginatedData.length > 0 ? (
|
||||
paginatedData.map((row, i) => {
|
||||
const colors = getStatusColor(row.status);
|
||||
return (
|
||||
<TableRow key={i}>
|
||||
<TableCell
|
||||
sx={{
|
||||
pl: 2,
|
||||
whiteSpace: 'normal',
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1, // للتحكم في المسافة بين الأسطر
|
||||
}}
|
||||
>
|
||||
{row.restaurant}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{row.time}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={row.status}
|
||||
sx={{
|
||||
backgroundColor: colors.bg,
|
||||
color: colors.color,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', gap: 0.4 }}>
|
||||
<IconButton>
|
||||
<ReplayIcon />
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
<VisibilityIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ py: 5 }}>
|
||||
No orders found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
{/* صفوف تعويضية فارغة لتثبيت ارتفاع الجدول */}
|
||||
{emptyRowsCount > 0 &&
|
||||
[...Array(emptyRowsCount)].map((_, idx) => (
|
||||
<TableRow key={`empty-${idx}`} sx={{ height: rowHeight }}>
|
||||
<TableCell colSpan={4} />
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Pagination */}
|
||||
<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} -{' '}
|
||||
{Math.min(currentPage * itemsPerPage, filteredOrders.length)} of {filteredOrders.length}
|
||||
</Typography>
|
||||
|
||||
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderList;
|
||||
154
src/components/Home/Supplier/contect/PopularProductSection.js
Normal file
154
src/components/Home/Supplier/contect/PopularProductSection.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, Grid, useTheme, IconButton } from '@mui/material';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
const ITEMS_PER_PAGE = 10;
|
||||
|
||||
/* مكوّن الباجينيشن */
|
||||
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage > 1) onChange(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (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 PopularProductSection = ({ products = [], onProductClick }) => {
|
||||
const theme = useTheme();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
|
||||
const startIdx = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||
const visibleProducts = products.slice(startIdx, startIdx + ITEMS_PER_PAGE);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '10px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* الهيدر */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="h6" fontWeight={600}>All Products</Typography>
|
||||
</Box>
|
||||
|
||||
{/* المنتجات */}
|
||||
<Grid container spacing={2}>
|
||||
{visibleProducts.length > 0 ? (
|
||||
visibleProducts.map((product, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||
<ProductCard
|
||||
{...product}
|
||||
unit={product.unit || '/pk'}
|
||||
onClick={() => onProductClick && onProductClick(product)}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
) : (
|
||||
<Typography>There is no Product</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* الباجينيشن */}
|
||||
{totalPages > 1 && (
|
||||
<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 {startIdx + 1} - {Math.min(startIdx + ITEMS_PER_PAGE, products.length)} of {products.length}
|
||||
</Typography>
|
||||
|
||||
<SimplePagination
|
||||
currentPage={currentPage}
|
||||
pageCount={totalPages}
|
||||
onChange={setCurrentPage}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopularProductSection;
|
||||
117
src/components/Home/Supplier/contect/ProductCard.js
Normal file
117
src/components/Home/Supplier/contect/ProductCard.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Card, CardContent, CardMedia, IconButton } from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
const ProductCard = ({ name, price, discountedPrice, unit, image, onClick, onAdd }) => {
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
width: { xs: '160px', md: '152px' },
|
||||
height: 245,
|
||||
borderRadius: '20px',
|
||||
p: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#FCFCFC',
|
||||
boxShadow: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s ease-in-out',
|
||||
'&:hover': { transform: 'scale(1.02)' },
|
||||
}}
|
||||
>
|
||||
{/* صورة المنتج */}
|
||||
<CardMedia
|
||||
component="img"
|
||||
image={image || '/placeholder.png'}
|
||||
alt={name}
|
||||
sx={{
|
||||
width: '140px',
|
||||
height: '140px',
|
||||
objectFit: 'contain',
|
||||
mb: 2,
|
||||
pointerEvents: 'none', // ما يمنع الكليك على الكارد
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* تفاصيل المنتج */}
|
||||
<CardContent sx={{ p: 0, width: '100%' }}>
|
||||
<Typography variant="body1" fontWeight={600} fontSize={20} noWrap>
|
||||
{name}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pt: 2,
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{discountedPrice ? (
|
||||
<>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={600}
|
||||
sx={{
|
||||
textDecoration: 'line-through',
|
||||
color: 'text.secondary',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
${price}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={700}
|
||||
sx={{ color: '#FF914D', fontSize: '16px' }}
|
||||
>
|
||||
${discountedPrice}{' '}
|
||||
<Typography component="span" variant="caption" color="text.secondary">
|
||||
{unit}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={600}
|
||||
sx={{ color: '#FF914D', fontSize: '16px' }}
|
||||
>
|
||||
${price}{' '}
|
||||
<Typography component="span" variant="caption" color="text.secondary">
|
||||
{unit}
|
||||
</Typography>
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* زر الإضافة */}
|
||||
<IconButton
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // يمنع فتح التفاصيل عند الضغط على الزر
|
||||
if (onAdd) onAdd();
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: '#FF8551',
|
||||
color: '#fff',
|
||||
ml: 3,
|
||||
width: 28,
|
||||
height: 28,
|
||||
'&:hover': { backgroundColor: '#ff7043' },
|
||||
}}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductCard;
|
||||
94
src/components/Home/Supplier/contect/ProductDetail.js
Normal file
94
src/components/Home/Supplier/contect/ProductDetail.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import { Box, Typography, Button, IconButton, LinearProgress, useTheme } from "@mui/material";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import ShoppingCartOutlinedIcon from "@mui/icons-material/ShoppingCartOutlined";
|
||||
import { CartContext } from "../../../../contexts/CartContextR";
|
||||
// import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||
import api from '../../../../services/authService';
|
||||
|
||||
const ProductDetail = ({ product, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const { addToCart , createNewCart } = useContext(CartContext);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [totalPrice, setTotalPrice] = useState(Number(product?.price) || 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (product) {
|
||||
setTotalPrice(Number(product.price) * quantity);
|
||||
}
|
||||
}, [quantity, product]);
|
||||
|
||||
if (!product) return null;
|
||||
|
||||
const handleIncrease = () => quantity < 9 && setQuantity(q => q + 1);
|
||||
const handleDecrease = () => quantity > 1 && setQuantity(q => q - 1);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addToCart(product, quantity);
|
||||
createNewCart();
|
||||
alert(`${product.name} added to cart! Quantity: ${quantity}, Total: $${totalPrice.toFixed(2)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", backgroundColor: "#fff", borderRadius: "8px", p: 3, mx: "auto" }}>
|
||||
<Box component="img" src={product.image} alt={product.name} sx={{ width: "50%", objectFit: "contain" }} />
|
||||
|
||||
<Box sx={{ width: "50%", pl: 4, display: "flex", flexDirection: "column" }}>
|
||||
<Typography variant="h5" fontWeight={700} mb={0.5}>{product.name}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "#777", mb: 1 }}>{product.category}</Typography>
|
||||
<Typography variant="h6" sx={{ color: theme.palette.primary.main, fontWeight: 700, mb: 1 }}>
|
||||
${Number(product.price).toFixed(2)} {product.unit}
|
||||
</Typography>
|
||||
|
||||
{/* <Typography variant="caption" sx={{ fontWeight: 600 }}>20 Item left</Typography> */}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={100}
|
||||
sx={{
|
||||
mt: 3,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#f0f0f0",
|
||||
"& .MuiLinearProgress-bar": { backgroundColor: theme.palette.primary.main },
|
||||
mb: 2
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle2" fontWeight={600}>Quantity</Typography>
|
||||
<Box sx={{ display: "flex", alignItems: "center", border: "1px solid #ddd", borderRadius: "4px", width: "150px", mt: 1, mb: 0.5 }}>
|
||||
<IconButton size="small" onClick={handleDecrease}><RemoveIcon /></IconButton>
|
||||
<Typography sx={{ width: "40px", textAlign: "center" }}>{quantity}</Typography>
|
||||
<IconButton size="small" onClick={handleIncrease}><AddIcon /></IconButton>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ color: "#999", mb: 2 }}>Maximum purchase 9</Typography>
|
||||
|
||||
<Typography variant="h6" sx={{ color: theme.palette.primary.main, fontWeight: 700, mb: 2 }}>
|
||||
Total: ${totalPrice.toFixed(2)}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 10 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
startIcon={<ShoppingCartOutlinedIcon />}
|
||||
sx={{ borderColor: theme.palette.primary.main, color: theme.palette.primary.main, textTransform: "none" }}
|
||||
onClick={handleAddToCart}
|
||||
>
|
||||
Add To Cart
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onClick={onBack}
|
||||
sx={{ borderColor: "#999", color: "#999" }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail;
|
||||
140
src/components/Home/Supplier/contect/TopItems.js
Normal file
140
src/components/Home/Supplier/contect/TopItems.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
CardMedia,
|
||||
IconButton,
|
||||
Grid,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
const ProductCard = ({ name, price, unit, image }) => {
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
width: { xs: '160px', md: '150px' },
|
||||
height: 253,
|
||||
borderRadius: '20px',
|
||||
p: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#FCFCFC',
|
||||
boxShadow: 'none',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
image={image}
|
||||
alt={name}
|
||||
sx={{
|
||||
width: '120px',
|
||||
height: '400px',
|
||||
objectFit: 'contain',
|
||||
mb: 2,
|
||||
}}
|
||||
/>
|
||||
<CardContent sx={{ p: 0, width: '100%' }}>
|
||||
<Typography variant="body1" fontWeight={600} noWrap>
|
||||
{name}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pt: 2,
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={600}
|
||||
sx={{ color: '#FF914D', fontSize: '16px' }}
|
||||
>
|
||||
${price}{' '}
|
||||
<Typography component="span" variant="caption" color="text.secondary">
|
||||
{unit}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<IconButton
|
||||
color="primary"
|
||||
size="small"
|
||||
sx={{ backgroundColor: '#FF8551', color: '#fff', ml: 5, width: 28, height: 28 }}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const TopItems = ({ item, showAll, onToggleShowAll }) => {
|
||||
const theme = useTheme();
|
||||
const isXs = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const visibleProducts = showAll ? item : item.slice(0, 6);
|
||||
|
||||
const maxHeight = showAll ? 'auto' : isXs ? '340px' : '335px';
|
||||
const maxWidth = showAll ? { xs: '90%', sm: '100%' } : undefined;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mb: showAll ? { xs: 10, sm: 10 } : 0,
|
||||
mr: showAll ? { xs: 2, sm: 3, md: 0 } : { xs: 2, sm: 2, md: 0 },
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pr: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 3 },
|
||||
pt: { xs: 2, sm: 1.5 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
borderRadius: '10px',
|
||||
padding: '20px',
|
||||
backgroundColor: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
transition: 'max-height 0.3s ease-in-out',
|
||||
overflowY: showAll ? 'auto' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
Top Items
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="primary"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={onToggleShowAll}
|
||||
>
|
||||
{showAll ? 'Show Less' : 'See All'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} sx={{ width: '100%' }}>
|
||||
{visibleProducts.map((product, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||
<ProductCard
|
||||
name={product.name}
|
||||
price={product.price}
|
||||
unit={product.unit}
|
||||
image={product.image}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopItems;
|
||||
@@ -1,95 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
|
||||
const NoTraining = () => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Header Section */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||
mb: 3,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 3 },
|
||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||
gap: { xs: 2, sm: 0 }
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: '500',
|
||||
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
||||
color: '#121212'
|
||||
}}
|
||||
>
|
||||
Dashboard
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Empty State Content */}
|
||||
<Card
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
p: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
{/* Image Placeholder */}
|
||||
<Box
|
||||
component="img"
|
||||
src="/images/compeoter.png"
|
||||
alt="No products available"
|
||||
sx={{
|
||||
width: isSmallScreen ? 150 : 200,
|
||||
height: 'auto',
|
||||
mb: 1,
|
||||
opacity: 0.8
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Title with colored "Sorry!" */}
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: { xs: '14px', md: '18px' },
|
||||
mb: 2,
|
||||
color: '#5F6868',
|
||||
whiteSpace: 'pre-line' // هذا يسمح بكسر السطر عند المسافات
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ fontWeight: 600, fontSize: '18px' }}>Sorry!</Box>{' '}
|
||||
There are no updates now..
|
||||
{'\n'}check back later.
|
||||
</Typography>
|
||||
|
||||
{/* Action Button */}
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoTraining;
|
||||
لم تُعرض بعض الملفات لأن الكثير من الملفات تغيرت في هذا الاختلاف إظهار المزيد
المرجع في مشكلة جديدة
حظر مستخدم