1
0

Initial commit - restaurant dashboard

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

عرض الملف

@@ -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>

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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

عرض الملف

@@ -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;

عرض الملف

@@ -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>
);

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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, its 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, its 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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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',
},
}}
>

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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' },
];

عرض الملف

@@ -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
}}
>
Dont 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 Interiors */}
<Box sx={{ pt: 1 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Kitchen Interiors
</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 Equipments */}
<Box sx={{ pt: 1 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Kitchen Equipments
</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;

عرض الملف

@@ -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>

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;

لم تُعرض بعض الملفات لأن الكثير من الملفات تغيرت في هذا الاختلاف إظهار المزيد