diff --git a/package-lock.json b/package-lock.json
index 10d4ce7..00ab32b 100644
--- a/package-lock.json
+++ b/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",
diff --git a/package.json b/package.json
index b94d356..36a6a65 100644
--- a/package.json
+++ b/package.json
@@ -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"
diff --git a/public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf b/public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf
new file mode 100644
index 0000000..6b68991
Binary files /dev/null and b/public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf differ
diff --git a/public/fonts/Hind Siliguri/HindSiliguri-Light.ttf b/public/fonts/Hind Siliguri/HindSiliguri-Light.ttf
new file mode 100644
index 0000000..fed3dd6
Binary files /dev/null and b/public/fonts/Hind Siliguri/HindSiliguri-Light.ttf differ
diff --git a/public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf b/public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf
new file mode 100644
index 0000000..c953178
Binary files /dev/null and b/public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf differ
diff --git a/public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf b/public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf
new file mode 100644
index 0000000..864e220
Binary files /dev/null and b/public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf differ
diff --git a/public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf b/public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf
new file mode 100644
index 0000000..c0440c1
Binary files /dev/null and b/public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf differ
diff --git a/public/image.png b/public/image.png
index 01900c7..92b6e9e 100644
Binary files a/public/image.png and b/public/image.png differ
diff --git a/public/images/dash.png b/public/images/dash.png
new file mode 100644
index 0000000..384dcbe
Binary files /dev/null and b/public/images/dash.png differ
diff --git a/public/images/image.png b/public/images/image.png
new file mode 100644
index 0000000..4682bc5
Binary files /dev/null and b/public/images/image.png differ
diff --git a/src/App.css b/src/App.css
index bb7659d..2d8ce00 100644
--- a/src/App.css
+++ b/src/App.css
@@ -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 === */
diff --git a/src/App.js b/src/App.js
index 486d96e..6e8d6d5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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 (
-
-
- {/* الصفحات العامة */}
- } />
+
+
+
+ {/* ✅ لفّ كل المشروع بـ SnackbarProvider */}
+
+
+ {/* التوجيه التلقائي للصفحة الرئيسية */}
+ } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
- {/* الصفحات المحمية */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
+ {/* روابط الشروط والخصوصية */}
+ } />
+ } />
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
);
}
-
export default App;
diff --git a/src/components/Authentication/ForgetPassword/Congratulations.js b/src/components/Authentication/ForgetPassword/Congratulations.js
index 8f6fec2..7f9797a 100644
--- a/src/components/Authentication/ForgetPassword/Congratulations.js
+++ b/src/components/Authentication/ForgetPassword/Congratulations.js
@@ -13,7 +13,7 @@ const Congratulations = ({ emailOrPhone, setEmailOrPhone }) => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
- overflow: 'auto',
+ // overflow: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
diff --git a/src/components/Authentication/ForgetPassword/EmailInputSection.js b/src/components/Authentication/ForgetPassword/EmailInputSection.js
index dd801be..4133607 100644
--- a/src/components/Authentication/ForgetPassword/EmailInputSection.js
+++ b/src/components/Authentication/ForgetPassword/EmailInputSection.js
@@ -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 (
- <>
-
-
-
+ const validateEmail = (email) => {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ };
-
- Forget Password
-
+ const handleSend = async () => {
+ setInputError(''); // مسح الأخطاء السابقة
-
- Enter your email to reset it and regain access to your account.
-
+ if (!emailOrPhone) {
+ setInputError("Please enter your email.");
+ return;
+ }
-
-
- Email/Phone
-
- 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)'
- }
- }}
- />
-
+ if (!validateEmail(emailOrPhone)) {
+ setInputError("Please enter a valid email address.");
+ return;
+ }
- {/* send */}
-
+ setLoading(true);
+ const response = await authService.resetPassword(emailOrPhone);
+ setLoading(false);
-
- By log in, I agree to the
- Terms of Service and
- Privacy Policy
-
- >
- );
+ 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 (
+
+
+
+
+
+
+ Forget Password
+
+
+
+ Enter your email to reset it and regain access to your account.
+
+
+
+
+ Email
+
+ 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)',
+ },
+ }}
+ />
+
+
+
+ {/*
+
+ By log in, I agree to the
+
+ Terms of Service
+ {' '}
+ and
+
+ Privacy Policy
+
+ */}
+
+ {/* Terms */}
+
+ By logging in, I agree to the{' '}
+ window.open('/terms', 'TermsOfService')}
+ >
+ Terms of Service
+ {' '}
+ and{' '}
+ window.open('/privacy', 'PrivacyPolicy')}
+ >
+ Privacy Policy
+ .
+
+
+
+
+ {snackbarMessage}
+
+
+
+ );
};
export default EmailInputSection;
diff --git a/src/components/Authentication/ForgetPassword/ForgetFormMain.js b/src/components/Authentication/ForgetPassword/ForgetFormMain.js
index 84cbbae..41a469d 100644
--- a/src/components/Authentication/ForgetPassword/ForgetFormMain.js
+++ b/src/components/Authentication/ForgetPassword/ForgetFormMain.js
@@ -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 (
-
- {/* ✅ Sidebar يظهر فقط من sm+ */}
+
-
+
- {/* ✅ Form */}
{
backgroundColor: '#fff',
}}
>
-
+
-
-
- {/* إظهار المكون الحالي فقط */}
{
+ setStepCompleted(true);
+ setCurrentSlide(prev => prev + 1); // هنا تنقل مباشرة للصفحة التالية
+ }}
+ onBack={handleBack}
/>
-
-
- {/* ✅ مؤشر الشرائح السفلي */}
+
{
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)} // يمكنك تفعيلها لاحقًا
/>
))}
@@ -122,4 +114,5 @@ const ForgetForm = () => {
);
};
+
export default ForgetForm;
diff --git a/src/components/Authentication/ForgetPassword/NewPassword.js b/src/components/Authentication/ForgetPassword/NewPassword.js
index 2dcea07..595dc0e 100644
--- a/src/components/Authentication/ForgetPassword/NewPassword.js
+++ b/src/components/Authentication/ForgetPassword/NewPassword.js
@@ -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 (
- <>
-
-
-
+ if (!password || !passwordConfirm) {
+ alert("Please fill in both password fields.");
+ return;
+ }
+ if (password !== passwordConfirm) {
+ alert("Passwords do not match.");
+ return;
+ }
-
- Create a New Password
-
+ setLoading(true);
+ try {
+ const response = await authService.updatePassword({
+ email: emailOrPhone,
+ password,
+ confirmPassword: passwordConfirm
+ });
+ setLoading(false);
-
- Enter your email to reset it and regai access to your account.
-
+ 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 */}
-
-
- Password
-
-
-
- {showPassword ? : }
-
-
- )
- }}
- />
-
+ return (
+ <>
+
+
+
+
+ Create a New Password
+
- {/* Confirm Password Input */}
-
-
- Confirm Password
-
-
-
- {showConfirmPassword ? : }
-
-
- )
- }}
- />
-
+
+ Enter your new password to regain access to your account.
+
- {/* send */}
-
-
- By log in, I agree to the
- Terms of Service and
- Privacy Policy
-
- >
- );
+
+
+ Password
+
+ 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: (
+
+
+ {showPassword ? : }
+
+
+ )
+ }}
+ />
+
+
+
+
+ Confirm Password
+
+ 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: (
+
+
+ {showConfirmPassword ? : }
+
+
+ )
+ }}
+ />
+
+
+
+
+ {/* Terms */}
+
+ By logging in, I agree to the{' '}
+ window.open('/terms', 'TermsOfService')}
+ >
+ Terms of Service
+ {' '}
+ and{' '}
+ window.open('/privacy', 'PrivacyPolicy')}
+ >
+ Privacy Policy
+ .
+
+ >
+ );
};
-export default EmailInputSection;
+export default NewPassword;
diff --git a/src/components/Authentication/ForgetPassword/OtpVerification.js b/src/components/Authentication/ForgetPassword/OtpVerification.js
index dd36b1c..ea3775b 100644
--- a/src/components/Authentication/ForgetPassword/OtpVerification.js
+++ b/src/components/Authentication/ForgetPassword/OtpVerification.js
@@ -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
-
+
Kindly enter the OTP code sent to your registered email/phone for account verification.
-
-
+
{
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 = () => {
/>
))}
-
-
+
-
+ {/* Terms */}
- By log in, I agree to the
- Terms of Service and
- Privacy Policy
+ By logging in, I agree to the{' '}
+ window.open('/terms', 'TermsOfService')}
+ >
+ Terms of Service
+ {' '}
+ and{' '}
+ window.open('/privacy', 'PrivacyPolicy')}
+ >
+ Privacy Policy
+ .
+
+
+
+ {snackbarMessage}
+
+
+
>
);
};
diff --git a/src/components/Authentication/ForgetPassword/Side.js b/src/components/Authentication/ForgetPassword/Side.js
index e9c8712..addfd4d 100644
--- a/src/components/Authentication/ForgetPassword/Side.js
+++ b/src/components/Authentication/ForgetPassword/Side.js
@@ -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 (
{
Instructions for secure password modification. Follow simple steps for password change.
-
+
{steps.map((step, index) => (
{index !== steps.length - 1 && (
@@ -109,7 +109,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
-
+
{step.title}
@@ -121,13 +121,11 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
))}
- {/* ... (بقية محتوى الـ Side كما هو بدون تغيير) ... */}
-
{/* أزرار التنقل */}
{
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'
}
}}
diff --git a/src/components/Authentication/Legal/TermsPrivacyComponents.js b/src/components/Authentication/Legal/TermsPrivacyComponents.js
new file mode 100644
index 0000000..3e6347d
--- /dev/null
+++ b/src/components/Authentication/Legal/TermsPrivacyComponents.js
@@ -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 (
+
+
+
+ Terms of Service
+
+
+
+ Welcome to our Terms of Service. Please read carefully. Lorem ipsum dolor sit amet,
+ consectetur adipiscing elit. Integer posuere erat a ante.
+
+
+
+
+
+
+
+ );
+}
+
+export function PrivacyPage() {
+ React.useEffect(() => {
+ document.title = "Resturant-Privacy Policy";
+ }, []);
+
+ return (
+
+
+
+ Privacy Policy
+
+
+
+ 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.
+
+
+
+
+
+
+
+ );
+}
+
+
+/***********************
+ * 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 (
+
+
+ {/* Login title */}
+ Sign in
+
+
+ {/* Your existing Terms text (you provided earlier) */}
+
+ By logging in, I agree to the{' '}
+ openInNewTab('/terms', 'TermsOfService')}>
+ Terms of Service
+ {' '}
+ and{' '}
+ openInNewTab('/privacy', 'PrivacyPolicy')}>
+ Privacy Policy
+
+ .
+
+
+ {/* The rest of your login UI goes here (inputs, submit, etc.) */}
+
+
+ );
+}
+
+/*
+ Usage notes:
+ - If you're using a router (react-router), create routes for /terms and /privacy
+ that render and respectively.
+
+ Example (react-router v6):
+
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
+ import LoginCard, { TermsPage, PrivacyPage } from './TermsPrivacyComponents';
+
+ function App() {
+ return (
+
+
+ } />
+ } />
+ } />
+
+
+ );
+ }
+
+ export default App;
+
+ Security reminder: validate event.origin in the message handler in production.
+*/
diff --git a/src/components/Authentication/SignUp_In/LoginForm.js b/src/components/Authentication/SignUp_In/LoginForm.js
index b42b508..ca9faf7 100644
--- a/src/components/Authentication/SignUp_In/LoginForm.js
+++ b/src/components/Authentication/SignUp_In/LoginForm.js
@@ -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 (
{
Enter your username and password to access your account securely. Welcome back to our service!
+
{/* إدخال البريد */}
@@ -232,10 +280,10 @@ const LoginForm = () => {
- {/* Google Button */}
-
+ */}
{/* Facebook Button */}
-
Login with Facebook
-
-
+ */}
{/* Register Link */}
{
fontSize: '16px',
textAlign: 'center',
color: '#969BA7',
- pt: 3
+ pt: 2
}}
>
Don’t have an account?{' '}
@@ -338,6 +385,7 @@ const LoginForm = () => {
{/* Terms */}
+
{
}}
>
By logging in, I agree to the{' '}
-
+ window.open('/terms', 'TermsOfService')}
+ >
Terms of Service
- {' '}
+ {' '}
and{' '}
-
+ window.open('/privacy', 'PrivacyPolicy')}
+ >
Privacy Policy
- .
+ .
+
+
+
@@ -365,3 +424,4 @@ const LoginForm = () => {
};
export default LoginForm;
+
diff --git a/src/components/Authentication/SignUp_In/RegisterForm.js b/src/components/Authentication/SignUp_In/RegisterForm.js
index ed391cf..cca293c 100644
--- a/src/components/Authentication/SignUp_In/RegisterForm.js
+++ b/src/components/Authentication/SignUp_In/RegisterForm.js
@@ -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 (
-
+
-
+
{
xs: '120%',
sm: '100%',
md: '120%',
- lg: '120%'
+ lg: '120%',
},
- maxWidth: '600px'
+ maxWidth: '600px',
}}
>
+ {/* logo */}
{
width: '4vw',
maxWidth: '80px',
height: 'auto',
- objectFit: 'contain'
+ objectFit: 'contain',
}}
/>
@@ -121,323 +205,291 @@ const Register = () => {
fontSize: {
xs: '1.8rem',
sm: '2rem',
- md: '2.2rem'
- }
+ md: '2.2rem',
+ },
}}
>
Register
-
- Please fill out the registration form with accurate information to
- create your account successfully.
+
+ Please fill out the registration form with accurate information to create your account successfully.
-
-
- Email
-
-
+
+ Email
+
+ 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)',
+ },
+ }}
+ />
+
+
+ {/* حقل كلمة المرور */}
+
+
+ Password
+
+ 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: (
+
+
+ {showPassword ? : }
+
+
+ ),
+ }}
+ />
+
+
+ {/* حقل التاكيد لكلمة المرور */}
+
+
+ Confirm Password
+
+ 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: (
+
+
+ {showConfirmPassword ? : }
+
+
+ ),
+ }}
+ />
+
+
+ {/* Error & Success Message */}
+ {error && (
+
+ {error}
+
+ )}
+ {successMessage && (
+
+ {successMessage}
+
+ )}
+
+ {/* زر التسجيل */}
+
+
+
+
+
+ {/* Divider */}
+
+
+
+ Or
+
+
+
+
+ {/* زر التسجيل عبر جوجل */}
+ {/*
-
-
-
- Password
-
- 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: (
-
-
- {showPassword ? : }
-
-
- )
- }}
- />
-
-
-
-
- Confirm Password
-
- 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: (
-
-
- {showConfirmPassword ? : }
-
-
- )
- }}
- />
-
-
- {/* Error & Success Message */}
- {error && (
-
- {error}
-
- )}
- {successMessage && (
-
- {successMessage}
-
- )}
-
-
-
-
-
- {/* Divider */}
-
-
- 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
+
+
+
+ Register with Google
+ */}
+
+ {/* زر فيسبوك - فقط للعرض (لم تُضاف وظيفة) */}
+ {/* */}
+
+
+ Already have an account?{' '}
+
+ Login
+
-
-
-
- {/* Google */}
-
-
- {/* Facebook */}
-
- Already have an account?{' '}
-
- Login
-
+ By logging in, I agree to the{' '}
+ window.open('/terms', 'TermsOfService')}
+ >
+ Terms of Service
+ {' '}
+ and{' '}
+ window.open('/privacy', 'PrivacyPolicy')}
+ >
+ Privacy Policy
+ .
+
-
- By log in, I agree to the and{' '}
-
- Terms of Service
- {' '}
- and{' '}
-
- Privacy Policy
- .
-
diff --git a/src/components/Authentication/SignUp_In/SidePanel.js b/src/components/Authentication/SignUp_In/SidePanel.js
index f847db0..7735f8e 100644
--- a/src/components/Authentication/SignUp_In/SidePanel.js
+++ b/src/components/Authentication/SignUp_In/SidePanel.js
@@ -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 }) => {
{
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 (
-
-
@@ -94,15 +92,9 @@ const AnalyticsPage = () => {
duration: theme.transitions.duration.leavingScreen,
}),
}}>
- {isLoading ? (
- <>
-
-
-
- >
- ) : (
+
- )}
+
diff --git a/src/components/Home/Analytics&Reporting/AnalyticsContect.js b/src/components/Home/Analytics&Reporting/AnalyticsContect.js
index 8065230..acd06c1 100644
--- a/src/components/Home/Analytics&Reporting/AnalyticsContect.js
+++ b/src/components/Home/Analytics&Reporting/AnalyticsContect.js
@@ -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 (
-
+
+ {/* Tables Manager */}
+
+ {restaurantId && }
+
+
{/* Header Buttons */}
{
justifyContent="space-between"
alignItems="center"
gap={2}
- mb={3}
+ mb={1.5}
>
{
}
}}
>
- {[
- { 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 }) => (
))}
-
-
-
-
-
-
-
- {/* Data Sections */}
-
-
-
-
-
-
-
-
-
- {/* Chart Section */}
-
- {isLoading ? (
- <>
-
-
-
- >
- ) : (
-
- )}
+
+
+
+
+ {/* StatisticsCard */}
+
+ value}
+ timeFrame={timeFrame}
+ onTimeFrameChange={handleTimeFrameChange}
+ />
);
diff --git a/src/components/Home/Analytics&Reporting/SalesByLocation.js b/src/components/Home/Analytics&Reporting/SalesByLocation.js
index 94b19b3..1959365 100644
--- a/src/components/Home/Analytics&Reporting/SalesByLocation.js
+++ b/src/components/Home/Analytics&Reporting/SalesByLocation.js
@@ -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'
}}>
{
{
- 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 (
-
+
-
+ {/* */}
+
+
{/* Header */}
{/* Chart */}
-
+
+
))}
diff --git a/src/components/Home/Analytics&Reporting/TablesManager.js b/src/components/Home/Analytics&Reporting/TablesManager.js
new file mode 100644
index 0000000..5c5f5f8
--- /dev/null
+++ b/src/components/Home/Analytics&Reporting/TablesManager.js
@@ -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 (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: "8px",
+ backgroundColor: "#FFECE0",
+ // "&:hover": { backgroundColor: "#FFD6B5" },
+ color: theme.palette.primary.main,
+ // "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
+ }}
+ >
+
+
+
+ );
+};
+
+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 (
+
+ {/* Header */}
+
+
+ Tables
+
+
+
+ {/* فلتر */}
+ }
+ onClick={handleFilterClick}
+ >
+ {isMobile ? "" : "Filters"}
+
+
+ }
+ 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"}
+
+
+
+
+ {/* Menu الفلاتر */}
+
+
+ {/* جدول */}
+
+
+
+
+ Table Number
+ Capacity
+ Status
+ Actions
+
+
+
+ {loading
+ ? Array.from({ length: itemsPerPage }).map((_, idx) => (
+
+
+
+
+
+
+ ))
+ : (
+ <>
+ {addingRow && (
+
+
+ setNewRow({ ...newRow, table_number: e.target.value })}
+ />
+
+
+ setNewRow({ ...newRow, capacity: e.target.value })}
+ />
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {paginatedRows.map((row) => (
+
+ {row.table_number}
+ {row.capacity}
+
+
+
+
+ handleDeleteRow(row)}>
+
+
+
+
+ ))}
+ >
+ )}
+
+
+
+
+ {/* Pagination Footer */}
+
+
+ Showing {(currentPage - 1) * itemsPerPage + 1} -{" "}
+ {Math.min(currentPage * itemsPerPage, filteredRows.length)} of {filteredRows.length}
+
+
+
+
+ {/* Dialog الحذف */}
+
+
+ );
+};
+
+export default TablesManager;
diff --git a/src/components/Home/Analytics&Reporting/TopSellingProduct.js b/src/components/Home/Analytics&Reporting/TopSellingProduct.js
deleted file mode 100644
index 83fce6e..0000000
--- a/src/components/Home/Analytics&Reporting/TopSellingProduct.js
+++ /dev/null
@@ -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 (
-
- {/* العنوان وزر الفلاتر */}
-
-
- Top Selling Product
-
- }
- >
- {isMobile ? '' : 'Filters'}
-
-
-
- {/* جدول البيانات */}
-
-
-
-
- Product
- {!isMobile && Sales}
- Amount
- {!isMobile && Price}
- Status
-
-
-
- {paginatedData.map((row, i) => (
-
-
-
-
-
-
-
- {row.product}
-
-
-
- {!isMobile && {row.sales}}
- ${row.amount.toLocaleString()}
- {!isMobile && ${row.price}}
-
-
-
-
- ))}
-
-
-
-
- {/* التذييل مع الترقيم */}
-
-
- Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, data.length)} of {data.length}
-
-
- 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',
- },
- },
- }}
- />
-
-
- );
-};
-
-export default TopSellingProduct;
diff --git a/src/components/Home/AppBar.js b/src/components/Home/AppBar.js
index 4dc6987..ea4fc38 100644
--- a/src/components/Home/AppBar.js
+++ b/src/components/Home/AppBar.js
@@ -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 (
{
justifyContent: 'space-between',
}}
>
- {/* Left side with toggle button */}
-
- {/* Toggle button for mobile/tablet */}
+ {/* Left side */}
+
{(isMobile || isMediumScreen) && (
-
+
)}
- {location.pathname === '/dashboard' ? (
-
- Welcome to KitchPlus
-
- ) : (
- option.title)}
- renderInput={(params) => (
-
-
-
- ),
- }}
- sx={{
- '& .MuiInputBase-root': {
- backgroundColor: 'white',
- height: { xs: 36, sm: 38, md: 40 },
- },
- }}
- />
- )}
- />
- )}
+
+ Welcome to KitchPlus
+
{/* Right side */}
- {location.pathname === '/dashboard' && !isSmallScreen && (
-
- )}
- {location.pathname === '/dashboard' && (
-
- )}
-
-
+ {location.pathname === '/dashboard' && }
+
+ navigate('/restaurant')}
+ >
+
-
+
{!isSmallScreen && (
- Admin@gmail.com
+ {user?.email || 'Admin@gmail.com'}
)}
-
-
-
-
);
};
-export default KitchPlusAppBar;
\ No newline at end of file
+export default KitchPlusAppBar;
diff --git a/src/components/Home/Cart/Cart.js b/src/components/Home/Cart/Cart.js
new file mode 100644
index 0000000..8a39298
--- /dev/null
+++ b/src/components/Home/Cart/Cart.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Cart;
diff --git a/src/components/Home/Cart/contect/CartDetails.js b/src/components/Home/Cart/contect/CartDetails.js
new file mode 100644
index 0000000..04c6395
--- /dev/null
+++ b/src/components/Home/Cart/contect/CartDetails.js
@@ -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 (
+ <>
+
+
+ Cart #{cart.id} Details
+
+
+ {editMode ? (
+ <>
+ setTotalPrice(e.target.value)}
+ fullWidth
+ sx={{ mb: 2 }}
+ />
+
+ Items:
+
+ {cartItems.map(item => (
+
+ handleChangeQuantity(item.id, Number(e.target.value))}
+ sx={{ width: '120px' }}
+ />
+ }
+ />
+
+ ))}
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+ Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}
+
+ Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
+
+ Items:
+
+ {cartItems.map(item => (
+
+
+
+ ))}
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ {/* Delete Confirmation Dialog */}
+
+ >
+ );
+};
+
+export default CartDetails;
\ No newline at end of file
diff --git a/src/components/Home/Cart/contect/CartView.js b/src/components/Home/Cart/contect/CartView.js
new file mode 100644
index 0000000..de7a7f8
--- /dev/null
+++ b/src/components/Home/Cart/contect/CartView.js
@@ -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 ;
+
+ return (
+
+ {/* السهم للعودة */}
+
+
+
+
+
+ Current Cart
+
+
+
+ {cart.length === 0 ? (
+ No items in cart.
+ ) : (
+ <>
+ {cart.map(item => (
+
+
+ {item.name}
+ Unit: {item.unit}
+
+
+ Quantity: {item.quantity}
+ Total: ${item.totalPrice}
+
+
+ ))}
+
+
+
+ Total Price: ${totalPrice.toFixed(2)}
+
+
+ >
+ )}
+
+ {cart.length > 0 && (
+
+
+
+
+ )}
+
+ );
+};
+
+export default CartView;
diff --git a/src/components/Home/Cart/contect/Orders.js b/src/components/Home/Cart/contect/Orders.js
new file mode 100644
index 0000000..e705972
--- /dev/null
+++ b/src/components/Home/Cart/contect/Orders.js
@@ -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 (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: '8px',
+ backgroundColor: '#FFECE0',
+ '&:hover': { backgroundColor: '#FFD6B5' },
+ color: theme.palette.primary.main,
+ '&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
+ }}
+ >
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+ }
+
+ if (selectedCart) {
+ return (
+
+ );
+ }
+
+ if (showCartView) {
+ return (
+ setShowCartView(false)}
+ onSend={handleAddOrder}
+ onCartCreated={(newCart) => {
+ setOrdersData(prev => [newCart, ...prev]);
+ setShowCartView(false);
+ }}
+ adminId={adminId}
+ />
+ );
+ }
+
+ // ────────────── Main Orders Table ──────────────
+ return (
+
+
+
+ Cart
+
+
+
+
+
+
+
+
+
+ Cart ID
+ Total Price
+ Created At
+ Items Count
+ Action
+
+
+
+ {loading
+ ? Array.from({ length: itemsPerPage }).map((_, idx) => (
+
+
+
+
+
+
+
+ ))
+ : (
+ <>
+ {paginatedOrders.map(order => {
+ const attributes = order?.attributes || {};
+ const relationships = order?.relationships || {};
+ const cartItems = relationships?.cartItems || relationships?.cart_items || [];
+ return (
+ handleRowClick(order.id)}
+ >
+ {order?.id || '--'}
+ {attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}
+ {attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}
+ {cartItems.length}
+
+
+
+
+ );
+ })}
+
+ {paginatedOrders.length < itemsPerPage &&
+ Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
+
+
+
+ ))
+ }
+ >
+ )}
+
+
+
+
+
+
+ Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
+
+
+
+
+
+ );
+};
+
+export default Orders;
diff --git a/src/components/Home/Cashier/Cashier.js b/src/components/Home/Cashier/Cashier.js
index ab11add..c294836 100644
--- a/src/components/Home/Cashier/Cashier.js
+++ b/src/components/Home/Cashier/Cashier.js
@@ -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 = () => {
{
+ 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 (
+
+
+ {/* Left: Logo */}
+
+
+
+ KITCH
+
+
+ PLUS
+
+
+
+ {/* Right: Log Out Button */}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default KitchPlusAppBar;
diff --git a/src/components/Home/CreateNewRestaurant/CreateRestaurant.js b/src/components/Home/CreateNewRestaurant/CreateRestaurant.js
new file mode 100644
index 0000000..d6ec97c
--- /dev/null
+++ b/src/components/Home/CreateNewRestaurant/CreateRestaurant.js
@@ -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 = [
+ setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
+ onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />,
+ setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
+ onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />,
+ setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
+ onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />,
+ setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
+ onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />,
+ setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
+ onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />,
+ 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 (
+
+
+
+
+
+
+
+
+ {/* Sidebar profile for desktop */}
+
+
+ setCurrentStep(prev => Math.max(prev - 1, 0))}
+ />
+
+
+
+ {/* Step content */}
+
+
+ {steps[currentStep]}
+
+
+
+
+
+
+
+
+
+ {snackbar.message}
+
+
+
+ );
+};
+
+export default CreateRestaurant;
diff --git a/src/components/Home/HostKitchen/SideProfile.js b/src/components/Home/CreateNewRestaurant/SideProfile.js
similarity index 81%
rename from src/components/Home/HostKitchen/SideProfile.js
rename to src/components/Home/CreateNewRestaurant/SideProfile.js
index a146e38..5ef8e16 100644
--- a/src/components/Home/HostKitchen/SideProfile.js
+++ b/src/components/Home/CreateNewRestaurant/SideProfile.js
@@ -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 }) => {
>
{/* العنوان الرئيسي */}
- Host Kitchen Flow
+ Create Your Restaurant
- Complete this process to register your restaurant on our amazing food platform.
+ Complete this process to register your restaurant on our amazing food platform.
{/* الخطوات */}
-
+
{steps.map((step, index) => (
{/* الخط الرأسي بين الدوائر */}
@@ -45,10 +45,10 @@ 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
}}
>
+
+
+
+ );
};
export default AdditionalNotes;
diff --git a/src/components/Home/CreateYourRestaurant/contcet/Budget.js b/src/components/Home/CreateYourRestaurant/contcet/Budget.js
index 773af14..a2ea84c 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/Budget.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/Budget.js
@@ -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 (
{
{/* Estimated Budget Input */}
-
+
{/* Expansion Plans Through Cloud Kitchen (No of Branches) Input */}
-
-
-
+
{/* Next Button */}
-
{
Next
- {/* زر Back تحت زر Next */}
{
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
-
);
};
-// مكون فرعي لتقليل التكرار في الحقول
-const InputField = ({ label, placeholder, theme }) => (
+const InputField = ({ label, placeholder, theme, value, onChange }) => (
{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' },
diff --git a/src/components/Home/CreateYourRestaurant/contcet/CloudKitchenProject.js b/src/components/Home/CreateYourRestaurant/contcet/CloudKitchenProject.js
index 7e17b58..939dacb 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/CloudKitchenProject.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/CloudKitchenProject.js
@@ -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 (
{
- {[
- { label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
- { label: 'Cuisine Type', placeholder: 'Italian, Chinese, etc.' },
- ].map((field, index) => (
-
-
- {field.label}
-
-
+
+ Restaurant Name
+
+
-
- ))}
+ },
+ '& .MuiOutlinedInput-root.Mui-focused': {
+ borderColor: '#3f51b5',
+ boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
+ }
+ }}
+ />
+
+ {/* Cuisine Type */}
+
+
+ Cuisine Type
+
+
+ {cuisineTypes.length > 0 ? (
+ cuisineTypes.map((type) => (
+
+ ))
+ ) : (
+
+ )}
+
+
-
+ {/* Existing Brand */}
+
Existing Brand
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
+
+
+
+ }
+ label="Yes"
+ />
+
+ }
+ label="No"
+ />
+
+ {errors.is_existing_brand && (
+ {errors.is_existing_brand}
+ )}
+
- {/* Further Brand Details Input */}
+ {/* Further Brand Details */}
Further Brand Details
{
/>
- {[
- { label: 'Age Group', placeholder: '15 - 75' },
- { label: 'Location', placeholder: 'Street 123, Jordan' },
- ].map((field, index) => (
-
-
- {field.label}
-
-
+
+ Age Group
+
+
-
- ))}
+ },
+ '& .MuiOutlinedInput-root.Mui-focused': {
+ borderColor: '#3f51b5',
+ boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
+ }
+ }}
+ />
+
+ {/* Location */}
+
+
+ Location
+
+
+
-
+ {/* Menu Status */}
+
Menu Status
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
- {/* Further Brand Details Input */}
-
-
- Need Help
-
-
+
+
+
+ }
+ label="Yes"
+ />
+
+ }
+ label="No"
+ />
+
+ {errors.menu_status && (
+ {errors.menu_status}
+ )}
+
+ {/* زر Next */}
{
>
Next
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
);
diff --git a/src/components/Home/CreateYourRestaurant/contcet/ConfirmationDialog.js b/src/components/Home/CreateYourRestaurant/contcet/ConfirmationDialog.js
index 6cf94a1..296eae3 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/ConfirmationDialog.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/ConfirmationDialog.js
@@ -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 (
- {/* العنوان بجانب الدائرة */}
{
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
@@ -128,7 +127,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
gap: 2,
}}
>
- {/* الزر الأول - Filled */}
+
{
},
}}
>
- Done
+ Censel
+ {/* الزر الأول - Filled */}
+
+ {loading ? (
+
+ ) : (
+ 'Submit'
+ )}
+
diff --git a/src/components/Home/CreateYourRestaurant/contcet/OperationalDetails.js b/src/components/Home/CreateYourRestaurant/contcet/OperationalDetails.js
index d3c6f64..ab59167 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/OperationalDetails.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/OperationalDetails.js
@@ -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
+
{
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
- {/* Staff MembersInput */}
+ {/* Staff Members Input */}
-
+
Staff Members
{
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)',
+ },
}}
/>
-
{/* Country Selector */}
-
+
Country
{
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)',
+ },
}}
-
>
{countries.map((country) => (
-
-
- {/* Expansion Plan Cities MembersInput */}
+ {/* Expansion Plan Cities Input */}
-
- Expansion Plan Cities
+
+ Expansion Plan Cities
{
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)',
+ },
}}
/>
-
- {/* Next Button */}
+
+
+ {/* Next Button */}
{
Next
- {/* زر Back تحت زر Next */}
+ {/* Back Button */}
{
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
-
);
diff --git a/src/components/Home/CreateYourRestaurant/contcet/RequiredEquipments.js b/src/components/Home/CreateYourRestaurant/contcet/RequiredEquipments.js
index f43f732..12d4028 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/RequiredEquipments.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/RequiredEquipments.js
@@ -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 (
{
- {/* Operational Hours Input */}
+ {/* Equipment Input */}
Equipment
{
/>
-
+ {/* Need Specialized Equipment */}
+
Need Specialized Equipment
-
+
{
label="No"
/>
+ {errors.needSpecializedEquipment && (
+
+ {errors.needSpecializedEquipment}
+
+ )}
-
-
- {/* Specialized Equipments For Food Preparation Input */}
-
-
- Specialized Equipments For Food Preparation
-
-
+
+ Specialized Equipments For Food Preparation
+
+
-
-
- {/* 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)',
+ }
+ }}
+ />
+
+ )}
+ {/* Buttons */}
{
Next
- {/* زر Back تحت زر Next */}
{
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
-
);
diff --git a/src/components/Home/CreateYourRestaurant/contcet/VisualIdentity.js b/src/components/Home/CreateYourRestaurant/contcet/VisualIdentity.js
index 872894a..ef55b04 100644
--- a/src/components/Home/CreateYourRestaurant/contcet/VisualIdentity.js
+++ b/src/components/Home/CreateYourRestaurant/contcet/VisualIdentity.js
@@ -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 (
{
-
-
+
Visual Identity
-
+
{
label="No"
/>
+ {errors.visualIdentity && (
+
+ {errors.visualIdentity}
+
+ )}
- {/* OR Separator */}
+ {/* Upload Logo / Color Scheme */}
-
Upload Logo / Color Scheme If Any
- {/* Upload Menu Picture */}
{
}
}}
>
- Upload Picture
-
+ {formData.logo ? (formData.logo.name || 'Uploaded Image') : 'Upload Picture'}
+
+ {errors.logo && (
+
+ {errors.logo}
+
+ )}
-
- {/* Buttons: Back and Next */}
-
+ {/* Buttons */}
{
Next
- {/* زر Back تحت زر Next */}
{
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
-
);
diff --git a/src/components/Home/Dashboard/AnalyticsPage.js b/src/components/Home/Dashboard/AnalyticsPage.js
new file mode 100644
index 0000000..18e8bd0
--- /dev/null
+++ b/src/components/Home/Dashboard/AnalyticsPage.js
@@ -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 (
+
+ {/* Header Buttons */}
+
+
+ {[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
+ handleTimeFrameChange(value)}
+ >
+ {label}
+
+ ))}
+
+
+ {/* Date Picker + Today Button */}
+
+
+ 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
+
+
+
+
+ {/* StatisticsCard */}
+
+ value}
+ timeFrame={timeFrame}
+ onTimeFrameChange={handleTimeFrameChange}
+ height={320}
+ />
+
+
+ );
+};
+
+export default AnalyticsPage;
diff --git a/src/components/Home/Dashboard/AnalyticsPageOccupancy.js b/src/components/Home/Dashboard/AnalyticsPageOccupancy.js
new file mode 100644
index 0000000..8ab787f
--- /dev/null
+++ b/src/components/Home/Dashboard/AnalyticsPageOccupancy.js
@@ -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 (
+
+ {/* Occupancy Card */}
+
+
+
+
+ );
+};
+
+export default AnalyticsPageOccupancy;
diff --git a/src/components/Home/Dashboard/AnalyticsPagebill.js b/src/components/Home/Dashboard/AnalyticsPagebill.js
new file mode 100644
index 0000000..8e0ee10
--- /dev/null
+++ b/src/components/Home/Dashboard/AnalyticsPagebill.js
@@ -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 (
+
+ {/* Header Buttons */}
+
+
+ {[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
+ handleTimeFrameChange(value)}
+ >
+ {label}
+
+ ))}
+
+
+ {/* Date Picker + Today Button */}
+
+
+ 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
+
+
+
+
+ {/* StatisticsCard */}
+
+ value}
+ timeFrame={timeFrame}
+ onTimeFrameChange={handleTimeFrameChange}
+ height={420}
+ />
+
+
+ );
+};
+
+export default AnalyticsPagebill;
diff --git a/src/components/Home/Dashboard/Dashboard.js b/src/components/Home/Dashboard/Dashboard.js
index 7969b6a..6001f9d 100644
--- a/src/components/Home/Dashboard/Dashboard.js
+++ b/src/components/Home/Dashboard/Dashboard.js
@@ -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 = () => {
{
duration: theme.transitions.duration.leavingScreen,
}),
}}>
- {isLoading ? (
+ {/* {isLoading ? (
<>
>
) : (
- hasProducts ? :
- )}
+ hasProducts ?
+
+ :
+ )} */}
+
diff --git a/src/components/Home/Dashboard/DashboardContcet.js b/src/components/Home/Dashboard/DashboardContcet.js
index 39977bf..7455f82 100644
--- a/src/components/Home/Dashboard/DashboardContcet.js
+++ b/src/components/Home/Dashboard/DashboardContcet.js
@@ -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' }) => (
);
-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 (
-
-
-
-
- {React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
-
-
- {statusText}
-
-
-
-
- {title}
-
-
-
- {extraButton ? extraButton : }
-
-
-
-
-
-
- );
-};
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 = () => {
{/* Buttons */}
- {
>
Check Inventory
-
+ */}
{/* Status Cards */}
- {
iconSpacing={{ xs: 3, sm: 5, md: 7 }}
title="Delivery"
/>
-
+ */}
{/* OrderStatusCard/ StatisticsCard */}
{
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
}}
>
-
+
{/* StatisticsCard */}
@@ -370,11 +262,11 @@ const DashboardContect = () => {
sx={{
flexGrow: 1,
minWidth: 0,
- ml: { xs: 0, sm: 2 },
+ // ml: { xs: 0, sm: 2 },
// backgroundColor:'red'
}}
>
-
+
@@ -382,11 +274,11 @@ const DashboardContect = () => {
{/* RecentActivity */}
-
+
>
);
diff --git a/src/components/Home/Dashboard/NoProdectDash.js b/src/components/Home/Dashboard/NoProdectDash.js
index a472d02..8298865 100644
--- a/src/components/Home/Dashboard/NoProdectDash.js
+++ b/src/components/Home/Dashboard/NoProdectDash.js
@@ -68,7 +68,7 @@ const NoProdectDash = () => {
fontSize: { xs: '14px', md: '18px' },
mb: 2,
color: '#5F6868',
- whiteSpace: 'pre-line' // هذا يسمح بكسر السطر عند المسافات
+ whiteSpace: 'pre-line' // يسمح بكسر السطر عند المسافات
}}
>
Sorry!{' '}
diff --git a/src/components/Home/Dashboard/OrderStatusCard.js b/src/components/Home/Dashboard/OrderStatusCard.js
index b672730..ffce155 100644
--- a/src/components/Home/Dashboard/OrderStatusCard.js
+++ b/src/components/Home/Dashboard/OrderStatusCard.js
@@ -1,260 +1,241 @@
-import React from 'react';
+import React, { useState } from 'react';
import {
- Box,
- Typography,
- Card,
- CardContent,
- Divider,
- Chip,
- useTheme,
- IconButton,
- useMediaQuery
+ Box,
+ Typography,
+ Card,
+ CardContent,
+ useTheme,
+ IconButton,
+ useMediaQuery,
+ Menu,
+ MenuItem,
+ Button,
+ ButtonGroup,
+ TextField
} from '@mui/material';
import {
- TrendingUp as TrendingUpIcon,
- TrendingDown as TrendingDownIcon,
- CheckCircle as CheckCircleIcon,
- Cancel as CancelIcon,
- Pending as PendingIcon
+ TrendingUp as TrendingUpIcon,
+ TrendingDown as TrendingDownIcon,
} from '@mui/icons-material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import {
- CircularProgressbar,
- buildStyles
+ CircularProgressbar,
+ buildStyles
} from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css';
-import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
-import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
-// import { Box, IconButton, Paper, Typography, useTheme, useMediaQuery } from '@mui/material';
-import {
-
- ResponsiveContainer,
-
-} from 'recharts';
-
+import { ResponsiveContainer } from 'recharts';
+import dayjs from 'dayjs';
const OrderStatusCard = ({
- title = 'Order Status',
- period = 'This Quarter',
- progress = 70.5,
- progressChange = '+10%',
- progressDirection = 'up', // or 'down'
- description = 'You succeeded in earning $240 today, it’s higher than yesterday',
- completed = '$20k',
- canceled = '$16k',
- pending = '$1.5k'
+ title = 'Order Status',
+ period = 'This Quarter',
+ progress = 70.5,
+ description = 'You succeeded in earning $240 today, it’s higher than yesterday',
+ timeFrame,
+ setTimeFrame,
+ customDate,
+ setCustomDate
}) => {
- const theme = useTheme();
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
+ const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
+ const [anchorEl, setAnchorEl] = useState(null);
- const progressColor = progressDirection === 'up' ? theme.palette.success.main : theme.palette.error.main;
- const progressIcon = progressDirection === 'up' ? : ;
- const highlightedDescription = description.replace(
- /(\$\d+(?:\.\d+)?[kKmM]?)/g,
- '$1'
- );
- 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 (
-
+
+
+ {/* زر القائمة العلوية */}
+
-
- {/* زر الإجراءات في الزاوية العلوية اليمنى */}
-
+
+
+ {/* القائمة المنسدلة */}
+
- ml: 0 // هامش يسار لإفساح المجال للأيقونة
- }}
- >
-
- {title}
-
-
- {period}
-
-
+ {/* المحتوى الأساسي للكارت */}
+
+
+
+ {title}
+
+
+ {period}
+
+
- {/* Progress Circle */}
-
- {/* تغيير النسبة تحت الدائرة */}
-
-
-
+ {/* دائرة التقدم */}
+
+
+
+
+
+
- {/* دائرة التقدم */}
-
-
-
-
-
+ {/* النص داخل الدائرة */}
+
+ {`${progress}%`}
+
+
+
- {/* النص داخل الدائرة */}
-
- {`${progress}%`}
-
-
-
-
- {/* الوصف */}
-
-
-
-
- {/* الحقول الثلاثة (Completed / Canceled / Pending) */}
-
- {/* لكل قسم: */}
- {[{
- label: 'Completed',
- icon: ,
- value: completed
- }, {
- label: 'Canceled',
- icon: ,
- value: canceled
- }, {
- label: 'Pending',
- icon: ,
- value: pending
- }].map(({ label, icon, value }, i) => (
-
-
- {label}
-
-
- {icon}
-
- {value}
-
-
-
- ))}
-
-
-
-
- );
+ {/* الوصف */}
+
+ {description}
+
+
+
+
+ );
};
export default OrderStatusCard;
diff --git a/src/components/Home/Dashboard/RecentActivity.js b/src/components/Home/Dashboard/RecentActivity.js
index f24b20f..6f16218 100644
--- a/src/components/Home/Dashboard/RecentActivity.js
+++ b/src/components/Home/Dashboard/RecentActivity.js
@@ -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
diff --git a/src/components/Home/Dashboard/StatisticsCard.js b/src/components/Home/Dashboard/StatisticsCard.js
index 24874b1..fcaa5e6 100644
--- a/src/components/Home/Dashboard/StatisticsCard.js
+++ b/src/components/Home/Dashboard/StatisticsCard.js
@@ -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 (
-
-
+
{
right: { xs: 4, sm: 8 },
color: '#667085'
}}
+ onClick={handleMenuOpen}
>
-
+ {/* */}
+
+
+ {/* Header */}
-
- Statistics
-
-
- Delivery Times
-
+
+
+ {title}
+
+
+ {subtitle}
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+ {dataKeys.map(({ key, color }) => (
+
+
+
+
+ ))}
-
+
+
-
-
-
-
-
-
+ {dataKeys.map(({ key, name, color }) => (
+
+ ))}
@@ -193,4 +212,19 @@ const StatisticsCard = () => {
);
};
-export default StatisticsCard;
\ No newline at end of file
+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;
diff --git a/src/components/Home/Employ/Employ.js b/src/components/Home/Employ/Employ.js
new file mode 100644
index 0000000..f7c49e3
--- /dev/null
+++ b/src/components/Home/Employ/Employ.js
@@ -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 (
+
+ setSidebarOpen(!sidebarOpen)}
+ isMobile={isMobile}
+ drawerWidth={drawerWidth}
+ />
+
+
+ setSidebarOpen(!sidebarOpen)}
+ sidebarOpen={sidebarOpen}
+ isMobile={isMobile}
+ />
+
+
+ {/* المحتوى: Waiter و Cooker */}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Employ;
diff --git a/src/components/Home/Employ/contcet/Accountant.js b/src/components/Home/Employ/contcet/Accountant.js
new file mode 100644
index 0000000..cb7ea9c
--- /dev/null
+++ b/src/components/Home/Employ/contcet/Accountant.js
@@ -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 (
+
+
+
+ Accountant
+
+
+ {Accountants.length > 3 && (
+ setShowAll(!showAll)}
+ sx={{
+ borderRadius: '8px',
+ fontWeight: 600,
+ fontSize: '14px',
+ height: '40px',
+ textTransform: 'none',
+ minWidth: 90,
+ }}
+ variant="outlined"
+ size="small"
+ >
+ {showAll ? 'See Less' : 'See All'}
+
+ )}
+
+ }
+ onClick={handleFilterClick}
+ >
+ {isMobile ? '' : 'Filters'}
+
+
+
+ Add Accountant
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ Email
+
+
+ Shift Type
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+ Loading...
+
+
+ ) : displayedAccountants.length === 0 ? (
+
+
+ No activities found.
+
+
+ ) : (
+ displayedAccountants.map((Accountant) => (
+
+
+ handleOpenDetails(Accountant.id)}
+ >
+ {Accountant.name}
+
+
+
+
+
+ {Accountant.email}
+
+
+
+
+
+
+
+
+
+ handleSwitchChange(Accountant.id, Accountant.email)}
+ inputProps={{ 'aria-label': 'accountant switch' }}
+ />
+ handleDeleteClick(Accountant)}
+ />
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ {/* مودال تأكيد الحذف */}
+
+
+ setDetailsModalOpen(false)}
+ employee={AccountantDetails}
+ type="Accountant"
+ />
+
+
+
+ );
+};
+
+export default Accountant;
diff --git a/src/components/Home/Employ/contcet/AddEmployModal.js b/src/components/Home/Employ/contcet/AddEmployModal.js
new file mode 100644
index 0000000..e008dde
--- /dev/null
+++ b/src/components/Home/Employ/contcet/AddEmployModal.js
@@ -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 (
+
+ );
+};
+
+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;
diff --git a/src/components/Home/HostKitchen/contcet/ConfirmationDialog.js b/src/components/Home/Employ/contcet/ConfirmationDialog.js
similarity index 100%
rename from src/components/Home/HostKitchen/contcet/ConfirmationDialog.js
rename to src/components/Home/Employ/contcet/ConfirmationDialog.js
diff --git a/src/components/Home/Employ/contcet/EmployeeDetailsModal.js b/src/components/Home/Employ/contcet/EmployeeDetailsModal.js
new file mode 100644
index 0000000..988eaf0
--- /dev/null
+++ b/src/components/Home/Employ/contcet/EmployeeDetailsModal.js
@@ -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 (
+
+ );
+};
+
+export default EmployeeDetailsModal;
diff --git a/src/components/Home/Employ/contcet/IOSSwitch.js b/src/components/Home/Employ/contcet/IOSSwitch.js
new file mode 100644
index 0000000..be34356
--- /dev/null
+++ b/src/components/Home/Employ/contcet/IOSSwitch.js
@@ -0,0 +1,57 @@
+import { styled } from '@mui/material/styles';
+import Switch from '@mui/material/Switch';
+
+const IOSSwitch = styled((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;
diff --git a/src/components/Home/Employ/contcet/cooker.js b/src/components/Home/Employ/contcet/cooker.js
new file mode 100644
index 0000000..398758b
--- /dev/null
+++ b/src/components/Home/Employ/contcet/cooker.js
@@ -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 (
+
+
+
+ Cooker
+
+
+ {Cookers.length > 3 && (
+ setShowAll(!showAll)}
+ sx={{
+ borderRadius: '8px',
+ fontWeight: 600,
+ fontSize: '14px',
+ height: '40px',
+ textTransform: 'none',
+ minWidth: 90,
+ }}
+ variant="outlined"
+ size="small"
+ >
+ {showAll ? 'See Less' : 'See All'}
+
+ )}
+
+ }
+ onClick={handleFilterClick}
+ >
+ {isMobile ? '' : 'Filters'}
+
+
+
+ Add Cooker
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ Email
+
+
+ Shift Type
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+ Loading...
+
+
+ ) : displayedCookers.length === 0 ? (
+
+
+ No activities found.
+
+
+ ) : (
+ displayedCookers.map((Cooker) => (
+
+
+ handleOpenDetails(Cooker.id)}
+ >
+ {Cooker.name}
+
+
+
+
+
+ {Cooker.email}
+
+
+
+
+
+
+
+
+
+ handleSwitchChange(Cooker.id, Cooker.email)}
+ inputProps={{ 'aria-label': 'accountant switch' }}
+ />
+
+ handleDeleteClick(Cooker)}
+ />
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ {/* مودال تأكيد الحذف */}
+
+
+ setDetailsModalOpen(false)}
+ employee={CookerDetails}
+ type="Cooker"
+ />
+
+
+
+ );
+};
+
+export default Cooker;
diff --git a/src/components/Home/Employ/contcet/waiter.js b/src/components/Home/Employ/contcet/waiter.js
new file mode 100644
index 0000000..a62c5be
--- /dev/null
+++ b/src/components/Home/Employ/contcet/waiter.js
@@ -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 (
+
+
+
+ Waiter
+
+
+ {waiters.length > 3 && (
+ setShowAll(!showAll)}
+ sx={{
+ borderRadius: '8px',
+ fontWeight: 600,
+ fontSize: '14px',
+ height: '40px',
+ textTransform: 'none',
+ minWidth: 90,
+ }}
+ variant="outlined"
+ size="small"
+ >
+ {showAll ? 'See Less' : 'See All'}
+
+ )}
+
+ }
+ onClick={handleFilterClick}
+ >
+ {isMobile ? '' : 'Filters'}
+
+
+
+ Add Waiter
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ Email
+
+
+ Shift Type
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+ Loading...
+
+
+ ) : displayedWaiters.length === 0 ? (
+
+
+ No activities found.
+
+
+ ) : (
+ displayedWaiters.map((waiter) => (
+
+
+ handleOpenDetails(waiter.id)}
+ >
+ {waiter.name}
+
+
+
+
+
+ {waiter.email}
+
+
+
+
+
+
+
+
+
+ handleSwitchChange(waiter.id, waiter.email)}
+ inputProps={{ 'aria-label': 'accountant switch' }}
+ />
+
+ handleDeleteClick(waiter)}
+ />
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ {/* مودال تأكيد الحذف */}
+
+
+ setDetailsModalOpen(false)}
+ employee={waiterDetails} // بدل waiter
+ type="waiter"
+ />
+
+
+
+ );
+};
+
+export default Waiter;
diff --git a/src/components/Home/HostKitchen/HostKitchen.js b/src/components/Home/HostKitchen/HostKitchen.js
deleted file mode 100644
index 592316d..0000000
--- a/src/components/Home/HostKitchen/HostKitchen.js
+++ /dev/null
@@ -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 = [
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- 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 (
-
-
-
-
-
- {/*
-
- */}
-
-
-
-
-
- setCurrentStep(prev => Math.max(prev - 1, 0))}
- />
-
-
-
-
-
-
-
- {steps[currentStep]}
-
-
-
-
-
-
- );
-};
-
-export default HostKitchen;
diff --git a/src/components/Home/HostKitchen/contcet/CloudKitchenHosting.js b/src/components/Home/HostKitchen/contcet/CloudKitchenHosting.js
deleted file mode 100644
index 19030da..0000000
--- a/src/components/Home/HostKitchen/contcet/CloudKitchenHosting.js
+++ /dev/null
@@ -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 (
-
-
-
- Cloud Kitchen Hosting
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
- {/* 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) => (
-
-
- {field.label}
-
-
-
- ))}
-
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
-
- );
-};
-
-export default CloudKitchenHosting;
diff --git a/src/components/Home/HostKitchen/contcet/Expansion.js b/src/components/Home/HostKitchen/contcet/Expansion.js
deleted file mode 100644
index af7596f..0000000
--- a/src/components/Home/HostKitchen/contcet/Expansion.js
+++ /dev/null
@@ -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 (
-
-
-
- Expansion & Future Cooperation
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
-
- Hosting Multiple Cloud Kitchens
-
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
- {/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
- {[
- { label: 'Future Kitchen Plans', placeholder: '20' },
- ].map((field, index) => (
-
-
- {field.label}
-
-
-
- ))}
-
-
-
- Partnership & Support
-
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
- {/* ✅ Confirmation Modal */}
-
-
- );
-};
-
-export default Expansion;
diff --git a/src/components/Home/HostKitchen/contcet/Facilitation.js b/src/components/Home/HostKitchen/contcet/Facilitation.js
deleted file mode 100644
index 2d0360a..0000000
--- a/src/components/Home/HostKitchen/contcet/Facilitation.js
+++ /dev/null
@@ -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 (
-
-
-
- Facilitation & Cooperation
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
-
-
-
- Additional Services Provided
-
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
- {/* Additional Services Input */}
-
-
- Additional Services
-
-
-
-
-
-
-
-
- Ventilation and Cooling System
-
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
- {/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
- {[
- { label: 'Working Hours', placeholder: '8 hours' },
- ].map((field, index) => (
-
-
- {field.label}
-
-
-
- ))}
-
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
-
- );
-};
-
-export default Facilitation;
diff --git a/src/components/Home/HostKitchen/contcet/Infrastructure.js b/src/components/Home/HostKitchen/contcet/Infrastructure.js
deleted file mode 100644
index a5010aa..0000000
--- a/src/components/Home/HostKitchen/contcet/Infrastructure.js
+++ /dev/null
@@ -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 (
-
-
-
- Infrastructure & Equipments
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
- {/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
- {[
- { label: 'Current Equipment', placeholder: 'oven, refrgerators' },
- ].map((field, index) => (
-
-
- {field.label}
-
-
-
- ))}
-
-
-
-
- Additional Facilities Needed
-
-
-
- }
- label="Yes"
- />
-
- }
- label="No"
- />
-
-
-
-
- {/* Description Input */}
-
-
- Description
-
-
-
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
-
- );
-};
-
-export default Infrastructure;
diff --git a/src/components/Home/HostKitchen/contcet/PostSubmission.js b/src/components/Home/HostKitchen/contcet/PostSubmission.js
deleted file mode 100644
index 7a4cc1b..0000000
--- a/src/components/Home/HostKitchen/contcet/PostSubmission.js
+++ /dev/null
@@ -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' }) => (
-
-
-
- {children}
-
-
-);
-
-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 (
-
-
-
-
- {React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
-
-
- {statusText}
-
-
-
-
- {title}
-
-
-
- {extraButton ? extraButton : }
-
-
-
-
-
-
- );
-};
-
-const PostSubmission = () => {
- const theme = useTheme();
- const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
- const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
-
- return (
- <>
- {/* Header Section */}
-
- {/* Title */}
-
- Post Submission
-
-
- {/* Buttons */}
-
-
-
- {!isSmallScreen && 'Interested'}
-
-
-
-
-
-
- >
- );
-};
-
-export default PostSubmission;
diff --git a/src/components/Home/HostKitchen/contcet/RstaurantOperations.js b/src/components/Home/HostKitchen/contcet/RstaurantOperations.js
deleted file mode 100644
index 67dbb66..0000000
--- a/src/components/Home/HostKitchen/contcet/RstaurantOperations.js
+++ /dev/null
@@ -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 (
-
-
-
- Rstaurant Operations
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
- {/* 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) => (
-
-
- {field.label}
-
-
-
- ))}
-
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
-
- );
-};
-
-export default CloudKitchenHosting;
diff --git a/src/components/Home/Inventory/Inventory.js b/src/components/Home/Inventory/Inventory.js
index dd91307..376e029 100644
--- a/src/components/Home/Inventory/Inventory.js
+++ b/src/components/Home/Inventory/Inventory.js
@@ -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',
};
diff --git a/src/components/Home/Inventory/InventoryTablePage.js b/src/components/Home/Inventory/contect/InventoryTablePage.js
similarity index 99%
rename from src/components/Home/Inventory/InventoryTablePage.js
rename to src/components/Home/Inventory/contect/InventoryTablePage.js
index f8672b2..a0c2e77 100644
--- a/src/components/Home/Inventory/InventoryTablePage.js
+++ b/src/components/Home/Inventory/contect/InventoryTablePage.js
@@ -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');
};
diff --git a/src/components/Home/Inventory/NoProdectDash.js b/src/components/Home/Inventory/contect/NoProdectDash.js
similarity index 100%
rename from src/components/Home/Inventory/NoProdectDash.js
rename to src/components/Home/Inventory/contect/NoProdectDash.js
diff --git a/src/components/Home/Inventory/ProductEntry.js b/src/components/Home/Inventory/contect/ProductEntry.js
similarity index 98%
rename from src/components/Home/Inventory/ProductEntry.js
rename to src/components/Home/Inventory/contect/ProductEntry.js
index 857f2d1..7f0779f 100644
--- a/src/components/Home/Inventory/ProductEntry.js
+++ b/src/components/Home/Inventory/contect/ProductEntry.js
@@ -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',
},
}}
>
diff --git a/src/components/Home/Inventory/TableView.js b/src/components/Home/Inventory/contect/TableView.js
similarity index 100%
rename from src/components/Home/Inventory/TableView.js
rename to src/components/Home/Inventory/contect/TableView.js
diff --git a/src/components/Home/Meal/Meal.js b/src/components/Home/Meal/Meal.js
new file mode 100644
index 0000000..04f5292
--- /dev/null
+++ b/src/components/Home/Meal/Meal.js
@@ -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 (
+
+
+
+
+
+
+
+ {selectedProduct ? (
+ setSelectedProduct(null)}
+ />
+ ) : (
+ <>
+
+
+ {selectedCategoryId ? (
+
+
+ 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
+ });
+ }}
+ />
+
+
+ ) : !showAllPopular ? (
+
+
+ {
+ const category = categories.find(c => c.id === meal.category_id);
+ setSelectedProduct({
+ ...meal,
+ category: category ? category.name : '',
+ category_id: meal.category_id,
+ restaurant_id: restaurantId
+ });
+ }}
+ />
+
+
+ ) : null}
+ >
+ )}
+
+
+
+ );
+};
+
+export default Meal;
\ No newline at end of file
diff --git a/src/components/Home/Meal/contect/AccountSettings.js b/src/components/Home/Meal/contect/AccountSettings.js
new file mode 100644
index 0000000..42db264
--- /dev/null
+++ b/src/components/Home/Meal/contect/AccountSettings.js
@@ -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 (
+
+ {/* رأس القسم */}
+
+
+ Categories
+
+
+
+ {
+ setSelectedCategoryForEdit(null);
+ setOpenAddModal(true);
+ }}
+ >
+ Add Category
+
+
+ scroll(-200)}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: '#fff',
+ width: 40,
+ height: 40,
+ '&:hover': { backgroundColor: '#ffddbfff' },
+ }}
+ >
+
+
+
+ scroll(200)}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: '#fff',
+ width: 40,
+ height: 40,
+ '&:hover': { backgroundColor: '#ffddbfff' },
+ }}
+ >
+
+
+
+
+
+ {/* قائمة الفئات */}
+ {/* قائمة الفئات */}
+
+ {loading ? (
+ // Skeleton Loader
+ Array.from({ length: 5 }).map((_, index) => (
+
+ ))
+ ) : categories.length === 0 ? (
+
+
+ No categories available
+
+
+ ) : (
+
+ )}
+
+
+
+ {/* مودال الإضافة / التعديل */}
+ setOpenAddModal(false)}
+ aria-labelledby="add-category-modal"
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ p: 2,
+ }}
+ >
+
+
+
+
+
+ {/* قائمة السياق */}
+
+
+ {/* تأكيد الحذف */}
+
+
+ );
+};
+
+export default AccountSettings;
diff --git a/src/components/Home/Meal/contect/AddCategory.js b/src/components/Home/Meal/contect/AddCategory.js
new file mode 100644
index 0000000..30d487a
--- /dev/null
+++ b/src/components/Home/Meal/contect/AddCategory.js
@@ -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 (
+
+
+ Category Name
+
+ 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)',
+ },
+ },
+ }}
+ />
+
+
+ {editingCategory ? 'Update Category' : 'Add Category'}
+
+
+ );
+};
+
+export default AddCategory;
diff --git a/src/components/Home/Meal/contect/AddMeal.js b/src/components/Home/Meal/contect/AddMeal.js
new file mode 100644
index 0000000..d998e24
--- /dev/null
+++ b/src/components/Home/Meal/contect/AddMeal.js
@@ -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 (
+
+ );
+};
+
+export default AddMeal;
diff --git a/src/components/Home/Meal/contect/AllMeals.js b/src/components/Home/Meal/contect/AllMeals.js
new file mode 100644
index 0000000..d31ec3b
--- /dev/null
+++ b/src/components/Home/Meal/contect/AllMeals.js
@@ -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 (
+
+
+ All Meals
+
+
+ {loading ? (
+
+ {Array.from({ length: itemsPerPage }).map((_, index) => (
+
+
+
+
+
+ ))}
+
+ ) : error ? (
+
+ {error}
+
+ ) : products.length === 0 ? (
+
+ There is no meal available
+
+ ) : (
+
+ {products.map(product => (
+ onProductClick(product)} >
+ onProductClick(product)}
+ />
+
+ ))}
+
+ )}
+
+ {/* Pagination Footer */}
+ {pagination && !loading && (
+
+
+ Showing {(page - 1) * itemsPerPage + 1} to {Math.min(page * itemsPerPage, products.length)} of {pagination.total} meals
+
+
+ setPage(newPage)}
+ />
+
+ )}
+
+ );
+};
+
+export default AllMeals;
diff --git a/src/components/Home/Meal/contect/CategoryScrollList.js b/src/components/Home/Meal/contect/CategoryScrollList.js
new file mode 100644
index 0000000..827b42e
--- /dev/null
+++ b/src/components/Home/Meal/contect/CategoryScrollList.js
@@ -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 (
+
+ {categories.map((cat) => {
+ const isSelected = selectedCategory === cat.id;
+
+ return (
+ 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',
+ },
+ }}
+ >
+
+ {cat.name}
+
+
+ );
+ })}
+
+ );
+};
+
+export default CategoryScrollList;
diff --git a/src/components/Home/Meal/contect/MealCard.js b/src/components/Home/Meal/contect/MealCard.js
new file mode 100644
index 0000000..5871767
--- /dev/null
+++ b/src/components/Home/Meal/contect/MealCard.js
@@ -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 (
+
+ {/* صورة الوجبة */}
+
+
+ {/* تفاصيل الوجبة */}
+
+
+ {name}
+
+
+ {/* السعر */}
+
+ ${price}{" "}
+
+ {unit || "/pcs"}
+
+
+
+ {/* الوصف (أول عنصر فقط للتصغير) */}
+ {description && description.length > 0 && (
+
+ {description[0]}
+
+ )}
+
+ {/* الإضافات (أول 2 فقط) */}
+ {/* {additions && additions.length > 0 && (
+
+ {additions.slice(0, 2).map((add, i) => (
+
+ ))}
+
+ )} */}
+
+ {/* زر الإضافة */}
+ {/* {
+ e.stopPropagation();
+ if (onAdd) onAdd(meal);
+ }}
+ sx={{
+ backgroundColor: "#FF8551",
+ color: "#fff",
+ mt: 1,
+ width: 28,
+ height: 28,
+ "&:hover": { backgroundColor: "#ff7043" },
+ }}
+ >
+
+ */}
+
+
+
+ );
+};
+
+export default MealCard;
diff --git a/src/components/Home/Meal/contect/MealsByCateg.js b/src/components/Home/Meal/contect/MealsByCateg.js
new file mode 100644
index 0000000..53a59de
--- /dev/null
+++ b/src/components/Home/Meal/contect/MealsByCateg.js
@@ -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 (
+
+ {/* العنوان + أزرار */}
+
+
+ {categoryName || "Meals"}
+
+
+ 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
+
+
+ Back
+
+
+
+
+ {/* حالة التحميل أو الخطأ */}
+ {loading ? (
+
+ {Array.from({ length: itemsPerPage }).map((_, index) => (
+
+
+
+
+
+ ))}
+
+ ) : error ? (
+
+ {error}
+
+ ) : products.length === 0 ? (
+
+
+ There is no meal available
+
+
+ ) : (
+
+ {products.map((product) => (
+
+ onProductClick({
+ ...product,
+ category: categoryName,
+ category_id: categoryId,
+ })
+ }
+ >
+
+ onProductClick({
+ ...product,
+ category: categoryName,
+ category_id: categoryId,
+ })
+ }
+ onAdd={onAddMeal}
+ />
+
+ ))}
+
+ )}
+
+ {/* Pagination Footer */}
+ {pagination && !loading && pagination.last_page > 1 && (
+
+
+ Showing {(page - 1) * itemsPerPage + 1} to{" "}
+ {Math.min(page * itemsPerPage, pagination.total)} of {pagination.total} meals
+
+
+ setPage(newPage)}
+ />
+
+ )}
+
+ {/* Add Meal Modal */}
+ setOpenModal(false)}
+ onAdd={handleAddProduct}
+ selectedCategory={categoryId}
+ restaurantId={restaurantId}
+ />
+
+ );
+};
+
+export default MealsByCateg;
diff --git a/src/components/Home/Meal/contect/ProductDetail.js b/src/components/Home/Meal/contect/ProductDetail.js
new file mode 100644
index 0000000..b09ce98
--- /dev/null
+++ b/src/components/Home/Meal/contect/ProductDetail.js
@@ -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 (
+
+ {/* صندوق الصورة */}
+
+
+
+
+
+
+
+
+
+ {isEditing && (
+
+ }
+ sx={{
+ textTransform: 'none',
+ borderColor: errors.photoFile ? 'red' : theme.palette.primary.main,
+ color: errors.photoFile ? 'red' : theme.palette.primary.main
+ }}
+ >
+ Change Image
+
+
+ {errors.photoFile && {errors.photoFile}}
+ {formData.photoFile && Selected: {formData.photoFile.name}}
+
+ )}
+
+
+ {/* التفاصيل */}
+
+ {/* الاسم */}
+
+ {isEditing ? (
+
+ ) : (
+ {product.name}
+ )}
+
+
+ {/* الفئة */}
+
+ {isEditing ? (
+
+
+ {errors.category_id && (
+ {errors.category_id}
+ )}
+
+ ) : (
+
+ {product.category ||
+ categories.find((c) => c.id === (product.category_id || formData.category_id))?.name ||
+ `Category #${product.category_id || formData.category_id}`}
+
+ )}
+
+
+
+ {/* السعر */}
+
+ Price
+ {isEditing ? (
+
+ ) : (
+
+ ${Number(product.price).toFixed(2)} {product.unit || "/pcs"}
+
+ )}
+
+
+ {/* الوصف */}
+
+
+ Description {isEditing && "*"}
+
+ {isEditing ? (
+
+ {formData.description.map((desc, idx) => (
+
+ handleEditDescription(idx, e.target.value)}
+ placeholder={`Description ${idx + 1}`}
+ size="small"
+ />
+
+ ))}
+ {errors.description && {errors.description}}
+
+ ) : (
+
+ {formData.description.map((desc, idx) => • {desc})}
+
+ )}
+
+
+ {/* الإضافات */}
+
+
+ Additions {isEditing && "*"}
+
+ {isEditing ? (
+
+ {formData.additions.map((add, idx) => (
+
+ handleEditAddition(idx, e.target.value)}
+ placeholder={`Addition ${idx + 1}`}
+ size="small"
+ />
+ handleRemoveAddition(idx)} size="small">
+
+
+
+ ))}
+
+ setNewAddition(e.target.value)}
+ size="small"
+ />
+
+
+
+
+ {errors.additions && {errors.additions}}
+
+ ) : (
+
+ {formData.additions.map((add, idx) => )}
+
+ )}
+
+
+ {/* شريط تقدم */}
+
+
+
+ {/* أزرار التحكم */}
+
+ {isEditing ? (
+ <>
+
+ {isSaving ? "Saving..." : "Save Changes"}
+
+
+ {
+ 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
+
+ >
+ ) : (
+ <>
+ setIsEditing(true)}
+ >
+ Edit Meal
+
+
+ setConfirmDeleteOpen(true)}
+ >
+ Delete Meal
+
+ >
+ )}
+
+
+
+
+ {/* مودال تأكيد الحذف */}
+
+
+ );
+};
+
+export default ProductDetail;
diff --git a/src/components/Home/Order/Order.js b/src/components/Home/Order/Order.js
new file mode 100644
index 0000000..1d67eb4
--- /dev/null
+++ b/src/components/Home/Order/Order.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Order;
diff --git a/src/components/Home/Order/contect/CartView.js b/src/components/Home/Order/contect/CartView.js
new file mode 100644
index 0000000..de7a7f8
--- /dev/null
+++ b/src/components/Home/Order/contect/CartView.js
@@ -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 ;
+
+ return (
+
+ {/* السهم للعودة */}
+
+
+
+
+
+ Current Cart
+
+
+
+ {cart.length === 0 ? (
+ No items in cart.
+ ) : (
+ <>
+ {cart.map(item => (
+
+
+ {item.name}
+ Unit: {item.unit}
+
+
+ Quantity: {item.quantity}
+ Total: ${item.totalPrice}
+
+
+ ))}
+
+
+
+ Total Price: ${totalPrice.toFixed(2)}
+
+
+ >
+ )}
+
+ {cart.length > 0 && (
+
+
+ Send Cart to Server
+
+
+ Clear Cart
+
+
+ )}
+
+ );
+};
+
+export default CartView;
diff --git a/src/components/Home/Order/contect/OrderDetails.js b/src/components/Home/Order/contect/OrderDetails.js
new file mode 100644
index 0000000..04c6395
--- /dev/null
+++ b/src/components/Home/Order/contect/OrderDetails.js
@@ -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 (
+ <>
+
+
+ Cart #{cart.id} Details
+
+
+ {editMode ? (
+ <>
+ setTotalPrice(e.target.value)}
+ fullWidth
+ sx={{ mb: 2 }}
+ />
+
+ Items:
+
+ {cartItems.map(item => (
+
+ handleChangeQuantity(item.id, Number(e.target.value))}
+ sx={{ width: '120px' }}
+ />
+ }
+ />
+
+ ))}
+
+
+
+
+ {loading ? "Saving..." : "Save All"}
+
+ setEditMode(false)}>
+ Cancel
+
+
+ >
+ ) : (
+ <>
+ Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}
+
+ Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
+
+ Items:
+
+ {cartItems.map(item => (
+
+
+
+ ))}
+
+
+
+ setEditMode(true)}>
+ Edit
+
+ setOpenDeleteDialog(true)}
+ sx={{ ml: 'auto' }}
+ >
+ Delete Cart
+
+
+ Back to Orders
+
+
+ >
+ )}
+
+
+ {/* Delete Confirmation Dialog */}
+
+ >
+ );
+};
+
+export default CartDetails;
\ No newline at end of file
diff --git a/src/components/Home/Order/contect/Orders.js b/src/components/Home/Order/contect/Orders.js
new file mode 100644
index 0000000..e82ea5a
--- /dev/null
+++ b/src/components/Home/Order/contect/Orders.js
@@ -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 (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: '8px',
+ backgroundColor: '#FFECE0',
+ '&:hover': { backgroundColor: '#FFD6B5' },
+ color: theme.palette.primary.main,
+ '&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
+ }}
+ >
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+ }
+
+ if (selectedCart) {
+ return (
+
+ );
+ }
+
+ // عرض CartView بدل الطلبات إذا تم الضغط على الزر
+ // داخل Orders.js
+if (showCartView) {
+ return (
+ setShowCartView(false)}
+ onSend={handleAddOrder}
+ onCartCreated={(newCart) => {
+ setOrdersData(prev => [newCart, ...prev]); // ضف الكارت الجديد مباشرة
+ setShowCartView(false);
+ }}
+ adminId={adminId} // ← أرسل الـ adminId هنا
+ />
+ );
+}
+
+
+
+ return (
+
+
+
+ Order
+
+
+
+
+
+
+ {/* جدول الطلبات */}
+
+
+
+
+ Cart ID
+ Total Price
+ Created At
+ Items Count
+
+
+
+ {loading
+ ? Array.from({ length: itemsPerPage }).map((_, idx) => (
+
+
+
+
+
+
+ ))
+ : (
+ <>
+ {paginatedOrders.map(order => {
+ const attributes = order?.attributes || {};
+ const relationships = order?.relationships || {};
+ const cartItems = relationships?.cartItems || relationships?.cart_items || [];
+ return (
+ handleRowClick(order.id)}
+ >
+ {order?.id || '--'}
+ {attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}
+ {attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}
+ {cartItems.length}
+
+ );
+ })}
+
+ {paginatedOrders.length < itemsPerPage &&
+ Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
+
+
+
+ ))
+ }
+
+
+ >
+ )
+ }
+
+
+
+
+
+
+ Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
+
+
+
+
+
+ );
+};
+
+export default Orders;
diff --git a/src/components/Home/ResList/AppBar.js b/src/components/Home/ResList/AppBar.js
new file mode 100644
index 0000000..bf65dd5
--- /dev/null
+++ b/src/components/Home/ResList/AppBar.js
@@ -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 (
+
+
+ {/* Left: Logo */}
+
+
+
+ KITCH
+
+
+ PLUS
+
+
+
+ {/* Right: Log Out Button */}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default KitchPlusAppBar;
diff --git a/src/components/Home/ResList/RestaurantDetailsModal.js b/src/components/Home/ResList/RestaurantDetailsModal.js
new file mode 100644
index 0000000..c8a1f32
--- /dev/null
+++ b/src/components/Home/ResList/RestaurantDetailsModal.js
@@ -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 (
+
+ );
+};
+
+export default RestaurantDetailsModal;
diff --git a/src/components/Home/ResList/RestaurantProfile.js b/src/components/Home/ResList/RestaurantProfile.js
new file mode 100644
index 0000000..a7052b3
--- /dev/null
+++ b/src/components/Home/ResList/RestaurantProfile.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default RestaurantProfile;
diff --git a/src/components/Home/ResList/RestaurantSelection.js b/src/components/Home/ResList/RestaurantSelection.js
new file mode 100644
index 0000000..79522fc
--- /dev/null
+++ b/src/components/Home/ResList/RestaurantSelection.js
@@ -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 (
+
+ {/* زر إنشاء مطعم */}
+
+
+
+
+
+
+
+ Create New Restaurant
+
+
+ Start from scratch and build your restaurant profile
+
+
+
+
+ {/* قائمة المطاعم */}
+
+
+ Existing Restaurants
+
+
+ {restaurants.length === 0 && !loading ? (
+
+ No restaurants found. Create one to get started.
+
+ ) : (
+
+ {(loading ? Array.from(new Array(3)) : restaurants).map((restaurant, index) => (
+
+ {loading ? (
+
+ ) : (
+ handleSelectRestaurant(restaurant.id)}
+ >
+ {/* أيقونة فتح المودال */}
+ {
+ 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"
+ >
+
+
+
+ {/* صورة المطعم */}
+
+
+
+
+ {/* باقي الكارت */}
+
+
+ {restaurant.name}
+
+
+
+ {restaurant.location || ' '}
+
+
+
+ )}
+
+ ))}
+
+ )}
+
+
+ {/* مودال المطعم */}
+ setModalOpen(false)}
+ restaurant={selectedRestaurant}
+ />
+
+ );
+};
+
+export default RestaurantSelection;
diff --git a/src/components/Home/RestaurantProfile/RestaurantProfile.js b/src/components/Home/RestaurantProfile/RestaurantProfile.js
index be3da8d..f67ad6b 100644
--- a/src/components/Home/RestaurantProfile/RestaurantProfile.js
+++ b/src/components/Home/RestaurantProfile/RestaurantProfile.js
@@ -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 = [
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
- setCurrentStep(currentStep - 1)} />,
+ setCurrentStep((p) => p + 1)}
+ />,
+ setCurrentStep((p) => p + 1)}
+ onBack={() => setCurrentStep((p) => p - 1)}
+ />,
+ setCurrentStep((p) => p + 1)}
+ onBack={() => setCurrentStep((p) => p - 1)}
+ />,
+ setCurrentStep((p) => p + 1)}
+ onBack={() => setCurrentStep((p) => p - 1)}
+ />,
+ setCurrentStep((p) => p + 1)}
+ onBack={() => setCurrentStep((p) => p - 1)}
+ />,
+ 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 (
-
-
-
-
-
-
-
-
-
-
- setCurrentStep(prev => Math.max(prev - 1, 0))}
- />
-
-
-
-
-
-
-
- {steps[currentStep]}
-
-
+
+
+ {/* إظهار SideProfile فقط إذا لم يتم التحويل إلى AccountProfile */}
+ {!showSetting && (
+
+
+ setCurrentStep((prev) => Math.max(prev - 1, 0))}
+ />
+ )}
+
+
+
+ {showSetting ? : steps[currentStep]}
+
+
+ {/* مودال بدون أزرار */}
+ {}} hideButtons={true} />
);
};
diff --git a/src/components/Home/RestaurantProfile/SideProfile.js b/src/components/Home/RestaurantProfile/SideProfile.js
index e7508c0..c63f45c 100644
--- a/src/components/Home/RestaurantProfile/SideProfile.js
+++ b/src/components/Home/RestaurantProfile/SideProfile.js
@@ -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' },
];
diff --git a/src/components/Home/RestaurantProfile/all.js b/src/components/Home/RestaurantProfile/all.js
new file mode 100644
index 0000000..b5ab999
--- /dev/null
+++ b/src/components/Home/RestaurantProfile/all.js
@@ -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 (
+
+ {/* البار الجانبي */}
+
+
+ {/* المحتوى الرئيسي */}
+
+ {/* البار العلوي */}
+
+
+ {/* المحتوى حسب وجود البروفايل */}
+
+ {loading ? (
+ // Skeleton placeholder
+
+
+
+
+
+
+ ) : hasProfile ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default RestaurantWrapper;
diff --git a/src/components/Home/RestaurantProfile/contcet/BasicInformation.js b/src/components/Home/RestaurantProfile/contcet/BasicInformation.js
index 9b03896..d531179 100644
--- a/src/components/Home/RestaurantProfile/contcet/BasicInformation.js
+++ b/src/components/Home/RestaurantProfile/contcet/BasicInformation.js
@@ -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 (
{
width: { xs: '85%', sm: '90%' },
}}
>
-
+
Basic Information
+
{
- {/* 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) => (
-
+ {fields.map((fieldObj, index) => (
+
- {field.label}
+ {fieldObj.label}
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 }) => {
))}
-
{
Next
- {/* زر Back تحت زر Next */}
{
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
-
);
diff --git a/src/components/Home/RestaurantProfile/contcet/BusinessHours.js b/src/components/Home/RestaurantProfile/contcet/BusinessHours.js
index 4cc47ac..aa64844 100644
--- a/src/components/Home/RestaurantProfile/contcet/BusinessHours.js
+++ b/src/components/Home/RestaurantProfile/contcet/BusinessHours.js
@@ -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 (
{
width: { xs: '85%', sm: '90%' },
}}
>
-
-
+
+
Business Hours
-
+
Enter your business hours to proceed to registration of your own restaurant on this platform
@@ -72,9 +92,13 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
Operational Hours
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 */}
-
+
Closed Days
-
- {days.map((day) => {
+
+ {days.map(day => {
const isChecked = selectedDays.includes(day);
return (
{
sx={{
transform: 'scale(1.5)',
color: '#F0EDED',
- '&.Mui-checked': {
- color: '#FF914D',
- },
+ '&.Mui-checked': { color: '#FF914D' },
}}
/>
}
label={
-
+
{day}
}
- sx={{
- m: 0,
- width: { xs: '45%', sm: '30%', md: '22%' },
- }}
+ sx={{ m: 0, width: { xs: '45%', sm: '30%', md: '22%' } }}
/>
);
})}
+ {errors.closedDays && (
+
+ {errors.closedDays}
+
+ )}
- {/* Next Button */}
-
+ {/* Next & Back Buttons */}
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
+
+ Next
+
+
+
+ Back
+
+
);
diff --git a/src/components/Home/RestaurantProfile/contcet/ConfirmationDialog.js b/src/components/Home/RestaurantProfile/contcet/ConfirmationDialog.js
index ed1108a..b92b113 100644
--- a/src/components/Home/RestaurantProfile/contcet/ConfirmationDialog.js
+++ b/src/components/Home/RestaurantProfile/contcet/ConfirmationDialog.js
@@ -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 (
);
};
-export default ConfirmationDialog;
\ No newline at end of file
+export default ConfirmationDialog;
diff --git a/src/components/Home/RestaurantProfile/contcet/ContactInformation.js b/src/components/Home/RestaurantProfile/contcet/ContactInformation.js
index 4f5d00c..a2cece0 100644
--- a/src/components/Home/RestaurantProfile/contcet/ContactInformation.js
+++ b/src/components/Home/RestaurantProfile/contcet/ContactInformation.js
@@ -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%' },
}}
>
-
-
+
+
Contact Information
+
-
+
Enter your contact information to proceed to registration of your own restaurant on this platform
@@ -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 }) => {
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 }) => {
}}
/>
- {/* Next Button */}
-
-
- Next
-
+ {/* Next & Back Buttons */}
+
+
+ Next
+
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
+
+ Back
+
+
);
diff --git a/src/components/Home/RestaurantProfile/contcet/Equipment.js b/src/components/Home/RestaurantProfile/contcet/Equipment.js
index a3fc4d3..2473e32 100644
--- a/src/components/Home/RestaurantProfile/contcet/Equipment.js
+++ b/src/components/Home/RestaurantProfile/contcet/Equipment.js
@@ -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',
}}
>
-
+
Equipment
-
+
Enter your kitchen equipment details to complete your restaurant registration.
- {/* Oven Input */}
-
-
- {/* Grill Input */}
-
-
- {/* Refrigerators Input */}
-
+ {/* Dynamic Equipment Fields */}
+ {Object.keys(equipment).map((key) => (
+ handleInputChange(key, val)}
+ error={errors[key]}
+ />
+ ))}
{/* Add More Equipment Button */}
@@ -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
{/* Next Button */}
-
-
+
{
textTransform: 'none',
color: 'white',
backgroundColor: theme.palette.primary.main,
- '&:hover': {
- backgroundColor: theme.palette.primary.hover,
- },
+ '&:hover': { backgroundColor: theme.palette.primary.hover },
}}
>
Next
- {/* زر Back تحت زر Next */}
{
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
-
+
+ {/* Modal for Adding Equipment */}
+ setModalOpen(false)}>
+
+
+ Add New Equipment
+
+ setNewEquipment({ ...newEquipment, name: e.target.value })}
+ sx={{ mb: 2 }}
+ />
+ setNewEquipment({ ...newEquipment, quantity: e.target.value })}
+ sx={{ mb: 2 }}
+ />
+
+ Add
+
+
+
);
};
-// مكون فرعي لتقليل التكرار في الحقول
-const InputField = ({ label, placeholder, theme }) => (
+const InputField = ({ label, placeholder, theme, value, onChange, error }) => (
{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' },
diff --git a/src/components/Home/RestaurantProfile/contcet/LoginRestaurant.js b/src/components/Home/RestaurantProfile/contcet/LoginRestaurant.js
deleted file mode 100644
index 8c91a08..0000000
--- a/src/components/Home/RestaurantProfile/contcet/LoginRestaurant.js
+++ /dev/null
@@ -1,261 +0,0 @@
-import React, { useState } from 'react';
-import { TextField, Button, Typography, Stack, Box, IconButton, InputAdornment } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
-import { VisibilityOutlined } from '@mui/icons-material';
-import { useNavigate } from 'react-router-dom';
-import { Link } from 'react-router-dom';
-
-
-const LoginForm = () => {
-
- const navigate = useNavigate();
- const theme = useTheme();
- const [showPassword, setShowPassword] = useState(false);
-
- const handleTogglePassword = () => {
- setShowPassword((prev) => !prev);
- };
- const handleLogin = () => {
- // بعد تنفيذ عملية تسجيل الدخول بنجاح، يتم التوجيه
- navigate('/dashboard');
- };
- return (
-
-
-
-
- {/* Login Content */}
-
-
-
-
-
-
-
- Login
-
-
-
- Enter your username and password to access your account securely. Welcome back to our service!
-
-
- {/* Email Input */}
-
-
- Email
-
-
-
-
- {/* Password Input */}
-
-
- Password
-
-
-
- {showPassword ? : }
-
-
- )
- }}
- />
-
-
- {/* Login Button */}
-
-
- Log in
-
-
-
- {/* Divider */}
-
-
-
- Or
-
-
-
-
- {/* Google Button */}
-
-
-
-
- Login with Google
-
-
-
-
-
- {/* Register Link */}
-
- Don’t have an account?{' '}
-
- Register
-
-
-
-
-
-
-
-
- );
-};
-
-export default LoginForm;
diff --git a/src/components/Home/RestaurantProfile/contcet/OperationalCapacity.js b/src/components/Home/RestaurantProfile/contcet/OperationalCapacity.js
index 418b9ea..5547b5f 100644
--- a/src/components/Home/RestaurantProfile/contcet/OperationalCapacity.js
+++ b/src/components/Home/RestaurantProfile/contcet/OperationalCapacity.js
@@ -1,16 +1,83 @@
-import React from 'react';
+import React, { useState } from 'react';
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
-const OperationalCapacity = ({ currentStepIndex = 0, onNext, onBack }) => {
+const OperationalCapacity = ({ formData, updateFormData, onNext, onBack }) => {
const theme = useTheme();
+ const [capacity, setCapacity] = useState({
+ Maximum_orders_per_day: formData.Maximum_orders_per_day || '',
+ Number_of_Cheff: formData.Number_of_Cheff || '',
+ Number_of_Waiters: formData.Number_of_Waiters || '',
+ Number_of_Cookers: formData.Number_of_Cookers || '',
+ });
+
+ const [errors, setErrors] = useState({});
+
+ const handleChange = (field, value) => {
+ if (/^\d*$/.test(value)) { // السماح فقط بالأرقام
+ setCapacity(prev => ({ ...prev, [field]: value }));
+ }
+ };
+
+ const validateFields = () => {
+ const newErrors = {};
+ Object.entries(capacity).forEach(([key, value]) => {
+ if (!value || isNaN(Number(value)) || Number(value) < 0) {
+ newErrors[key] = 'Please enter a positive number';
+ }
+ });
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const handleNextClick = () => {
+ if (validateFields()) {
+ updateFormData(capacity); // تحديث الأب عند الضغط على Next فقط
+ if (onNext) onNext();
+ }
+ };
+
+ const InputField = ({ label, value, onChange, placeholder, error }) => (
+
+
+ {label}
+
+ onChange(e.target.value)}
+ error={!!error}
+ helperText={error || ' '}
+ sx={{
+ '& input': { fontWeight: 500, fontSize: '15px' },
+ '& input::placeholder': { color: '#969BA7' },
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '10px',
+ transition: '0.3s',
+ '&.Mui-focused fieldset': {
+ borderColor: theme.palette.primary.main,
+ boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
+ }
+ },
+ '& .MuiOutlinedInput-root.Mui-focused': {
+ borderColor: theme.palette.primary.main,
+ boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
+ }
+ }}
+ />
+
+ );
+
return (
{
width: { xs: '85%', sm: '90%' },
}}
>
-
-
+
+
Operational Capacity
+
-
+
Enter your basic information to proceed to registration of your own restaurant on this platform
- {/* Maximum Orders Per Day Input */}
-
-
- Maximum Orders Per Day
-
-
-
+ handleChange('Maximum_orders_per_day', val)}
+ placeholder="5000"
+ error={errors.Maximum_orders_per_day}
+ />
- {/* Number of Cheff Input */}
-
-
- Number of Cheff
-
-
-
+ handleChange('Number_of_Cheff', val)}
+ placeholder="20"
+ error={errors.Number_of_Cheff}
+ />
- {/* Number of Waiters Input */}
-
-
- Number of Waiters
-
-
-
+ handleChange('Number_of_Waiters', val)}
+ placeholder="200"
+ error={errors.Number_of_Waiters}
+ />
- {/* Number of Kitchen Assistant Input */}
-
-
- Number of Kitchen Assistant
-
-
-
+ handleChange('Number_of_Cookers', val)}
+ placeholder="200"
+ error={errors.Number_of_Cookers}
+ />
{/* Buttons */}
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
+
+
+ Next
+
+
+
+ Back
+
+
);
diff --git a/src/components/Home/RestaurantProfile/contcet/TypeOfRestaurant.js b/src/components/Home/RestaurantProfile/contcet/TypeOfRestaurant.js
index e271a57..1b1e69b 100644
--- a/src/components/Home/RestaurantProfile/contcet/TypeOfRestaurant.js
+++ b/src/components/Home/RestaurantProfile/contcet/TypeOfRestaurant.js
@@ -1,165 +1,157 @@
-// TypeOfRestaurant.jsx
-import React from 'react';
-import {
- Box,
- Typography,
- Stack,
- Button,
- useTheme,
- TextField
-} from '@mui/material';
+import React, { useState, useEffect } from 'react';
+import { Box, Typography, Stack, Button, useTheme, FormHelperText, TextField, Select, MenuItem } from '@mui/material';
-const TypeOfRestaurant = ({ currentStepIndex = 0, onNext, onBack }) => {
- const theme = useTheme();
+const TypeOfRestaurant = ({ formData, updateFormData, onBack, onRegister }) => {
+ const theme = useTheme();
+ const [typeData, setTypeData] = useState({
+ ownRestaurantType: formData.ownRestaurantType || '',
+ collaborateRestaurantType: formData.collaborateRestaurantType || ''
+ });
+ const [errors, setErrors] = useState({});
+ const [isSubmitting, setIsSubmitting] = useState(false);
- return (
-
-
- {/* العنوان */}
-
- Type of Restaurant
-
+ const collaborationOptions = [
+ { value: 'fine_dining', label: 'Fine Dining' },
+ { value: 'catering_services', label: 'Catering Services' },
+ { value: 'fast_food', label: 'Fast Food' },
+ { value: 'cloud_kitchen', label: 'Cloud Kitchen' }
+ ];
- {/* الوصف */}
-
-
- Enter your restaurant type you want to collaborate to proceed to registration of your own restaurant on this platform
-
-
+ useEffect(() => {
+ updateFormData(typeData);
+ }, [typeData]);
- {/* الحقول */}
-
-
- Open to Host Restaurant/Cousin type*
-
-
-
+ const handleChange = (field, value) => setTypeData(prev => ({ ...prev, [field]: value }));
-
-
- Select Restaurant type Willing to Collaborate With
-
-
-
+ const validateFields = () => {
+ const newErrors = {};
+ if (!typeData.ownRestaurantType) newErrors.ownRestaurantType = 'This field is required';
+ if (!typeData.collaborateRestaurantType) newErrors.collaborateRestaurantType = 'This field is required';
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
+ const handleRegister = async () => {
+ if (!validateFields()) return;
-
+ setIsSubmitting(true);
+ try {
+ // استدعاء الدالة المرسلة من الأب لتسجيل المطعم
+ await onRegister();
+ } catch (error) {
+ console.error('Registration failed:', error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+ Type of Restaurant
+
+
+
+
+ Enter your restaurant type you want to collaborate to proceed to registration of your own restaurant on this platform
+
- );
+
+
+
+ Open to Host Restaurant/Cousin type*
+
+ handleChange('ownRestaurantType', e.target.value)}
+ error={!!errors.ownRestaurantType}
+ helperText={errors.ownRestaurantType || ' '}
+ sx={{
+ '& input': { fontWeight: 500, fontSize: '15px' },
+ '& input::placeholder': { color: '#969BA7' },
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '10px',
+ '&.Mui-focused fieldset': { borderColor: theme.palette.primary.main }
+ }
+ }}
+ />
+
+
+
+
+ Select Restaurant type Willing to Collaborate With*
+
+
+ {errors.collaborateRestaurantType || ' '}
+
+
+
+
+ {isSubmitting ? 'Registering...' : 'Register'}
+
+
+
+ Back
+
+
+
+
+ );
};
export default TypeOfRestaurant;
diff --git a/src/components/Home/RestaurantProfile/contcet/UploadMenu.js b/src/components/Home/RestaurantProfile/contcet/UploadMenu.js
deleted file mode 100644
index eb860f1..0000000
--- a/src/components/Home/RestaurantProfile/contcet/UploadMenu.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import React from 'react';
-import {
- Box,
- Typography,
- Stack,
- Button,
- useTheme,
- TextField
-} from '@mui/material';
-import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
-
-const UploadMenu = ({ currentStepIndex = 0, onNext, onBack }) => {
- const theme = useTheme();
-
- return (
-
-
-
- Upload Menu
-
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
- {/* Item Name Input */}
-
-
- Item Name
-
-
-
-
- {/* Price Input */}
-
-
- Price
-
-
-
-
- {/* Description Input */}
-
-
- Description
-
-
-
-
- {/* OR Separator */}
-
-
-
-
- OR
-
-
-
-
- {/* Upload Menu Picture */}
-
- Upload Menu Picture
-
-
-
-
- {/* Buttons: Back and Next */}
-
-
-
- Next
-
-
- {/* زر Back تحت زر Next */}
-
- Back
-
-
-
-
-
- );
-};
-
-export default UploadMenu;
diff --git a/src/components/Home/RestaurantProfile/contcet/UploadPhotos.js b/src/components/Home/RestaurantProfile/contcet/UploadPhotos.js
deleted file mode 100644
index ecff280..0000000
--- a/src/components/Home/RestaurantProfile/contcet/UploadPhotos.js
+++ /dev/null
@@ -1,183 +0,0 @@
-import React, { useState } from 'react';
-import {
- Box,
- Typography,
- Stack,
- Button,
- useTheme
-} from '@mui/material';
-import { useNavigate } from 'react-router-dom';
-import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
-
-const UploadPhotos = ({ currentStepIndex = 0, onNext, onBack }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const [openModal, setOpenModal] = useState(false);
-
- const handleOpenModal = () => setOpenModal(true);
- const handleCloseModal = () => setOpenModal(false);
- const handleConfirmNext = () => {
- handleCloseModal();
- onNext();
- };
-
- return (
-
-
-
- Upload Photos
-
-
-
-
- Enter your basic information to proceed to registration of your own restaurant on this platform
-
-
-
- {/* Kitchen Interior’s */}
-
-
- Kitchen Interior’s
-
-
-
- Upload Interior Picture
-
-
-
-
-
- {/* Kitchen Equipment’s */}
-
-
- Kitchen Equipment’s
-
-
-
- Upload Equipment Picture
-
-
-
-
-
- {/* Buttons */}
-
-
- Next
-
-
-
- Back
-
-
-
-
- {/* ✅ Confirmation Modal */}
-
-
- );
-};
-
-export default UploadPhotos;
diff --git a/src/components/Home/Settings/AccountProfile.js b/src/components/Home/Settings/AccountProfile.js
new file mode 100644
index 0000000..1ce714c
--- /dev/null
+++ b/src/components/Home/Settings/AccountProfile.js
@@ -0,0 +1,403 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ Typography,
+ TextField,
+ Button,
+ Select,
+ MenuItem,
+ Checkbox,
+ ListItemText,
+ OutlinedInput,
+ styled,
+ useTheme,
+ LinearProgress
+} from '@mui/material';
+import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
+import authService from '../../../services/authService';
+import { useRestaurant } from '../../../contexts/RestaurantContext';
+import AddIcon from "@mui/icons-material/Add";
+const AccountProfile = () => {
+ const theme = useTheme();
+ const { restaurantId } = useRestaurant();
+ const [editMode, setEditMode] = useState(false);
+ const [profileImage, setProfileImage] = useState(null);
+ const [form, setForm] = useState({
+ restaurant_name: '',
+ restaurant_type: '',
+ Address: '',
+ City: '',
+ Postal_code: '',
+ Phone: '',
+ Email: '',
+ Operation_hour: '',
+ host_type: '',
+ collaboration_type: '',
+ closed_days: [],
+ equipment: [], // array of {name, quantity}
+ Maximum_orders_per_day: 0,
+ Number_of_Cheff: 0,
+ Number_of_Waiters: 0,
+ Number_of_Cookers: 0,
+ });
+
+ const daysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
+
+ const Input = styled('input')({ display: 'none' });
+
+ useEffect(() => {
+ const fetchProfile = async () => {
+ if (!restaurantId) return;
+ const data = await authService.getRestaurantProfile(restaurantId);
+ console.log("PROFILE DATA ===>", data); // 👈 يوضح الأسماء الحقيقية
+ if (data) {
+ setForm({
+ restaurant_name: data.restaurant_name,
+ restaurant_type: data.restaurant_type,
+ Address: data.Address,
+ City: data.City,
+ Postal_code: data.Postal_code,
+ Phone: data.Phone,
+ Email: data.Email,
+ Operation_hour: data.Operation_hour,
+ host_type: data.host_type,
+ collaboration_type: data.collaboration_type,
+ closed_days: data.closed_days || [],
+ equipment: Object.entries(data.equipment || {}).map(([name, quantity]) => ({ name, quantity })),
+ Maximum_orders_per_day: data.Maximum_orders_per_day ?? data.maximum_orders_per_day ?? 0,
+ Number_of_Cheff: data.Number_of_Cheff ?? data.number_of_cheff ?? 0,
+ Number_of_Waiters: data.Number_of_Waiters ?? data.number_of_waiters ?? 0,
+ Number_of_Cookers: data.Number_of_Cookers ?? data.number_of_cookers ?? 0,
+ });
+ }
+ };
+ fetchProfile();
+ }, [restaurantId]);
+
+
+ const handleImageUpload = (event) => {
+ if (event.target.files && event.target.files[0]) {
+ setProfileImage(event.target.files[0]);
+ }
+ };
+
+ const handleChange = (field, value) => setForm(prev => ({ ...prev, [field]: value }));
+
+ const handleEquipmentChange = (index, key, value) => {
+ const newEquipment = [...form.equipment];
+ newEquipment[index][key] = value;
+ setForm(prev => ({ ...prev, equipment: newEquipment }));
+ };
+
+ const addEquipment = () => {
+ setForm(prev => ({
+ ...prev,
+ equipment: [...prev.equipment, { name: '', quantity: 0 }]
+ }));
+ };
+
+ const removeEquipment = (index) => {
+ const newEquipment = [...form.equipment];
+ newEquipment.splice(index, 1);
+ setForm(prev => ({ ...prev, equipment: newEquipment }));
+ };
+
+ const renderField = (label, value, fieldKey, options = null) => (
+
+
+ {label}
+
+
+ {editMode ? (
+ options ? (
+
+ ) : (
+ handleChange(fieldKey, e.target.value)}
+ fullWidth
+ variant="outlined"
+ sx={{
+ '& input': { fontWeight: 500, fontSize: '16px', color: 'black' },
+ '& input::placeholder': { color: '#969BA7' },
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '10px',
+ transition: '0.3s',
+ '&.Mui-focused fieldset': {
+ borderColor: theme.palette.primary.main,
+ boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
+ },
+ },
+ }}
+ />
+ )
+ ) : (
+
+ {Array.isArray(value) ? value.join(', ') : value}
+
+ )}
+
+);
+
+
+
+ const handleSave = async () => {
+ if (!restaurantId) return;
+
+ const equipmentObj = {};
+ form.equipment.forEach(eq => {
+ if (eq.name) equipmentObj[eq.name] = eq.quantity;
+ });
+
+ const payload = {
+ ...form,
+ equipment: equipmentObj
+ };
+
+ if (profileImage) payload.logo = profileImage;
+
+ const result = await authService.updateRestaurantProfile(restaurantId, payload);
+
+ if (result.success) {
+ alert("Profile updated successfully!");
+ setEditMode(false);
+
+ const updated = await authService.getRestaurantProfile(restaurantId);
+ if (updated) setForm({
+ ...updated,
+ equipment: Object.entries(updated.equipment || {}).map(([name, quantity]) => ({ name, quantity })),
+ });
+ } else {
+ alert(`Update failed: ${result.message}`);
+ }
+ };
+
+
+ // داخل AccountProfile
+
+ const collaborationOptions = [
+ { value: 'fine_dining', label: 'Fine Dining' },
+ { value: 'catering_services', label: 'Catering Services' },
+ { value: 'fast_food', label: 'Fast Food' },
+ { value: 'cloud_kitchen', label: 'Cloud Kitchen' }
+ ];
+
+
+ return (
+
+ Restaurant Profile
+
+ {/* صورة البروفايل */}
+ {/*
+
+ */}
+
+
+
+ {/* الحقول */}
+
+
+ {renderField('Restaurant Name', form.restaurant_name, 'restaurant_name')}
+
+ {renderField('Restaurant Type', form.restaurant_type, 'restaurant_type')}
+
+
+ {renderField('Address', form.Address, 'Address')}
+ {renderField('City', form.City, 'City')}
+
+
+ {renderField('Postal Code', form.Postal_code, 'Postal_code')}
+ {renderField('Phone', form.Phone, 'Phone')}
+
+
+ {renderField('Email', form.Email, 'Email')}
+ {renderField('Operation Hours', form.Operation_hour, 'Operation_hour')}
+
+
+ {renderField('Host Type', form.host_type, 'host_type')}
+
+ Collaboration Type
+ {editMode ? (
+
+ ) : (
+
+ {form.collaboration_type ? collaborationOptions.find(opt => opt.value === form.collaboration_type)?.label : 'None'}
+
+ )}
+
+
+
+
+ {renderField('Closed Days', form.closed_days, 'closed_days', daysOfWeek)}
+
+
+ {/* القدرة التشغيلية */}
+
+ {renderField('Maximum Orders per Day', form.Maximum_orders_per_day, 'Maximum_orders_per_day')}
+ {renderField('Number of Chefs', form.Number_of_Cheff, 'Number_of_Cheff')}
+
+
+ {renderField('Number of Waiters', form.Number_of_Waiters, 'Number_of_Waiters')}
+ {renderField('Number of Cookers', form.Number_of_Cookers, 'Number_of_Cookers')}
+
+
+ {/* المعدات */}
+
+ Equipment Name
+ {form.equipment.map((eq, index) => (
+
+ handleEquipmentChange(index, 'name', e.target.value)}
+ fullWidth
+ variant="outlined"
+ disabled={!editMode}
+ sx={{ '& input': { fontWeight: 500, fontSize: '16px' } }}
+ />
+ handleEquipmentChange(index, 'quantity', Number(e.target.value))}
+ fullWidth
+ variant="outlined"
+ disabled={!editMode}
+ sx={{ '& input': { fontWeight: 500, fontSize: '16px' } ,borderRadius:20}}
+ />
+ {editMode && (
+ removeEquipment(index)}>
+ Delete
+
+ )}
+
+ ))}
+ {editMode && (
+ } sx={{ mt: 1, width: 'fit-content' , textTransform:'none' }}>
+ Add Equipment
+
+ )}
+
+
+
+ {/* أزرار التحكم */}
+
+ {editMode ? (
+ <>
+ setEditMode(false)}
+ sx={{ px: 4, py: 1.5, borderRadius: 2, textTransform: 'none', fontWeight: 'bold' }}
+ >
+ Cancel
+
+
+ Save
+
+ >
+ ) : (
+ setEditMode(true)}
+ sx={{
+ px: 4, py: 1.5, borderRadius: 2,
+ textTransform: 'none', fontWeight: 'bold',
+ color: '#FFF', backgroundColor: theme.palette.primary.main
+ }}
+ >
+ Edit Profile
+
+ )}
+
+
+ );
+};
+
+export default AccountProfile;
diff --git a/src/components/Home/Settings/AccountSettings.js b/src/components/Home/Settings/AccountSettings.js
deleted file mode 100644
index fd07243..0000000
--- a/src/components/Home/Settings/AccountSettings.js
+++ /dev/null
@@ -1,217 +0,0 @@
-import React, { useState } from 'react';
-import {
- Box,
- Typography,
- TextField,
- Button,
- styled,
- useTheme
-} from '@mui/material';
-import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
-
-const AccountSettings = () => {
- const theme = useTheme();
- const [profileImage, setProfileImage] = useState(null);
- const [fullName, setFullName] = useState('');
- const [username, setUsername] = useState('');
- const [bio, setBio] = useState('');
- const [email, setEmail] = useState('');
- const [phone, setPhone] = useState('');
-
- const handleImageUpload = (event) => {
- if (event.target.files && event.target.files[0]) {
- setProfileImage(URL.createObjectURL(event.target.files[0]));
- }
- };
-
- const Input = styled('input')({
- display: 'none',
- });
-
- const customInputStyle = {
- '& input': { fontWeight: 500, fontSize: '15px' },
- '& input::placeholder': { color: '#969BA7' },
- '& .MuiOutlinedInput-root': {
- borderRadius: '10px',
- transition: '0.3s',
- '&.Mui-focused fieldset': {
- borderColor: theme.palette.primary.main,
- boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
- },
- },
- '& .MuiOutlinedInput-root.Mui-focused': {
- borderColor: '#3f51b5',
- boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
- },
- };
-
- const renderField = (label, value, onChange, type = 'text') => (
-
-
- {label}
-
-
-
- );
-
- return (
-
- {/* العنوان الرئيسي */}
-
- Account Setting
-
-
- {/* صورة الملف الشخصي */}
-
- Your Profile Picture
-
-
-
-
-
-
- {/* الحقول النصية */}
-
-
- Personal Information
-
-
- {/* السطر الأول: Full Name + Email */}
-
- {renderField('Full Name', fullName, (e) => setFullName(e.target.value))}
- {renderField('Email', email, (e) => setEmail(e.target.value), 'email')}
-
-
- {/* السطر الثاني: Username + Phone Number */}
-
- {renderField('Username', username, (e) => setUsername(e.target.value))}
- {renderField('Phone Number', phone, (e) => setPhone(e.target.value), 'tel')}
-
-
- {/* الحقل Bio منفصل */}
-
-
- Bio
-
- setBio(e.target.value)}
- fullWidth
- multiline
- rows={3}
- sx={customInputStyle}
- />
-
-
-
- {/* الأزرار */}
-
-
- Cancel
-
-
- Update
-
-
-
- );
-};
-
-export default AccountSettings;
diff --git a/src/components/Home/Settings/Setting.js b/src/components/Home/Settings/Setting.js
index 8c9d906..3c121e3 100644
--- a/src/components/Home/Settings/Setting.js
+++ b/src/components/Home/Settings/Setting.js
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Box, useTheme, useMediaQuery } from '@mui/material';
import KitchPlusAppBar from '../AppBar';
import Sidebar from '../SideHome';
-import AccountSettings from './AccountSettings';
+import AccountSettings from './AccountProfile';
const drawerWidth = 230;
diff --git a/src/components/Home/SideHome.js b/src/components/Home/SideHome.js
index 396e0e6..daa5c08 100644
--- a/src/components/Home/SideHome.js
+++ b/src/components/Home/SideHome.js
@@ -10,39 +10,28 @@ import {
useTheme,
Typography
} from '@mui/material';
-import {
- DashboardRounded as DashboardIcon,
- PointOfSale as CashierIcon,
- AllInbox as SupplierIcon,
- Store as InventoryIcon,
- School as TrainingIcon,
- AutoGraph as AnalyticsIcon,
- Restaurant as RestaurantIcon,
- CountertopsTwoTone as HostKitchenIcon,
- Queue as CreateKitchenIcon,
- Settings as SettingsIcon,
- Logout as LogoutIcon
-} from '@mui/icons-material';
+import DashboardIcon from '@mui/icons-material/DashboardRounded';
+import AnalyticsIcon from '@mui/icons-material/AutoGraph';
+import RestaurantIcon from '@mui/icons-material/Restaurant';
+import Employ from '@mui/icons-material/CountertopsTwoTone';
+import CreateKitchenIcon from '@mui/icons-material/Queue';
+import LogoutIcon from '@mui/icons-material/Logout';
+import CategoryIcon from '@mui/icons-material/Category';
+import authService from '../../services/authService';
-import authService from '../../services/authService'; // تأكد من المسار الصحيح
const menuItems = [
- { text: 'Dashboard', icon: , path: '/dashboard' },
- { text: 'Cashier', icon: , path: '/cashier' },
- { text: 'Supplier', icon: , path: '/supplier' },
- { text: 'Inventory', icon: , path: '/inventory' },
- { text: 'Training', icon: , path: '/training' },
- { text: 'Analytics & Reporting', icon: , path: '/analytics' },
+ { text: 'Order & Occupancy', icon: , path: '/dashboard' },
+ { text: 'Tabels & Reservations', icon: , path: '/analytics' },
{ text: 'Restaurant Profile', icon: , path: '/profile' },
- // { text: 'Host Kitchen', icon: , path: '/host-kitchen' },
+ { text: 'Employ', icon: , path: '/employ' },
{ text: 'Create Kitchen', icon: , path: '/create-kitchen' },
+ { text: 'Catigores & Meals', icon: , path: '/categori-meal' },
];
const bottomItems = [
- { text: 'Settings', icon: , path: '/settings' },
{ text: 'Log Out', icon: , path: '/login' },
];
-
const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
const theme = useTheme();
const navigate = useNavigate();
@@ -175,7 +164,6 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
overflowY: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': { display: 'none' },
- py: 1,
}}
>
{renderListItems(menuItems)}
diff --git a/src/components/Home/SimplePagination.js b/src/components/Home/SimplePagination.js
new file mode 100644
index 0000000..3cd4628
--- /dev/null
+++ b/src/components/Home/SimplePagination.js
@@ -0,0 +1,71 @@
+// SimplePagination.jsx
+import React from 'react';
+import { Box, IconButton, useTheme } from '@mui/material';
+import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+
+const SimplePagination = ({ currentPage, pageCount, onChange }) => {
+ const theme = useTheme();
+
+ const handlePrev = () => {
+ if (currentPage > 1) onChange(currentPage - 1);
+ };
+
+ const handleNext = () => {
+ if (currentPage < pageCount) onChange(currentPage + 1);
+ };
+
+ return (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: '8px',
+ backgroundColor: '#FFECE0',
+ '&:hover': { backgroundColor: '#FFD6B5' },
+ color: theme.palette.primary.main,
+ '&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
+ }}
+ >
+
+
+
+ );
+};
+
+export default SimplePagination;
diff --git a/src/components/Home/Supplier/Supplier.js b/src/components/Home/Supplier/Supplier.js
index 76ef908..e305554 100644
--- a/src/components/Home/Supplier/Supplier.js
+++ b/src/components/Home/Supplier/Supplier.js
@@ -1,67 +1,122 @@
+import React, { useState, useEffect } from "react";
+import { Box, useTheme, useMediaQuery } from "@mui/material";
+import KitchPlusAppBar from "../AppBar";
+import Sidebar from "../SideHome";
-import React, { useState, useEffect } from 'react';
-import { Box, useTheme, useMediaQuery, Typography } from '@mui/material';
-import KitchPlusAppBar from '../AppBar';
-import Sidebar from '../SideHome';
+import AccountSettings from "./contect/AccountSettings";
+import AllProducts from "./contect/AllProducts";
+import PopularProductSection from "./contect/PopularProductSection";
+import ProductDetail from "./contect/ProductDetail";
+
+import api from "../../../services/authService";
+
+const IMAGE_URL = "http://127.0.0.1:8000/storage/";
const drawerWidth = 230;
const Supplier = () => {
const theme = useTheme();
- const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
- const [hasProducts, setHasProducts] = useState(false); // حالة لتتبع وجود المنتجات
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
+ const [categories, setCategories] = useState([]);
+ const [products, setProducts] = useState([]);
+ const [selectedCategory, setSelectedCategory] = useState(null);
+ const [selectedCategoryName, setSelectedCategoryName] = useState(null);
+
+ const [selectedProduct, setSelectedProduct] = useState(null);
+
+ const [showAllPopular, setShowAllPopular] = useState(false);
+ const [showAllTopItems, setShowAllTopItems] = useState(false);
useEffect(() => {
- const checkProducts = async () => {
-
- const productsExist = await checkIfProductsExist();
- setHasProducts(productsExist);
- };
-
- checkProducts();
- }, []);
-
-
- const checkIfProductsExist = async () => {
- return false;
- };
-
- useEffect(() => {
- if (window.innerWidth >= theme.breakpoints.values.md) {
- setSidebarOpen(true);
- } else {
- setSidebarOpen(false);
- }
+ setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
}, [theme.breakpoints.values.md]);
useEffect(() => {
const handleResize = () => {
- if (window.innerWidth >= theme.breakpoints.values.md) {
- setSidebarOpen(true);
- } else {
- setSidebarOpen(false);
+ setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
+ };
+ handleResize();
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, [theme.breakpoints.values.md]);
+
+ useEffect(() => {
+ const fetchCategories = async () => {
+ try {
+ const res = await api.getAllCategory();
+ if (res.success) {
+ const formatted = res.data.map((category) => ({
+ id: category.id,
+ name: category.name,
+ icon: category.image
+ ? IMAGE_URL + category.image
+ : "/images/default-product.png",
+ }));
+ setCategories(formatted);
+ }
+ } catch (err) {
+ console.error("❌ Error fetching categories:", err);
}
};
- handleResize();
- window.addEventListener('resize', handleResize);
+ fetchCategories();
+ }, []);
- return () => window.removeEventListener('resize', handleResize);
- }, [theme.breakpoints.values.md]);
+ useEffect(() => {
+ const fetchProducts = async () => {
+ if (!selectedCategory) return;
+
+ try {
+ const res = await api.getProductsByCategory(selectedCategory, 1);
+ if (res.success) {
+ const formatted = res.data.map((product) => ({
+ id: product.id,
+ name: product.name,
+ category: product.supplier_category_id,
+ price: product.price,
+ unit: "Kg",
+ image: product.image
+ ? IMAGE_URL + product.image
+ : "/images/default-product.png",
+ }));
+ setProducts(formatted);
+
+ const categoryName = categories.find(
+ (cat) => cat.id === selectedCategory
+ )?.name;
+ setSelectedCategoryName(categoryName || "");
+ }
+ } catch (err) {
+ console.error("Error fetching products:", err);
+ }
+ };
+
+ fetchProducts();
+ }, [selectedCategory, categories]);
const handleDrawerToggle = () => {
setSidebarOpen(!sidebarOpen);
};
+ const toggleShowAllPopular = () => {
+ setShowAllPopular((prev) => !prev);
+ };
+
+ const toggleShowAllTopItems = () => {
+ setShowAllTopItems((prev) => !prev);
+ };
+
return (
-
+
{
drawerWidth={drawerWidth}
/>
-
+
- Supplier
+
+
+ {selectedProduct ? (
+ setSelectedProduct(null)}
+ />
+ ) : (
+ <>
+
+
+ {selectedCategory ? (
+
+ setSelectedCategory(null)}
+ onProductClick={setSelectedProduct}
+ categoryName={selectedCategoryName}
+ />
+
+ ) : !showAllPopular && !showAllTopItems ? (
+
+
+
+
+
+ ) : showAllPopular ? (
+
+
+
+ ) : showAllTopItems ? (
+
+
+ ) : null}
+ >
+ )}
+
);
};
-export default Supplier;
\ No newline at end of file
+export default Supplier;
diff --git a/src/components/Home/Supplier/Supplieree.js b/src/components/Home/Supplier/Supplieree.js
new file mode 100644
index 0000000..593ab8c
--- /dev/null
+++ b/src/components/Home/Supplier/Supplieree.js
@@ -0,0 +1,247 @@
+import React, { useState, useEffect } from 'react';
+import { Box, useTheme, useMediaQuery } from '@mui/material';
+import KitchPlusAppBar from '../AppBar';
+import Sidebar from '../SideHome';
+
+// بدل هذه المكونات عند عرض التفاصيل
+import AccountSettings from './contect/AccountSettings';
+import AllProducts from './contect/AllProducts';
+import PopularProductSection from './contect/PopularProductSection';
+import TopItems from './contect/TopItems';
+import OrderList from './contect/OrderList';
+import ProductDetail from './contect/ProductDetail'; // 👈 مكون التفاصيل
+//import auth servise
+import api from '../../../services/authService';
+const IMAGE_URL = 'http://127.0.0.1:8000/storage/' ;
+
+
+const categoriesInit0 = await api.getAllCategory();
+const categoriesInit = categoriesInit0.data.map(category => ({
+ id: category.id,
+ name: category.name,
+ icon: category.image || "/images/default-product.png",
+}));
+
+
+const productList0 = await api.getAllProduct();
+const productList = productList0.data.map(product => ({
+ id: product.id,
+ name: product.name,
+ category: product.supplier_category_id,
+ price: product.price,
+ unit: "Kg",
+ image: IMAGE_URL + product.image || "/images/default-product.png",
+}));
+
+// const productList = [
+// { id: 1, name: 'Cabbage 1', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 2, name: 'vegetables 2', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 3, name: 'Brocoly 3', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 4, name: 'Onion 4', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 5, name: 'Bread 5', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
+// { id: 6, name: 'Meat 6', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 7, name: 'Cabbage', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 8, name: 'vegetables', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 9, name: 'Brocoly', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 10, name: 'Onion', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 11, name: 'Bread', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
+// { id: 12, name: 'Meat', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 13, name: 'Cabbage', category: 'Vegetable', price: '15.10', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 14, name: 'vegetables', category: 'Vegetable', price: '8.34', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 15, name: 'Brocoly', category: 'Vegetable', price: '5.60', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 16, name: 'Onion', category: 'Vegetable', price: '6.45', unit: '/kg', image: '/images/waitress2.png' },
+// { id: 17, name: 'Bread', category: 'Bread', price: '3.50', unit: '/pcs', image: '/images/waitress2.png' },
+// { id: 18, name: 'Meat', category: 'Meat', price: '20.00', unit: '/kg', image: '/images/waitress2.png' },
+// ];
+
+const item = [
+ { name: 'Cabbage', price: '15.10', unit: '/kg', image: '/images/cabbage.png' },
+ { name: 'Kale vegetables', price: '8.34', unit: '/kg', image: '/images/kale.png' },
+ { name: 'Brocoly', price: '5.60', unit: '/kg', image: '/images/broccoli.png' },
+ { name: 'Celery', price: '4.80', unit: '/kg', image: '/images/celery.png' },
+ { name: 'Onion', price: '6.45', unit: '/kg', image: '/images/onion.png' },
+ { name: 'Garlic', price: '9.90', unit: '/kg', image: '/images/garlic.png' },
+];
+
+const drawerWidth = 230;
+
+const Supplier = () => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
+ const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
+
+ // حالات العرض
+ const [showAllPopular, setShowAllPopular] = useState(false);
+ const [showAllTopItems, setShowAllTopItems] = useState(false);
+ const [selectedCategory, setSelectedCategory] = useState(null);
+
+ // ✅ المنتج المختار
+ const [selectedProduct, setSelectedProduct] = useState(null);
+
+ useEffect(() => {
+ setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
+ }, [theme.breakpoints.values.md]);
+
+ useEffect(() => {
+ const handleResize = () => {
+ setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
+ };
+ handleResize();
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, [theme.breakpoints.values.md]);
+
+ const handleDrawerToggle = () => {
+ setSidebarOpen(!sidebarOpen);
+ };
+
+ const toggleShowAllPopular = () => {
+ setShowAllPopular(prev => !prev);
+ };
+
+ const toggleShowAllTopItems = () => {
+ setShowAllTopItems(prev => !prev);
+ };
+
+ // اسم الفئة
+ const selectedCategoryName = selectedCategory
+ ? categoriesInit.find(cat => cat.id === selectedCategory)?.name
+ : null;
+
+ return (
+
+
+
+
+
+
+
+ {/* ✅ إذا تم اختيار منتج نظهر تفاصيله فقط */}
+ {selectedProduct ? (
+ setSelectedProduct(null)} />
+ ) : (
+ <>
+
+
+ {selectedCategory ? (
+
+ p.category.toLowerCase() === selectedCategoryName?.toLowerCase()
+ )}
+ showAll={true}
+ onToggleShowAll={() => setSelectedCategory(null)}
+ onProductClick={setSelectedProduct}
+ categoryName={selectedCategoryName} // ✅ بدل القيمة الثابتة
+ />
+
+
+ ) : !showAllPopular && !showAllTopItems ? (
+
+
+
+ {/*
+
+ */}
+
+
+ {/*
+
+
+
+ */}
+
+ ) : showAllPopular ? (
+
+
+
+ ) : showAllTopItems ? (
+
+ {/* */}
+
+ ) : null}
+ >
+ )}
+
+
+
+ );
+};
+
+export default Supplier;
diff --git a/src/components/Home/Supplier/contect/AccountSettings.js b/src/components/Home/Supplier/contect/AccountSettings.js
new file mode 100644
index 0000000..a00e5d0
--- /dev/null
+++ b/src/components/Home/Supplier/contect/AccountSettings.js
@@ -0,0 +1,307 @@
+import React, { useState, useRef, useEffect } from 'react';
+import {
+ Menu,
+ MenuItem,
+ Modal,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ useMediaQuery,
+ Box,
+ Typography,
+ Button,
+ useTheme,
+ IconButton,
+} from '@mui/material';
+import AddCategory from './AddCategory'; // مكون لإضافة/تعديل الفئة (يفترض موجود)
+import AddIcon from '@mui/icons-material/Add';
+import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+import TuneIcon from '@mui/icons-material/Tune';
+import CategoryScrollList from './CategoryScrollList'; // مكون لعرض الفئات بشكل أفقي مع تمرير
+//import auth servise
+import api from '../../../../services/authService';
+
+const IMAGE_URL = 'http://127.0.0.1:8000/storage/' ;
+
+const categoriesInit0 = await api.getAllCategory();
+const categoriesInit = categoriesInit0.data.map(category => (
+ {
+ id: category.id,
+ name: category.name,
+ icon: IMAGE_URL + category.icon || "/images/default-product.png",
+}));
+
+
+const AccountSettings = ({ selectedCategory, setSelectedCategory }) => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
+
+ const [categories, setCategories] = useState(categoriesInit);
+ const [selectedCategoryForEdit, setSelectedCategoryForEdit] = useState(null);
+ const [contextMenu, setContextMenu] = useState(null);
+ const [openAddModal, setOpenAddModal] = useState(false);
+ const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
+
+ const scrollRef = useRef();
+
+ const scroll = (offset) => {
+ if (scrollRef.current) {
+ scrollRef.current.scrollLeft += offset;
+ }
+ };
+
+ const handleAddCategory = (newCategory) => {
+ setCategories((prev) => [...prev, { ...newCategory, id: Date.now() }]);
+ setOpenAddModal(false);
+ };
+
+ const handleUpdateCategory = (updatedCategory) => {
+ setCategories((prev) =>
+ prev.map((cat) =>
+ cat.id === selectedCategoryForEdit.id ? { ...cat, ...updatedCategory } : cat
+ )
+ );
+ setOpenAddModal(false);
+ };
+
+ const handleDeleteCategory = () => {
+ setCategories((prev) => prev.filter((cat) => cat.id !== selectedCategoryForEdit.id));
+ setConfirmDeleteOpen(false);
+ setSelectedCategoryForEdit(null);
+
+ if (selectedCategory === selectedCategoryForEdit?.id) {
+ setSelectedCategory(null); // أبطل اختيار الفئة
+ }
+ };
+
+
+ useEffect(() => {
+ if (!openAddModal) {
+ setSelectedCategoryForEdit(null);
+ }
+ }, [openAddModal]);
+
+ return (
+
+ {/* رأس القسم */}
+
+
+ Categories
+
+
+
+ {/* {
+ setSelectedCategoryForEdit(null);
+ setOpenAddModal(true);
+ }}
+ >
+ Add Category
+ */}
+
+ }
+ >
+ {isMobile ? '' : 'Filters'}
+
+
+ scroll(-200)}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: '#fff',
+ width: 40,
+ height: 40,
+ '&:hover': { backgroundColor: '#e96b00' },
+ }}
+ >
+
+
+
+ scroll(200)}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: '#fff',
+ width: 40,
+ height: 40,
+ '&:hover': { backgroundColor: '#e96b00' },
+ }}
+ >
+
+
+
+
+
+ {/* قائمة التمرير للفئات */}
+
+
+
+
+
+
+ {/* مودال الإضافة / التعديل */}
+ setOpenAddModal(false)}
+ aria-labelledby="add-category-modal"
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ p: 2,
+ }}
+ >
+
+
+
+
+
+ {/* قائمة السياق (الزر الأيمن) */}
+
+
+ {/* تأكيد الحذف */}
+
+
+ );
+};
+
+export default AccountSettings;
diff --git a/src/components/Home/Supplier/contect/AddCategory.js b/src/components/Home/Supplier/contect/AddCategory.js
new file mode 100644
index 0000000..8244f62
--- /dev/null
+++ b/src/components/Home/Supplier/contect/AddCategory.js
@@ -0,0 +1,186 @@
+import React, { useState, useEffect } from 'react';
+import { Box, Button, TextField, Typography, IconButton } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
+
+const AddCategory = ({ onAdd, editingCategory }) => {
+ const [productName, setProductName] = useState('');
+ const [description, setDescription] = useState('');
+ const [price, setPrice] = useState('');
+ const [discount, setDiscount] = useState('');
+ const [productImage, setProductImage] = useState(null);
+
+ useEffect(() => {
+ if (editingCategory) {
+ setProductName(editingCategory.name || '');
+ setDescription(editingCategory.description || '');
+ setPrice(editingCategory.price || '');
+ setDiscount(editingCategory.discount || '');
+ setProductImage(editingCategory.icon || null);
+ }
+ }, [editingCategory]);
+
+ const handleImageUpload = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setProductImage(reader.result);
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleRemoveImage = () => {
+ setProductImage(null);
+ };
+
+ const handleSubmit = () => {
+ const newCategory = {
+ name: productName,
+ description,
+ price,
+ discount,
+ icon: productImage,
+ };
+
+ if (onAdd) {
+ onAdd(newCategory);
+ }
+
+ // إعادة تعيين الحقول فقط عند الإضافة الجديدة
+ if (!editingCategory) {
+ setProductName('');
+ setDescription('');
+ setPrice('');
+ setDiscount('');
+ setProductImage(null);
+ }
+ };
+
+ return (
+
+ {/* ✅ صورة التصنيف */}
+ {productImage ? (
+
+
+
+
+
+
+ ) : (
+
+ Upload Menu Picture
+
+
+
+ )}
+
+
+
+ Categories name
+
+ setProductName(e.target.value)}
+ margin="normal"
+ sx={{
+ '& input': { fontWeight: 500, fontSize: '15px' },
+ '& input::placeholder': { color: '#969BA7' },
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '10px',
+ transition: '0.3s',
+ '&.Mui-focused fieldset': {
+ borderColor: '#FF914D',
+ boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
+ }
+ },
+ '& .MuiOutlinedInput-root.Mui-focused': {
+ borderColor: '#FF914D',
+ boxShadow: '0 0 0 2px rgba(255,145,77,0.15)'
+ }
+ }}
+ />
+
+
+
+ {editingCategory ? 'Update Category' : 'Add Category'}
+
+
+ );
+};
+
+export default AddCategory;
diff --git a/src/components/Home/Supplier/contect/AllProducts.js b/src/components/Home/Supplier/contect/AllProducts.js
new file mode 100644
index 0000000..d0c66c0
--- /dev/null
+++ b/src/components/Home/Supplier/contect/AllProducts.js
@@ -0,0 +1,93 @@
+import React from "react";
+import { Box, Typography } from "@mui/material";
+import ProductCard from "./ProductCard";
+
+const AllProducts = ({ products, showAll, onToggleShowAll, onProductClick, categoryName }) => {
+ const visibleProducts = showAll ? products : products.slice(0, 6);
+
+ return (
+
+ {/* العنوان */}
+
+
+ {categoryName ? categoryName : "All Products"}
+
+
+
+
+
+
+ {showAll ? "Show Less" : "See All"}
+
+
+
+ {/* عرض المنتجات أو رسالة لا يوجد منتجات */}
+ {visibleProducts.length === 0 ? (
+
+
+ {categoryName
+ ? `There is no product in ${categoryName}`
+ : "There is no product"}
+
+
+ ) : (
+
+ {visibleProducts.map((product, index) => (
+ onProductClick(product)}
+ >
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default AllProducts;
diff --git a/src/components/Home/Supplier/contect/CategoryScrollList.js b/src/components/Home/Supplier/contect/CategoryScrollList.js
new file mode 100644
index 0000000..7319a02
--- /dev/null
+++ b/src/components/Home/Supplier/contect/CategoryScrollList.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import { Box, Avatar, Typography } from '@mui/material';
+import { useTheme } from '@mui/material/styles';
+
+const CategoryScrollList = ({
+ categories,
+ selectedCategory,
+ setSelectedCategory,
+ setSelectedCategoryForEdit,
+ setContextMenu,
+ scrollRef,
+}) => {
+ const theme = useTheme();
+
+ return (
+
+
+ {categories.map((cat) => (
+ setSelectedCategory(cat.id)}
+ onContextMenu={(e) => {
+ e.preventDefault();
+ setSelectedCategoryForEdit(cat);
+ setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
+ }}
+ >
+
+
+
+ {cat.name}
+
+
+ ))}
+
+
+ );
+};
+
+export default CategoryScrollList;
diff --git a/src/components/Home/Supplier/contect/OrderList.js b/src/components/Home/Supplier/contect/OrderList.js
new file mode 100644
index 0000000..10d4446
--- /dev/null
+++ b/src/components/Home/Supplier/contect/OrderList.js
@@ -0,0 +1,359 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Typography,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Chip,
+ IconButton,
+ TableContainer,
+ Button,
+ Menu,
+ MenuItem,
+} from '@mui/material';
+import TuneIcon from '@mui/icons-material/Tune';
+import ReplayIcon from '@mui/icons-material/Replay';
+import VisibilityIcon from '@mui/icons-material/Visibility';
+import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+import { useTheme } from '@mui/material/styles';
+import { useMediaQuery } from '@mui/material';
+
+const SimplePagination = ({ currentPage, pageCount, onChange }) => {
+ const theme = useTheme();
+
+ const handlePrev = () => {
+ if (currentPage > 1) onChange(currentPage - 1);
+ };
+
+ const handleNext = () => {
+ if (currentPage < pageCount) onChange(currentPage + 1);
+ };
+
+ return (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: '8px',
+ backgroundColor: '#FFECE0',
+ '&:hover': { backgroundColor: '#FFD6B5' },
+ color: theme.palette.primary.main,
+ '&.Mui-disabled': {
+ color: '#ccc',
+ backgroundColor: '#FFF5E6',
+ },
+ }}
+ >
+
+
+
+ );
+};
+
+const OrderList = () => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
+
+ const initialOrders = [
+ { restaurant: 'المطعم الاول المطعم الاول ', time: '5:00 Pm', status: 'Pending' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'In-Progress' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Pending' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Pending' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Delivered' },
+ { restaurant: 'Al-Baik', time: '5:00 Pm', status: 'Completed' },
+ ];
+
+ const [currentPage, setCurrentPage] = useState(1);
+ const [filterAnchorEl, setFilterAnchorEl] = useState(null);
+ const [statusFilter, setStatusFilter] = useState('all'); // 'all' يعني بدون فلتر
+
+ const itemsPerPage = 5;
+
+ // افتح قائمة الفلتر
+ const handleFilterClick = (event) => {
+ setFilterAnchorEl(event.currentTarget);
+ };
+
+ // اغلق قائمة الفلتر
+ const handleFilterClose = () => {
+ setFilterAnchorEl(null);
+ };
+
+ // عند اختيار فلتر جديد
+ const handleFilterSelect = (filter) => {
+ setStatusFilter(filter);
+ setCurrentPage(1); // العودة للصفحة الأولى بعد تغيير الفلتر
+ handleFilterClose();
+ };
+
+ // تصفية البيانات حسب الفلتر
+ const filteredOrders =
+ statusFilter === 'all'
+ ? initialOrders
+ : initialOrders.filter(
+ (order) => order.status.toLowerCase() === statusFilter.toLowerCase()
+ );
+
+ const pageCount = Math.ceil(filteredOrders.length / itemsPerPage);
+
+ const paginatedData = filteredOrders.slice(
+ (currentPage - 1) * itemsPerPage,
+ currentPage * itemsPerPage
+ );
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'Pending':
+ return { bg: '#FFF3E0', color: '#E65100' };
+ case 'Delivered':
+ return { bg: '#E8F5E9', color: '#2E7D32' };
+ case 'Completed':
+ return { bg: '#F3E5F5', color: '#6A1B9A' };
+ case 'In-Progress':
+ return { bg: '#F1F8E9', color: '#9E9D24' };
+ default:
+ return { bg: '#E0E0E0', color: '#424242' };
+ }
+ };
+
+ const rowHeight = 73; // ارتفاع الصف الواحد تقريبي
+ const emptyRowsCount = itemsPerPage - paginatedData.length;
+
+ return (
+
+ {/* Header */}
+
+
+ Order List
+
+ }
+ onClick={handleFilterClick}
+ >
+ {isMobile ? '' : 'Filters'}
+
+
+ {/* قائمة الفلاتر */}
+
+
+
+ {/* Table */}
+
+
+
+
+ Restaurant
+ Time
+ Status
+ Action
+
+
+
+
+ {paginatedData.length > 0 ? (
+ paginatedData.map((row, i) => {
+ const colors = getStatusColor(row.status);
+ return (
+
+
+ {row.restaurant}
+
+
+ {row.time}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })
+ ) : (
+
+
+ No orders found.
+
+
+ )}
+
+ {/* صفوف تعويضية فارغة لتثبيت ارتفاع الجدول */}
+ {emptyRowsCount > 0 &&
+ [...Array(emptyRowsCount)].map((_, idx) => (
+
+
+
+ ))}
+
+
+
+
+ {/* Pagination */}
+
+
+ Showing {(currentPage - 1) * itemsPerPage + 1} -{' '}
+ {Math.min(currentPage * itemsPerPage, filteredOrders.length)} of {filteredOrders.length}
+
+
+
+
+
+ );
+};
+
+export default OrderList;
diff --git a/src/components/Home/Supplier/contect/PopularProductSection.js b/src/components/Home/Supplier/contect/PopularProductSection.js
new file mode 100644
index 0000000..8196a94
--- /dev/null
+++ b/src/components/Home/Supplier/contect/PopularProductSection.js
@@ -0,0 +1,154 @@
+import React, { useState } from 'react';
+import { Box, Typography, Grid, useTheme, IconButton } from '@mui/material';
+import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+import ProductCard from './ProductCard';
+
+const ITEMS_PER_PAGE = 10;
+
+/* مكوّن الباجينيشن */
+const SimplePagination = ({ currentPage, pageCount, onChange }) => {
+ const theme = useTheme();
+
+ const handlePrev = () => {
+ if (currentPage > 1) onChange(currentPage - 1);
+ };
+
+ const handleNext = () => {
+ if (currentPage < pageCount) onChange(currentPage + 1);
+ };
+
+ return (
+
+
+
+
+
+
+ {currentPage}
+
+
+ = pageCount}
+ sx={{
+ borderRadius: '8px',
+ backgroundColor: '#FFECE0',
+ '&:hover': { backgroundColor: '#FFD6B5' },
+ color: theme.palette.primary.main,
+ '&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
+ }}
+ >
+
+
+
+ );
+};
+
+/* قسم المنتجات */
+const PopularProductSection = ({ products = [], onProductClick }) => {
+ const theme = useTheme();
+ const [currentPage, setCurrentPage] = useState(1);
+
+ const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
+ const startIdx = (currentPage - 1) * ITEMS_PER_PAGE;
+ const visibleProducts = products.slice(startIdx, startIdx + ITEMS_PER_PAGE);
+
+ return (
+
+ {/* الهيدر */}
+
+ All Products
+
+
+ {/* المنتجات */}
+
+ {visibleProducts.length > 0 ? (
+ visibleProducts.map((product, index) => (
+
+ onProductClick && onProductClick(product)}
+ />
+
+ ))
+ ) : (
+ There is no Product
+ )}
+
+
+ {/* الباجينيشن */}
+ {totalPages > 1 && (
+
+
+ Showing {startIdx + 1} - {Math.min(startIdx + ITEMS_PER_PAGE, products.length)} of {products.length}
+
+
+
+
+ )}
+
+ );
+};
+
+export default PopularProductSection;
diff --git a/src/components/Home/Supplier/contect/ProductCard.js b/src/components/Home/Supplier/contect/ProductCard.js
new file mode 100644
index 0000000..5a1cada
--- /dev/null
+++ b/src/components/Home/Supplier/contect/ProductCard.js
@@ -0,0 +1,117 @@
+import React from 'react';
+import { Box, Typography, Card, CardContent, CardMedia, IconButton } from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+
+const ProductCard = ({ name, price, discountedPrice, unit, image, onClick, onAdd }) => {
+ return (
+
+ {/* صورة المنتج */}
+
+
+ {/* تفاصيل المنتج */}
+
+
+ {name}
+
+
+
+ {discountedPrice ? (
+ <>
+
+ ${price}
+
+
+ ${discountedPrice}{' '}
+
+ {unit}
+
+
+ >
+ ) : (
+
+ ${price}{' '}
+
+ {unit}
+
+
+ )}
+
+ {/* زر الإضافة */}
+ {
+ e.stopPropagation(); // يمنع فتح التفاصيل عند الضغط على الزر
+ if (onAdd) onAdd();
+ }}
+ sx={{
+ backgroundColor: '#FF8551',
+ color: '#fff',
+ ml: 3,
+ width: 28,
+ height: 28,
+ '&:hover': { backgroundColor: '#ff7043' },
+ }}
+ >
+
+
+
+
+
+ );
+};
+
+export default ProductCard;
diff --git a/src/components/Home/Supplier/contect/ProductDetail.js b/src/components/Home/Supplier/contect/ProductDetail.js
new file mode 100644
index 0000000..318a155
--- /dev/null
+++ b/src/components/Home/Supplier/contect/ProductDetail.js
@@ -0,0 +1,94 @@
+import React, { useState, useContext, useEffect } from "react";
+import { Box, Typography, Button, IconButton, LinearProgress, useTheme } from "@mui/material";
+import RemoveIcon from "@mui/icons-material/Remove";
+import AddIcon from "@mui/icons-material/Add";
+import ShoppingCartOutlinedIcon from "@mui/icons-material/ShoppingCartOutlined";
+import { CartContext } from "../../../../contexts/CartContextR";
+// import { useRestaurant } from '../../../contexts/RestaurantContext';
+import api from '../../../../services/authService';
+
+const ProductDetail = ({ product, onBack }) => {
+ const theme = useTheme();
+ const { addToCart , createNewCart } = useContext(CartContext);
+ const [quantity, setQuantity] = useState(1);
+ const [totalPrice, setTotalPrice] = useState(Number(product?.price) || 0);
+
+ useEffect(() => {
+ if (product) {
+ setTotalPrice(Number(product.price) * quantity);
+ }
+ }, [quantity, product]);
+
+ if (!product) return null;
+
+ const handleIncrease = () => quantity < 9 && setQuantity(q => q + 1);
+ const handleDecrease = () => quantity > 1 && setQuantity(q => q - 1);
+
+ const handleAddToCart = () => {
+ addToCart(product, quantity);
+ createNewCart();
+ alert(`${product.name} added to cart! Quantity: ${quantity}, Total: $${totalPrice.toFixed(2)}`);
+ };
+
+ return (
+
+
+
+
+ {product.name}
+ {product.category}
+
+ ${Number(product.price).toFixed(2)} {product.unit}
+
+
+ {/* 20 Item left */}
+
+
+ Quantity
+
+
+ {quantity}
+
+
+ Maximum purchase 9
+
+
+ Total: ${totalPrice.toFixed(2)}
+
+
+
+ }
+ sx={{ borderColor: theme.palette.primary.main, color: theme.palette.primary.main, textTransform: "none" }}
+ onClick={handleAddToCart}
+ >
+ Add To Cart
+
+
+ Back
+
+
+
+
+ );
+};
+
+export default ProductDetail;
diff --git a/src/components/Home/Supplier/contect/TopItems.js b/src/components/Home/Supplier/contect/TopItems.js
new file mode 100644
index 0000000..5d06d3e
--- /dev/null
+++ b/src/components/Home/Supplier/contect/TopItems.js
@@ -0,0 +1,140 @@
+import React from 'react';
+import {
+ Box,
+ Typography,
+ Card,
+ CardContent,
+ CardMedia,
+ IconButton,
+ Grid,
+ useTheme,
+ useMediaQuery,
+} from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+
+const ProductCard = ({ name, price, unit, image }) => {
+ return (
+
+
+
+
+ {name}
+
+
+
+ ${price}{' '}
+
+ {unit}
+
+
+
+
+
+
+
+
+ );
+};
+
+const TopItems = ({ item, showAll, onToggleShowAll }) => {
+ const theme = useTheme();
+ const isXs = useMediaQuery(theme.breakpoints.down('sm'));
+
+ const visibleProducts = showAll ? item : item.slice(0, 6);
+
+ const maxHeight = showAll ? 'auto' : isXs ? '340px' : '335px';
+ const maxWidth = showAll ? { xs: '90%', sm: '100%' } : undefined;
+
+ return (
+
+
+
+ Top Items
+
+
+ {showAll ? 'Show Less' : 'See All'}
+
+
+
+
+ {visibleProducts.map((product, index) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+export default TopItems;
diff --git a/src/components/Home/Training/NoTraining.js b/src/components/Home/Training/NoTraining.js
deleted file mode 100644
index c02c2d9..0000000
--- a/src/components/Home/Training/NoTraining.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react';
-import {
- Box,
- Button,
- Card,
- CardContent,
- Typography,
- useMediaQuery
-} from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-
-
-const NoTraining = () => {
- const theme = useTheme();
- const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
-
- return (
- <>
- {/* Header Section */}
-
-
- Dashboard
-
-
-
- {/* Empty State Content */}
-
-
- {/* Image Placeholder */}
-
-
- {/* Title with colored "Sorry!" */}
-
- Sorry!{' '}
- There are no updates now..
- {'\n'}check back later.
-
-
- {/* Action Button */}
-
-
-
- >
- );
-};
-
-export default NoTraining;
\ No newline at end of file
diff --git a/src/components/Home/Training/Training.js b/src/components/Home/Training/Training.js
deleted file mode 100644
index 1877735..0000000
--- a/src/components/Home/Training/Training.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Box, useTheme, useMediaQuery, Skeleton } from '@mui/material';
-import KitchPlusAppBar from '../AppBar';
-import Sidebar from '../SideHome';
-
-import NoTraining from './NoTraining';
-
-const drawerWidth = 230;
-
-const Dashboard = () => {
- const theme = useTheme();
- const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
- const [hasProducts, setHasProducts] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
-
- // محاكاة التحقق من المنتجات
- useEffect(() => {
- const checkProducts = async () => {
- setIsLoading(true);
- const productsExist = await checkIfProductsExist(); // استبدل بمنطقك
- setHasProducts(productsExist);
- setIsLoading(false);
- };
-
- checkProducts();
- }, []);
-
- const checkIfProductsExist = async () => {
- return new Promise((resolve) => setTimeout(() => resolve(true), 1500)); // محاكاة تأخير
- };
-
- 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 (
-
-
-
-
-
-
-
- {isLoading ? (
- <>
-
-
-
- >
- ) : (
-
- )}
-
-
-
- );
-};
-
-export default Dashboard;
diff --git a/src/components/Routes/PrivateRoute.js b/src/components/Routes/PrivateRoute.js
index b26d9b6..3e5c59d 100644
--- a/src/components/Routes/PrivateRoute.js
+++ b/src/components/Routes/PrivateRoute.js
@@ -5,7 +5,6 @@ const PrivateRoute = ({ children }) => {
const token = localStorage.getItem('token');
if (!token) {
- // لو مافي توكن، نعيد التوجيه للصفحة تسجيل الدخول
return ;
}
diff --git a/src/components/Routes/ProtectedRoutes.js b/src/components/Routes/ProtectedRoutes.js
new file mode 100644
index 0000000..863f0a6
--- /dev/null
+++ b/src/components/Routes/ProtectedRoutes.js
@@ -0,0 +1,46 @@
+
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import PrivateRoute from './PrivateRoute';
+
+import Dashboard from '../Home/Dashboard/Dashboard';
+import Inventory from '../Home/Inventory/Inventory';
+import Analytics from '../Home/Analytics&Reporting/Analytics';
+import Supplier from '../Home/Supplier/Supplier';
+import Cashier from '../Home/Cashier/Cashier';
+import Cart from '../Home/Cart/Cart';
+import Order from '../Home/Order/Order';
+// import Training from '../Home/Training/Training';
+import RestaurantProfile from '../Home/RestaurantProfile/all';
+import Employ from '../Home/Employ/Employ';
+import Settings from '../Home/Settings/Setting';
+import RestaurantSelection from '../Home/ResList/RestaurantProfile';
+import Meal from '../Home/Meal/Meal';
+import CreateYourRestaurant from '../Home/CreateYourRestaurant/CreateRestaurant';
+import CreateNewRestaurant from '../Home/CreateNewRestaurant/CreateRestaurant';
+import { CartProvider } from "../../contexts/CartContextR";
+
+const ProtectedRoutes = () => {
+ return (
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ );
+};
+
+export default ProtectedRoutes;
diff --git a/src/components/Routes/PublicRoute.js b/src/components/Routes/PublicRoute.js
index 1030091..ecba7f1 100644
--- a/src/components/Routes/PublicRoute.js
+++ b/src/components/Routes/PublicRoute.js
@@ -2,11 +2,10 @@ import React from 'react';
import { Navigate } from 'react-router-dom';
const PublicRoute = ({ children }) => {
- const token = localStorage.getItem('token');
+ const token = localStorage.getItem('token');
if (token) {
- // لو فيه توكن، نعيد التوجيه للداشبورد
- return ;
+ return ;
}
return children;
diff --git a/src/components/common/CustomSnackbar.js b/src/components/common/CustomSnackbar.js
new file mode 100644
index 0000000..4f7d96d
--- /dev/null
+++ b/src/components/common/CustomSnackbar.js
@@ -0,0 +1,32 @@
+import React from "react";
+import { Snackbar, Alert } from "@mui/material";
+
+const CustomSnackbar = ({ open, message, severity, onClose }) => {
+ return (
+
+
+ {message}
+
+
+ );
+};
+
+export default CustomSnackbar;
diff --git a/src/contexts/CartContextR.js b/src/contexts/CartContextR.js
new file mode 100644
index 0000000..65b3752
--- /dev/null
+++ b/src/contexts/CartContextR.js
@@ -0,0 +1,113 @@
+import React, { createContext, useState, useEffect, useContext } from "react";
+import axios from "axios";
+import { useRestaurant } from "./RestaurantContext";
+
+export const CartContext = createContext();
+
+const API_BASE_URL = "http://127.0.0.1:8000/api";
+
+export const CartProvider = ({ children }) => {
+ const [cart, setCart] = useState([]);
+ const { restaurantId } = useRestaurant();
+
+ useEffect(() => {
+ const storedCart = localStorage.getItem("cart");
+ if (storedCart) setCart(JSON.parse(storedCart));
+ }, []);
+
+ useEffect(() => {
+ localStorage.setItem("cart", JSON.stringify(cart));
+ }, [cart]);
+
+ const addToCart = (product, quantity = 1) => {
+ setCart(prev => {
+ const existingIdx = prev.findIndex(item => item.id === product.id);
+ if (existingIdx >= 0) {
+ const updated = [...prev];
+ updated[existingIdx].quantity += quantity;
+ updated[existingIdx].totalPrice = updated[existingIdx].quantity * product.price;
+ return updated;
+ } else {
+ return [
+ ...prev,
+ {
+ id: product.id,
+ name: product.name,
+ unit: product.unit,
+ price: product.price,
+ quantity: quantity,
+ totalPrice: product.price * quantity
+ }
+ ];
+ }
+ });
+ };
+
+ const clearCart = () => {
+ setCart([]);
+ localStorage.removeItem("cart");
+ };
+
+ const createNewCart = async () => {
+ if (cart.length === 0) {
+ alert("Cart is empty!");
+ return;
+ }
+
+ if (!restaurantId) {
+ alert("Restaurant ID not found!");
+ return;
+ }
+
+ console.log("restaurantId:", restaurantId);
+
+ const payload = {
+ data: {
+ type: "cart",
+ attributes: {
+ totalPrice: cart.reduce((sum, item) => sum + item.totalPrice, 0)
+ },
+ relationships: {
+ restaurant: {
+ data: { id: restaurantId } // ✅ استخدم المتغير مش رقم ثابت
+ },
+ cartItems: cart.map(item => ({
+ attributes: { quantity: item.quantity },
+ relationships: { product: { data: { id: item.id } } }
+ }))
+ }
+ }
+ };
+
+
+ // try {
+ const token = localStorage.getItem("token");
+ if (!token) throw new Error("No token found");
+
+ const response = await axios.post(`${API_BASE_URL}/Cart_Supplier/carts`, payload, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json"
+ }
+ });
+
+ if (response.data?.data) {
+ alert("Cart sent to server successfully!");
+ console.log(response.data?.data);
+ clearCart();
+ } else {
+ alert("Failed to create cart on server");
+ }
+ // } catch (error) {
+ // console.error("Error creating cart:", error);
+ // alert(error.response?.data?.message || error.message || "Network error");
+ // }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/src/contexts/RestaurantContext.js b/src/contexts/RestaurantContext.js
new file mode 100644
index 0000000..94eb5ac
--- /dev/null
+++ b/src/contexts/RestaurantContext.js
@@ -0,0 +1,26 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+// إنشاء السياق
+const RestaurantContext = createContext();
+
+export const useRestaurant = () => useContext(RestaurantContext);
+
+export const RestaurantProvider = ({ children }) => {
+ const [restaurantId, setRestaurantId] = useState(() => {
+ // قراءة القيمة من localStorage عند أول تحميل
+ const storedId = localStorage.getItem('restaurantId');
+ return storedId ? Number(storedId) : null;
+ });
+
+ // تحديث localStorage عند تغيير restaurantId
+ const saveRestaurantId = (id) => {
+ setRestaurantId(id);
+ localStorage.setItem('restaurantId', id);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/contexts/SnackbarContext.js b/src/contexts/SnackbarContext.js
new file mode 100644
index 0000000..f5f2ed0
--- /dev/null
+++ b/src/contexts/SnackbarContext.js
@@ -0,0 +1,35 @@
+// src/context/SnackbarContext.js
+import React, { createContext, useContext, useState } from "react";
+import CustomSnackbar from "../components/common/CustomSnackbar";
+
+const SnackbarContext = createContext();
+
+export const SnackbarProvider = ({ children }) => {
+ const [snackbar, setSnackbar] = useState({
+ open: false,
+ message: "",
+ severity: "info",
+ });
+
+ const showSnackbar = (message, severity = "info") => {
+ setSnackbar({ open: true, message, severity });
+ };
+
+ const closeSnackbar = () => {
+ setSnackbar((prev) => ({ ...prev, open: false }));
+ };
+
+ return (
+
+ {children}
+
+
+ );
+};
+
+export const useSnackbar = () => useContext(SnackbarContext);
diff --git a/src/contexts/UserContext.js b/src/contexts/UserContext.js
new file mode 100644
index 0000000..348e085
--- /dev/null
+++ b/src/contexts/UserContext.js
@@ -0,0 +1,23 @@
+// src/contexts/UserContext.jsx
+import React, { createContext, useState, useEffect } from 'react';
+
+export const UserContext = createContext();
+
+export const UserProvider = ({ children }) => {
+ const [user, setUser] = useState({
+ email: localStorage.getItem('user_email') || '',
+ adminData: JSON.parse(localStorage.getItem('adminData')) || null,
+ });
+
+ useEffect(() => {
+ // تحديث localStorage عند تغير user
+ if (user.email) localStorage.setItem('user_email', user.email);
+ if (user.adminData) localStorage.setItem('adminData', JSON.stringify(user.adminData));
+ }, [user]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/index.js b/src/index.js
index f037b25..b2b3ef3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,10 +2,12 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+import { GoogleOAuthProvider } from '@react-oauth/google';
+
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
+
-
+
);
diff --git a/src/services/authService.js b/src/services/authService.js
index a8d40a5..6fda20d 100644
--- a/src/services/authService.js
+++ b/src/services/authService.js
@@ -1,47 +1,161 @@
import axios from 'axios';
-const API_BASE_URL = 'http://127.0.0.1:8000/api';
-
+const API_BASE_URL = 'http://192.168.196.165:8000/api';
+const BASE_URL = process.env.REACT_APP_BASE_URL || 'http://192.168.196.165:8000';
+// const API_BASE_URL = 'https://kitchplus.velocrea.com/api';
+// const BASE_URL = 'https://kitchplus.velocrea.com/';
const authService = {
-
- register: async (email, password, confirmPassword) => {
+ // =======================
+ // تسجيل المسؤول
+ // =======================
+ register: async (email, password, password_confirmation) => {
try {
const response = await axios.post(`${API_BASE_URL}/Admin/register`, {
email,
password,
- password_confirmation: confirmPassword
+ password_confirmation,
}, {
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- }
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
});
+ if (response.data?.success && response.data.data?.Admin) {
+ localStorage.setItem('user_email', email);
+ localStorage.setItem('token', response.data.data.token);
+ localStorage.setItem('refresh_token', response.data.data.refresh_token);
+ localStorage.setItem('adminData', JSON.stringify(response.data.data.Admin));
+ }
+
return response.data;
} catch (error) {
- // في حال كان الرد يحتوي على رسائل خطأ من الباك
- if (error.response) {
- return {
- success: false,
- status: error.response.status,
- message: error.response.data.message || 'Registration failed',
- errors: error.response.data.errors || null
- };
- }
-
- // في حال كانت المشكلة من الشبكة أو من axios نفسه
- return {
+ return error.response ? {
success: false,
- message: 'Network error. Please try again.'
- };
+ status: error.response.status,
+ message: error.response.data.message || 'Registration failed',
+ errors: error.response.data.errors || null
+ } : { success: false, message: 'Network error. Please try again.' };
}
},
+ // =======================
+ // تسجيل الدخول
+ // =======================
login: async (email, password) => {
try {
- const response = await axios.post(`${API_BASE_URL}/Admin/login`, {
+ const response = await axios.post(`${API_BASE_URL}/Admin/login`, { email, password }, {
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
+ });
+
+ if (response.data?.success && response.data.data?.Admin) {
+ localStorage.setItem('user_email', email);
+ localStorage.setItem('token', response.data.data.token);
+ localStorage.setItem('refresh_token', response.data.data.refresh_token);
+ localStorage.setItem('adminData', JSON.stringify(response.data.data.Admin));
+ }
+
+ return response.data;
+ } catch (error) {
+ return error.response ? {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || 'Login failed',
+ errors: error.response.data.errors || null
+ } : { success: false, message: 'Network error. Please try again.' };
+ }
+ },
+
+ // =======================
+ // تسجيل الخروج
+ // =======================
+ logout: () => {
+ localStorage.removeItem('token');
+ localStorage.removeItem('refresh_token');
+ localStorage.removeItem('user_email');
+ localStorage.removeItem('adminData');
+ return { success: true, message: 'Logged out successfully' };
+ },
+
+ // =======================
+ // التحقق من وجود توكن
+ // =======================
+ isTokenValid: () => !!localStorage.getItem('token'),
+
+ // =======================
+ // استرجاع بيانات المسؤول
+ // =======================
+ getAdminData: () => {
+ const adminData = JSON.parse(localStorage.getItem('adminData'));
+ return adminData || null;
+ },
+
+ // =======================
+ // استرجاع ID المسؤول
+ // =======================
+ getAdminId: () => {
+ const adminData = JSON.parse(localStorage.getItem('adminData'));
+ return adminData?.id || null;
+ },
+
+ // =======================
+ // استرجاع التوكن
+ // =======================
+ getToken: () => localStorage.getItem('token') || null,
+ getRefreshToken: () => localStorage.getItem('refresh_token') || null,
+
+ resetPassword: async (email) => {
+ try {
+ const response = await axios.post(`${API_BASE_URL}/Admin/reset_password`, { email });
+ return response.data;
+ } catch (error) {
+ return error.response
+ ? {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || 'Reset request failed',
+ }
+ : { success: false, message: 'Network error. Please try again.' };
+ }
+ },
+
+ verifyCode: async (email, code) => {
+ try {
+ const response = await axios.post(`${API_BASE_URL}/Admin/verify`, { email, code });
+ return response.data;
+ } catch (error) {
+ return error.response
+ ? {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || 'Verification failed',
+ }
+ : { success: false, message: 'Network error. Please try again.' };
+ }
+ },
+
+ updatePassword: async ({ email, password, confirmPassword }) => {
+ try {
+ const response = await axios.post(`${API_BASE_URL}/Admin/updatePassword`, {
email,
- password
+ password,
+ password_confirmation: confirmPassword
+ });
+
+ return response.data;
+ } catch (error) {
+ return error.response
+ ? {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || 'Update failed',
+ errors: error.response.data.errors || null
+ }
+ : { success: false, message: 'Network error. Please try again.' };
+ }
+ },
+
+ googleLogin: async (googleAccessToken) => {
+ try {
+ const response = await axios.post(`${API_BASE_URL}/Admin/google-login`, {
+ token: googleAccessToken,
}, {
headers: {
'Content-Type': 'application/json',
@@ -55,47 +169,1822 @@ const authService = {
return {
success: false,
status: error.response.status,
- message: error.response.data.message || 'Login failed',
- errors: error.response.data.errors || null
+ message: error.response.data.message || 'Google login failed',
};
}
-
return {
success: false,
message: 'Network error. Please try again.'
};
}
},
- logout: async () => {
+
+ createNewRestaurant: async (restaurantData) => {
try {
const token = localStorage.getItem('token');
if (!token) {
return { success: false, message: 'No token found' };
}
- const response = await axios.post(`${API_BASE_URL}/Admin/logout`, {}, {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Accept': 'application/json'
- }
- });
+ const hasFile = restaurantData.logo instanceof File;
+
+ if (hasFile) {
+ const fd = new FormData();
+ for (const key in restaurantData) {
+ const value = restaurantData[key];
+ if (value === null || value === undefined) continue;
+
+ if (key === 'logo') {
+ fd.append('logo', value);
+ continue;
+ }
+
+ if (typeof value === 'boolean') {
+ // أرسل 1/0 أو "true"/"false" حسب ما يقبله الـ backend. Laravel يقبل "true"/"false" و "1"/"0".
+ fd.append(key, value ? '1' : '0');
+ } else if (typeof value === 'object') {
+ // إذا يحتوي على مصفوفة أو كائن — حوله لـ JSON
+ fd.append(key, JSON.stringify(value));
+ } else {
+ fd.append(key, String(value));
+ }
+ }
+
+ // خيار: لعرض ما سترسله (debug)
+ // for (let pair of fd.entries()) console.log(pair[0], pair[1]);
+
+ const response = await axios.post(`${API_BASE_URL}/restaurants`, fd, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ // 'Content-Type': 'multipart/form-data'
+ }
+ });
+
+ return response.data;
+ } else {
+ // لا يوجد ملف → أرسل JSON طبيعي (booleans تبقى boolean)
+ const response = await axios.post(`${API_BASE_URL}/restaurants`, restaurantData, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ });
+ return response.data;
+ }
- return response.data;
} catch (error) {
if (error.response) {
return {
success: false,
status: error.response.status,
- message: error.response.data.message || 'Logout failed',
+ message: error.response.data.message || 'Creation failed',
+ errors: error.response.data.errors || null
+ };
+ }
+ return { success: false, message: 'Network error. Please try again.' };
+ }
+ },
+
+ cuisineTypes: async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { success: false, message: 'No token found' };
+ }
+
+ const response = await axios.get(`${API_BASE_URL}/restaurants/getCusineTypes`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ });
+
+ const cuisines = response?.data?.data?.data || [];
+ return cuisines;
+ } catch (error) {
+ console.error('Error fetching cuisine types:', error);
+ return [];
+ }
+ },
+
+ registerWaiter: async (waiterData) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { success: false, message: 'No token found' };
+ }
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Admin/registerWaiter`,
+ waiterData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ return response.data;
+
+ } catch (error) {
+ if (error.response) {
+ // تفاصيل الأخطاء إن وجدت
+ const detailedErrors = error.response.data.errors || null;
+ const message = error.response.data.message || 'Failed to register waiter';
+
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ errors: detailedErrors,
};
}
return {
success: false,
- message: 'Network error. Please try again.'
+ message: 'Network error. Please try again.',
};
}
},
+ registerCooker: async (cookerData) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { success: false, message: 'No token found' };
+ }
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Admin/registerCooker`,
+ cookerData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ return response.data;
+
+ } catch (error) {
+ if (error.response) {
+ const detailedErrors = error.response.data.errors || null;
+ const message = error.response.data.message || 'Failed to register cooker';
+
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ errors: detailedErrors,
+ };
+ }
+ return {
+ success: false,
+ message: 'Network error. Please try again.',
+ };
+ }
+ },
+ registerAccountant: async (accountantData) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { success: false, message: 'No token found' };
+ }
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Admin/registerAccountant`,
+ accountantData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ return response.data;
+
+ } catch (error) {
+ if (error.response) {
+ const detailedErrors = error.response.data.errors || null;
+ const message = error.response.data.message || 'Failed to register accountant';
+
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ errors: detailedErrors,
+ };
+ }
+ return {
+ success: false,
+ message: 'Network error. Please try again.',
+ };
+ }
+ },
+
+ getAllWaiters: async (restaurantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_all_waiters/${restaurantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ /*
+ مثال الاستجابة:
+ {
+ "waiters": [
+ { "id": 1, "name": "hussain", "email": "gjhu@gmail.com", "shift_type": "morning", "active": 0 },
+ { "id": 2, "name": "hussain", "email": "gjhu11@gmail.com", "shift_type": "morning", "active": 0 }
+ ]
+ }
+ */
+
+ } catch (error) {
+ if (error.response) {
+ const message =
+ error.response.data.message || "Failed to fetch waiters";
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ };
+ }
+ return {
+ success: false,
+ message: "Network error. Please try again.",
+ };
+ }
+ },
+
+ deleteWaiter: async (waiterId) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { message: 'No token found' };
+ }
+
+ const response = await axios.delete(
+ `${API_BASE_URL}/admin/delete_waiter/${waiterId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ // هنا نرجع الرسالة مباشرة مثل ما أرسلها السيرفر
+ return { message: response.data.message };
+
+ } catch (error) {
+ if (error.response) {
+ return {
+ message: error.response.data.message || 'Failed to delete waiter',
+ status: error.response.status,
+ };
+ }
+ return {
+ message: 'Network error. Please try again.',
+ };
+ }
+ },
+
+ getWaiterById: async (waiterId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_waiter/${waiterId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data; // هترجع { waiter: { ... } }
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Failed to fetch waiter",
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+ },
+ // ✅ إحضار كل الطباخين لمطعم محدد
+ getAllCookers: async (restaurantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_all_cookers/${restaurantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ /*
+ مثال استجابة متوقعة:
+ {
+ "cookers": [
+ { "id": 1, "name": "Ali", "email": "ali@gmail.com", "shift_type": "evening", "active": 1 },
+ { "id": 2, "name": "Sara", "email": "sara@gmail.com", "shift_type": "morning", "active": 0 }
+ ]
+ }
+ */
+
+ } catch (error) {
+ if (error.response) {
+ const message =
+ error.response.data.message || "Failed to fetch cookers";
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ };
+ }
+ return {
+ success: false,
+ message: "Network error. Please try again.",
+ };
+ }
+ },
+
+ // ✅ حذف طباخ
+ deleteCooker: async (cookerId) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { message: 'No token found' };
+ }
+
+ const response = await axios.delete(
+ `${API_BASE_URL}/admin/delete_cooker/${cookerId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ // نرجع الرسالة مثل ما يرسلها السيرفر
+ return { message: response.data.message };
+
+ } catch (error) {
+ if (error.response) {
+ return {
+ message: error.response.data.message || 'Failed to delete cooker',
+ status: error.response.status,
+ };
+ }
+ return {
+ message: 'Network error. Please try again.',
+ };
+ }
+ },
+
+ // ✅ إحضار تفاصيل طباخ محدد
+ getCookerById: async (cookerId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_cooker/${cookerId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data; // { cooker: { ... } }
+
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Failed to fetch cooker",
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+ },
+
+ // ✅ إحضار كل المحاسبين لمطعم محدد
+ getAllAccountants: async (restaurantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_all_accountants/${restaurantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ /*
+ مثال استجابة متوقعة:
+ {
+ "accountants": [
+ { "id": 1, "name": "Ahmed", "email": "ahmed@gmail.com", "active": 1 },
+ { "id": 2, "name": "Lina", "email": "lina@gmail.com", "active": 0 }
+ ]
+ }
+ */
+
+ } catch (error) {
+ if (error.response) {
+ const message =
+ error.response.data.message || "Failed to fetch accountants";
+ return {
+ success: false,
+ status: error.response.status,
+ message: message,
+ };
+ }
+ return {
+ success: false,
+ message: "Network error. Please try again.",
+ };
+ }
+ },
+
+ // ✅ حذف محاسب
+ deleteAccountant: async (accountantId) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { message: 'No token found' };
+ }
+
+ const response = await axios.delete(
+ `${API_BASE_URL}/admin/delete_accountant/${accountantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ }
+ );
+
+ return { message: response.data.message };
+
+ } catch (error) {
+ if (error.response) {
+ return {
+ message: error.response.data.message || 'Failed to delete accountant',
+ status: error.response.status,
+ };
+ }
+ return {
+ message: 'Network error. Please try again.',
+ };
+ }
+ },
+
+ // ✅ إحضار تفاصيل محاسب محدد
+ getAccountantById: async (accountantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/show_accountant/${accountantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data; // { accountant: { ... } }
+
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Failed to fetch accountant",
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+ },
+
+ toggleAccountStatus: async (email, model) => {
+ try {
+ const token = localStorage.getItem('token'); // جلب التوكن
+ if (!token) {
+ throw new Error("No token found. Please log in.");
+ }
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Admin/active`,
+ { email, model },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error("Toggle account error:", error);
+ throw error;
+ }
+ },
+
+
+ // =====================
+ // دالة لتحويل مسار الصور
+ // =====================
+processImages: (items, imagePathKey = 'image', defaultImage = '/images/default.png') => {
+ return items.map(item => {
+ let imageUrl = defaultImage;
+
+ // دعم المسارات المتداخلة مثل visual_identity.logo_url
+ const pathParts = imagePathKey.split('.');
+ let value = item;
+ for (let key of pathParts) {
+ if (value && value[key] !== undefined && value[key] !== null) {
+ value = value[key];
+ } else {
+ value = null;
+ break;
+ }
+ }
+
+ if (value) {
+ // إذا القيمة تبدأ بـ "http" اعتبرها رابط كامل ولا تضف BASE_URL
+ if (value.startsWith('http://') || value.startsWith('https://')) {
+ imageUrl = value;
+ } else {
+ // أضف BASE_URL مع "storage" إذا لم يكن رابط كامل
+ imageUrl = `${BASE_URL}/storage/${value}`;
+ }
+ }
+
+ return {
+ ...item,
+ image_url: imageUrl
+ };
+ });
+},
+
+ getCategories: async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) return { success: false, message: 'No token found' };
+
+ const response = await axios.get(`${API_BASE_URL}/CategoryS/read_CS`, {
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
+ });
+
+ if (response.data?.data) {
+ const categories = authService.processImages(response.data.data, 'image');
+ return { success: true, data: categories };
+ }
+
+ return { success: false, message: 'No data found' };
+ } catch (error) {
+ return {
+ success: false,
+ message: error.response?.data?.message || 'Network error. Please try again.'
+ };
+ }
+ },
+
+ getCountries: async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return { success: false, message: 'No token found' };
+ }
+
+ const response = await axios.get(`${API_BASE_URL}/restaurants/getCountries`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ });
+
+ if (response.data.status) {
+ // لاحظ: البيانات داخل data.data
+ const countries = response.data.data.data.map((country) => ({
+ id: country.id,
+ name: country.name,
+ }));
+ return { success: true, data: countries };
+ }
+
+ return { success: false, message: 'Failed to fetch countries' };
+ } catch (error) {
+ return {
+ success: false,
+ message: error.response?.data?.message || 'Network error. Please try again.',
+ };
+ }
+ },
+
+ isTokenValid: () => {
+ return !!localStorage.getItem('token');
+ },
+
+ // createCart: async (cartData) => {
+ // if (!authService.isTokenValid()) {
+ // return { success: false, message: 'Invalid token. Please login again.' };
+ // }
+
+ // try {
+ // const token = localStorage.getItem('token');
+ // const response = await axios.post(`${API_BASE_URL}/Cart_Supplier/carts`, cartData, {
+ // headers: {
+ // Authorization: `Bearer ${token}`,
+ // Accept: 'application/json',
+ // 'Content-Type': 'application/json',
+ // },
+ // });
+
+ // if (response.data) {
+ // return { success: true, data: response.data.data };
+ // }
+
+ // return { success: false, message: 'Failed to create cart' };
+ // } catch (error) {
+ // console.error('Full error response from server:', error.response?.data);
+ // return {
+ // success: false,
+ // status: error.response?.status || null,
+ // message: error.response?.data?.message || 'Unknown error',
+ // errors: error.response?.data?.errors || null
+ // };
+ // }
+ // },
+
+
+ getCart: async () => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.get(`${API_BASE_URL}/Cart_Supplier/carts`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ });
+
+ if (response.data && response.data.data) {
+ return { success: true, data: response.data.data };
+ }
+
+ return { success: false, message: "No cart data found" };
+ } catch (error) {
+ console.error("Error fetching cart:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ };
+ }
+ },
+
+ getCartById: async (cartId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.get(`${API_BASE_URL}/Cart_Supplier/carts/${cartId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ });
+
+ if (response.data && response.data.data) {
+ return { success: true, data: response.data.data };
+ }
+
+ return { success: false, message: "No cart data found" };
+ } catch (error) {
+ console.error("Error fetching cart by ID:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ };
+ }
+ },
+
+ updateCart: async (cartId, cartData) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) return { success: false, message: 'No token found' };
+
+ const response = await axios.put(`${API_BASE_URL}/Cart_Supplier/carts/${cartId}`, cartData, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (response.data && response.data.data) {
+ return { success: true, data: response.data.data };
+ }
+
+ return { success: false, message: 'Failed to update cart' };
+
+ } catch (error) {
+ console.error('Error updating cart:', error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || 'Network error. Please try again.',
+ errors: error.response?.data?.errors || null,
+ };
+ }
+ },
+
+ deleteCart: async (cartId) => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) return { success: false, message: 'No token found' };
+
+ const response = await axios.delete(`${API_BASE_URL}/Cart_Supplier/carts/${cartId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ },
+ });
+
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting cart:', error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || 'Network error. Please try again.',
+ };
+ }
+ },
+
+ addCategory: async (categoryData) => {
+ try {
+ const token = localStorage.getItem("token");
+ const response = await axios.post(
+ `${API_BASE_URL}/admin/add_category`,
+ categoryData,
+ {
+ headers: {
+ Authorization: token ? `Bearer ${token}` : "",
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error("❌ Error adding category:", error);
+ throw error.response?.data || { message: "Something went wrong" };
+ }
+ },
+
+ updateCategory: async (categoryId, categoryData) => {
+ try {
+ const token = localStorage.getItem("token");
+ const response = await axios.put(
+ `${API_BASE_URL}/admin/update_category/${categoryId}`,
+ categoryData,
+ {
+ headers: {
+ Authorization: token ? `Bearer ${token}` : "",
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ return response.data; // { message: "category updated successfully", category: {...} }
+ } catch (error) {
+ console.error("❌ Error updating category:", error);
+ throw error.response?.data || { message: "Something went wrong" };
+ }
+ },
+
+ deleteCategory: async (categoryId) => {
+ try {
+ const token = localStorage.getItem("token");
+ const response = await axios.delete(
+ `${API_BASE_URL}/admin/delete_category/${categoryId}`,
+ {
+ headers: {
+ Authorization: token ? `Bearer ${token}` : "",
+ Accept: "application/json",
+ },
+ }
+ );
+ return response.data; // { message: "category deleted successfully" }
+ } catch (error) {
+ console.error("❌ Error deleting category:", error);
+ throw error.response?.data || { message: "Something went wrong" };
+ }
+ },
+
+ getCategoriesByRestaurant: async (restaurantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/all_categories/${restaurantId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ if (response.data?.categories) {
+ return { success: true, data: response.data.categories };
+ }
+
+ return { success: false, message: "No categories found" };
+ } catch (error) {
+ console.error("❌ Error fetching categories:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ };
+ }
+ },
+
+ getMealsByCategory: async (restaurantId, categoryId, page = 1) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [], pagination: null };
+
+ // ✅ شيل restaurantId من الـ URL
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/category_meals/${categoryId}?page=${page}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const mealsData = response.data?.data?.category?.meals?.data || [];
+ const meta = response.data?.data?.category?.meals?.meta || {};
+
+ const meals = mealsData.map(meal => ({
+ id: meal.id,
+ category_id: meal.category_id,
+ restaurant_id: meal.restaurant_id,
+ name: meal.name,
+ description: meal.description || [],
+ additions: meal.additions || [],
+ price: meal.price,
+ photo: meal.photo || "/images/default-product.png",
+ created_at: meal.created_at,
+ updated_at: meal.updated_at,
+ }));
+
+ return {
+ success: true,
+ data: meals,
+ pagination: {
+ current_page: meta.current_page || 1,
+ per_page: meta.per_page || meals.length,
+ total: meta.total || meals.length,
+ last_page: meta.last_page || 1,
+ },
+ };
+ } catch (error) {
+ console.error("❌ Error fetching category meals:", error);
+ return { success: false, message: "Failed to fetch meals", data: [], pagination: null };
+ }
+ },
+
+ getAllMealsByRestaurant: async (restaurantId, page = 1) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/all_meals/${restaurantId}?page=${page}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const mealsData = response.data.meals?.data || [];
+
+ // تحويل كل وجبة بحيث تحتوي على الحقل photo مباشرة
+ const meals = mealsData.map((meal) => ({
+ id: meal.id,
+ category_id: meal.category_id,
+ restaurant_id: meal.restaurant_id,
+ name: meal.name,
+ description: meal.description || [],
+ additions: meal.additions || [],
+ price: meal.price,
+ photo: meal.photo || "/images/default-product.png",
+ created_at: meal.created_at,
+ updated_at: meal.updated_at,
+ }));
+
+ return {
+ success: true,
+ data: meals,
+ pagination: {
+ current_page: response.data.meals.current_page,
+ per_page: response.data.meals.per_page,
+ total: response.data.meals.total,
+ last_page: response.data.meals.last_page,
+ next_page_url: response.data.meals.next_page_url,
+ prev_page_url: response.data.meals.prev_page_url,
+ },
+ };
+ } catch (error) {
+ console.error("❌ Error fetching meals:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message:
+ error.response?.data?.message || "Network error. Please try again.",
+ };
+ }
+ },
+
+ addMeal: async (mealData) => {
+ try {
+ const token = localStorage.getItem("token");
+ const formData = new FormData();
+
+ Object.keys(mealData).forEach(key => {
+ const value = mealData[key];
+ if (Array.isArray(value)) {
+ value.forEach((item, idx) => formData.append(`${key}[${idx}]`, item));
+ } else if (key === 'photo' && value) {
+ formData.append('photo', value);
+ } else if (value !== undefined && value !== null) {
+ formData.append(key, value);
+ }
+ });
+
+ const response = await axios.post(`${API_BASE_URL}/admin/add_meal`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: token ? `Bearer ${token}` : "",
+ Accept: "application/json",
+ },
+ });
+
+ // التأكد أن دائمًا هناك message
+ return {
+ message: response.data?.message || "Meal added successfully",
+ data: response.data?.data || null,
+ };
+ } catch (error) {
+ console.error("❌ Error adding meal:", error);
+ return {
+ message: error.response?.data?.message || "Something went wrong",
+ data: null,
+ };
+ }
+ }
+ ,
+
+ updateMeal: async (mealId, data) => {
+ const token = localStorage.getItem("token");
+ if (!token) throw { message: "No token found. Please login." };
+
+ const formData = new FormData();
+ formData.append("name", data.name);
+ formData.append("category_id", data.category_id);
+ formData.append("restaurant_id", data.restaurant_id);
+
+ // إضافة الوصف فقط مرة واحدة
+ (data.description || []).forEach((d) => formData.append('description[]', d));
+
+ // إضافة الإضافات
+ (data.additions || []).forEach((a) => formData.append('additions[]', a));
+
+ formData.append("price", data.price);
+
+ // إضافة الصور إذا كانت موجودة
+ (data.photo || []).forEach((file) => formData.append("photo", file));
+
+ const res = await axios.post(
+ `${API_BASE_URL}/admin/update_meal/${mealId}`,
+ formData,
+ {
+ headers: {
+ // لا تضف Content-Type هنا!
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+ return res.data;
+ },
+ deleteMeal: async (mealId, restaurantId, categoryId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) throw { message: "No token found. Please login." };
+
+ const formData = new FormData();
+ formData.append("_method", "DELETE"); // محاكاة طلب DELETE
+ formData.append("id", mealId);
+ formData.append("restaurant_id", restaurantId);
+ formData.append("category_id", categoryId);
+
+ const response = await axios.post(
+ `${API_BASE_URL}/admin/delete_meal/${mealId}`,
+ formData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (err) {
+ console.error("Error deleting meal:", err);
+ throw err.response?.data || { message: "Failed to delete meal." };
+ }
+ },
+
+
+ // 📌 جلب جميع الطاولات لمطعم معيّن
+ getTablesByRestaurant: async (restaurantId, page = 1) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [] };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/table/${restaurantId}?page=${page}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const tablesData = response.data?.data?.data || [];
+ const meta = response.data?.data || {};
+
+ return {
+ success: true,
+ data: tablesData,
+ pagination: {
+ current_page: meta.current_page || 1,
+ per_page: meta.per_page || tablesData.length,
+ total: meta.total || tablesData.length,
+ last_page: meta.last_page || 1,
+ },
+ };
+ } catch (error) {
+ console.error("❌ Error fetching tables:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message:
+ error.response?.data?.message || "Network error. Please try again.",
+ data: [],
+ pagination: null,
+ };
+ }
+ },
+ // 📌 إضافة طاولة جديدة
+ addTable: async (tableData) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.post(
+ `${API_BASE_URL}/table`,
+ tableData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return {
+ success: response.data?.success || false,
+ message: response.data?.message || "Table created successfully",
+ data: response.data?.data || null,
+ };
+ } catch (error) {
+ console.error("❌ Error adding table:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Something went wrong",
+ errors: error.response?.data?.errors || null,
+ };
+ }
+ },
+ deleteTable: async (tableId) => {
+ try {
+ const token = localStorage.getItem("token"); // إذا تستخدم توكن
+ const response = await axios.delete(`${API_BASE_URL}/table/${tableId}`, {
+ headers: {
+ Authorization: token ? `Bearer ${token}` : "",
+ "Content-Type": "application/json",
+ },
+ });
+ return { success: true, data: response.data };
+ } catch (error) {
+ console.error("❌ Error deleting table:", error);
+ return { success: false, message: error.response?.data || error.message };
+ }
+ },
+
+ createRestaurantProfile: async (restaurantId, profileData) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ return { success: false, message: "No token found" };
+ }
+
+ // نتأكد من وجود id
+ if (!restaurantId) {
+ return { success: false, message: "Restaurant ID is required" };
+ }
+
+ // هل يوجد ملف (مثلاً logo)؟
+ const hasFile = profileData.logo instanceof File;
+
+ if (hasFile) {
+ const fd = new FormData();
+ for (const key in profileData) {
+ const value = profileData[key];
+ if (value === null || value === undefined) continue;
+
+ if (key === "logo") {
+ fd.append("logo", value);
+ continue;
+ }
+
+ if (typeof value === "boolean") {
+ fd.append(key, value ? "1" : "0");
+ } else if (typeof value === "object") {
+ fd.append(key, JSON.stringify(value));
+ } else {
+ fd.append(key, String(value));
+ }
+ }
+
+ const response = await axios.post(
+ `${API_BASE_URL}/restaurants/${restaurantId}/profile`,
+ fd,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ // axios يضبط Content-Type أوتوماتيك مع FormData
+ },
+ }
+ );
+
+ return { success: true, data: response.data };
+ } else {
+ // لا يوجد ملف → إرسال JSON طبيعي
+ const response = await axios.post(
+ `${API_BASE_URL}/restaurants/${restaurantId}/profile`,
+ profileData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ return { success: true, data: response.data };
+ }
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Profile creation failed",
+ errors: error.response.data.errors || null,
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+ },
+// دالة لجلب بيانات ملف المطعم
+ getRestaurantProfile: async(restaurantId)=> {
+ try {
+ const response = await fetch( `${API_BASE_URL}/restaurants/${restaurantId}/profile`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ // إضافة Authorization هنا إذا كان مطلوبًا
+ // 'Authorization': 'Bearer YOUR_TOKEN'
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data; // ترجع البيانات كاملة كما في المثال
+ } catch (error) {
+ console.error("Error fetching restaurant profile:", error);
+ return null;
+ }
+},
+updateRestaurantProfile: async (restaurantId, profileData) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+ if (!restaurantId) return { success: false, message: "Restaurant ID is required" };
+
+ const hasFile = profileData.logo instanceof File;
+
+ if (hasFile) {
+ const fd = new FormData();
+ for (const key in profileData) {
+ const value = profileData[key];
+ if (value === null || value === undefined) continue;
+
+ if (key === "logo") {
+ fd.append("logo", value);
+ continue;
+ }
+
+ if (typeof value === "object") {
+ fd.append(key, JSON.stringify(value));
+ } else {
+ fd.append(key, String(value));
+ }
+ }
+
+ const response = await axios.put(
+ `${API_BASE_URL}/restaurants/${restaurantId}/profile`,
+ fd,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+ return { success: true, data: response.data };
+ } else {
+ const response = await axios.put(
+ `${API_BASE_URL}/restaurants/${restaurantId}/profile`,
+ profileData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ return { success: true, data: response.data };
+ }
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Profile update failed",
+ errors: error.response.data.errors || null,
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+},
+// 📌 جلب إحصائيات الحجوزات لمطعم معيّن حسب الفترة
+ getReservationStatistics: async (restaurantId, period = "daily", date = null) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [] };
+ if (!restaurantId) return { success: false, message: "Restaurant ID is required", data: [] };
+
+ // التاريخ الافتراضي: اليوم الحالي
+ const selectedDate = date || new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/reservation_statistics/${restaurantId}`,
+ {
+ params: { period, date: selectedDate }, // إرسال period و date كـ query params
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const statsData = response.data?.data || [];
+
+ return {
+ success: response.data?.success || false,
+ data: statsData,
+ };
+ } catch (error) {
+ console.error("❌ Error fetching reservation statistics:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ data: [],
+ };
+ }
+ },
+ getOrderStatistics: async (restaurantId, period = "daily", date = null) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [] };
+ if (!restaurantId) return { success: false, message: "Restaurant ID is required", data: [] };
+
+ // التاريخ الافتراضي: اليوم الحالي
+ const selectedDate = date || new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/order_statistics/${restaurantId}`,
+ {
+ params: { period, date: selectedDate }, // إرسال period و date كـ query params
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const statsData = response.data?.data || [];
+
+ return {
+ success: response.data?.success || false,
+ data: statsData,
+ };
+ } catch (error) {
+ console.error("❌ Error fetching reservation statistics:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ data: [],
+ };
+ }
+ },
+ getBillStatistics: async (restaurantId, period = "daily", date = null) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [] };
+ if (!restaurantId) return { success: false, message: "Restaurant ID is required", data: [] };
+
+ // التاريخ الافتراضي: اليوم الحالي
+ const selectedDate = date || new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/bill_statistics/${restaurantId}`,
+ {
+ params: { period, date: selectedDate }, // إرسال period و date كـ query params
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const statsData = response.data?.data || [];
+
+ return {
+ success: response.data?.success || false,
+ data: statsData,
+ };
+ } catch (error) {
+ console.error("❌ Error fetching reservation statistics:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ data: [],
+ };
+ }
+ },
+// 📌 جلب نسبة الإشغال (Occupancy Rate) لمطعم معيّن حسب الفترة
+getOccupancyRate: async (restaurantId, period = "daily", date = null) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: null };
+ if (!restaurantId) return { success: false, message: "Restaurant ID is required", data: null };
+
+ // التاريخ الافتراضي: اليوم الحالي
+ const selectedDate = date || new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const response = await axios.get(
+ `${API_BASE_URL}/admin/occupancy_rate/${restaurantId}`,
+ {
+ params: { period, date: selectedDate }, // period و date كـ query params
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const occupancyRate = response.data?.occupancy_rate || "0%";
+
+ return {
+ success: response.data?.success || false,
+ period: response.data?.period || period,
+ date: response.data?.date || selectedDate,
+ data: occupancyRate,
+ };
+ } catch (error) {
+ console.error("❌ Error fetching occupancy rate:", error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || "Network error. Please try again.",
+ data: null,
+ };
+ }
+},
+
+ getRestaurants: async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) return { success: false, message: 'No token found' };
+
+ const response = await axios.get(`${API_BASE_URL}/restaurants/allresturant`, {
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
+ });
+
+ if (response.data.success && response.data.data?.data) {
+ // استخدم الدالة الموحدة لتحويل الصور
+ const restaurants = authService.processImages(response.data.data.data, 'visual_identity.logo_url', '/images/default-restaurant.png');
+ return { success: true, data: restaurants };
+ }
+
+ return { success: false, message: 'No data found' };
+ } catch (error) {
+ console.error('Error fetching restaurants:', error);
+ return {
+ success: false,
+ status: error.response?.status || null,
+ message: error.response?.data?.message || 'Network error. Please try again.'
+ };
+ }
+ },
+
+
+getRestaurantById: async (restaurantId) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found" };
+
+ const response = await axios.get(`${API_BASE_URL}/restaurants/${restaurantId}`, {
+ headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
+ });
+
+ if (response.data.success && response.data.data) {
+ const restaurant = response.data.data;
+
+ // تأكد من تعريف BASE_URL في أعلى الملف
+ const processed = authService.processImages(
+ [restaurant],
+ 'visual_identity.logo_url',
+ '/images/default-restaurant.png'
+ );
+
+ return { success: true, data: processed[0] };
+ }
+
+ return { success: false, message: "No data found" };
+ } catch (error) {
+ if (error.response) {
+ return {
+ success: false,
+ status: error.response.status,
+ message: error.response.data.message || "Failed to fetch restaurant",
+ };
+ }
+ return { success: false, message: "Network error. Please try again." };
+ }
+},
+
+// /////////////////////////////////////////////////////////
+// /////////////////////////////////////////////////////////
+// /////////////////////////////////////////////////////////
+// /////////////////////////////////////////////////////////
+
+
+
+
+
+
+
+
+ getAllCategory: async () => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [], pagination: null };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/CategoryS/read_CS`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ // const CatSData = response.data?.data?.category?.CatS?.data || [];
+ const CatSData = response.data?.data || [];
+ const meta = response.data?.data?.category?.CatS?.meta || {};
+
+ const CatS = CatSData.map(Category => ({
+ id: Category.id,
+ name: Category.name,
+ icon: Category.image,
+ }));
+
+ return {
+ success: true,
+ data: CatS
+ };
+ } catch (error) {
+ console.error("❌ Error fetching category meals:", error);
+ return { success: false, message: "Failed to fetch meals", data: [], pagination: null };
+ }
+ },
+
+
+ getAllProduct: async () => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [], pagination: null };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/ProductS/read_PS`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const Product = response.data?.data || [];
+ const meta = response.data?.data?.category?.CatS?.meta || {};
+
+ const Pro = Product.map(product => ({
+ id: product.id,
+ name: product.name,
+ category: product.supplier_category_id,
+ price: product.price,
+ unit: "Kg",
+ image: product.image,
+ }));
+
+ return {
+ success: true,
+ data: Pro
+ };
+ } catch (error) {
+ console.error("Error fetching category meals:", error);
+ return { success: false, message: "Failed to fetch meals", data: [], pagination: null };
+ }
+ },
+
+
+
+
+createCart: async (cartData) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: null };
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Cart_Supplier/carts`,
+ cartData,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return {
+ success: true,
+ data: response.data?.data || null,
+ };
+ } catch (error) {
+ console.error("Error creating cart:", error);
+ return { success: false, message: "Failed to create cart", data: null };
+ }
+},
+
+
+
+getProductsByCategory: async (categoryId, page = 1) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token)
+ return {
+ success: false,
+ message: "No token found",
+ data: [],
+ pagination: null,
+ };
+
+ const response = await axios.get(
+ `${API_BASE_URL}/ProductS/getproductsbycategory/${categoryId}?page=${page}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const productsData = response.data?.data || [];
+ const meta = response.data?.meta || {};
+
+ const products = productsData.map((product) => ({
+ id: product.id,
+ name: product.name,
+ price: product.price,
+ category_id: product.supplier_category_id,
+ image: product.image
+ // ? `${API_BASE_URL}/${product.image}`
+ || "/images/default-product.png",
+ created_at: product.created_at,
+ updated_at: product.updated_at,
+ }));
+
+ return {
+ success: true,
+ data: products,
+ pagination: {
+ current_page: meta.current_page || 1,
+ per_page: meta.per_page || products.length,
+ total: meta.total || products.length,
+ last_page: meta.last_page || 1,
+ },
+ };
+ } catch (error) {
+ console.error("Error fetching products by category:", error);
+ return {
+ success: false,
+ message: "Failed to fetch products",
+ data: [],
+ pagination: null,
+ };
+ }
+},
+
+
+
+
+
+
+ getOrders: async (page = 1) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: [], pagination: null };
+
+ const response = await axios.get(`${API_BASE_URL}/Cart_Supplier/carts`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ },
+ });
+
+ const ordersData = response.data?.data || [];
+ const meta = response.data?.meta || {};
+
+ const orders = ordersData.map(order => ({
+ id: order.id,
+ totalPrice: order.attributes?.total_price || order.attributes?.totalPrice || "0.00",
+ createdAt: order.attributes?.createdAt || null,
+ cartItems: order.relationships?.cart_items || order.relationships?.cartItems || [],
+ }));
+
+ return {
+ success: true,
+ data: orders,
+ pagination: {
+ current_page: meta.current_page || 1,
+ per_page: meta.per_page || orders.length,
+ total: meta.total || orders.length,
+ last_page: meta.last_page || 1,
+ },
+ };
+
+ } catch (error) {
+ console.error("Error fetching orders:", error);
+ return { success: false, message: "Failed to fetch orders", data: [], pagination: null };
+ }
+ },
+
+
+
+
+
+ confirmOrder: async (order) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) return { success: false, message: "No token found", data: null };
+
+ const response = await axios.post(
+ `${API_BASE_URL}/Order_Supplier/myorders`,
+ order,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ return {
+ success: true,
+ data: response.data?.data || null,
+ };
+ } catch (error) {
+ console.error("Error creating cart:", error);
+ return { success: false, message: "Failed to create cart", data: null };
+ }
+},
+
+
+
};
export default authService;
diff --git a/src/services/cartHelpers.js b/src/services/cartHelpers.js
new file mode 100644
index 0000000..057fbf8
--- /dev/null
+++ b/src/services/cartHelpers.js
@@ -0,0 +1,140 @@
+
+import authService from './authService';
+
+// === مفاتيح التخزين ===
+const CART_KEY = 'activeCartId';
+
+// === LocalStorage Helpers ===
+export const setActiveCartId = (id) => {
+ try { localStorage.setItem(CART_KEY, String(id)); } catch {}
+};
+export const getActiveCartId = () => {
+ try { return localStorage.getItem(CART_KEY); } catch { return null; }
+};
+export const clearActiveCartId = () => {
+ try { localStorage.removeItem(CART_KEY); } catch {}
+};
+
+// === تحويل عناصر الكارت لصيغة JSON:API ===
+export const buildCartPayload = (items, totalPrice) => ({
+ data: {
+ type: "cart",
+ attributes: {
+ totalPrice: typeof totalPrice === 'number' ? Number(totalPrice) : undefined,
+ },
+ relationships: {
+ cartItems: items.map(it => ({
+ attributes: { quantity: Number(it.quantity) },
+ relationships: {
+ product: { data: { id: it.productId } }
+ }
+ }))
+ }
+ }
+});
+
+// === استخراج الـ productId من أي شكل محتمل ===
+export const extractProductId = (product) => (
+ product?.relationships?.supplier_product?.id ||
+ product?.relationships?.product?.data?.id ||
+ product?.supplier_product_id ||
+ product?.product_id ||
+ product?.id
+);
+
+// === استخراج عناصر الكارت من استجابة الباك ===
+export const getItemsFromCart = (cartData) => {
+ const rel = cartData?.relationships || {};
+ const list = rel.cartItems || rel.cart_items || [];
+ return list.map(item => ({
+ id: item.id,
+ productId: item.relationships?.supplier_product?.id ||
+ item.relationships?.product?.data?.id,
+ quantity: Number(item?.attributes?.quantity || 0),
+ })).filter(x => x.productId);
+};
+
+// === إضافة/تحديث عنصر في قائمة الكارت ===
+export const upsertItem = (items, productId, quantity) => {
+ const idx = items.findIndex(x => String(x.productId) === String(productId));
+ if (idx >= 0) {
+ const merged = [...items];
+ merged[idx] = { ...merged[idx], quantity: Number(merged[idx].quantity) + Number(quantity) };
+ return merged;
+ }
+ return [...items, { productId, quantity: Number(quantity) }];
+};
+
+// === حساب إجمالي مؤقت إن توفر priceMap ===
+export const estimateTotal = (items, priceMap) => {
+ if (!priceMap) return undefined;
+ return items.reduce((sum, it) => sum + (Number(priceMap[it.productId]) || 0) * Number(it.quantity), 0);
+};
+
+// === التأكد من وجود كارت فعّالة وإنشاؤها إذا لم توجد ===
+export const ensureActiveCart = async () => {
+ const existing = getActiveCartId();
+ if (existing) return existing;
+
+ const token = localStorage.getItem('token');
+ if (!token) return null;
+
+ const payload = {
+ data: {
+ type: "cart",
+ attributes: { totalPrice: 0 },
+ relationships: { cartItems: [] }
+ }
+ };
+
+ const res = await authService.createCart(payload);
+ if (res?.success && res?.data?.id) {
+ setActiveCartId(res.data.id);
+ return res.data.id;
+ }
+ return null;
+};
+
+// === إضافة منتج للكارت الفعّالة ===
+export const addItemToActiveCart = async ({ product, quantity = 1, priceMap = null }) => {
+ const token = localStorage.getItem('token');
+ if (!token) return { success: false, message: 'No token found' };
+
+ const cartId = await ensureActiveCart();
+ if (!cartId) return { success: false, message: 'Failed to create or get cart' };
+
+ const current = await authService.getCartById(cartId);
+ if (!current?.success || !current?.data) return { success: false, message: 'Failed to fetch active cart' };
+
+ const existingItems = getItemsFromCart(current.data);
+ const productId = extractProductId(product);
+ if (!productId) return { success: false, message: 'Invalid product id' };
+
+ const mergedItems = upsertItem(existingItems, productId, quantity);
+ const totalPrice = estimateTotal(mergedItems, priceMap);
+ const updatePayload = buildCartPayload(mergedItems, totalPrice);
+
+ const updated = await authService.updateCart(cartId, updatePayload);
+ if (updated?.success && updated?.data?.id) {
+ setActiveCartId(updated.data.id);
+ return { success: true, data: updated.data, cartId: updated.data.id };
+ }
+
+ return { success: false, message: updated?.message || 'Failed to update cart', errors: updated?.errors || null };
+};
+
+// === تصدير الأدوات كموديل واحد ===
+const cartHelpers = {
+ setActiveCartId,
+ getActiveCartId,
+ clearActiveCartId,
+ ensureActiveCart,
+ addItemToActiveCart,
+ buildCartPayload,
+ extractProductId,
+ getItemsFromCart,
+ upsertItem,
+ estimateTotal,
+};
+
+export default cartHelpers;
diff --git a/src/test.js b/src/test.js
index b28e130..e2d768a 100644
--- a/src/test.js
+++ b/src/test.js
@@ -3,9 +3,9 @@ import { useNavigate } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';
import { Box, Typography } from '@mui/material';
-const Test = () => { // لاحظ الحرف الكبير في بداية الاسم
- const navigate = useNavigate(); // صحيح الآن لأنها داخل مكون React
- const theme = useTheme(); // تعريف theme باستخدام useTheme
+const Test = () => {
+ const navigate = useNavigate();
+ const theme = useTheme();
const handleClick = () => {
navigate('/dashboard');
diff --git a/src/theme.js b/src/theme.js
index 0a61cca..96abf0a 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -16,7 +16,7 @@ const theme = createTheme({
},
palette: {
primary: {
- main: '#FF914D', // ← اللون الأساسي الجديد
+ main: '#FF914D',
hover: '#e57f3f'
},