1
0

Initial commit - restaurant dashboard

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

148
package-lock.json مولّد
عرض الملف

@@ -1,6 +1,6 @@
{
"name": "resturant-dashBord",
"name": "resturant-dashbord",
"version": "0.1.0",
@@ -12,10 +12,12 @@
"": {
"name": "resturant-dashBord",
"name": "resturant-dashbord",
"version": "0.1.0",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.14.0",
@@ -28,6 +30,8 @@
"@mui/x-date-pickers": "^8.5.2",
"@react-oauth/google": "^0.12.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
@@ -40,12 +44,18 @@
"date-fns": "^4.1.0",
"dayjs": "^1.11.13",
"file-saver": "^2.0.5",
"framer-motion": "^12.23.0",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.5.23",
"jwt-decode": "^4.0.0",
"react": "^19.1.0",
"react-circular-progressbar": "^2.2.0",
@@ -7210,6 +7220,26 @@
},
"node_modules/@react-oauth/google": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz",
"integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
@@ -14650,6 +14680,18 @@
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.0",
@@ -18674,6 +18716,60 @@
},
"node_modules/framer-motion": {
"version": "12.23.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.0.tgz",
"integrity": "sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.22.0",
"motion-utils": "^12.19.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fresh": {
"version": "0.5.2",
@@ -24062,6 +24158,24 @@
},
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": {
"version": "4.5.4",
@@ -24970,6 +25084,36 @@
},
"node_modules/motion-dom": {
"version": "12.22.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.22.0.tgz",
"integrity": "sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.19.0"
}
},
"node_modules/motion-utils": {
"version": "12.19.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz",
"integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",

عرض الملف

@@ -18,6 +18,8 @@
"@mui/x-date-pickers": "^8.5.2",
"@react-oauth/google": "^0.12.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
@@ -30,12 +32,18 @@
"date-fns": "^4.1.0",
"dayjs": "^1.11.13",
"file-saver": "^2.0.5",
"framer-motion": "^12.23.0",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.5.23",
"jwt-decode": "^4.0.0",
"react": "^19.1.0",
"react-circular-progressbar": "^2.2.0",
@@ -106,8 +114,6 @@
"main": "restored-file.js",
"devDependencies": {},
"author": "",
"license": "ISC"

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ثنائية
public/image.png

ملف ثنائي غير معروض.

قبل

العرض:  |  الارتفاع:  |  الحجم: 6.2 KiB

بعد

العرض:  |  الارتفاع:  |  الحجم: 1.0 MiB

ثنائية
public/images/dash.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 121 KiB

ثنائية
public/images/image.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 80 KiB

عرض الملف

@@ -2,7 +2,6 @@
margin: 0;
padding: 0;
text-align: center;
/* background-color: #9aa0b8; */
}
body,
@@ -122,4 +121,27 @@ html,
font-weight: 400;
}
@font-face {
font-family: Hind Siliguri;
src: url('/public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf');
font-weight: 700;
}
@font-face {
font-family: Hind Siliguri;
src: url('/public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf');
font-weight: 500;
}
@font-face {
font-family: Hind Siliguri;
src: url('/public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf');
font-weight: 600;
}
@font-face {
font-family: Hind Siliguri;
src: url('/public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf');
font-weight: 400;
}
/* === CUSTOM FONTS === */

عرض الملف

@@ -1,129 +1,62 @@
import React from 'react';
import './App.css';
import { ThemeProvider } from '@mui/material/styles';
import theme from './theme';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ThemeProvider } from '@mui/material/styles';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { GoogleOAuthProvider } from '@react-oauth/google';
import PrivateRoute from '../src/components/Routes/PrivateRoute';
// الصفحات العامة
import LoginForm from './components/Authentication/SignUp_In/LoginForm';
import RegisterForm from './components/Authentication/SignUp_In/RegisterForm';
import ForgetPassword from './components/Authentication/ForgetPassword/ForgetFormMain';
import Dashboard from './components/Home/Dashboard/Dashboard';
import Inventory from './components/Home/Inventory/Inventory';
import Analytics from './components/Home/Analytics&Reporting/Analytics';
import Supplier from './components/Home/Supplier/Supplier';
import Cashier from './components/Home/Cashier/Cashier';
import CreateYourRestaurant from './components/Home/CreateYourRestaurant/CreateRestaurant';
import Training from './components/Home/Training/Training';
import RestaurantProfile from './components/Home/RestaurantProfile/RestaurantProfile';
import HostKitchen from './components/Home/HostKitchen/HostKitchen';
import Settings from './components/Home/Settings/Setting';
import PrivateRoute from './components/Routes/PrivateRoute';
import PublicRoute from './components/Routes/PublicRoute';
import { Navigate } from 'react-router-dom';
import { TermsPage, PrivacyPage } from './components/Authentication/Legal/TermsPrivacyComponents';
import { RestaurantProvider } from './contexts/RestaurantContext';
import ProtectedRoutes from './components/Routes/ProtectedRoutes';
import { SnackbarProvider } from "./contexts/SnackbarContext";
import { UserProvider } from './contexts/UserContext';
function App() {
return (
<ThemeProvider theme={theme}>
<Router>
<Routes>
{/* الصفحات العامة */}
<Route path="/" element={<Navigate to="/login" replace />} />
<GoogleOAuthProvider clientId="443509481732-4i5ne811hr90rn75q3fbqevqi9eu3lvk.apps.googleusercontent.com">
<Router>
<UserProvider>
{/* ✅ لفّ كل المشروع بـ SnackbarProvider */}
<SnackbarProvider>
<Routes>
{/* التوجيه التلقائي للصفحة الرئيسية */}
<Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/" element={<PublicRoute><LoginForm /></PublicRoute>} />
<Route path="/login" element={<PublicRoute><LoginForm /></PublicRoute>} />
<Route path="/register" element={<PublicRoute><RegisterForm /></PublicRoute>} />
<Route path="/forget" element={<PublicRoute><ForgetPassword /></PublicRoute>} />
<Route path="/login" element={<PublicRoute><LoginForm /></PublicRoute>} />
<Route path="/register" element={<PublicRoute><RegisterForm /></PublicRoute>} />
<Route path="/forget" element={<PublicRoute><ForgetPassword /></PublicRoute>} />
{/* الصفحات المحمية */}
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
<Route
path="/inventory"
element={
<PrivateRoute>
<Inventory />
</PrivateRoute>
}
/>
<Route
path="/settings"
element={
<PrivateRoute>
<Settings />
</PrivateRoute>
}
/>
<Route
path="/profile"
element={
<PrivateRoute>
<RestaurantProfile />
</PrivateRoute>
}
/>
<Route
path="/create-kitchen"
element={
<PrivateRoute>
<CreateYourRestaurant />
</PrivateRoute>
}
/>
<Route
path="/host-kitchen"
element={
<PrivateRoute>
<HostKitchen />
</PrivateRoute>
}
/>
<Route
path="/cashier"
element={
<PrivateRoute>
<Cashier />
</PrivateRoute>
}
/>
<Route
path="/supplier"
element={
<PrivateRoute>
<Supplier />
</PrivateRoute>
}
/>
<Route
path="/analytics"
element={
<PrivateRoute>
<Analytics />
</PrivateRoute>
}
/>
<Route
path="/training"
element={
<PrivateRoute>
<Training />
</PrivateRoute>
}
/>
</Routes>
</Router>
{/* روابط الشروط والخصوصية */}
<Route path="/terms" element={<TermsPage />} />
<Route path="/privacy" element={<PrivacyPage />} />
<Route
path="/*"
element={
<RestaurantProvider>
<ProtectedRoutes />
</RestaurantProvider>
}
/>
</Routes>
</SnackbarProvider>
</UserProvider>
</Router>
</GoogleOAuthProvider>
</ThemeProvider>
);
}
export default App;

عرض الملف

@@ -13,7 +13,7 @@ const Congratulations = ({ emailOrPhone, setEmailOrPhone }) => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
// overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',

عرض الملف

@@ -1,147 +1,226 @@
// EmailInputSection.js
import React, { useState } from 'react';
import { Box, Button, TextField, Typography ,useTheme} from '@mui/material';
import { Box, Button, TextField, Typography, useTheme, Snackbar, Alert } from '@mui/material';
import authService from '../../../services/authService';
const EmailInputSection = ({ emailOrPhone, setEmailOrPhone }) => {
const theme = useTheme();
const [currentSlide, setCurrentSlide] = useState(0);
const slides = [0, 1, 2, 3];
const EmailInputSection = ({ emailOrPhone, setEmailOrPhone, onStepComplete }) => {
const theme = useTheme();
const [loading, setLoading] = useState(false);
const handleSend = () => {
// عملية الإرسال
};
// حالات Snackbar
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarSeverity, setSnackbarSeverity] = useState('success');
const [inputError, setInputError] = useState('');
const handleNext = () => {
if (currentSlide < slides.length - 1) {
setCurrentSlide(prev => prev + 1);
}
};
const handleCloseSnackbar = (event, reason) => {
if (reason === 'clickaway') return;
setSnackbarOpen(false);
};
const handleBack = () => {
if (currentSlide > 0) {
setCurrentSlide(prev => prev - 1);
}
};
return (
<>
<Box>
<Box
component="img"
src="/image.png"
alt="logo"
sx={{
width: { xs: '5vh', sm: '7vh' },
maxWidth: { xs: '40px', sm: '80px' },
height: 'auto',
objectFit: 'contain',
overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
}}
/>
</Box>
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
<Typography
variant="h4"
fontWeight={700}
sx={{
fontSize: {
xs: '1.8rem',
sm: '2rem',
md: '2.2rem'
}
}}
>
Forget Password
</Typography>
const handleSend = async () => {
setInputError(''); // مسح الأخطاء السابقة
<Typography variant="body1" color="text.secondary">
Enter your email to reset it and regain access to your account.
</Typography>
if (!emailOrPhone) {
setInputError("Please enter your email.");
return;
}
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography
variant="body2"
color="black"
sx={{
fontWeight: '500',
fontSize: '16px'
}}
>
Email/Phone
</Typography>
<TextField
placeholder="Enter your email"
type="email"
variant="outlined"
fullWidth
value={emailOrPhone}
onChange={(e) => setEmailOrPhone(e.target.value)}
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: '#3f51b5',
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
}
}}
/>
</Box>
if (!validateEmail(emailOrPhone)) {
setInputError("Please enter a valid email address.");
return;
}
{/* send */}
<Button
variant="contained"
fullWidth
onClick={handleSend}
sx={{
color:'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '30px',
textTransform: 'none',
fontWeight: 600,
fontSize: '16px',
height: '48px',
'&:hover': {
backgroundColor: theme.palette.primary.hover ,
transition: 'all 0.4s ease'
}
}}
>
Send
</Button>
setLoading(true);
const response = await authService.resetPassword(emailOrPhone);
setLoading(false);
<Typography
textAlign="center"
fontWeight={500}
sx={{
fontSize: '16px',
display: 'block',
width: '100%',
mx: 'auto',
color: '#969BA7'
}}
>
By log in, I agree to the&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
</Typography>
</>
);
if (response.success !== false) {
setSnackbarMessage("A reset code has been sent to your email.");
setSnackbarSeverity('success');
setSnackbarOpen(true);
setTimeout(() => {
if (onStepComplete) onStepComplete();
}, 5000);
} else {
setInputError('');
let errorMessage = "";
if (response.errors) {
const firstError = Object.values(response.errors)[0];
errorMessage = Array.isArray(firstError) ? firstError[0] : firstError;
} else if (response.message) {
errorMessage = response.message;
} else {
errorMessage = "Failed to send reset code.";
}
if (
errorMessage.toLowerCase().includes("not found") ||
errorMessage.toLowerCase().includes("not registered") ||
errorMessage.toLowerCase().includes("doesn't exist") ||
errorMessage.toLowerCase().includes("email")
) {
setInputError("This email is not registered.");
} else if (errorMessage.toLowerCase().includes("invalid credentials")) {
setInputError("Email or password is incorrect.");
} else {
setSnackbarMessage(errorMessage);
setSnackbarSeverity('error');
setSnackbarOpen(true);
}
}
};
return (
<Box sx={{ width: '100%' }}>
<Box>
<Box
component="img"
src="/image.png"
alt="logo"
sx={{
width: { xs: '5vh', sm: '7vh' },
maxWidth: { xs: '40px', sm: '80px' },
height: 'auto',
objectFit: 'contain',
overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': { display: 'none' },
}}
/>
</Box>
<Typography
variant="h4"
fontWeight={700}
sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}
>
Forget Password
</Typography>
<Typography variant="body1" color="text.secondary" mb={2}>
Enter your email to reset it and regain access to your account.
</Typography>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Email
</Typography>
<TextField
placeholder="Enter your email"
type="email"
variant="outlined"
fullWidth
value={emailOrPhone}
onChange={(e) => setEmailOrPhone(e.target.value)}
error={!!inputError}
helperText={inputError}
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: '#3f51b5',
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
},
}}
/>
</Box>
<Button
variant="contained"
fullWidth
onClick={handleSend}
disabled={loading}
sx={{
color: 'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '30px',
textTransform: 'none',
fontWeight: 600,
fontSize: '16px',
height: '48px',
'&:hover': {
backgroundColor: theme.palette.primary.hover,
transition: 'all 0.4s ease',
},
}}
>
{loading ? "Sending..." : "Send"}
</Button>
{/*
<Typography
textAlign="center"
fontWeight={500}
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
>
By log in, I agree to the&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>
Terms of Service
</a>{' '}
and&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>
Privacy Policy
</a>
</Typography> */}
{/* Terms */}
<Typography
// variant="caption"
textAlign="center"
fontWeight={500}
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
>
By logging in, I agree to the{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/terms', 'TermsOfService')}
>
Terms of Service
</Box>{' '}
and{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
>
Privacy Policy
</Box>.
</Typography>
<Snackbar
open={snackbarOpen}
autoHideDuration={4000}
onClose={handleCloseSnackbar}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
onClose={handleCloseSnackbar}
severity={snackbarSeverity}
sx={{ width: { xs: '40%', sm: '60%', md: '100%' }, color: 'white', fontSize: '16px', fontWeight: '500', backgroundColor: '#e57f3f', borderRadius: 6, mb: 6 }}
variant="filled"
>
{snackbarMessage}
</Alert>
</Snackbar>
</Box>
);
};
export default EmailInputSection;

عرض الملف

@@ -1,17 +1,17 @@
import React, { useState } from 'react';
import { Box, Stack ,useTheme} from '@mui/material';
import { Box, Stack, useTheme } from '@mui/material';
import Side from './Side';
import EmailInputSection from './EmailInputSection';
import OtpVerification from './OtpVerification';
import NewPassword from './NewPassword';
import Congratulations from './Congratulations';
const ForgetForm = () => {
const [emailOrPhone, setEmailOrPhone] = useState('');
const [currentSlide, setCurrentSlide] = useState(0);
const theme = useTheme();
const [stepCompleted, setStepCompleted] = useState(false);
const theme = useTheme();
const slides = [
{ component: EmailInputSection, title: "Email Verification" },
{ component: OtpVerification, title: "OTP Verification" },
@@ -22,33 +22,37 @@ const ForgetForm = () => {
const handleNext = () => {
if (currentSlide < slides.length - 1) {
setCurrentSlide(prev => prev + 1);
setStepCompleted(false);
}
};
const handleBack = () => {
if (currentSlide > 0) {
setCurrentSlide(prev => prev - 1);
setStepCompleted(true);
}
};
const CurrentComponent = slides[currentSlide].component;
return (
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
minHeight: '100vh',
backgroundColor: '#fff',
}}
>
{/* ✅ Sidebar يظهر فقط من sm+ */}
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
minHeight: '100vh',
backgroundColor: '#fff' ,
'&::-webkit-scrollbar': { display: 'none' },
}}>
<Box sx={{ width: { sm: '40%', xs: '0%' }, display: { xs: 'none', sm: 'block' } }}>
<Side currentStepIndex={currentSlide} onNext={handleNext} onBack={handleBack} />
<Side
currentStepIndex={currentSlide}
onNext={handleNext}
onBack={handleBack}
stepCompleted={stepCompleted}
/>
</Box>
{/* ✅ Form */}
<Box
sx={{
width: { sm: '60%' },
@@ -62,32 +66,25 @@ const ForgetForm = () => {
backgroundColor: '#fff',
}}
>
<Box
sx={{
width: '100%',
maxWidth: 686,
pr: { xs: 3, sm: 2, md: 1 },
pt: { xs: 2, sm: 4, md: 1 },
}}
>
<Box sx={{ width: '100%', maxWidth: 686, pr: { xs: 3, sm: 2, md: 1 }, pt: { xs: 2, sm: 4, md: 1 } }}>
<Stack spacing={2} alignItems="flex-start">
{/* إظهار المكون الحالي فقط */}
<CurrentComponent
emailOrPhone={emailOrPhone}
setEmailOrPhone={setEmailOrPhone}
onStepComplete={() => {
setStepCompleted(true);
setCurrentSlide(prev => prev + 1); // هنا تنقل مباشرة للصفحة التالية
}}
onBack={handleBack}
/>
</Stack>
</Box>
{/* ✅ مؤشر الشرائح السفلي */}
<Box
sx={{
position: { xs: 'absolute', sm: 'absolute' },
bottom: { xs: '5%', sm: '-1%', md: '2%' },
position: 'absolute',
bottom: { xs: '5%', sm: '5%', md: '2%' },
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
@@ -95,11 +92,7 @@ const ForgetForm = () => {
width: '80%',
maxWidth: 600,
justifyContent: 'center',
height: {
xs: 3,
sm: 5,
md: 6,
},
height: { xs: 3, sm: 5, md: 6 },
}}
>
{slides.map((_, index) => (
@@ -108,12 +101,11 @@ const ForgetForm = () => {
sx={{
width: '100vh',
height: 5,
bgcolor: index === currentSlide ? theme.palette.primary.main : 'rgba(0, 0, 0, 0.2)',
bgcolor: index === currentSlide ? theme.palette.primary.main : 'rgba(0, 0, 0, 0.2)',
borderRadius: 2,
cursor: 'pointer',
transition: 'background-color 0.3s',
}}
// onClick={() => setCurrentSlide(index)} // يمكنك تفعيلها لاحقًا
/>
))}
</Box>
@@ -122,4 +114,5 @@ const ForgetForm = () => {
);
};
export default ForgetForm;

عرض الملف

@@ -1,217 +1,222 @@
// EmailInputSection.js
import React, { useState } from 'react';
import { Box, Button, TextField, Typography } from '@mui/material';
import { Box, Button, TextField, Typography, IconButton, InputAdornment } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { IconButton, InputAdornment } from '@mui/material';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import { VisibilityOutlined } from '@mui/icons-material';
import authService from '../../../services/authService';
const EmailInputSection = ({ emailOrPhone, setEmailOrPhone }) => {
const theme = useTheme();
const handleTogglePassword = () => {
setShowPassword((prev) => !prev);
};
const NewPassword = ({ emailOrPhone, onStepComplete }) => {
const theme = useTheme();
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [currentSlide, setCurrentSlide] = useState(0);
const slides = [0, 1, 2, 3];
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
const handleSend = () => {
// عملية الإرسال
};
const handleToggleConfirmPassword = () => {
setShowConfirmPassword((prev) => !prev);
};
const handleTogglePassword = () => setShowPassword(prev => !prev);
const handleToggleConfirmPassword = () => setShowConfirmPassword(prev => !prev);
const handleNext = () => {
if (currentSlide < slides.length - 1) {
setCurrentSlide(prev => prev + 1);
}
};
const handleSend = async () => {
setErrors({});
const handleBack = () => {
if (currentSlide > 0) {
setCurrentSlide(prev => prev - 1);
}
};
return (
<>
<Box>
<Box
component="img"
src="/image.png"
alt="logo"
sx={{
width: { xs: '5vh', sm: '7vh' },
maxWidth: { xs: '40px', sm: '80px' },
height: 'auto',
objectFit: 'contain'
}}
/>
</Box>
if (!password || !passwordConfirm) {
alert("Please fill in both password fields.");
return;
}
if (password !== passwordConfirm) {
alert("Passwords do not match.");
return;
}
<Typography
variant="h4"
fontWeight={700}
sx={{
fontSize: {
xs: '1.8rem',
sm: '2rem',
md: '2.2rem'
}
}}
>
Create a New Password
</Typography>
setLoading(true);
try {
const response = await authService.updatePassword({
email: emailOrPhone,
password,
confirmPassword: passwordConfirm
});
setLoading(false);
<Typography variant="body1" color="text.secondary">
Enter your email to reset it and regai access to your account.
</Typography>
if (response.success) {
alert("Password updated successfully.");
if (onStepComplete) onStepComplete();
} else {
alert(response.message || "Failed to update password.");
}
} catch (error) {
setLoading(false);
if (error.response && error.response.status === 422) {
setErrors(error.response.data.errors || {});
} else {
alert("Error: " + (error.message || "Unknown error"));
}
}
};
{/* Password Input */}
<Box sx={{ width: '100%', 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)' // ظل برتقالي خفيف
}
},
'& input::-ms-reveal, & input::-ms-clear': {
display: 'none',
},
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleTogglePassword} edge="end">
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
return (
<>
<Box>
<Box
component="img"
src="/image.png"
alt="logo"
sx={{
width: { xs: '5vh', sm: '7vh' },
maxWidth: { xs: '40px', sm: '80px' },
height: 'auto',
objectFit: 'contain'
}}
/>
</Box>
<Typography
variant="h4"
fontWeight={700}
sx={{
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
}}
>
Create a New Password
</Typography>
{/* Confirm Password Input */}
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography
variant="body2"
color="black"
sx={{
fontWeight: '500',
fontSize: '16px'
}}
>
Confirm Password
</Typography>
<TextField
type={showConfirmPassword ? 'text' : 'password'}
placeholder="Confirm 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: '#FF914D',
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={handleToggleConfirmPassword} edge="end">
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
Enter your new password to regain access to your account.
</Typography>
{/* send */}
<Button
variant="contained"
fullWidth
onClick={handleSend}
sx={{
color: 'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '30px',
textTransform: 'none',
fontWeight: 600,
fontSize: '16px',
height: '48px',
'&:hover': {
backgroundColor: theme.palette.primary.hover ,
transition: 'all 0.4s ease'
}
}}
>
Send
</Button>
<Typography
textAlign="center"
fontWeight={500}
sx={{
fontSize: '16px',
display: 'block',
width: '100%',
mx: 'auto',
color: '#969BA7'
}}
>
By log in, I agree to the&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
</Typography>
</>
);
<Box sx={{ width: '100%', 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 your password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={password}
onChange={e => setPassword(e.target.value)}
error={!!errors.password}
helperText={errors.password && errors.password.join(' ')}
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)'
}
},
'& input::-ms-reveal, & input::-ms-clear': { display: 'none' },
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleTogglePassword} edge="end">
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 3 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Confirm Password
</Typography>
<TextField
type={showConfirmPassword ? 'text' : 'password'}
placeholder="Confirm your password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={passwordConfirm}
onChange={e => setPasswordConfirm(e.target.value)}
error={!!errors.password_confirmation}
helperText={errors.password_confirmation && errors.password_confirmation.join(' ')}
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: '#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={handleToggleConfirmPassword} edge="end">
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
<Button
variant="contained"
fullWidth
onClick={handleSend}
disabled={loading}
sx={{
color: 'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '30px',
textTransform: 'none',
fontWeight: 600,
fontSize: '16px',
height: '48px',
'&:hover': {
backgroundColor: theme.palette.primary.hover,
transition: 'all 0.4s ease'
}
}}
>
{loading ? "Sending..." : "Send"}
</Button>
{/* Terms */}
<Typography
// variant="caption"
textAlign="center"
fontWeight={500}
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
>
By logging in, I agree to the{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/terms', 'TermsOfService')}
>
Terms of Service
</Box>{' '}
and{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
>
Privacy Policy
</Box>.
</Typography>
</>
);
};
export default EmailInputSection;
export default NewPassword;

عرض الملف

@@ -1,18 +1,54 @@
import React, { useState, useRef } from 'react';
import { Box, TextField, Typography, Button, Stack ,useTheme} from '@mui/material';
import {
Box, TextField, Typography, Button, Stack, useTheme,
Snackbar, Alert
} from '@mui/material';
import authService from '../../../services/authService';
const OtpVerification = () => {
const [emailOrPhone, setEmailOrPhone] = useState('');
const [currentSlide, setCurrentSlide] = useState(0);
const slides = [0, 1, 2, 3];
const OtpVerification = ({ emailOrPhone, onStepComplete }) => {
const [otp, setOtp] = useState(Array(6).fill(''));
const inputRefs = useRef(Array(6).fill().map(() => React.createRef()));
const [loading, setLoading] = useState(false);
const [otp, setOtp] = useState(Array(7).fill(''));
const inputRefs = useRef(Array(7).fill().map(() => React.createRef()));
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarSeverity, setSnackbarSeverity] = useState('success');
const handleSend = () => {
const theme = useTheme();
const handleSnackbarClose = (event, reason) => {
if (reason === 'clickaway') return;
setSnackbarOpen(false);
};
const handleSend = async () => {
const otpCode = otp.join('');
console.log('Submitted OTP:', otpCode);
// عملية التحقق من الكود
if (otpCode.length < 6) {
setSnackbarMessage("Please enter the complete OTP code.");
setSnackbarSeverity("error");
setSnackbarOpen(true);
return;
}
setLoading(true);
try {
const response = await authService.verifyCode(emailOrPhone, otpCode);
setLoading(false);
if (response.success !== false) {
setSnackbarMessage("OTP verified successfully.");
setSnackbarSeverity("success");
setSnackbarOpen(true);
if (onStepComplete) onStepComplete(); // الانتقال للخطوة التالية
} else {
setSnackbarMessage(response.message || "Invalid OTP code.");
setSnackbarSeverity("error");
setSnackbarOpen(true);
}
} catch (error) {
setLoading(false);
setSnackbarMessage("Failed to verify OTP. Please try again.");
setSnackbarSeverity("error");
setSnackbarOpen(true);
}
};
const handleOtpChange = (e, index) => {
@@ -22,30 +58,17 @@ const OtpVerification = () => {
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
if (value && index < 6) {
if (value && index < otp.length - 1) {
inputRefs.current[index + 1].current.focus();
}
};
const handleKeyDown = (e, index) => {
if (e.key === 'Backspace' && !otp[index] && index > 0) {
inputRefs.current[index - 1].current.focus();
}
};
const handleNext = () => {
if (currentSlide < slides.length - 1) {
setCurrentSlide(prev => prev + 1);
}
};
const handleBack = () => {
if (currentSlide > 0) {
setCurrentSlide(prev => prev - 1);
}
};
const theme = useTheme();
return (
<>
@@ -77,12 +100,11 @@ const OtpVerification = () => {
Enter OTP Verification
</Typography>
<Typography variant="body1" color="text.secondary">
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
Kindly enter the OTP code sent to your registered email/phone for account verification.
</Typography>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 3 }}>
<Stack
direction="row"
spacing={{ xs: 0, sm: 1, md: 1.5 }}
@@ -113,15 +135,15 @@ const OtpVerification = () => {
height: { xs: '3rem', sm: '3.25rem', md: '4rem' },
'& .MuiOutlinedInput-root': {
borderRadius: '0.5rem',
height: '100%', // تأكيد تطابق الطول
height: '100%',
'& fieldset': {
borderColor: '#ccc',
},
'&:hover fieldset': {
borderColor: theme.palette.primary.main,
borderColor: theme.palette.primary.main,
},
'&.Mui-focused fieldset': {
borderColor: theme.palette.primary.main,
borderColor: theme.palette.primary.main,
},
},
'& .MuiInputBase-input': {
@@ -135,47 +157,83 @@ const OtpVerification = () => {
/>
))}
</Stack>
</Box>
<Button
variant="contained"
fullWidth
onClick={handleSend}
disabled={loading}
sx={{
color:'white',
backgroundColor: theme.palette.primary.main,
color: 'white',
backgroundColor: theme.palette.primary.main,
borderRadius: '30px',
textTransform: 'none',
fontWeight: 600,
fontSize: '16px',
width: '95%',
height: '48px',
mx: 'auto',
display: 'block',
'&:hover': {
backgroundColor: theme.palette.primary.hover ,
backgroundColor: theme.palette.primary.hover,
transition: 'all 0.4s ease'
}
}}
>
Send
{loading ? "Sending..." : "Send"}
</Button>
{/* Terms */}
<Typography
// variant="caption"
textAlign="center"
fontWeight={500}
sx={{
fontSize: '16px',
display: 'block',
width: '100%',
mx: 'auto',
color: '#969BA7'
}}
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
>
By log in, I agree to the&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and&nbsp;
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
By logging in, I agree to the{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/terms', 'TermsOfService')}
>
Terms of Service
</Box>{' '}
and{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
>
Privacy Policy
</Box>.
</Typography>
<Snackbar
open={snackbarOpen}
autoHideDuration={4000}
onClose={handleSnackbarClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
onClose={handleSnackbarClose}
severity={snackbarSeverity}
sx={{
width: '100%',
fontSize: '16px',
fontWeight: '500',
backgroundColor: theme.palette.primary.main,
borderRadius: 6,
mb: 6
}}
variant="filled"
>
{snackbarMessage}
</Alert>
</Snackbar>
</>
);
};

عرض الملف

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Typography, Stack, Button ,useTheme } from '@mui/material';
import { Box, Typography, Stack, Button, useTheme } from '@mui/material';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
@@ -27,7 +27,7 @@ const steps = [
];
const Side = ({ currentStepIndex, onNext, onBack }) => {
const theme = useTheme();
const theme = useTheme();
return (
<Box
sx={{
@@ -46,7 +46,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
Instructions for secure password modification. Follow simple steps for password change.
</Typography>
<Stack spacing={6} position="relative" sx={{ ml: 3 }}>
<Stack spacing={6} position="relative" sx={{ ml: {xs:-1,md:2.1} }}>
{steps.map((step, index) => (
<Box key={index} display="flex" alignItems="flex-start" gap={2} position="relative">
{index !== steps.length - 1 && (
@@ -109,7 +109,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
</Box>
</Box>
<Box>
<Box >
<Typography variant="subtitle1" sx={{ fontSize: '16px', fontWeight: 600 }}>
{step.title}
</Typography>
@@ -121,13 +121,11 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
))}
</Stack>
{/* ... (بقية محتوى الـ Side كما هو بدون تغيير) ... */}
{/* أزرار التنقل */}
<Box
sx={{
position: 'absolute',
bottom: 2,
bottom: {sm:31 , md:2},
left: '50%',
transform: 'translateX(-50%)',
width: '90%',
@@ -152,7 +150,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
textTransform: 'none',
'&:hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main
color: theme.palette.primary.main
}
}}
>
@@ -172,7 +170,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
textTransform: 'none',
'&:hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main,
color: theme.palette.primary.main,
transition: 'all 0.4s ease'
}
}}

عرض الملف

@@ -0,0 +1,192 @@
import React, { useEffect } from 'react';
import { Button, Typography, Container, Box, Paper } from '@mui/material';
/*
File: TermsPrivacyComponents.jsx
Purpose: Two simple standalone pages (Terms and Privacy) and an example LoginCard
which opens them in a new tab/window and listens for the "agree" message.
How it works:
- The LoginCard opens a new tab using window.open(...).
- The opened page (TermsPage / PrivacyPage) will send a postMessage back to the opener
when the user clicks "Agree & Confirm", then attempt to close itself.
- The LoginCard listens for that postMessage and can react (e.g. show a toast,
update state, or navigate). This keeps the original page in control of what
happens after the user agreed.
Note: Some browsers may block window.close() on tabs not opened by window.open.
Because LoginCard uses window.open, the close() call should succeed. If the
page is opened directly (no opener) the Agree button will fall back to
navigating back (history.back()).
*/
/***********************
* Terms / Privacy Pages
***********************/
function sendAgreeToOpener(kind = 'terms') {
try {
if (window.opener && !window.opener.closed) {
// Structured message so opener can identify the response
window.opener.postMessage({ type: 'AGREE_CONFIRM', page: kind }, '*');
// Try to close this tab/window (works when opened by window.open)
window.close();
} else {
// No opener (user opened directly) — go back in history as a fallback
if (window.history && window.history.length > 1) {
window.history.back();
} else {
// Last fallback: navigate to the site's root
window.location.href = '/';
}
}
} catch (e) {
console.error('Failed to send message to opener or close window', e);
// fallback navigation
window.location.href = '/';
}
}
export function TermsPage() {
React.useEffect(() => {
document.title = "Resturant-Terms of Service";
}, []);
return (
<Container maxWidth="md" sx={{ py: 6 }}>
<Paper elevation={3} sx={{ p: 4 }}>
<Typography variant="h4" gutterBottom>
Terms of Service
</Typography>
<Typography variant="body1" paragraph>
Welcome to our Terms of Service. Please read carefully. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Integer posuere erat a ante.
</Typography>
<Box textAlign="center" sx={{ mt: 4 , color:'white'}}>
<Button variant="contained" onClick={() => sendAgreeToOpener('terms')} sx={{color:'white' , textTransform:'none'}}>
Agree & Confirm
</Button>
</Box>
</Paper>
</Container>
);
}
export function PrivacyPage() {
React.useEffect(() => {
document.title = "Resturant-Privacy Policy";
}, []);
return (
<Container maxWidth="md" sx={{ py: 6 }}>
<Paper elevation={3} sx={{ p: 4 }}>
<Typography variant="h4" gutterBottom>
Privacy Policy
</Typography>
<Typography variant="body1" paragraph>
This Privacy Policy explains how we collect and use personal information. Lorem ipsum dolor
sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore.
</Typography>
<Box textAlign="center" sx={{ mt: 4 ,color:'white'}}>
<Button variant="contained" onClick={() => sendAgreeToOpener('privacy')} sx={{color:'white' , textTransform:'none'}}>
Agree & Confirm
</Button>
</Box>
</Paper>
</Container>
);
}
/***********************
* Example LoginCard
***********************/
export default function LoginCard() {
useEffect(() => {
// Listen to messages from the terms/privacy pages.
const handler = (event) => {
// IMPORTANT: In production, check event.origin to be sure the message comes from
// a trusted origin. For demo purposes we accept any origin.
const data = event.data || {};
if (data && data.type === 'AGREE_CONFIRM') {
// data.page === 'terms' or 'privacy'
const page = data.page || 'unknown';
// Do whatever you need when user agreed: update state, show toast, close dialog, etc.
// Example: simple alert (replace with MUI Snackbar or app-level handler)
alert(`User agreed to ${page === 'terms' ? 'Terms of Service' : 'Privacy Policy'}`);
}
};
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}, []);
// Helper that opens a new tab (ensures opener exists)
function openInNewTab(path, name) {
// If your app uses router paths (e.g. /terms), build a full URL. Here we create
// a URL relative to current origin so the opened page belongs to the same origin
const url = new URL(path, window.location.href).toString();
// Use window.open (not anchor target="_blank") so the new tab has window.opener
window.open(url, name, 'noopener=false');
}
return (
<Paper elevation={2} sx={{ p: 4, maxWidth: 420, margin: '40px auto' }}>
<Typography variant="h6" textAlign="center" gutterBottom>
{/* Login title */}
Sign in
</Typography>
{/* Your existing Terms text (you provided earlier) */}
<Typography
variant="caption"
sx={{ fontSize: '14px', textAlign: 'center', color: '#969BA7', pt: 5 }}
>
By logging in, I agree to the{' '}
<Box component="span" sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }} onClick={() => openInNewTab('/terms', 'TermsOfService')}>
Terms of Service
</Box>{' '}
and{' '}
<Box component="span" sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }} onClick={() => openInNewTab('/privacy', 'PrivacyPolicy')}>
Privacy Policy
</Box>
.
</Typography>
{/* The rest of your login UI goes here (inputs, submit, etc.) */}
</Paper>
);
}
/*
Usage notes:
- If you're using a router (react-router), create routes for /terms and /privacy
that render <TermsPage /> and <PrivacyPage /> respectively.
Example (react-router v6):
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import LoginCard, { TermsPage, PrivacyPage } from './TermsPrivacyComponents';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<LoginCard />} />
<Route path="/terms" element={<TermsPage />} />
<Route path="/privacy" element={<PrivacyPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Security reminder: validate event.origin in the message handler in production.
*/

عرض الملف

@@ -7,10 +7,12 @@ import SidePanel from './SidePanel';
import { useNavigate, Link } from 'react-router-dom';
import authService from '../../../services/authService';
import { GoogleLogin, useGoogleLogin } from '@react-oauth/google';
import { jwtDecode } from "jwt-decode";
const LoginForm = () => {
const navigate = useNavigate();
const theme = useTheme();
const [showPassword, setShowPassword] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
@@ -28,21 +30,66 @@ const LoginForm = () => {
const result = await authService.login(email, password);
if (!result.success) {
setError(result.message || 'Login failed');
// 🔹 أولًا: نتحقق إن كانت الرسالة تشير إلى أن الحساب غير موجود
if (result.message && result.message.includes('Account not found')) {
setError('This account is not registered.');
}
// 🔹 ثانيًا: نتحقق من "Invalid credentials"
else if (result.message && result.message.includes('Invalid credentials')) {
setError('Email or password is incorrect.');
}
// 🔹 ثالثًا: نعرض أول خطأ موجود في كائن errors
else if (result.errors) {
const firstError = Object.values(result.errors)[0][0];
setError(firstError);
}
// 🔹 أخيرًا: fallback لأي رسالة أخرى
else {
setError(result.message || 'Login failed');
}
setLoading(false);
return;
}
localStorage.setItem('token', result.data.token);
localStorage.setItem('refresh_token', result.data.refresh_token);
setLoading(false);
// الانتقال للداشبورد مع منع الرجوع
navigate('/dashboard', { replace: true });
navigate('/restaurant', { replace: true });
};
const handleGoogleSuccess = async (tokenResponse) => {
try {
const res = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
headers: {
Authorization: `Bearer ${tokenResponse.access_token}`,
},
});
const userInfo = await res.json();
console.log('Google User Info:', userInfo);
if (userInfo.email) {
setEmail(userInfo.email);
}
setPassword('');
localStorage.setItem("google_user", JSON.stringify(userInfo));
} catch (e) {
console.error("Google login failed:", e);
setError("Google login failed");
}
};
const handleGoogleError = () => {
setError("Google login failed");
};
const loginWithGoogle = useGoogleLogin({
onSuccess: handleGoogleSuccess,
onError: handleGoogleError,
});
return (
<Box
bgcolor={"background.default"}
@@ -116,6 +163,7 @@ const LoginForm = () => {
Enter your username and password to access your account securely. Welcome back to our service!
</Typography>
{/* إدخال البريد */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
@@ -232,10 +280,10 @@ const LoginForm = () => {
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
</Box>
{/* Google Button */}
<Button
{/* <Button
variant="outlined"
fullWidth
onClick={loginWithGoogle}
sx={{
fontWeight: 500,
fontSize: '16px',
@@ -248,8 +296,8 @@ const LoginForm = () => {
'&:hover': {
borderColor: 'black',
backgroundColor: 'transparent'
}
, fontFamily: 'PlusJakartaSans'
},
fontFamily: 'PlusJakartaSans'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
@@ -260,10 +308,10 @@ const LoginForm = () => {
/>
</Box>
Login with Google
</Button>
</Button> */}
{/* Facebook Button */}
<Button
{/* <Button
variant="outlined"
fullWidth
sx={{
@@ -290,8 +338,7 @@ const LoginForm = () => {
/>
</Box>
Login with Facebook
</Button>
</Button> */}
{/* Register Link */}
<Typography
@@ -300,7 +347,7 @@ const LoginForm = () => {
fontSize: '16px',
textAlign: 'center',
color: '#969BA7',
pt: 3
pt: 2
}}
>
Dont have an account?{' '}
@@ -338,6 +385,7 @@ const LoginForm = () => {
</Typography>
{/* Terms */}
<Typography
variant="caption"
sx={{
@@ -348,14 +396,25 @@ const LoginForm = () => {
}}
>
By logging in, I agree to the{' '}
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/terms', 'TermsOfService')}
>
Terms of Service
</a>{' '}
</Box>{' '}
and{' '}
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
>
Privacy Policy
</a>.
</Box>.
</Typography>
</Stack>
</Box>
</Box>
@@ -365,3 +424,4 @@ const LoginForm = () => {
};
export default LoginForm;

عرض الملف

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState ,useContext } 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';
@@ -6,6 +6,19 @@ import { VisibilityOutlined } from '@mui/icons-material';
import SidePanel from './SidePanel';
import { Link, useNavigate } from 'react-router-dom';
import authService from '../../../services/authService';
import { UserContext } from '../../../contexts/UserContext';
// import { useGoogleRegister } from '@react-oauth/google';
const generateStrongPassword = () => {
const length = 12;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+";
let password = "";
for (let i = 0, n = charset.length; i < length; ++i) {
password += charset.charAt(Math.floor(Math.random() * n));
}
return password;
};
const Register = () => {
const theme = useTheme();
@@ -19,7 +32,11 @@ const Register = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
const { setUser } = useContext(UserContext);
// تفعيل/تعطيل إظهار كلمة السر
const handleTogglePassword = () => {
setShowPassword((prev) => !prev);
};
@@ -28,7 +45,8 @@ const Register = () => {
setShowConfirmPassword((prev) => !prev);
};
const handleRegister = async () => {
const handleRegister = async () => {
setLoading(true);
setError('');
setSuccessMessage('');
@@ -52,28 +70,93 @@ const Register = () => {
localStorage.setItem('token', result.data.token);
localStorage.setItem('refresh_token', result.data.refresh_token);
// ✅ تحديث الـ User Context مباشرة بعد التسجيل
setUser({
email: email,
token: result.data.token,
refresh_token: result.data.refresh_token,
adminData: result.data.Admin || null,
});
setTimeout(() => {
navigate('/dashboard', { replace: true }); // استبدال الرابط وعدم السماح بالعودة
navigate('/restaurant', { replace: true });
setLoading(false);
}, 500);
};
const handleGoogleSuccess = async (tokenResponse) => {
try {
const res = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
headers: {
Authorization: `Bearer ${tokenResponse.access_token}`,
},
});
const userInfo = await res.json();
console.log('Google User Info:', userInfo);
if (userInfo.email) {
const generatedPassword = generateStrongPassword(); // كلمة مرور مقترحة
setEmail(userInfo.email);
setPassword(generatedPassword);
setConfirmPassword(generatedPassword);
setError('');
setTimeout(() => {
setSuccessMessage('');
}, 5000);
} else {
setError('Google Register failed: No email found');
}
} catch (e) {
console.error('Google Register failed:', e);
setError('Google Register failed');
}
};
const handleGoogleError = () => {
setError('Google Register failed');
};
const handleSnackbarClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSnackbarOpen(false);
};
// // تهيئة تسجيل الدخول عبر جوجل باستخدام useGoogleRegister
// const RegisterWithGoogle = useGoogleRegister({
// onSuccess: handleGoogleSuccess,
// onError: handleGoogleError,
// });
return (
<Box bgcolor={"background.default"} color={"text.primary"} display="flex" sx={{
width: '100vw',
height: '100vh',
overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
}}>
<Box
bgcolor={'background.default'}
color={'text.primary'}
display="flex"
sx={{
width: '100vw',
height: '100vh',
overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
}}
>
<SidePanel />
<Box sx={{
marginLeft: { xs: 0, sm: '50%' },
width: { xs: '100%', sm: '50%' },
}}>
<Box
sx={{
marginLeft: { xs: 0, sm: '50%' },
width: { xs: '100%', sm: '50%' },
}}
>
<Box
flex={1}
paddingTop={4}
@@ -95,12 +178,13 @@ const Register = () => {
xs: '120%',
sm: '100%',
md: '120%',
lg: '120%'
lg: '120%',
},
maxWidth: '600px'
maxWidth: '600px',
}}
>
<Stack spacing={2}>
{/* logo */}
<Box>
<img
src="/image.png"
@@ -109,7 +193,7 @@ const Register = () => {
width: '4vw',
maxWidth: '80px',
height: 'auto',
objectFit: 'contain'
objectFit: 'contain',
}}
/>
</Box>
@@ -121,323 +205,291 @@ const Register = () => {
fontSize: {
xs: '1.8rem',
sm: '2rem',
md: '2.2rem'
}
md: '2.2rem',
},
}}
>
Register
</Typography>
<Typography
variant="body2"
color="text.secondary"
fontWeight={500}
sx={{ pb: 1 }}
>
Please fill out the registration form with accurate information to
create your account successfully.
<Typography variant="body2" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
Please fill out the registration form with accurate information to create your account successfully.
</Typography>
<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"
{/* حقل الايميل */}
<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
value={email}
onChange={(e) => setEmail(e.target.value)}
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={{ 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"
value={password}
onChange={(e) => setPassword(e.target.value)}
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>
{/* حقل التاكيد لكلمة المرور */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Confirm Password
</Typography>
<TextField
type={showConfirmPassword ? 'text' : 'password'}
placeholder="Confirm your password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
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: '#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={handleToggleConfirmPassword} edge="end">
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
),
}}
/>
</Box>
{/* Error & Success Message */}
{error && (
<Typography color="error" textAlign="center">
{error}
</Typography>
)}
{successMessage && (
<Typography color="green" textAlign="center">
{successMessage}
</Typography>
)}
{/* زر التسجيل */}
<Box sx={{ pt: 2 }}>
<Button
variant="contained"
fullWidth
onClick={handleRegister}
disabled={loading}
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,
},
}}
>
{loading ? 'Loading...' : 'Continue'}
</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' }}>
Or
</Typography>
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
</Box>
{/* زر التسجيل عبر جوجل */}
{/* <Button
variant="outlined"
fullWidth
value={email}
onChange={(e) => setEmail(e.target.value)}
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={{ 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"
value={password}
onChange={(e) => setPassword(e.target.value)}
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>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography
variant="body2"
color="black"
sx={{
fontWeight: '500',
fontSize: '16px'
}}
>
Confirm Password
</Typography>
<TextField
type={showConfirmPassword ? 'text' : 'password'}
placeholder="Confirm your password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
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: '#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={handleToggleConfirmPassword} edge="end">
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
{/* Error & Success Message */}
{error && (
<Typography color="error" textAlign="center">
{error}
</Typography>
)}
{successMessage && (
<Typography color="green" textAlign="center">
{successMessage}
</Typography>
)}
<Box sx={{ pt: 2 }}>
<Button
variant="contained"
fullWidth
onClick={handleRegister}
disabled={loading}
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
}
}}
>
{loading ? 'Loading...' : 'Continue'}
</Button>
</Box>
{/* Divider */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, py: 2 }}>
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
<Typography
variant="body1"
// onClick={() => RegisterWithGoogle()}
sx={{
fontWeight: 500,
fontSize: '16px',
color: 'black'
borderRadius: '50px',
height: '50px',
textTransform: 'none',
gap: 1,
borderColor: '#E6E6E6',
color: 'black',
'&:hover': {
borderColor: 'black',
backgroundColor: 'transparent',
},
}}
>
Or
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg"
alt="Google"
style={{ width: 25, height: 25 }}
/>
</Box>
Register with 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' }}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/0/05/Facebook_Logo_%282019%29.png"
alt="Facebook"
style={{ width: 25, height: 25 }}
/>
</Box>
Register with Facebook
</Button> */}
<Typography
variant="body2"
sx={{
fontSize: '16px',
textAlign: 'center',
color: '#969BA7',
pt: 3,
}}
>
Already have an account?{' '}
<Link
to="/login"
style={{
color: '#2261FF',
textDecoration: 'none',
}}
>
Login
</Link>
</Typography>
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
</Box>
{/* Google */}
<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' }}>
<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>
{/* Facebook */}
<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' }}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/0/05/Facebook_Logo_%282019%29.png"
alt="Facebook"
style={{ width: 25, height: 25 }}
/>
</Box>
Login with Facebook
</Button>
<Typography
variant="body2"
sx={{
fontSize: '16px',
textAlign: 'center',
color: '#969BA7',
pt: 3
}}
variant="caption"
sx={{
fontSize: '14px',
textAlign: 'center',
color: '#969BA7',
pt: 2
}}
>
Already have an account?{' '}
<Link
to="/login"
style={{
color: '#2261FF',
textDecoration: 'none'
}}
>
Login
</Link>
By logging in, I agree to the{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/terms', 'TermsOfService')}
>
Terms of Service
</Box>{' '}
and{' '}
<Box
component="span"
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
>
Privacy Policy
</Box>.
</Typography>
<Typography
variant="caption"
sx={{
fontSize: '14px',
textAlign: 'center',
color: '#969BA7',
pt: 5
}}
>
By log in, I agree to the and{' '}
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
Terms of Service
</a>{' '}
and{' '}
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
Privacy Policy
</a>.
</Typography>
</Stack>
</Box>
</Box>

عرض الملف

@@ -1,14 +1,11 @@
import React, { useState, useEffect } from 'react';
import {
Button,
Typography,
Stack,
Box
} from '@mui/material';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; // استيراد أيقونة السهم
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
const SidePanel = ({ setMode, mode }) => {
const [currentSlide, setCurrentSlide] = useState(0);
@@ -61,7 +58,7 @@ const SidePanel = ({ setMode, mode }) => {
sx={{
display: { xs: "none", sm: "block" },
position: 'sticky',
height: 'calc(102vh - 40px)', // لضمان ملاءمته للشاشة
height: 'calc(102vh - 40px)',
}}
>
@@ -96,9 +93,9 @@ const SidePanel = ({ setMode, mode }) => {
left: 0,
width: '100%',
height: {
md: '37vh', // الافتراضي للشاشات الصغيرة
md: '37vh',
// xs: '5vh',
sm: '42vh',// عندما تكون الشاشة متوسطة أو أكبر
sm: '42vh',
lg: '31vh', // من 1200px إلى أقل من 1536px
},
@@ -119,17 +116,17 @@ const SidePanel = ({ setMode, mode }) => {
<Button
variant="contained"
size="small"
onClick={() => window.open('https://www.google.com', '_blank')}
onClick={() => window.open('https://landing.velocrea.com/', '_blank')}
sx={{
mb: 1,
bgcolor: 'rgba(255, 255, 255, 0.24)',
color: 'white',
textTransform: 'none',
borderRadius: '12px', // زاوية منحنية
display: 'flex', // لتوزيع الأيقونة والنص بشكل أفقي
alignItems: 'center', // لضبط الأيقونة والنص في المنتصف
padding: '6px 20px', // يمكن تعديل المسافة حول النص والأيقونة
borderRadius: '12px',
display: 'flex',
alignItems: 'center',
padding: '6px 20px',
borderRadius: '50px',
fontSize: '16px',
fontWeight: 500,
@@ -138,8 +135,8 @@ const SidePanel = ({ setMode, mode }) => {
>
KITCHPLUS
{/* إضافة أيقونة السهم مع تعديل زاوية الدوران */}
<ArrowForwardIcon sx={{ ml: 1, transform: 'rotate(-45deg)' }} /> {/* تعديل زاوية السهم */}
<ArrowForwardIcon sx={{ ml: 1, transform: 'rotate(-45deg)' }} />
</Button>
<Typography

عرض الملف

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

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