نسخ من RaghadAlkhous/RestaurantDash
Initial commit - restaurant dashboard
هذا الالتزام موجود في:
148
package-lock.json
مولّد
148
package-lock.json
مولّد
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
"name": "resturant-dashBord",
|
"name": "resturant-dashbord",
|
||||||
|
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
||||||
@@ -12,10 +12,12 @@
|
|||||||
|
|
||||||
"": {
|
"": {
|
||||||
|
|
||||||
"name": "resturant-dashBord",
|
"name": "resturant-dashbord",
|
||||||
|
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
||||||
|
"license": "ISC",
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
@@ -28,6 +30,8 @@
|
|||||||
|
|
||||||
"@mui/x-date-pickers": "^8.5.2",
|
"@mui/x-date-pickers": "^8.5.2",
|
||||||
|
|
||||||
|
"@react-oauth/google": "^0.12.2",
|
||||||
|
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
@@ -40,12 +44,18 @@
|
|||||||
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
|
||||||
|
"framer-motion": "^12.23.0",
|
||||||
|
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
|
||||||
"jspdf-autotable": "^3.5.23",
|
"jspdf-autotable": "^3.5.23",
|
||||||
|
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
|
||||||
"react-circular-progressbar": "^2.2.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": {
|
"node_modules/@rollup/plugin-babel": {
|
||||||
|
|
||||||
"version": "5.3.1",
|
"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": {
|
"node_modules/debug": {
|
||||||
|
|
||||||
"version": "4.4.0",
|
"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": {
|
"node_modules/fresh": {
|
||||||
|
|
||||||
"version": "0.5.2",
|
"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": {
|
"node_modules/keyv": {
|
||||||
|
|
||||||
"version": "4.5.4",
|
"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": {
|
"node_modules/ms": {
|
||||||
|
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
"@mui/x-date-pickers": "^8.5.2",
|
"@mui/x-date-pickers": "^8.5.2",
|
||||||
|
|
||||||
|
"@react-oauth/google": "^0.12.2",
|
||||||
|
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
@@ -30,12 +32,18 @@
|
|||||||
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
|
||||||
|
"framer-motion": "^12.23.0",
|
||||||
|
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
|
||||||
"jspdf-autotable": "^3.5.23",
|
"jspdf-autotable": "^3.5.23",
|
||||||
|
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
|
||||||
"react-circular-progressbar": "^2.2.0",
|
"react-circular-progressbar": "^2.2.0",
|
||||||
@@ -106,8 +114,6 @@
|
|||||||
|
|
||||||
"main": "restored-file.js",
|
"main": "restored-file.js",
|
||||||
|
|
||||||
"devDependencies": {},
|
|
||||||
|
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
|||||||
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf
Normal file
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Bold.ttf
Normal file
ملف ثنائي غير معروض.
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Light.ttf
Normal file
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Light.ttf
Normal file
ملف ثنائي غير معروض.
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf
Normal file
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Medium.ttf
Normal file
ملف ثنائي غير معروض.
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf
Normal file
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-Regular.ttf
Normal file
ملف ثنائي غير معروض.
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf
Normal file
ثنائية
public/fonts/Hind Siliguri/HindSiliguri-SemiBold.ttf
Normal file
ملف ثنائي غير معروض.
ثنائية
public/image.png
ثنائية
public/image.png
ملف ثنائي غير معروض.
|
قبل العرض: | الارتفاع: | الحجم: 6.2 KiB بعد العرض: | الارتفاع: | الحجم: 1.0 MiB |
ثنائية
public/images/dash.png
Normal file
ثنائية
public/images/dash.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 121 KiB |
ثنائية
public/images/image.png
Normal file
ثنائية
public/images/image.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 80 KiB |
24
src/App.css
24
src/App.css
@@ -2,7 +2,6 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
/* background-color: #9aa0b8; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
@@ -122,4 +121,27 @@ html,
|
|||||||
font-weight: 400;
|
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 === */
|
/* === CUSTOM FONTS === */
|
||||||
|
|||||||
151
src/App.js
151
src/App.js
@@ -1,129 +1,62 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
import { ThemeProvider } from '@mui/material/styles';
|
|
||||||
import theme from './theme';
|
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 LoginForm from './components/Authentication/SignUp_In/LoginForm';
|
||||||
import RegisterForm from './components/Authentication/SignUp_In/RegisterForm';
|
import RegisterForm from './components/Authentication/SignUp_In/RegisterForm';
|
||||||
import ForgetPassword from './components/Authentication/ForgetPassword/ForgetFormMain';
|
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 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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Router>
|
<GoogleOAuthProvider clientId="443509481732-4i5ne811hr90rn75q3fbqevqi9eu3lvk.apps.googleusercontent.com">
|
||||||
<Routes>
|
<Router>
|
||||||
{/* الصفحات العامة */}
|
<UserProvider>
|
||||||
<Route path="/" element={<Navigate to="/login" replace />} />
|
{/* ✅ لفّ كل المشروع بـ SnackbarProvider */}
|
||||||
|
<SnackbarProvider>
|
||||||
|
<Routes>
|
||||||
|
{/* التوجيه التلقائي للصفحة الرئيسية */}
|
||||||
|
<Route path="/" element={<Navigate to="/login" replace />} />
|
||||||
|
|
||||||
<Route path="/" element={<PublicRoute><LoginForm /></PublicRoute>} />
|
<Route path="/login" element={<PublicRoute><LoginForm /></PublicRoute>} />
|
||||||
<Route path="/login" element={<PublicRoute><LoginForm /></PublicRoute>} />
|
<Route path="/register" element={<PublicRoute><RegisterForm /></PublicRoute>} />
|
||||||
<Route path="/register" element={<PublicRoute><RegisterForm /></PublicRoute>} />
|
<Route path="/forget" element={<PublicRoute><ForgetPassword /></PublicRoute>} />
|
||||||
<Route path="/forget" element={<PublicRoute><ForgetPassword /></PublicRoute>} />
|
|
||||||
|
|
||||||
{/* الصفحات المحمية */}
|
{/* روابط الشروط والخصوصية */}
|
||||||
<Route
|
<Route path="/terms" element={<TermsPage />} />
|
||||||
path="/dashboard"
|
<Route path="/privacy" element={<PrivacyPage />} />
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
<Route
|
||||||
<Dashboard />
|
path="/*"
|
||||||
</PrivateRoute>
|
element={
|
||||||
}
|
<RestaurantProvider>
|
||||||
/>
|
|
||||||
<Route
|
<ProtectedRoutes />
|
||||||
path="/inventory"
|
|
||||||
element={
|
</RestaurantProvider>
|
||||||
<PrivateRoute>
|
}
|
||||||
<Inventory />
|
/>
|
||||||
</PrivateRoute>
|
</Routes>
|
||||||
}
|
</SnackbarProvider>
|
||||||
/>
|
</UserProvider>
|
||||||
<Route
|
</Router>
|
||||||
path="/settings"
|
</GoogleOAuthProvider>
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<Settings />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/profile"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<RestaurantProfile />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/create-kitchen"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<CreateYourRestaurant />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/host-kitchen"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<HostKitchen />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/cashier"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<Cashier />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/supplier"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<Supplier />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/analytics"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<Analytics />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/training"
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<Training />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</Router>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const Congratulations = ({ emailOrPhone, setEmailOrPhone }) => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
overflow: 'auto',
|
// overflow: 'auto',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
'&::-webkit-scrollbar': {
|
'&::-webkit-scrollbar': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
|
|||||||
@@ -1,147 +1,226 @@
|
|||||||
// EmailInputSection.js
|
|
||||||
import React, { useState } from 'react';
|
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 EmailInputSection = ({ emailOrPhone, setEmailOrPhone, onStepComplete }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const [loading, setLoading] = useState(false);
|
||||||
const slides = [0, 1, 2, 3];
|
|
||||||
|
|
||||||
const handleSend = () => {
|
// حالات Snackbar
|
||||||
// عملية الإرسال
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
};
|
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||||
|
const [snackbarSeverity, setSnackbarSeverity] = useState('success');
|
||||||
|
const [inputError, setInputError] = useState('');
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleCloseSnackbar = (event, reason) => {
|
||||||
if (currentSlide < slides.length - 1) {
|
if (reason === 'clickaway') return;
|
||||||
setCurrentSlide(prev => prev + 1);
|
setSnackbarOpen(false);
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const handleBack = () => {
|
const validateEmail = (email) => {
|
||||||
if (currentSlide > 0) {
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
setCurrentSlide(prev => prev - 1);
|
return emailRegex.test(email);
|
||||||
}
|
};
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box>
|
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src="/image.png"
|
|
||||||
alt="logo"
|
|
||||||
sx={{
|
|
||||||
width: { xs: '5vh', sm: '7vh' },
|
|
||||||
maxWidth: { xs: '40px', sm: '80px' },
|
|
||||||
height: 'auto',
|
|
||||||
objectFit: 'contain',
|
|
||||||
overflow: 'auto',
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography
|
const handleSend = async () => {
|
||||||
variant="h4"
|
setInputError(''); // مسح الأخطاء السابقة
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Forget Password
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
if (!emailOrPhone) {
|
||||||
Enter your email to reset it and regain access to your account.
|
setInputError("Please enter your email.");
|
||||||
</Typography>
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
if (!validateEmail(emailOrPhone)) {
|
||||||
<Typography
|
setInputError("Please enter a valid email address.");
|
||||||
variant="body2"
|
return;
|
||||||
color="black"
|
}
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Email/Phone
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder="Enter your email"
|
|
||||||
type="email"
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
value={emailOrPhone}
|
|
||||||
onChange={(e) => setEmailOrPhone(e.target.value)}
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
|
||||||
borderColor: '#FF914D',
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* send */}
|
setLoading(true);
|
||||||
<Button
|
const response = await authService.resetPassword(emailOrPhone);
|
||||||
variant="contained"
|
setLoading(false);
|
||||||
fullWidth
|
|
||||||
onClick={handleSend}
|
|
||||||
sx={{
|
|
||||||
color:'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
borderRadius: '30px',
|
|
||||||
textTransform: 'none',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '16px',
|
|
||||||
height: '48px',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover ,
|
|
||||||
transition: 'all 0.4s ease'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Typography
|
if (response.success !== false) {
|
||||||
textAlign="center"
|
setSnackbarMessage("A reset code has been sent to your email.");
|
||||||
fontWeight={500}
|
setSnackbarSeverity('success');
|
||||||
sx={{
|
setSnackbarOpen(true);
|
||||||
fontSize: '16px',
|
|
||||||
display: 'block',
|
setTimeout(() => {
|
||||||
width: '100%',
|
if (onStepComplete) onStepComplete();
|
||||||
mx: 'auto',
|
}, 5000);
|
||||||
color: '#969BA7'
|
|
||||||
}}
|
} else {
|
||||||
>
|
setInputError('');
|
||||||
By log in, I agree to the
|
let errorMessage = "";
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and
|
if (response.errors) {
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
|
const firstError = Object.values(response.errors)[0];
|
||||||
</Typography>
|
errorMessage = Array.isArray(firstError) ? firstError[0] : firstError;
|
||||||
</>
|
} else if (response.message) {
|
||||||
);
|
errorMessage = response.message;
|
||||||
|
} else {
|
||||||
|
errorMessage = "Failed to send reset code.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
errorMessage.toLowerCase().includes("not found") ||
|
||||||
|
errorMessage.toLowerCase().includes("not registered") ||
|
||||||
|
errorMessage.toLowerCase().includes("doesn't exist") ||
|
||||||
|
errorMessage.toLowerCase().includes("email")
|
||||||
|
) {
|
||||||
|
setInputError("This email is not registered.");
|
||||||
|
} else if (errorMessage.toLowerCase().includes("invalid credentials")) {
|
||||||
|
setInputError("Email or password is incorrect.");
|
||||||
|
} else {
|
||||||
|
setSnackbarMessage(errorMessage);
|
||||||
|
setSnackbarSeverity('error');
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src="/image.png"
|
||||||
|
alt="logo"
|
||||||
|
sx={{
|
||||||
|
width: { xs: '5vh', sm: '7vh' },
|
||||||
|
maxWidth: { xs: '40px', sm: '80px' },
|
||||||
|
height: 'auto',
|
||||||
|
objectFit: 'contain',
|
||||||
|
overflow: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
fontWeight={700}
|
||||||
|
sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}
|
||||||
|
>
|
||||||
|
Forget Password
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" color="text.secondary" mb={2}>
|
||||||
|
Enter your email to reset it and regain access to your account.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Email
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="Enter your email"
|
||||||
|
type="email"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={emailOrPhone}
|
||||||
|
onChange={(e) => setEmailOrPhone(e.target.value)}
|
||||||
|
error={!!inputError}
|
||||||
|
helperText={inputError}
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: '#FF914D',
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={handleSend}
|
||||||
|
disabled={loading}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: '30px',
|
||||||
|
textTransform: 'none',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
height: '48px',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.hover,
|
||||||
|
transition: 'all 0.4s ease',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? "Sending..." : "Send"}
|
||||||
|
</Button>
|
||||||
|
{/*
|
||||||
|
<Typography
|
||||||
|
textAlign="center"
|
||||||
|
fontWeight={500}
|
||||||
|
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
|
||||||
|
>
|
||||||
|
By log in, I agree to the
|
||||||
|
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>
|
||||||
|
Terms of Service
|
||||||
|
</a>{' '}
|
||||||
|
and
|
||||||
|
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>
|
||||||
|
Privacy Policy
|
||||||
|
</a>
|
||||||
|
</Typography> */}
|
||||||
|
|
||||||
|
{/* Terms */}
|
||||||
|
<Typography
|
||||||
|
// variant="caption"
|
||||||
|
textAlign="center"
|
||||||
|
fontWeight={500}
|
||||||
|
|
||||||
|
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
|
||||||
|
|
||||||
|
>
|
||||||
|
By logging in, I agree to the{' '}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/terms', 'TermsOfService')}
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</Box>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Box>.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={snackbarOpen}
|
||||||
|
autoHideDuration={4000}
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
severity={snackbarSeverity}
|
||||||
|
sx={{ width: { xs: '40%', sm: '60%', md: '100%' }, color: 'white', fontSize: '16px', fontWeight: '500', backgroundColor: '#e57f3f', borderRadius: 6, mb: 6 }}
|
||||||
|
variant="filled"
|
||||||
|
>
|
||||||
|
{snackbarMessage}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmailInputSection;
|
export default EmailInputSection;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Stack ,useTheme} from '@mui/material';
|
import { Box, Stack, useTheme } from '@mui/material';
|
||||||
import Side from './Side';
|
import Side from './Side';
|
||||||
import EmailInputSection from './EmailInputSection';
|
import EmailInputSection from './EmailInputSection';
|
||||||
import OtpVerification from './OtpVerification';
|
import OtpVerification from './OtpVerification';
|
||||||
import NewPassword from './NewPassword';
|
import NewPassword from './NewPassword';
|
||||||
import Congratulations from './Congratulations';
|
import Congratulations from './Congratulations';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ForgetForm = () => {
|
const ForgetForm = () => {
|
||||||
const [emailOrPhone, setEmailOrPhone] = useState('');
|
const [emailOrPhone, setEmailOrPhone] = useState('');
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
const theme = useTheme();
|
const [stepCompleted, setStepCompleted] = useState(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const slides = [
|
const slides = [
|
||||||
{ component: EmailInputSection, title: "Email Verification" },
|
{ component: EmailInputSection, title: "Email Verification" },
|
||||||
{ component: OtpVerification, title: "OTP Verification" },
|
{ component: OtpVerification, title: "OTP Verification" },
|
||||||
@@ -22,33 +22,37 @@ const ForgetForm = () => {
|
|||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
if (currentSlide < slides.length - 1) {
|
if (currentSlide < slides.length - 1) {
|
||||||
setCurrentSlide(prev => prev + 1);
|
setCurrentSlide(prev => prev + 1);
|
||||||
|
setStepCompleted(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (currentSlide > 0) {
|
if (currentSlide > 0) {
|
||||||
setCurrentSlide(prev => prev - 1);
|
setCurrentSlide(prev => prev - 1);
|
||||||
|
setStepCompleted(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const CurrentComponent = slides[currentSlide].component;
|
const CurrentComponent = slides[currentSlide].component;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={{
|
||||||
sx={{
|
display: 'flex',
|
||||||
display: 'flex',
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
flexDirection: { xs: 'column', sm: 'row' },
|
minHeight: '100vh',
|
||||||
minHeight: '100vh',
|
backgroundColor: '#fff' ,
|
||||||
backgroundColor: '#fff',
|
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{/* ✅ Sidebar يظهر فقط من sm+ */}
|
|
||||||
<Box sx={{ width: { sm: '40%', xs: '0%' }, display: { xs: 'none', sm: 'block' } }}>
|
<Box sx={{ width: { sm: '40%', xs: '0%' }, display: { xs: 'none', sm: 'block' } }}>
|
||||||
<Side currentStepIndex={currentSlide} onNext={handleNext} onBack={handleBack} />
|
<Side
|
||||||
|
currentStepIndex={currentSlide}
|
||||||
|
onNext={handleNext}
|
||||||
|
onBack={handleBack}
|
||||||
|
stepCompleted={stepCompleted}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* ✅ Form */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: { sm: '60%' },
|
width: { sm: '60%' },
|
||||||
@@ -62,32 +66,25 @@ const ForgetForm = () => {
|
|||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box sx={{ width: '100%', maxWidth: 686, pr: { xs: 3, sm: 2, md: 1 }, pt: { xs: 2, sm: 4, md: 1 } }}>
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: 686,
|
|
||||||
pr: { xs: 3, sm: 2, md: 1 },
|
|
||||||
pt: { xs: 2, sm: 4, md: 1 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2} alignItems="flex-start">
|
<Stack spacing={2} alignItems="flex-start">
|
||||||
|
|
||||||
|
|
||||||
{/* إظهار المكون الحالي فقط */}
|
|
||||||
<CurrentComponent
|
<CurrentComponent
|
||||||
emailOrPhone={emailOrPhone}
|
emailOrPhone={emailOrPhone}
|
||||||
setEmailOrPhone={setEmailOrPhone}
|
setEmailOrPhone={setEmailOrPhone}
|
||||||
|
onStepComplete={() => {
|
||||||
|
setStepCompleted(true);
|
||||||
|
setCurrentSlide(prev => prev + 1); // هنا تنقل مباشرة للصفحة التالية
|
||||||
|
}}
|
||||||
|
onBack={handleBack}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* ✅ مؤشر الشرائح السفلي */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: { xs: 'absolute', sm: 'absolute' },
|
position: 'absolute',
|
||||||
bottom: { xs: '5%', sm: '-1%', md: '2%' },
|
bottom: { xs: '5%', sm: '5%', md: '2%' },
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -95,11 +92,7 @@ const ForgetForm = () => {
|
|||||||
width: '80%',
|
width: '80%',
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: {
|
height: { xs: 3, sm: 5, md: 6 },
|
||||||
xs: 3,
|
|
||||||
sm: 5,
|
|
||||||
md: 6,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{slides.map((_, index) => (
|
{slides.map((_, index) => (
|
||||||
@@ -108,12 +101,11 @@ const ForgetForm = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
width: '100vh',
|
width: '100vh',
|
||||||
height: 5,
|
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,
|
borderRadius: 2,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background-color 0.3s',
|
transition: 'background-color 0.3s',
|
||||||
}}
|
}}
|
||||||
// onClick={() => setCurrentSlide(index)} // يمكنك تفعيلها لاحقًا
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -122,4 +114,5 @@ const ForgetForm = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default ForgetForm;
|
export default ForgetForm;
|
||||||
|
|||||||
@@ -1,217 +1,222 @@
|
|||||||
// EmailInputSection.js
|
|
||||||
import React, { useState } from 'react';
|
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 { useTheme } from '@mui/material/styles';
|
||||||
import { IconButton, InputAdornment } from '@mui/material';
|
|
||||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||||
import { VisibilityOutlined } from '@mui/icons-material';
|
import { VisibilityOutlined } from '@mui/icons-material';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
const EmailInputSection = ({ emailOrPhone, setEmailOrPhone }) => {
|
const NewPassword = ({ emailOrPhone, onStepComplete }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const handleTogglePassword = () => {
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
setShowPassword((prev) => !prev);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
};
|
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [password, setPassword] = useState('');
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [passwordConfirm, setPasswordConfirm] = useState('');
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const [loading, setLoading] = useState(false);
|
||||||
const slides = [0, 1, 2, 3];
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleTogglePassword = () => setShowPassword(prev => !prev);
|
||||||
// عملية الإرسال
|
const handleToggleConfirmPassword = () => setShowConfirmPassword(prev => !prev);
|
||||||
};
|
|
||||||
const handleToggleConfirmPassword = () => {
|
|
||||||
setShowConfirmPassword((prev) => !prev);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleSend = async () => {
|
||||||
if (currentSlide < slides.length - 1) {
|
setErrors({});
|
||||||
setCurrentSlide(prev => prev + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBack = () => {
|
if (!password || !passwordConfirm) {
|
||||||
if (currentSlide > 0) {
|
alert("Please fill in both password fields.");
|
||||||
setCurrentSlide(prev => prev - 1);
|
return;
|
||||||
}
|
}
|
||||||
};
|
if (password !== passwordConfirm) {
|
||||||
return (
|
alert("Passwords do not match.");
|
||||||
<>
|
return;
|
||||||
<Box>
|
}
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src="/image.png"
|
|
||||||
alt="logo"
|
|
||||||
sx={{
|
|
||||||
width: { xs: '5vh', sm: '7vh' },
|
|
||||||
maxWidth: { xs: '40px', sm: '80px' },
|
|
||||||
height: 'auto',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography
|
setLoading(true);
|
||||||
variant="h4"
|
try {
|
||||||
fontWeight={700}
|
const response = await authService.updatePassword({
|
||||||
sx={{
|
email: emailOrPhone,
|
||||||
fontSize: {
|
password,
|
||||||
xs: '1.8rem',
|
confirmPassword: passwordConfirm
|
||||||
sm: '2rem',
|
});
|
||||||
md: '2.2rem'
|
setLoading(false);
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create a New Password
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
if (response.success) {
|
||||||
Enter your email to reset it and regai access to your account.
|
alert("Password updated successfully.");
|
||||||
</Typography>
|
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 */}
|
return (
|
||||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<>
|
||||||
<Typography
|
<Box>
|
||||||
variant="body2"
|
<Box
|
||||||
color="black"
|
component="img"
|
||||||
sx={{
|
src="/image.png"
|
||||||
fontWeight: '500',
|
alt="logo"
|
||||||
fontSize: '16px'
|
sx={{
|
||||||
}}
|
width: { xs: '5vh', sm: '7vh' },
|
||||||
>
|
maxWidth: { xs: '40px', sm: '80px' },
|
||||||
Password
|
height: 'auto',
|
||||||
</Typography>
|
objectFit: 'contain'
|
||||||
<TextField
|
}}
|
||||||
type={showPassword ? 'text' : 'password'}
|
/>
|
||||||
placeholder="Enter your password"
|
</Box>
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
autoComplete="new-password"
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& input::-ms-reveal, & input::-ms-clear': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={handleTogglePassword} edge="end">
|
|
||||||
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
fontWeight={700}
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create a New Password
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{/* Confirm Password Input */}
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
Enter your new password to regain access to your account.
|
||||||
<Typography
|
</Typography>
|
||||||
variant="body2"
|
|
||||||
color="black"
|
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Confirm Password
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
type={showConfirmPassword ? 'text' : 'password'}
|
|
||||||
placeholder="Confirm your password"
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
autoComplete="new-password"
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
|
||||||
borderColor: '#FF914D',
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
},
|
|
||||||
'& input::-ms-reveal, & input::-ms-clear': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
|
||||||
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* send */}
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
<Button
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
variant="contained"
|
Password
|
||||||
fullWidth
|
</Typography>
|
||||||
onClick={handleSend}
|
<TextField
|
||||||
sx={{
|
type={showPassword ? 'text' : 'password'}
|
||||||
color: 'white',
|
placeholder="Enter your password"
|
||||||
backgroundColor: theme.palette.primary.main,
|
fullWidth
|
||||||
borderRadius: '30px',
|
variant="outlined"
|
||||||
textTransform: 'none',
|
autoComplete="new-password"
|
||||||
fontWeight: 600,
|
value={password}
|
||||||
fontSize: '16px',
|
onChange={e => setPassword(e.target.value)}
|
||||||
height: '48px',
|
error={!!errors.password}
|
||||||
'&:hover': {
|
helperText={errors.password && errors.password.join(' ')}
|
||||||
backgroundColor: theme.palette.primary.hover ,
|
sx={{
|
||||||
transition: 'all 0.4s ease'
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
}
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
}}
|
'& .MuiOutlinedInput-root': {
|
||||||
>
|
borderRadius: '10px',
|
||||||
Send
|
transition: '0.3s',
|
||||||
</Button>
|
'&.Mui-focused fieldset': {
|
||||||
<Typography
|
borderColor: theme.palette.primary.main,
|
||||||
textAlign="center"
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||||
fontWeight={500}
|
}
|
||||||
sx={{
|
},
|
||||||
fontSize: '16px',
|
'& input::-ms-reveal, & input::-ms-clear': { display: 'none' },
|
||||||
display: 'block',
|
}}
|
||||||
width: '100%',
|
InputProps={{
|
||||||
mx: 'auto',
|
endAdornment: (
|
||||||
color: '#969BA7'
|
<InputAdornment position="end">
|
||||||
}}
|
<IconButton onClick={handleTogglePassword} edge="end">
|
||||||
>
|
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
By log in, I agree to the
|
</IconButton>
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and
|
</InputAdornment>
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
|
)
|
||||||
</Typography>
|
}}
|
||||||
</>
|
/>
|
||||||
);
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 3 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Confirm Password
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={passwordConfirm}
|
||||||
|
onChange={e => setPasswordConfirm(e.target.value)}
|
||||||
|
error={!!errors.password_confirmation}
|
||||||
|
helperText={errors.password_confirmation && errors.password_confirmation.join(' ')}
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: '#FF914D',
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||||
|
},
|
||||||
|
'& input::-ms-reveal, & input::-ms-clear': { display: 'none' },
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
||||||
|
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={handleSend}
|
||||||
|
disabled={loading}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: '30px',
|
||||||
|
textTransform: 'none',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
height: '48px',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.hover,
|
||||||
|
transition: 'all 0.4s ease'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? "Sending..." : "Send"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Terms */}
|
||||||
|
<Typography
|
||||||
|
// variant="caption"
|
||||||
|
textAlign="center"
|
||||||
|
fontWeight={500}
|
||||||
|
|
||||||
|
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
|
||||||
|
|
||||||
|
>
|
||||||
|
By logging in, I agree to the{' '}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/terms', 'TermsOfService')}
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</Box>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Box>.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmailInputSection;
|
export default NewPassword;
|
||||||
|
|||||||
@@ -1,18 +1,54 @@
|
|||||||
import React, { useState, useRef } from 'react';
|
import 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 OtpVerification = ({ emailOrPhone, onStepComplete }) => {
|
||||||
const [emailOrPhone, setEmailOrPhone] = useState('');
|
const [otp, setOtp] = useState(Array(6).fill(''));
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const inputRefs = useRef(Array(6).fill().map(() => React.createRef()));
|
||||||
const slides = [0, 1, 2, 3];
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const [otp, setOtp] = useState(Array(7).fill(''));
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
const inputRefs = useRef(Array(7).fill().map(() => React.createRef()));
|
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('');
|
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) => {
|
const handleOtpChange = (e, index) => {
|
||||||
@@ -22,30 +58,17 @@ const OtpVerification = () => {
|
|||||||
const newOtp = [...otp];
|
const newOtp = [...otp];
|
||||||
newOtp[index] = value;
|
newOtp[index] = value;
|
||||||
setOtp(newOtp);
|
setOtp(newOtp);
|
||||||
|
|
||||||
if (value && index < 6) {
|
if (value && index < otp.length - 1) {
|
||||||
inputRefs.current[index + 1].current.focus();
|
inputRefs.current[index + 1].current.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e, index) => {
|
const handleKeyDown = (e, index) => {
|
||||||
if (e.key === 'Backspace' && !otp[index] && index > 0) {
|
if (e.key === 'Backspace' && !otp[index] && index > 0) {
|
||||||
inputRefs.current[index - 1].current.focus();
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -77,12 +100,11 @@ const OtpVerification = () => {
|
|||||||
Enter OTP Verification
|
Enter OTP Verification
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
Kindly enter the OTP code sent to your registered email/phone for account verification.
|
Kindly enter the OTP code sent to your registered email/phone for account verification.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 1, mb: 3 }}>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={{ xs: 0, sm: 1, md: 1.5 }}
|
spacing={{ xs: 0, sm: 1, md: 1.5 }}
|
||||||
@@ -113,15 +135,15 @@ const OtpVerification = () => {
|
|||||||
height: { xs: '3rem', sm: '3.25rem', md: '4rem' },
|
height: { xs: '3rem', sm: '3.25rem', md: '4rem' },
|
||||||
'& .MuiOutlinedInput-root': {
|
'& .MuiOutlinedInput-root': {
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
height: '100%', // تأكيد تطابق الطول
|
height: '100%',
|
||||||
'& fieldset': {
|
'& fieldset': {
|
||||||
borderColor: '#ccc',
|
borderColor: '#ccc',
|
||||||
},
|
},
|
||||||
'&:hover fieldset': {
|
'&:hover fieldset': {
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
'&.Mui-focused fieldset': {
|
'&.Mui-focused fieldset': {
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'& .MuiInputBase-input': {
|
'& .MuiInputBase-input': {
|
||||||
@@ -135,47 +157,83 @@ const OtpVerification = () => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
|
disabled={loading}
|
||||||
sx={{
|
sx={{
|
||||||
color:'white',
|
color: 'white',
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
borderRadius: '30px',
|
borderRadius: '30px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
width: '95%',
|
width: '95%',
|
||||||
height: '48px',
|
height: '48px',
|
||||||
|
mx: 'auto',
|
||||||
|
display: 'block',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.primary.hover ,
|
backgroundColor: theme.palette.primary.hover,
|
||||||
transition: 'all 0.4s ease'
|
transition: 'all 0.4s ease'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Send
|
{loading ? "Sending..." : "Send"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Terms */}
|
||||||
<Typography
|
<Typography
|
||||||
|
// variant="caption"
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
fontWeight={500}
|
fontWeight={500}
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
sx={{ fontSize: '16px', width: '100%', mx: 'auto', color: '#969BA7', mt: 2 }}
|
||||||
display: 'block',
|
|
||||||
width: '100%',
|
|
||||||
mx: 'auto',
|
|
||||||
color: '#969BA7'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
By log in, I agree to the
|
By logging in, I agree to the{' '}
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Terms of Service</a> and
|
<Box
|
||||||
<a href="#" style={{ color: '#007bff', textDecoration: 'none' }}>Privacy Policy</a>
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/terms', 'TermsOfService')}
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</Box>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Box>.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={snackbarOpen}
|
||||||
|
autoHideDuration={4000}
|
||||||
|
onClose={handleSnackbarClose}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={handleSnackbarClose}
|
||||||
|
severity={snackbarSeverity}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: '500',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: 6,
|
||||||
|
mb: 6
|
||||||
|
}}
|
||||||
|
variant="filled"
|
||||||
|
>
|
||||||
|
{snackbarMessage}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import 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 ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ const steps = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const Side = ({ currentStepIndex, onNext, onBack }) => {
|
const Side = ({ currentStepIndex, onNext, onBack }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -46,7 +46,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
|
|||||||
Instructions for secure password modification. Follow simple steps for password change.
|
Instructions for secure password modification. Follow simple steps for password change.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Stack spacing={6} position="relative" sx={{ ml: 3 }}>
|
<Stack spacing={6} position="relative" sx={{ ml: {xs:-1,md:2.1} }}>
|
||||||
{steps.map((step, index) => (
|
{steps.map((step, index) => (
|
||||||
<Box key={index} display="flex" alignItems="flex-start" gap={2} position="relative">
|
<Box key={index} display="flex" alignItems="flex-start" gap={2} position="relative">
|
||||||
{index !== steps.length - 1 && (
|
{index !== steps.length - 1 && (
|
||||||
@@ -109,7 +109,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box >
|
||||||
<Typography variant="subtitle1" sx={{ fontSize: '16px', fontWeight: 600 }}>
|
<Typography variant="subtitle1" sx={{ fontSize: '16px', fontWeight: 600 }}>
|
||||||
{step.title}
|
{step.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -121,13 +121,11 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
|
|||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* ... (بقية محتوى الـ Side كما هو بدون تغيير) ... */}
|
|
||||||
|
|
||||||
{/* أزرار التنقل */}
|
{/* أزرار التنقل */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 2,
|
bottom: {sm:31 , md:2},
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
width: '90%',
|
width: '90%',
|
||||||
@@ -152,7 +150,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
|
|||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
color: theme.palette.primary.main
|
color: theme.palette.primary.main
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -172,7 +170,7 @@ const Side = ({ currentStepIndex, onNext, onBack }) => {
|
|||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
transition: 'all 0.4s ease'
|
transition: 'all 0.4s ease'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
192
src/components/Authentication/Legal/TermsPrivacyComponents.js
Normal file
192
src/components/Authentication/Legal/TermsPrivacyComponents.js
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Button, Typography, Container, Box, Paper } from '@mui/material';
|
||||||
|
|
||||||
|
/*
|
||||||
|
File: TermsPrivacyComponents.jsx
|
||||||
|
Purpose: Two simple standalone pages (Terms and Privacy) and an example LoginCard
|
||||||
|
which opens them in a new tab/window and listens for the "agree" message.
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
- The LoginCard opens a new tab using window.open(...).
|
||||||
|
- The opened page (TermsPage / PrivacyPage) will send a postMessage back to the opener
|
||||||
|
when the user clicks "Agree & Confirm", then attempt to close itself.
|
||||||
|
- The LoginCard listens for that postMessage and can react (e.g. show a toast,
|
||||||
|
update state, or navigate). This keeps the original page in control of what
|
||||||
|
happens after the user agreed.
|
||||||
|
|
||||||
|
Note: Some browsers may block window.close() on tabs not opened by window.open.
|
||||||
|
Because LoginCard uses window.open, the close() call should succeed. If the
|
||||||
|
page is opened directly (no opener) the Agree button will fall back to
|
||||||
|
navigating back (history.back()).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***********************
|
||||||
|
* Terms / Privacy Pages
|
||||||
|
***********************/
|
||||||
|
|
||||||
|
function sendAgreeToOpener(kind = 'terms') {
|
||||||
|
try {
|
||||||
|
if (window.opener && !window.opener.closed) {
|
||||||
|
// Structured message so opener can identify the response
|
||||||
|
window.opener.postMessage({ type: 'AGREE_CONFIRM', page: kind }, '*');
|
||||||
|
// Try to close this tab/window (works when opened by window.open)
|
||||||
|
window.close();
|
||||||
|
} else {
|
||||||
|
// No opener (user opened directly) — go back in history as a fallback
|
||||||
|
if (window.history && window.history.length > 1) {
|
||||||
|
window.history.back();
|
||||||
|
} else {
|
||||||
|
// Last fallback: navigate to the site's root
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to send message to opener or close window', e);
|
||||||
|
// fallback navigation
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TermsPage() {
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.title = "Resturant-Terms of Service";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md" sx={{ py: 6 }}>
|
||||||
|
<Paper elevation={3} sx={{ p: 4 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Terms of Service
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
Welcome to our Terms of Service. Please read carefully. Lorem ipsum dolor sit amet,
|
||||||
|
consectetur adipiscing elit. Integer posuere erat a ante.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box textAlign="center" sx={{ mt: 4 , color:'white'}}>
|
||||||
|
<Button variant="contained" onClick={() => sendAgreeToOpener('terms')} sx={{color:'white' , textTransform:'none'}}>
|
||||||
|
Agree & Confirm
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PrivacyPage() {
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.title = "Resturant-Privacy Policy";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md" sx={{ py: 6 }}>
|
||||||
|
<Paper elevation={3} sx={{ p: 4 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Privacy Policy
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
This Privacy Policy explains how we collect and use personal information. Lorem ipsum dolor
|
||||||
|
sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box textAlign="center" sx={{ mt: 4 ,color:'white'}}>
|
||||||
|
<Button variant="contained" onClick={() => sendAgreeToOpener('privacy')} sx={{color:'white' , textTransform:'none'}}>
|
||||||
|
Agree & Confirm
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***********************
|
||||||
|
* Example LoginCard
|
||||||
|
***********************/
|
||||||
|
|
||||||
|
export default function LoginCard() {
|
||||||
|
useEffect(() => {
|
||||||
|
// Listen to messages from the terms/privacy pages.
|
||||||
|
const handler = (event) => {
|
||||||
|
// IMPORTANT: In production, check event.origin to be sure the message comes from
|
||||||
|
// a trusted origin. For demo purposes we accept any origin.
|
||||||
|
const data = event.data || {};
|
||||||
|
if (data && data.type === 'AGREE_CONFIRM') {
|
||||||
|
// data.page === 'terms' or 'privacy'
|
||||||
|
const page = data.page || 'unknown';
|
||||||
|
// Do whatever you need when user agreed: update state, show toast, close dialog, etc.
|
||||||
|
// Example: simple alert (replace with MUI Snackbar or app-level handler)
|
||||||
|
alert(`User agreed to ${page === 'terms' ? 'Terms of Service' : 'Privacy Policy'}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handler);
|
||||||
|
return () => window.removeEventListener('message', handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Helper that opens a new tab (ensures opener exists)
|
||||||
|
function openInNewTab(path, name) {
|
||||||
|
// If your app uses router paths (e.g. /terms), build a full URL. Here we create
|
||||||
|
// a URL relative to current origin so the opened page belongs to the same origin
|
||||||
|
const url = new URL(path, window.location.href).toString();
|
||||||
|
// Use window.open (not anchor target="_blank") so the new tab has window.opener
|
||||||
|
window.open(url, name, 'noopener=false');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper elevation={2} sx={{ p: 4, maxWidth: 420, margin: '40px auto' }}>
|
||||||
|
<Typography variant="h6" textAlign="center" gutterBottom>
|
||||||
|
{/* Login title */}
|
||||||
|
Sign in
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Your existing Terms text (you provided earlier) */}
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{ fontSize: '14px', textAlign: 'center', color: '#969BA7', pt: 5 }}
|
||||||
|
>
|
||||||
|
By logging in, I agree to the{' '}
|
||||||
|
<Box component="span" sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }} onClick={() => openInNewTab('/terms', 'TermsOfService')}>
|
||||||
|
Terms of Service
|
||||||
|
</Box>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Box component="span" sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }} onClick={() => openInNewTab('/privacy', 'PrivacyPolicy')}>
|
||||||
|
Privacy Policy
|
||||||
|
</Box>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* The rest of your login UI goes here (inputs, submit, etc.) */}
|
||||||
|
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Usage notes:
|
||||||
|
- If you're using a router (react-router), create routes for /terms and /privacy
|
||||||
|
that render <TermsPage /> and <PrivacyPage /> respectively.
|
||||||
|
|
||||||
|
Example (react-router v6):
|
||||||
|
|
||||||
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
|
import LoginCard, { TermsPage, PrivacyPage } from './TermsPrivacyComponents';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<LoginCard />} />
|
||||||
|
<Route path="/terms" element={<TermsPage />} />
|
||||||
|
<Route path="/privacy" element={<PrivacyPage />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
Security reminder: validate event.origin in the message handler in production.
|
||||||
|
*/
|
||||||
@@ -7,10 +7,12 @@ import SidePanel from './SidePanel';
|
|||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
import authService from '../../../services/authService';
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
import { GoogleLogin, useGoogleLogin } from '@react-oauth/google';
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@@ -28,21 +30,66 @@ const LoginForm = () => {
|
|||||||
const result = await authService.login(email, password);
|
const result = await authService.login(email, password);
|
||||||
|
|
||||||
if (!result.success) {
|
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);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
localStorage.setItem('token', result.data.token);
|
localStorage.setItem('token', result.data.token);
|
||||||
localStorage.setItem('refresh_token', result.data.refresh_token);
|
localStorage.setItem('refresh_token', result.data.refresh_token);
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
navigate('/restaurant', { replace: true });
|
||||||
// الانتقال للداشبورد مع منع الرجوع
|
|
||||||
navigate('/dashboard', { 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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
bgcolor={"background.default"}
|
bgcolor={"background.default"}
|
||||||
@@ -116,6 +163,7 @@ const LoginForm = () => {
|
|||||||
Enter your username and password to access your account securely. Welcome back to our service!
|
Enter your username and password to access your account securely. Welcome back to our service!
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
{/* إدخال البريد */}
|
{/* إدخال البريد */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
@@ -232,10 +280,10 @@ const LoginForm = () => {
|
|||||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Google Button */}
|
{/* <Button
|
||||||
<Button
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
onClick={loginWithGoogle}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
@@ -248,8 +296,8 @@ const LoginForm = () => {
|
|||||||
'&:hover': {
|
'&:hover': {
|
||||||
borderColor: 'black',
|
borderColor: 'black',
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
}
|
},
|
||||||
, fontFamily: 'PlusJakartaSans'
|
fontFamily: 'PlusJakartaSans'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
@@ -260,10 +308,10 @@ const LoginForm = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
Login with Google
|
Login with Google
|
||||||
</Button>
|
</Button> */}
|
||||||
|
|
||||||
{/* Facebook Button */}
|
{/* Facebook Button */}
|
||||||
<Button
|
{/* <Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{
|
sx={{
|
||||||
@@ -290,8 +338,7 @@ const LoginForm = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
Login with Facebook
|
Login with Facebook
|
||||||
</Button>
|
</Button> */}
|
||||||
|
|
||||||
|
|
||||||
{/* Register Link */}
|
{/* Register Link */}
|
||||||
<Typography
|
<Typography
|
||||||
@@ -300,7 +347,7 @@ const LoginForm = () => {
|
|||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#969BA7',
|
color: '#969BA7',
|
||||||
pt: 3
|
pt: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Don’t have an account?{' '}
|
Don’t have an account?{' '}
|
||||||
@@ -338,6 +385,7 @@ const LoginForm = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Terms */}
|
{/* Terms */}
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -348,14 +396,25 @@ const LoginForm = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
By logging in, I agree to the{' '}
|
By logging in, I agree to the{' '}
|
||||||
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/terms', 'TermsOfService')}
|
||||||
|
>
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</a>{' '}
|
</Box>{' '}
|
||||||
and{' '}
|
and{' '}
|
||||||
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
|
||||||
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</a>.
|
</Box>.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -365,3 +424,4 @@ const LoginForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default LoginForm;
|
export default LoginForm;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState ,useContext } from 'react';
|
||||||
import { TextField, Button, Typography, Stack, Box, IconButton, InputAdornment } from '@mui/material';
|
import { TextField, Button, Typography, Stack, Box, IconButton, InputAdornment } from '@mui/material';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||||
@@ -6,6 +6,19 @@ import { VisibilityOutlined } from '@mui/icons-material';
|
|||||||
import SidePanel from './SidePanel';
|
import SidePanel from './SidePanel';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import authService from '../../../services/authService';
|
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 Register = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -19,7 +32,11 @@ const Register = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [successMessage, setSuccessMessage] = useState('');
|
const [successMessage, setSuccessMessage] = useState('');
|
||||||
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
|
||||||
|
const { setUser } = useContext(UserContext);
|
||||||
|
|
||||||
|
// تفعيل/تعطيل إظهار كلمة السر
|
||||||
const handleTogglePassword = () => {
|
const handleTogglePassword = () => {
|
||||||
setShowPassword((prev) => !prev);
|
setShowPassword((prev) => !prev);
|
||||||
};
|
};
|
||||||
@@ -28,7 +45,8 @@ const Register = () => {
|
|||||||
setShowConfirmPassword((prev) => !prev);
|
setShowConfirmPassword((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegister = async () => {
|
|
||||||
|
const handleRegister = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
setSuccessMessage('');
|
setSuccessMessage('');
|
||||||
@@ -52,28 +70,93 @@ const Register = () => {
|
|||||||
localStorage.setItem('token', result.data.token);
|
localStorage.setItem('token', result.data.token);
|
||||||
localStorage.setItem('refresh_token', result.data.refresh_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(() => {
|
setTimeout(() => {
|
||||||
navigate('/dashboard', { replace: true }); // استبدال الرابط وعدم السماح بالعودة
|
navigate('/restaurant', { replace: true });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, 500);
|
}, 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 (
|
return (
|
||||||
<Box bgcolor={"background.default"} color={"text.primary"} display="flex" sx={{
|
<Box
|
||||||
width: '100vw',
|
bgcolor={'background.default'}
|
||||||
height: '100vh',
|
color={'text.primary'}
|
||||||
overflow: 'auto',
|
display="flex"
|
||||||
scrollbarWidth: 'none',
|
sx={{
|
||||||
'&::-webkit-scrollbar': {
|
width: '100vw',
|
||||||
display: 'none',
|
height: '100vh',
|
||||||
},
|
overflow: 'auto',
|
||||||
}}>
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SidePanel />
|
<SidePanel />
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
marginLeft: { xs: 0, sm: '50%' },
|
sx={{
|
||||||
width: { xs: '100%', sm: '50%' },
|
marginLeft: { xs: 0, sm: '50%' },
|
||||||
}}>
|
width: { xs: '100%', sm: '50%' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
flex={1}
|
flex={1}
|
||||||
paddingTop={4}
|
paddingTop={4}
|
||||||
@@ -95,12 +178,13 @@ const Register = () => {
|
|||||||
xs: '120%',
|
xs: '120%',
|
||||||
sm: '100%',
|
sm: '100%',
|
||||||
md: '120%',
|
md: '120%',
|
||||||
lg: '120%'
|
lg: '120%',
|
||||||
},
|
},
|
||||||
maxWidth: '600px'
|
maxWidth: '600px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
{/* logo */}
|
||||||
<Box>
|
<Box>
|
||||||
<img
|
<img
|
||||||
src="/image.png"
|
src="/image.png"
|
||||||
@@ -109,7 +193,7 @@ const Register = () => {
|
|||||||
width: '4vw',
|
width: '4vw',
|
||||||
maxWidth: '80px',
|
maxWidth: '80px',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
objectFit: 'contain'
|
objectFit: 'contain',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -121,323 +205,291 @@ const Register = () => {
|
|||||||
fontSize: {
|
fontSize: {
|
||||||
xs: '1.8rem',
|
xs: '1.8rem',
|
||||||
sm: '2rem',
|
sm: '2rem',
|
||||||
md: '2.2rem'
|
md: '2.2rem',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography
|
<Typography variant="body2" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||||
variant="body2"
|
Please fill out the registration form with accurate information to create your account successfully.
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Please fill out the registration form with accurate information to
|
|
||||||
create your account successfully.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography
|
{/* حقل الايميل */}
|
||||||
variant="body2"
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
color="black"
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
sx={{
|
Email
|
||||||
fontWeight: '500',
|
</Typography>
|
||||||
fontSize: '16px'
|
<TextField
|
||||||
}}
|
placeholder="Enter your email"
|
||||||
>
|
type="email"
|
||||||
Email
|
variant="outlined"
|
||||||
</Typography>
|
fullWidth
|
||||||
<TextField
|
value={email}
|
||||||
placeholder="Enter your email"
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
type="email"
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* حقل كلمة المرور */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Password
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
},
|
||||||
|
'& input::-ms-reveal, & input::-ms-clear': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleTogglePassword} edge="end">
|
||||||
|
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* حقل التاكيد لكلمة المرور */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Confirm Password
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: '#FF914D',
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
},
|
||||||
|
'& input::-ms-reveal, & input::-ms-clear': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
||||||
|
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Error & Success Message */}
|
||||||
|
{error && (
|
||||||
|
<Typography color="error" textAlign="center">
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{successMessage && (
|
||||||
|
<Typography color="green" textAlign="center">
|
||||||
|
{successMessage}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* زر التسجيل */}
|
||||||
|
<Box sx={{ pt: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={handleRegister}
|
||||||
|
disabled={loading}
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
|
height: { xs: '45px', sm: '52px' },
|
||||||
|
borderRadius: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.hover,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? 'Loading...' : 'Continue'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, py: 2 }}>
|
||||||
|
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||||
|
Or
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* زر التسجيل عبر جوجل */}
|
||||||
|
{/* <Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={email}
|
// onClick={() => RegisterWithGoogle()}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="black"
|
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Password
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
type={showPassword ? 'text' : 'password'}
|
|
||||||
placeholder="Enter your password"
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
},
|
|
||||||
'& input::-ms-reveal, & input::-ms-clear': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={handleTogglePassword} edge="end">
|
|
||||||
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="black"
|
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Confirm Password
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
type={showConfirmPassword ? 'text' : 'password'}
|
|
||||||
placeholder="Confirm your password"
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
||||||
sx={{
|
|
||||||
'& input': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#969BA7'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: '#FF914D',
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
},
|
|
||||||
'& input::-ms-reveal, & input::-ms-clear': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
|
||||||
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Error & Success Message */}
|
|
||||||
{error && (
|
|
||||||
<Typography color="error" textAlign="center">
|
|
||||||
{error}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{successMessage && (
|
|
||||||
<Typography color="green" textAlign="center">
|
|
||||||
{successMessage}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={handleRegister}
|
|
||||||
disabled={loading}
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loading ? 'Loading...' : 'Continue'}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Divider */}
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, py: 2 }}>
|
|
||||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
color: 'black'
|
borderRadius: '50px',
|
||||||
|
height: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
gap: 1,
|
||||||
|
borderColor: '#E6E6E6',
|
||||||
|
color: 'black',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'black',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Or
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<img
|
||||||
|
src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg"
|
||||||
|
alt="Google"
|
||||||
|
style={{ width: 25, height: 25 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
Register with Google
|
||||||
|
</Button> */}
|
||||||
|
|
||||||
|
{/* زر فيسبوك - فقط للعرض (لم تُضاف وظيفة) */}
|
||||||
|
{/* <Button
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '16px',
|
||||||
|
borderRadius: '50px',
|
||||||
|
height: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
gap: 1,
|
||||||
|
borderColor: '#E6E6E6',
|
||||||
|
color: 'black',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'black',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<img
|
||||||
|
src="https://upload.wikimedia.org/wikipedia/commons/0/05/Facebook_Logo_%282019%29.png"
|
||||||
|
alt="Facebook"
|
||||||
|
style={{ width: 25, height: 25 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
Register with Facebook
|
||||||
|
</Button> */}
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontSize: '16px',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#969BA7',
|
||||||
|
pt: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Already have an account?{' '}
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
style={{
|
||||||
|
color: '#2261FF',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Google */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '16px',
|
|
||||||
borderRadius: '50px',
|
|
||||||
height: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
gap: 1,
|
|
||||||
borderColor: '#E6E6E6',
|
|
||||||
color: 'black',
|
|
||||||
'&:hover': {
|
|
||||||
borderColor: 'black',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<img
|
|
||||||
src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg"
|
|
||||||
alt="Google"
|
|
||||||
style={{ width: 25, height: 25 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
Login with Google
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* Facebook */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '16px',
|
|
||||||
borderRadius: '50px',
|
|
||||||
height: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
gap: 1,
|
|
||||||
borderColor: '#E6E6E6',
|
|
||||||
color: 'black',
|
|
||||||
'&:hover': {
|
|
||||||
borderColor: 'black',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<img
|
|
||||||
src="https://upload.wikimedia.org/wikipedia/commons/0/05/Facebook_Logo_%282019%29.png"
|
|
||||||
alt="Facebook"
|
|
||||||
style={{ width: 25, height: 25 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
Login with Facebook
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="caption"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '16px',
|
fontSize: '14px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#969BA7',
|
color: '#969BA7',
|
||||||
pt: 3
|
pt: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Already have an account?{' '}
|
By logging in, I agree to the{' '}
|
||||||
<Link
|
<Box
|
||||||
to="/login"
|
component="span"
|
||||||
style={{
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
color: '#2261FF',
|
onClick={() => window.open('/terms', 'TermsOfService')}
|
||||||
textDecoration: 'none'
|
>
|
||||||
}}
|
Terms of Service
|
||||||
>
|
</Box>{' '}
|
||||||
Login
|
and{' '}
|
||||||
</Link>
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: '#2261FF', cursor: 'pointer', textDecoration: 'none' }}
|
||||||
|
onClick={() => window.open('/privacy', 'PrivacyPolicy')}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Box>.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
sx={{
|
|
||||||
fontSize: '14px',
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#969BA7',
|
|
||||||
pt: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
By log in, I agree to the and{' '}
|
|
||||||
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
|
|
||||||
Terms of Service
|
|
||||||
</a>{' '}
|
|
||||||
and{' '}
|
|
||||||
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
|
|
||||||
Privacy Policy
|
|
||||||
</a>.
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
Box
|
Box
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; // استيراد أيقونة السهم
|
|
||||||
|
|
||||||
const SidePanel = ({ setMode, mode }) => {
|
const SidePanel = ({ setMode, mode }) => {
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
@@ -61,7 +58,7 @@ const SidePanel = ({ setMode, mode }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
display: { xs: "none", sm: "block" },
|
display: { xs: "none", sm: "block" },
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
height: 'calc(102vh - 40px)', // لضمان ملاءمته للشاشة
|
height: 'calc(102vh - 40px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -96,9 +93,9 @@ const SidePanel = ({ setMode, mode }) => {
|
|||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: {
|
height: {
|
||||||
md: '37vh', // الافتراضي للشاشات الصغيرة
|
md: '37vh',
|
||||||
// xs: '5vh',
|
// xs: '5vh',
|
||||||
sm: '42vh',// عندما تكون الشاشة متوسطة أو أكبر
|
sm: '42vh',
|
||||||
lg: '31vh', // من 1200px إلى أقل من 1536px
|
lg: '31vh', // من 1200px إلى أقل من 1536px
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -119,17 +116,17 @@ const SidePanel = ({ setMode, mode }) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => window.open('https://www.google.com', '_blank')}
|
onClick={() => window.open('https://landing.velocrea.com/', '_blank')}
|
||||||
sx={{
|
sx={{
|
||||||
|
|
||||||
mb: 1,
|
mb: 1,
|
||||||
bgcolor: 'rgba(255, 255, 255, 0.24)',
|
bgcolor: 'rgba(255, 255, 255, 0.24)',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
borderRadius: '12px', // زاوية منحنية
|
borderRadius: '12px',
|
||||||
display: 'flex', // لتوزيع الأيقونة والنص بشكل أفقي
|
display: 'flex',
|
||||||
alignItems: 'center', // لضبط الأيقونة والنص في المنتصف
|
alignItems: 'center',
|
||||||
padding: '6px 20px', // يمكن تعديل المسافة حول النص والأيقونة
|
padding: '6px 20px',
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@@ -138,8 +135,8 @@ const SidePanel = ({ setMode, mode }) => {
|
|||||||
|
|
||||||
>
|
>
|
||||||
KITCHPLUS
|
KITCHPLUS
|
||||||
{/* إضافة أيقونة السهم مع تعديل زاوية الدوران */}
|
|
||||||
<ArrowForwardIcon sx={{ ml: 1, transform: 'rotate(-45deg)' }} /> {/* تعديل زاوية السهم */}
|
<ArrowForwardIcon sx={{ ml: 1, transform: 'rotate(-45deg)' }} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, useTheme, useMediaQuery, Skeleton } from '@mui/material';
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
import KitchPlusAppBar from '../AppBar';
|
import KitchPlusAppBar from '../AppBar';
|
||||||
import Sidebar from '../SideHome';
|
import Sidebar from '../SideHome';
|
||||||
import AnalyticsContect from './AnalyticsContect';
|
import AnalyticsContect from './AnalyticsContect';
|
||||||
@@ -15,11 +15,11 @@ const AnalyticsPage = () => {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
// محاكاة التحقق من المنتجات
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkProducts = async () => {
|
const checkProducts = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const productsExist = await checkIfProductsExist(); // استبدل بمنطقك
|
const productsExist = await checkIfProductsExist();
|
||||||
setHasProducts(productsExist);
|
setHasProducts(productsExist);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
@@ -28,7 +28,7 @@ const AnalyticsPage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const checkIfProductsExist = async () => {
|
const checkIfProductsExist = async () => {
|
||||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500)); // محاكاة تأخير
|
return new Promise((resolve) => setTimeout(() => resolve(true), 1500));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -57,8 +57,6 @@ const AnalyticsPage = () => {
|
|||||||
setSidebarOpen(!sidebarOpen);
|
setSidebarOpen(!sidebarOpen);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||||
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||||
|
|
||||||
@@ -94,15 +92,9 @@ const AnalyticsPage = () => {
|
|||||||
duration: theme.transitions.duration.leavingScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}),
|
}),
|
||||||
}}>
|
}}>
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Skeleton variant="rectangular" height={50} sx={{ mb: 2 }} />
|
|
||||||
<Skeleton variant="rectangular" height={200} sx={{ mb: 2 }} />
|
|
||||||
<Skeleton variant="rectangular" height={300} sx={{ mb: 2 }} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<AnalyticsContect />
|
<AnalyticsContect />
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,104 +1,143 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
import StatisticsCard from './StatisticsCard';
|
import StatisticsCard from './StatisticsCard';
|
||||||
import TopSellingProduct from './TopSellingProduct';
|
import TablesManager from './TablesManager';
|
||||||
import SalesByLocation from './SalesByLocation';
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
useTheme,
|
useTheme,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
Skeleton,
|
|
||||||
Button,
|
Button,
|
||||||
Typography,
|
ButtonGroup,
|
||||||
ButtonGroup
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined';
|
import authService from '../../../services/authService';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const AnalyticsPage = () => {
|
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 theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const [hasProducts, setHasProducts] = useState(false);
|
const [chartData, setChartData] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
|
||||||
|
|
||||||
const dailyData = [
|
const handleTimeFrameChange = (newTimeFrame) => {
|
||||||
{ date: '10:00', visitors: 1500, conversions: 300 },
|
setTimeFrame(newTimeFrame);
|
||||||
{ 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 topSellingProducts = [
|
const handleCustomDateChange = (event) => {
|
||||||
{ product: 'Apple Watch', sales: 150, amount: 45000, price: 299, status: 'Published' },
|
setCustomDate(event.target.value);
|
||||||
{ product: 'Samsung Galaxy', sales: 90, amount: 36000, price: 400, status: 'Low Stock' },
|
};
|
||||||
{ product: 'Sony Headphones', sales: 60, amount: 18000, price: 299, status: 'Draft' },
|
|
||||||
];
|
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(() => {
|
useEffect(() => {
|
||||||
const checkProducts = async () => {
|
fetchStatistics();
|
||||||
setIsLoading(true);
|
}, [timeFrame, restaurantId, customDate]);
|
||||||
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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box p={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
|
||||||
p={{ xs: 1, sm: 2 }}
|
{/* Tables Manager */}
|
||||||
maxWidth="100%"
|
<Box mb={1.5}>
|
||||||
overflowX="hidden"
|
{restaurantId && <TablesManager restaurantId={restaurantId} />}
|
||||||
>
|
</Box>
|
||||||
|
|
||||||
{/* Header Buttons */}
|
{/* Header Buttons */}
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@@ -106,7 +145,7 @@ const AnalyticsPage = () => {
|
|||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={2}
|
gap={2}
|
||||||
mb={3}
|
mb={1.5}
|
||||||
>
|
>
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
size="small"
|
size="small"
|
||||||
@@ -131,94 +170,73 @@ const AnalyticsPage = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[
|
{[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
|
||||||
{ 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 }) => (
|
|
||||||
<Button
|
<Button
|
||||||
key={value}
|
key={value}
|
||||||
variant={timeFrame === value ? 'contained' : 'text'}
|
variant={timeFrame === value ? 'contained' : 'text'}
|
||||||
onClick={() => setTimeFrame(value)}
|
onClick={() => handleTimeFrameChange(value)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<Box display="flex" gap={2} flexWrap="wrap" justifyContent={{ xs: 'space-between', sm: 'flex-end' }} width={{ xs: '100%', sm: 'auto' }}>
|
{/* Date Picker + Today Button */}
|
||||||
<Button
|
<Box
|
||||||
variant="contained"
|
display="flex"
|
||||||
|
gap={1}
|
||||||
|
alignItems="center"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: 2,
|
||||||
|
p: '4px 8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={customDate}
|
||||||
|
onChange={handleCustomDateChange}
|
||||||
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'none',
|
width: isMobile ? '100%' : 150,
|
||||||
color: '#667085',
|
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||||
backgroundColor: 'white',
|
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||||
boxShadow: 'none',
|
|
||||||
borderRadius: '8px',
|
|
||||||
height: '40px',
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: 500,
|
|
||||||
border: '1px solid #e0e0e0',
|
|
||||||
gap: 1,
|
|
||||||
minWidth: '120px'
|
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<CalendarTodayOutlinedIcon sx={{ fontSize: 16 }} />
|
|
||||||
{!isSmallScreen && 'Select Dates'}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
borderRadius: '8px',
|
|
||||||
height: '40px',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '14px',
|
|
||||||
minWidth: '100px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
KPIs Filter
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Data Sections */}
|
|
||||||
<Box display="flex" flexDirection={{ xs: 'column', md: 'row' }} gap={2} mb={3}>
|
|
||||||
<Box flex={2}>
|
|
||||||
<TopSellingProduct data={topSellingProducts} />
|
|
||||||
</Box>
|
|
||||||
<Box flex={1}>
|
|
||||||
<SalesByLocation data={salesData} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Chart Section */}
|
|
||||||
<Box>
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Skeleton variant="rectangular" height={50} sx={{ mb: 2 }} />
|
|
||||||
<Skeleton variant="rectangular" height={200} sx={{ mb: 2 }} />
|
|
||||||
<Skeleton variant="rectangular" height={300} sx={{ mb: 2 }} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<StatisticsCard
|
|
||||||
title="Analytics Overview"
|
|
||||||
subtitle="Performance Metrics"
|
|
||||||
data={getData()}
|
|
||||||
dataKeys={[
|
|
||||||
{ key: 'visitors', name: 'Visitors', color: '#4CAF50' },
|
|
||||||
{ key: 'conversions', name: 'Conversions', color: '#9C27B0' },
|
|
||||||
]}
|
|
||||||
xDataKey={timeFrame === '24h' ? 'date' : 'label'}
|
|
||||||
timeFrame={timeFrame}
|
|
||||||
onTimeFrameChange={setTimeFrame}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setCustomDate(dayjs().format('YYYY-MM-DD'))}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
textTransform: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
fontSize: isMobile ? 12 : 13,
|
||||||
|
'&:hover': { backgroundColor: 'rgba(255, 117, 34, 0.15)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* StatisticsCard */}
|
||||||
|
<Box>
|
||||||
|
<StatisticsCard
|
||||||
|
title="Analytics Overview"
|
||||||
|
subtitle="Reservation Statistics"
|
||||||
|
data={chartData}
|
||||||
|
dataKeys={[
|
||||||
|
{ key: 'reservations_total', name: 'Reservations', color: '#4CAF50' },
|
||||||
|
{ key: 'number_of_people_total', name: 'People', color: '#9C27B0' },
|
||||||
|
]}
|
||||||
|
xDataKey="label"
|
||||||
|
valueFormatter={(value) => value}
|
||||||
|
timeFrame={timeFrame}
|
||||||
|
onTimeFrameChange={handleTimeFrameChange}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ const SalesByLocation = ({ data }) => {
|
|||||||
p: { xs: 1, sm: 2 },
|
p: { xs: 1, sm: 2 },
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.1)',
|
||||||
height: '95%', // إضافة هذه السطر
|
height: '95%',
|
||||||
display: 'flex', // إضافة
|
display: 'flex',
|
||||||
flexDirection: 'column' // إضافة
|
flexDirection: 'column'
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -56,7 +56,7 @@ const SalesByLocation = ({ data }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<List dense sx={{
|
<List dense sx={{
|
||||||
flexGrow: 1, // إضافة هذه السطر
|
flexGrow: 1,
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
'&::-webkit-scrollbar': {
|
'&::-webkit-scrollbar': {
|
||||||
display: 'none'
|
display: 'none'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -6,7 +6,11 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
useMediaQuery
|
useMediaQuery,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
Button
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
AreaChart,
|
AreaChart,
|
||||||
@@ -18,37 +22,64 @@ import {
|
|||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
CartesianGrid
|
CartesianGrid
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
// import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
|
||||||
const formatCurrency = (value) => {
|
|
||||||
if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`;
|
|
||||||
if (value >= 1000) return `$${(value / 1000).toFixed(1)}K`;
|
|
||||||
return `$${value}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatisticsCard = ({
|
const StatisticsCard = ({
|
||||||
title = "Statistics",
|
title = "Statistics",
|
||||||
subtitle = "Delivery Times",
|
subtitle = "Delivery Times",
|
||||||
data = [],
|
data = [],
|
||||||
dataKeys = [
|
dataKeys = [
|
||||||
{ key: 'revenue', name: 'Revenue', color: '#E46A11' }, // << هنا تغيير اللون
|
{ key: 'revenue', name: 'Revenue', color: '#E46A11' },
|
||||||
{ key: 'sales', name: 'Sales', color: '#0182FC' } // << وهنا أيضاً
|
{ key: 'sales', name: 'Sales', color: '#0182FC' }
|
||||||
],
|
],
|
||||||
xDataKey = 'month',
|
xDataKey = 'month',
|
||||||
valueFormatter = formatCurrency,
|
valueFormatter = (value) => value,
|
||||||
timeFrame = 'month',
|
timeFrame = 'month',
|
||||||
onTimeFrameChange
|
onTimeFrameChange
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
||||||
|
const [selectedDate, setSelectedDate] = useState('');
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
|
||||||
const handleTimeFrameChange = (newTimeFrame) => {
|
const handleMenuOpen = (event) => {
|
||||||
if (onTimeFrameChange) onTimeFrameChange(newTimeFrame);
|
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 (
|
return (
|
||||||
<Box sx={{ borderRadius: 2, width: { sm: '100%', md: '167vh' }}}>
|
<Box sx={{ borderRadius: 2, width: { sm: '100%', md: '167vh' } }}>
|
||||||
<Paper sx={{
|
<Paper sx={{
|
||||||
p: { xs: 1.5, sm: 2 },
|
p: { xs: 1.5, sm: 2 },
|
||||||
mb: { xs: 2, sm: 2 },
|
mb: { xs: 2, sm: 2 },
|
||||||
@@ -64,10 +95,34 @@ const StatisticsCard = ({
|
|||||||
right: { xs: 4, sm: 8 },
|
right: { xs: 4, sm: 8 },
|
||||||
color: '#667085'
|
color: '#667085'
|
||||||
}}
|
}}
|
||||||
|
onClick={handleMenuOpen}
|
||||||
>
|
>
|
||||||
<MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} />
|
{/* <MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} /> */}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
>
|
||||||
|
<MenuItem>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
size="small"
|
||||||
|
sx={{ width: 150 }}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem>
|
||||||
|
<Button variant="outlined" size="small" onClick={handleTodayClick}>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -88,7 +143,7 @@ const StatisticsCard = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<ResponsiveContainer width="100%" height={isMobile ? 250 : isTablet ? 290 : 295}>
|
<ResponsiveContainer width="100%" height={isMobile ? 350 : isTablet ? 390 : 395}>
|
||||||
<AreaChart data={data} margin={{
|
<AreaChart data={data} margin={{
|
||||||
top: isMobile ? -30 : -45,
|
top: isMobile ? -30 : -45,
|
||||||
right: isMobile ? 15 : 30,
|
right: isMobile ? 15 : 30,
|
||||||
@@ -112,18 +167,20 @@ const StatisticsCard = ({
|
|||||||
tickMargin={isMobile ? 8 : 15}
|
tickMargin={isMobile ? 8 : 15}
|
||||||
tick={{
|
tick={{
|
||||||
fontSize: isMobile ? 11 : 12,
|
fontSize: isMobile ? 11 : 12,
|
||||||
angle: -25, // تدوير النص لتفادي التزاحم
|
angle: -25,
|
||||||
textAnchor: 'end'
|
textAnchor: 'end'
|
||||||
}}
|
}}
|
||||||
interval={0} // عرض كل القيم على محور X
|
interval={0}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
|
ticks={ticksArray}
|
||||||
tickFormatter={valueFormatter}
|
tickFormatter={valueFormatter}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tickMargin={isMobile ? 8 : 15}
|
tickMargin={isMobile ? 8 : 15}
|
||||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
tick={{ fontSize: isMobile ? 11 : 12 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip formatter={valueFormatter} />
|
<Tooltip formatter={valueFormatter} />
|
||||||
<Legend
|
<Legend
|
||||||
verticalAlign="top"
|
verticalAlign="top"
|
||||||
@@ -138,9 +195,9 @@ const StatisticsCard = ({
|
|||||||
name={name}
|
name={name}
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey={key}
|
dataKey={key}
|
||||||
stroke={color} // لون الخط
|
stroke={color}
|
||||||
strokeWidth={isMobile ? 2 : 3}
|
strokeWidth={isMobile ? 2 : 3}
|
||||||
fill={`url(#color-${key})`} // لون التعبئة بتدرج
|
fill={`url(#color-${key})`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
|
|||||||
456
src/components/Home/Analytics&Reporting/TablesManager.js
Normal file
456
src/components/Home/Analytics&Reporting/TablesManager.js
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Menu,
|
||||||
|
Chip,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
||||||
|
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||||
|
import TuneIcon from "@mui/icons-material/Tune";
|
||||||
|
import authService from "../../../services/authService";
|
||||||
|
|
||||||
|
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const handlePrev = () => currentPage > 1 && onChange(currentPage - 1);
|
||||||
|
const handleNext = () => currentPage < pageCount && onChange(currentPage + 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handlePrev}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
sx={{
|
||||||
|
borderRadius: "8px",
|
||||||
|
backgroundColor: "#FFECE0",
|
||||||
|
// "&:hover": { backgroundColor: "#FFD6B5" },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIosNewIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: "8px",
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: "#fff",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentPage}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={currentPage >= pageCount}
|
||||||
|
sx={{
|
||||||
|
borderRadius: "8px",
|
||||||
|
backgroundColor: "#FFECE0",
|
||||||
|
// "&:hover": { backgroundColor: "#FFD6B5" },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowForwardIosIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusOptions = ["available", "unavailable"];
|
||||||
|
|
||||||
|
const TablesManager = ({ restaurantId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
const itemsPerPage = 4;
|
||||||
|
|
||||||
|
const [rows, setRows] = useState([]);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const [addingRow, setAddingRow] = useState(false);
|
||||||
|
const [newRow, setNewRow] = useState({
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
table_number: "",
|
||||||
|
capacity: "",
|
||||||
|
status: "available",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
const [rowToDelete, setRowToDelete] = useState(null);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
// فلتر
|
||||||
|
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||||
|
const [statusFilter, setStatusFilter] = useState("all");
|
||||||
|
|
||||||
|
const fetchTables = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getTablesByRestaurant(restaurantId);
|
||||||
|
if (result.success) setRows(result.data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Error fetching tables:", err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (restaurantId) fetchTables();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
const handleSaveRow = async () => {
|
||||||
|
if (!newRow.table_number || !newRow.capacity) return;
|
||||||
|
try {
|
||||||
|
const result = await authService.addTable(newRow);
|
||||||
|
if (result.success) {
|
||||||
|
setRows((prev) => [result.data, ...prev]);
|
||||||
|
setAddingRow(false);
|
||||||
|
setNewRow({
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
table_number: "",
|
||||||
|
capacity: "",
|
||||||
|
status: "available",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Error adding table:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteRow = (row) => {
|
||||||
|
setRowToDelete(row);
|
||||||
|
setConfirmDeleteOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
if (!rowToDelete) return;
|
||||||
|
setIsSaving(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.deleteTable(rowToDelete.id);
|
||||||
|
if (result.success) setRows((prev) => prev.filter((r) => r.id !== rowToDelete.id));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterClick = (event) => setFilterAnchorEl(event.currentTarget);
|
||||||
|
const handleFilterClose = () => setFilterAnchorEl(null);
|
||||||
|
const handleFilterSelect = (value) => {
|
||||||
|
setStatusFilter(value);
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredRows =
|
||||||
|
(statusFilter === "all" ? rows : rows.filter((r) => r.status === statusFilter))
|
||||||
|
.sort((a, b) => parseInt(a.table_number) - parseInt(b.table_number));
|
||||||
|
|
||||||
|
const pageCount = Math.ceil(filteredRows.length / itemsPerPage);
|
||||||
|
const paginatedRows = filteredRows.slice(
|
||||||
|
(currentPage - 1) * itemsPerPage,
|
||||||
|
currentPage * itemsPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
const getStatusChipProps = (status) => {
|
||||||
|
switch (status?.toLowerCase()) {
|
||||||
|
case "available":
|
||||||
|
return {
|
||||||
|
label: "Available",
|
||||||
|
sx: {
|
||||||
|
backgroundColor: "#E7F4EE",
|
||||||
|
color: "#0D894F",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "13px",
|
||||||
|
minWidth: 90,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "unavailable":
|
||||||
|
return {
|
||||||
|
label: "Unavailable",
|
||||||
|
sx: {
|
||||||
|
backgroundColor: "#fde8e8ff",
|
||||||
|
color: "#e41111ff",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "13px",
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
label: status,
|
||||||
|
sx: {
|
||||||
|
backgroundColor: "#FDF1E8",
|
||||||
|
color: "#E46A11",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "13px",
|
||||||
|
minWidth: 80,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: "100%", sm: "95.5%" },
|
||||||
|
pt: { xs: 2, sm: 2 },
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 2 },
|
||||||
|
// p: { xs: 1.5, sm: 2 },
|
||||||
|
// mb: { xs: 2, sm: 2 },
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: { xs: "column", sm: "row" },
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: { xs: "stretch", sm: "center" },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: "18px", sm: "20px" },
|
||||||
|
textAlign: { xs: "center", sm: "left" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tables
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||||
|
{/* فلتر */}
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
textTransform: "none",
|
||||||
|
color: "#667085",
|
||||||
|
borderColor: "#e0e0e0",
|
||||||
|
borderRadius: "8px",
|
||||||
|
height: "40px",
|
||||||
|
width: { xs: "120px", sm: "120px", md: "99px" },
|
||||||
|
fontSize: { xs: "0", sm: "13px", md: "14px" },
|
||||||
|
fontWeight: 600,
|
||||||
|
minWidth: "unset",
|
||||||
|
}}
|
||||||
|
startIcon={<TuneIcon fontSize={isMobile ? "small" : "small"} />}
|
||||||
|
onClick={handleFilterClick}
|
||||||
|
>
|
||||||
|
{isMobile ? "" : "Filters"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={addingRow ? "outlined" : "contained"}
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={() => setAddingRow(!addingRow)}
|
||||||
|
sx={{
|
||||||
|
color: addingRow ? "primary.main" : "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "14px",
|
||||||
|
height: "40px",
|
||||||
|
width: { xs: "100%", sm: "135px" },
|
||||||
|
textTransform: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{addingRow ? "Cancel" : "Add"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Menu الفلاتر */}
|
||||||
|
<Menu anchorEl={filterAnchorEl} open={Boolean(filterAnchorEl)} onClose={handleFilterClose}>
|
||||||
|
{["all", "available", "unavailable"].map((status) => (
|
||||||
|
<MenuItem
|
||||||
|
key={status}
|
||||||
|
selected={statusFilter === status}
|
||||||
|
onClick={() => handleFilterSelect(status)}
|
||||||
|
sx={{
|
||||||
|
color: statusFilter === status ? "#FF914D" : "#4F5867",
|
||||||
|
fontWeight: statusFilter === status ? 700 : 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* جدول */}
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: "none",
|
||||||
|
border: "1px solid #e0e0e0",
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
height: {xs:"310px",md:"349px"},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: "auto" }}
|
||||||
|
aria-label="tables"
|
||||||
|
size={isMobile ? "small" : "medium"}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: "#f5f5f5" }}>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ pl: { md: 7 } }}>Table Number</TableCell>
|
||||||
|
<TableCell>Capacity</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading
|
||||||
|
? Array.from({ length: itemsPerPage }).map((_, idx) => (
|
||||||
|
<TableRow key={`skeleton-${idx}`}>
|
||||||
|
<TableCell ><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{addingRow && (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell >
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
placeholder="Table Number"
|
||||||
|
value={newRow.table_number}
|
||||||
|
onChange={(e) => setNewRow({ ...newRow, table_number: e.target.value })}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
placeholder="Capacity"
|
||||||
|
value={newRow.capacity}
|
||||||
|
onChange={(e) => setNewRow({ ...newRow, capacity: e.target.value })}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip
|
||||||
|
label="Available"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#E7F4EE",
|
||||||
|
color: "#0D894F",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "13px",
|
||||||
|
minWidth: 90,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton color="success" onClick={handleSaveRow}>
|
||||||
|
<SaveIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{paginatedRows.map((row) => (
|
||||||
|
<TableRow key={row.id} >
|
||||||
|
<TableCell sx={{ pl: { md: 9 } ,color:'#689dffff'}}>{row.table_number}</TableCell>
|
||||||
|
<TableCell>{row.capacity}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip {...getStatusChipProps(row.status)} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton color="error" onClick={() => handleDeleteRow(row)}>
|
||||||
|
<DeleteOutlineIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
{/* Pagination Footer */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
pt={2}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Showing {(currentPage - 1) * itemsPerPage + 1} -{" "}
|
||||||
|
{Math.min(currentPage * itemsPerPage, filteredRows.length)} of {filteredRows.length}
|
||||||
|
</Typography>
|
||||||
|
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Dialog الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Are you sure you want to delete table "{rowToDelete?.table_number}"?
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleConfirmDelete}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
{isSaving ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TablesManager;
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { useTheme } from '@mui/material/styles';
|
|
||||||
import { useMediaQuery } from '@mui/material';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Typography,
|
|
||||||
Paper,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Chip,
|
|
||||||
Pagination,
|
|
||||||
Button,
|
|
||||||
Avatar,
|
|
||||||
TableContainer
|
|
||||||
} from '@mui/material';
|
|
||||||
import TuneIcon from '@mui/icons-material/Tune';
|
|
||||||
import { green } from '@mui/material/colors';
|
|
||||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
|
||||||
|
|
||||||
const TopSellingProduct = ({ data = [] }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const itemsPerPage = 5;
|
|
||||||
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
|
|
||||||
const pageCount = Math.ceil(data.length / itemsPerPage);
|
|
||||||
const paginatedData = data.slice(
|
|
||||||
(currentPage - 1) * itemsPerPage,
|
|
||||||
currentPage * itemsPerPage
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper sx={{
|
|
||||||
borderRadius: 2,
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: { xs: '100%', md: '115vh' },
|
|
||||||
height: { xs: 'auto', md: '100vh' },
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
overflow: 'hidden',
|
|
||||||
boxShadow: theme.shadows[1]
|
|
||||||
}}>
|
|
||||||
{/* العنوان وزر الفلاتر */}
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
p={{ xs: 1, sm: 2 }}
|
|
||||||
sx={{ backgroundColor: theme.palette.background.paper }}
|
|
||||||
>
|
|
||||||
<Typography variant="h6" sx={{ fontSize: { xs: '1rem', sm: '1.25rem' } }}>
|
|
||||||
Top Selling Product
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
fontSize: { xs: '12px', sm: '16px' },
|
|
||||||
fontWeight: 500,
|
|
||||||
border: '1px solid #e0e0e0',
|
|
||||||
color: '#667085',
|
|
||||||
textTransform: 'none',
|
|
||||||
p: { xs: '4px 8px', sm: '6px 16px' }
|
|
||||||
}}
|
|
||||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
|
||||||
>
|
|
||||||
{isMobile ? '' : 'Filters'}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* جدول البيانات */}
|
|
||||||
<TableContainer
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
overflowX: 'auto',
|
|
||||||
maxHeight: { xs: '60vh', md: 'calc(100vh - 160px)' },
|
|
||||||
'&::-webkit-scrollbar': { height: 4 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Table
|
|
||||||
size={isMobile ? 'small' : 'medium'}
|
|
||||||
sx={{
|
|
||||||
minWidth: 650,
|
|
||||||
'& .MuiTableCell-root': {
|
|
||||||
borderBottom: '1px solid #e0e0e0',
|
|
||||||
py: { xs: 0.5, sm: 1.5 },
|
|
||||||
px: { xs: 0.5, sm: 2 },
|
|
||||||
},
|
|
||||||
'& thead .MuiTableCell-root': {
|
|
||||||
borderBottom: 'none',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '11px', sm: '14px' },
|
|
||||||
px: { xs: 0.5, sm: 2 },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TableHead sx={{ backgroundColor: '#F0F1F3' }}>
|
|
||||||
<TableRow sx={{ height: { xs: '6vh', sm: '10vh' } }}>
|
|
||||||
<TableCell>Product</TableCell>
|
|
||||||
{!isMobile && <TableCell>Sales</TableCell>}
|
|
||||||
<TableCell>Amount</TableCell>
|
|
||||||
{!isMobile && <TableCell>Price</TableCell>}
|
|
||||||
<TableCell>Status</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{paginatedData.map((row, i) => (
|
|
||||||
<TableRow key={i} sx={{ height: { xs: '8vh', sm: '13vh' } }}>
|
|
||||||
<TableCell>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 1, sm: 2 } }}>
|
|
||||||
<Avatar
|
|
||||||
sx={{
|
|
||||||
bgcolor: green[50],
|
|
||||||
width: { xs: 28, sm: 40 },
|
|
||||||
height: { xs: 28, sm: 40 }
|
|
||||||
}}
|
|
||||||
variant="rounded"
|
|
||||||
>
|
|
||||||
<AssignmentIcon fontSize={isMobile ? 'small' : 'medium'} />
|
|
||||||
</Avatar>
|
|
||||||
<Box sx={{
|
|
||||||
fontSize: { xs: '11px', sm: '14px' },
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
maxWidth: { xs: '80px', sm: '200px' }
|
|
||||||
}}>
|
|
||||||
{row.product}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
{!isMobile && <TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>{row.sales}</TableCell>}
|
|
||||||
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>${row.amount.toLocaleString()}</TableCell>
|
|
||||||
{!isMobile && <TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>${row.price}</TableCell>}
|
|
||||||
<TableCell>
|
|
||||||
<Chip
|
|
||||||
label={isMobile ? row.status.substring(0, 3) : row.status}
|
|
||||||
size={isMobile ? 'small' : 'medium'}
|
|
||||||
sx={{
|
|
||||||
fontSize: { xs: '10px', sm: '14px' },
|
|
||||||
fontWeight: 600,
|
|
||||||
backgroundColor:
|
|
||||||
row.status === 'Published' ? '#E7F4EE' :
|
|
||||||
row.status === 'Low Stock' ? '#FDF1E8' :
|
|
||||||
row.status === 'Out of Stock' ? '#FFCDD2' : '#E0E0E0',
|
|
||||||
color:
|
|
||||||
row.status === 'Published' ? '#0D894F' :
|
|
||||||
row.status === 'Low Stock' ? '#E46A11' :
|
|
||||||
row.status === 'Out of Stock' ? '#C62828' : '#424242',
|
|
||||||
minWidth: { xs: '50px', sm: '100px' }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
|
|
||||||
{/* التذييل مع الترقيم */}
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
p={{ xs: 1, sm: 2 }}
|
|
||||||
sx={{
|
|
||||||
position: 'sticky',
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
borderTop: '1px solid #f0f0f0',
|
|
||||||
zIndex: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{
|
|
||||||
fontSize: { xs: '11px', sm: '14px' },
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, data.length)} of {data.length}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Pagination
|
|
||||||
count={pageCount}
|
|
||||||
page={currentPage}
|
|
||||||
onChange={(event, value) => setCurrentPage(value)}
|
|
||||||
size={isMobile ? 'small' : 'medium'}
|
|
||||||
sx={{
|
|
||||||
'& .MuiPaginationItem-root': {
|
|
||||||
fontSize: { xs: '12px', sm: '14px' },
|
|
||||||
minWidth: { xs: 24, sm: 32 },
|
|
||||||
height: { xs: 24, sm: 32 },
|
|
||||||
backgroundColor: '#FFECE0',
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
borderRadius: '8px',
|
|
||||||
'&.Mui-selected': {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: '#fff',
|
|
||||||
},
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#FFD6B5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TopSellingProduct;
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
// src/components/AppBar/KitchPlusAppBar.jsx
|
||||||
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
@@ -7,31 +8,37 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Divider,
|
Divider,
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
|
||||||
Autocomplete,
|
|
||||||
TextField,
|
|
||||||
InputAdornment,
|
|
||||||
useTheme,
|
useTheme,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import NotificationsOutlinedIcon from '@mui/icons-material/NotificationsOutlined';
|
import HomeIcon from '@mui/icons-material/Home';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import { useRestaurant } from '../../contexts/RestaurantContext';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import authService from '../../services/authService';
|
||||||
|
import { UserContext } from '../../contexts/UserContext'; // ✅ استدعاء UserContext
|
||||||
const top100Films = [
|
|
||||||
{ title: 'The Shawshank Redemption' },
|
|
||||||
{ title: 'The Godfather' },
|
|
||||||
{ title: 'The Dark Knight' },
|
|
||||||
{ title: 'Pulp Fiction' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
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 (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
@@ -54,156 +61,66 @@ const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Left side with toggle button */}
|
{/* Left side */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box
|
||||||
{/* Toggle button for mobile/tablet */}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
mr: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{(isMobile || isMediumScreen) && (
|
{(isMobile || isMediumScreen) && (
|
||||||
<IconButton
|
<IconButton color="inherit" edge="start" onClick={onDrawerToggle}>
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
edge="start"
|
|
||||||
onClick={onDrawerToggle}
|
|
||||||
sx={{ mr: 2 }}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{location.pathname === '/dashboard' ? (
|
<Typography variant="h6" sx={{ fontWeight: '500' }}>
|
||||||
<Typography
|
Welcome to KitchPlus
|
||||||
variant="h6"
|
</Typography>
|
||||||
component="div"
|
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: { xs: '18px', sm: '20px', md: '24px' },
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
maxWidth: { xs: '200px', sm: 'none' }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Welcome to KitchPlus
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Autocomplete
|
|
||||||
sx={{
|
|
||||||
width: { xs: '200px', sm: '200px', md: '250px' },
|
|
||||||
borderRadius: '8px',
|
|
||||||
}}
|
|
||||||
freeSolo
|
|
||||||
id="free-solo-2-demo"
|
|
||||||
disableClearable
|
|
||||||
options={top100Films.map((option) => option.title)}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
placeholder="Search"
|
|
||||||
InputProps={{
|
|
||||||
...params.InputProps,
|
|
||||||
type: 'search',
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<SearchIcon sx={{ color: '#667085' }} />
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
'& .MuiInputBase-root': {
|
|
||||||
backgroundColor: 'white',
|
|
||||||
height: { xs: 36, sm: 38, md: 40 },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Right side */}
|
{/* Right side */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||||
{location.pathname === '/dashboard' && !isSmallScreen && (
|
{location.pathname === '/dashboard' && <Divider orientation="vertical" flexItem sx={{ height: 40 }} />}
|
||||||
<Button
|
|
||||||
variant="contained"
|
<IconButton
|
||||||
sx={{
|
color="#667085"
|
||||||
display: { xs: 'none', sm: 'none', md: 'flex' }, // هذا السطر يخفي الزر عند xs و sm ويظهره من md وما فوق
|
size={isSmallScreen ? 'small' : 'medium'}
|
||||||
color: 'white',
|
onClick={() => navigate('/restaurant')}
|
||||||
height: { xs: 32, sm: 36, md: 40 },
|
>
|
||||||
mr: { xs: '6px', sm: '8px', md: '0px' },
|
<HomeIcon fontSize={isSmallScreen ? 'medium' : 'medium'} />
|
||||||
backgroundColor: '#61677F',
|
|
||||||
borderRadius: '8px',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
|
||||||
textTransform: 'none',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isMediumScreen ? 'Switch Company' : 'Switch Company Profits'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{location.pathname === '/dashboard' && (
|
|
||||||
<Divider
|
|
||||||
orientation="vertical"
|
|
||||||
flexItem
|
|
||||||
sx={{
|
|
||||||
height: { xs: 30, sm: 36, md: 40 },
|
|
||||||
alignSelf: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<IconButton color="#667085" size={isSmallScreen ? 'small' : 'medium'}>
|
|
||||||
<NotificationsOutlinedIcon fontSize={isSmallScreen ? 'small' : 'medium'} />
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Divider
|
<Divider orientation="vertical" flexItem sx={{ height: 40 }} />
|
||||||
orientation="vertical"
|
|
||||||
flexItem
|
|
||||||
sx={{
|
|
||||||
height: { xs: 30, sm: 36, md: 40 },
|
|
||||||
alignSelf: 'center'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Avatar
|
<Avatar
|
||||||
alt="Admin"
|
alt="Restaurant Logo"
|
||||||
src="/images/waitress3.png"
|
src={restaurantLogo}
|
||||||
sx={{
|
sx={{ width: { xs: 28, sm: 32, md: 40 }, height: { xs: 28, sm: 32, md: 40 } }}
|
||||||
width: { xs: 28, sm: 32, md: 40 },
|
|
||||||
height: { xs: 28, sm: 32, md: 40 }
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isSmallScreen && (
|
{!isSmallScreen && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{
|
sx={{
|
||||||
ml: { sm: 0.5, md: 1 },
|
ml: 1,
|
||||||
color: '#61677F',
|
color: '#61677F',
|
||||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
fontSize: { xs: 12, sm: 13, md: 14 },
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
overflow: 'hidden',
|
overflow: 'visible',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'clip',
|
||||||
maxWidth: { xs: '100px', sm: '120px', md: 'none' }
|
maxWidth: 200,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Admin@gmail.com
|
{user?.email || 'Admin@gmail.com'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
sx={{
|
|
||||||
mt: 0.5,
|
|
||||||
color: '#A6ACB8',
|
|
||||||
padding: { xs: '4px', sm: '8px' }
|
|
||||||
}}
|
|
||||||
size={isSmallScreen ? 'small' : 'medium'}
|
|
||||||
>
|
|
||||||
<KeyboardArrowDownIcon fontSize={isSmallScreen ? 'small' : 'medium'} />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KitchPlusAppBar;
|
export default KitchPlusAppBar;
|
||||||
|
|||||||
74
src/components/Home/Cart/Cart.js
Normal file
74
src/components/Home/Cart/Cart.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import KitchPlusAppBar from '../AppBar';
|
||||||
|
import Sidebar from '../SideHome';
|
||||||
|
import Orders from './contect/Orders';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const Cart = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [theme.breakpoints.values.md]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const admin = authService.getAdminData();
|
||||||
|
// console.log('Admin Info:', admin);
|
||||||
|
|
||||||
|
const adminId = authService.getAdminId();
|
||||||
|
// console.log('Admin ID:', adminId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const admin = authService.getAdminData();
|
||||||
|
const adminId = authService.getAdminId();
|
||||||
|
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||||
|
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
transition: theme.transitions.create(['width'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KitchPlusAppBar onDrawerToggle={handleDrawerToggle} sidebarOpen={sidebarOpen} isMobile={isMobile} />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 6,
|
||||||
|
width: { xs: '90%', sm: '95%', md: '96%' },
|
||||||
|
pt: { xs: 2, sm: 3 },
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 4 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Orders adminId={adminId} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Cart;
|
||||||
223
src/components/Home/Cart/contect/CartDetails.js
Normal file
223
src/components/Home/Cart/contect/CartDetails.js
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
CircularProgress
|
||||||
|
} from '@mui/material';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||||
|
|
||||||
|
const CartDetails = ({ cart, onClose, onUpdated, onDeleted }) => {
|
||||||
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [totalPrice, setTotalPrice] = useState(cart?.attributes?.total_price || "");
|
||||||
|
const [cartItems, setCartItems] = useState(cart.relationships?.cartItems || cart.relationships?.cart_items || []);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
const { showSnackbar } = useSnackbar();
|
||||||
|
if (!cart) return null;
|
||||||
|
|
||||||
|
const handleSaveAll = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const payload = {
|
||||||
|
data: {
|
||||||
|
type: "cart",
|
||||||
|
attributes: {
|
||||||
|
totalPrice: Number(totalPrice),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
cartItems: cartItems.map(item => ({
|
||||||
|
attributes: {
|
||||||
|
quantity: Number(item.attributes.quantity),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
product: {
|
||||||
|
data: {
|
||||||
|
id: item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await authService.updateCart(cart.id, payload);
|
||||||
|
if (result.success) {
|
||||||
|
onUpdated(result.data);
|
||||||
|
setEditMode(false);
|
||||||
|
} else {
|
||||||
|
// alert(result.message || 'Failed to update cart');
|
||||||
|
showSnackbar(result.message || "Failed to update cart", "error");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// alert('An error occurred while updating the cart');
|
||||||
|
showSnackbar("An error occurred while updating the cart", "error");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteCart = async () => {
|
||||||
|
setDeleteLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.deleteCart(cart.id);
|
||||||
|
if (result.success) {
|
||||||
|
onDeleted(cart.id);
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
// alert(result.message || 'Failed to delete cart');
|
||||||
|
showSnackbar(result.message || "Failed to update cart", "error");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// alert('An error occurred while deleting the cart');
|
||||||
|
showSnackbar("An error occurred while deleting the cart", "error");
|
||||||
|
} finally {
|
||||||
|
setDeleteLoading(false);
|
||||||
|
setOpenDeleteDialog(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeQuantity = (itemId, newQuantity) => {
|
||||||
|
setCartItems(prev =>
|
||||||
|
prev.map(item =>
|
||||||
|
item.id === itemId
|
||||||
|
? { ...item, attributes: { ...item.attributes, quantity: newQuantity } }
|
||||||
|
: item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Paper sx={{ p: 3, mt: 2, borderRadius: 2, border: '1px solid #e0e0e0' }}>
|
||||||
|
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
|
||||||
|
Cart #{cart.id} Details
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{editMode ? (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
label="Total Price"
|
||||||
|
type="number"
|
||||||
|
value={totalPrice}
|
||||||
|
onChange={(e) => setTotalPrice(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||||
|
<List>
|
||||||
|
{cartItems.map(item => (
|
||||||
|
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={`Item ID: ${item.id} | Product: ${item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}`}
|
||||||
|
secondary={
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
label="Quantity"
|
||||||
|
value={item.attributes.quantity}
|
||||||
|
onChange={(e) => handleChangeQuantity(item.id, Number(e.target.value))}
|
||||||
|
sx={{ width: '120px' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
|
<Button variant="contained" onClick={handleSaveAll} disabled={loading}>
|
||||||
|
{loading ? "Saving..." : "Save All"}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" onClick={() => setEditMode(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography>Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}</Typography>
|
||||||
|
<Typography>
|
||||||
|
Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||||
|
<List>
|
||||||
|
{cartItems.map(item => (
|
||||||
|
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={`Item ID: ${item.id}`}
|
||||||
|
secondary={`Quantity: ${item.attributes.quantity} | Product: ${item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
|
<Button variant="contained" onClick={() => setEditMode(true)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={() => setOpenDeleteDialog(true)}
|
||||||
|
sx={{ ml: 'auto' }}
|
||||||
|
>
|
||||||
|
Delete Cart
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" onClick={onClose}>
|
||||||
|
Back to Orders
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={openDeleteDialog}
|
||||||
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
|
>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Are you sure you want to delete this cart? This action cannot be undone.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenDeleteDialog(false)} disabled={deleteLoading}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteCart}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={deleteLoading}
|
||||||
|
>
|
||||||
|
{deleteLoading ? <CircularProgress size={24} /> : 'Delete'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CartDetails;
|
||||||
170
src/components/Home/Cart/contect/CartView.js
Normal file
170
src/components/Home/Cart/contect/CartView.js
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React, { useContext, useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, LinearProgress, IconButton } from "@mui/material";
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import { CartContext } from "../../../../contexts/CartContextR";
|
||||||
|
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||||
|
|
||||||
|
const CartView = ({ onClose, onCartCreated, adminId }) => {
|
||||||
|
const { cart, clearCart, createNewCart } = useContext(CartContext);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { showSnackbar } = useSnackbar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Admin ID from props in:CartView', adminId);
|
||||||
|
}, [adminId]);
|
||||||
|
|
||||||
|
const totalPrice = cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0);
|
||||||
|
|
||||||
|
const handleSendCart = async () => {
|
||||||
|
if (!cart.length) return;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// تحقق من أن adminId موجود ضمن قائمة صالحة (يمكنك تعديلها حسب بياناتك)
|
||||||
|
const validAdminIds = [1, 2]; // IDs موجودة في DB
|
||||||
|
if (!validAdminIds.includes(adminId)) {
|
||||||
|
console.error("Invalid admin ID");
|
||||||
|
showSnackbar("Selected admin is not valid.", "error");
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// تحقق من أن جميع المنتجات موجودة في قاعدة البيانات
|
||||||
|
const validProductIds = [1, 2, 3, 4]; // IDs المنتجات الموجودة
|
||||||
|
for (let item of cart) {
|
||||||
|
if (!validProductIds.includes(item.id)) {
|
||||||
|
console.error(`Invalid product ID: ${item.id}`);
|
||||||
|
// alert(`Product with ID ${item.id} does not exist.`);
|
||||||
|
showSnackbar(`Product with ID ${item.id} does not exist.`, "error");
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تجهيز البيانات حسب شكل الـ backend
|
||||||
|
const cartData = {
|
||||||
|
data: {
|
||||||
|
type: "cart",
|
||||||
|
attributes: {
|
||||||
|
totalPrice: cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
admin: {
|
||||||
|
data: { id: adminId },
|
||||||
|
},
|
||||||
|
cartItems: cart.map(item => ({
|
||||||
|
attributes: { quantity: item.quantity },
|
||||||
|
relationships: { product: { data: { id: item.id } } },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newCart = await createNewCart(cartData);
|
||||||
|
|
||||||
|
if (newCart && newCart.success) {
|
||||||
|
onCartCreated(newCart.data);
|
||||||
|
clearCart();
|
||||||
|
onClose();
|
||||||
|
showSnackbar("Cart sent successfully!", "success");
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create cart:", newCart.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error sending cart:", error);
|
||||||
|
showSnackbar("Failed to create cart.", "error");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (loading) return <LinearProgress />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: "100%", sm: "93.5%" },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: 2,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* السهم للعودة */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
|
<IconButton onClick={onClose} size="small" sx={{ mr: 1 }}>
|
||||||
|
<ArrowBackIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 600, fontSize: { xs: "18px", sm: "20px" } }}>
|
||||||
|
Current Cart
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{cart.length === 0 ? (
|
||||||
|
<Typography sx={{ mt: 2, textAlign: "center" }}>No items in cart.</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{cart.map(item => (
|
||||||
|
<Box
|
||||||
|
key={item.id}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 1,
|
||||||
|
border: "1px solid #e0e0e0",
|
||||||
|
backgroundColor: "#fafafa",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography fontWeight={600}>{item.name}</Typography>
|
||||||
|
<Typography variant="body2">Unit: {item.unit}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ textAlign: { xs: "left", sm: "right" } }}>
|
||||||
|
<Typography variant="body2">Quantity: {item.quantity}</Typography>
|
||||||
|
<Typography variant="body2">Total: ${item.totalPrice}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1 }}>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||||
|
Total Price: ${totalPrice.toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cart.length > 0 && (
|
||||||
|
<Box sx={{ display: "flex", gap: 1, mt: 2, flexWrap: { xs: "wrap", sm: "nowrap" }, justifyContent: { xs: "center", sm: "flex-start" } }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSendCart}
|
||||||
|
sx={{ color: 'white', borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '200px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '200px' } }}
|
||||||
|
>
|
||||||
|
Send Cart to Server
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={clearCart}
|
||||||
|
sx={{ borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '150px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '150px' } }}
|
||||||
|
>
|
||||||
|
Clear Cart
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CartView;
|
||||||
389
src/components/Home/Cart/contect/Orders.js
Normal file
389
src/components/Home/Cart/contect/Orders.js
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
CircularProgress,
|
||||||
|
Button
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import { useRestaurant } from "../../../../contexts/RestaurantContext";
|
||||||
|
import CartDetails from './CartDetails';
|
||||||
|
import CartView from './CartView';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const handlePrev = () => { if (currentPage > 1) onChange(currentPage - 1); };
|
||||||
|
const handleNext = () => { if (currentPage < pageCount) onChange(currentPage + 1); };
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handlePrev}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#FFECE0',
|
||||||
|
'&:hover': { backgroundColor: '#FFD6B5' },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
'&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIosNewIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: '#fff',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
userSelect: 'none',
|
||||||
|
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentPage}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={currentPage >= pageCount}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#FFECE0',
|
||||||
|
'&:hover': { backgroundColor: '#FFD6B5' },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
'&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowForwardIosIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Orders = ({ adminId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
const [ordersData, setOrdersData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selectedCart, setSelectedCart] = useState(null);
|
||||||
|
const [cartLoading, setCartLoading] = useState(false);
|
||||||
|
const [showCartView, setShowCartView] = useState(false);
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const itemsPerPage = 6;
|
||||||
|
|
||||||
|
// ────────────── Fetch Orders ──────────────
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchOrders = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getCart();
|
||||||
|
if (result.success) {
|
||||||
|
console.log(Array.isArray(result.data) ? result.data : []);
|
||||||
|
setOrdersData(Array.isArray(result.data) ? result.data : []);
|
||||||
|
} else {
|
||||||
|
console.error(result.message);
|
||||||
|
setOrdersData([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch orders:', error);
|
||||||
|
setOrdersData([]);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchOrders();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ────────────── Row Click ──────────────
|
||||||
|
const handleRowClick = async (cartId) => {
|
||||||
|
setCartLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getCartById(cartId);
|
||||||
|
if (result.success) setSelectedCart(result.data);
|
||||||
|
else console.error(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch cart details:', error);
|
||||||
|
}
|
||||||
|
setCartLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCartUpdated = (updatedCart) => {
|
||||||
|
setOrdersData(prev =>
|
||||||
|
prev.map(cart => cart.id === updatedCart.id ? updatedCart : cart)
|
||||||
|
);
|
||||||
|
setSelectedCart(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseCartDetails = () => { setSelectedCart(null); };
|
||||||
|
|
||||||
|
const handleAddOrder = (newOrder) => {
|
||||||
|
setOrdersData(prev => [newOrder, ...prev]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageCount = Math.ceil(ordersData.length / itemsPerPage);
|
||||||
|
const paginatedOrders = ordersData.slice(
|
||||||
|
(currentPage - 1) * itemsPerPage,
|
||||||
|
currentPage * itemsPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cartLoading) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCart) {
|
||||||
|
return (
|
||||||
|
<CartDetails
|
||||||
|
cart={selectedCart}
|
||||||
|
onClose={handleCloseCartDetails}
|
||||||
|
onUpdated={handleCartUpdated}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showCartView) {
|
||||||
|
return (
|
||||||
|
<CartView
|
||||||
|
onClose={() => setShowCartView(false)}
|
||||||
|
onSend={handleAddOrder}
|
||||||
|
onCartCreated={(newCart) => {
|
||||||
|
setOrdersData(prev => [newCart, ...prev]);
|
||||||
|
setShowCartView(false);
|
||||||
|
}}
|
||||||
|
adminId={adminId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────── Main Orders Table ──────────────
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '100%', sm: '93.5%' },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'stretch', sm: 'center' },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '18px', sm: '20px' },
|
||||||
|
mb: { xs: 1, sm: 0 },
|
||||||
|
textAlign: { xs: 'center', sm: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cart
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowCartView(true)}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '100%', sm: '135px' },
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: { xs: 'unset', sm: '135px' },
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Current Cart
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
height: '380px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: 'auto' }}
|
||||||
|
aria-label="orders table"
|
||||||
|
size={isMobile ? 'small' : 'medium'}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||||
|
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Cart ID</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Total Price</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Created At</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Items Count</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Action</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading
|
||||||
|
? Array.from({ length: itemsPerPage }).map((_, idx) => (
|
||||||
|
<TableRow key={`skeleton-${idx}`}>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{paginatedOrders.map(order => {
|
||||||
|
const attributes = order?.attributes || {};
|
||||||
|
const relationships = order?.relationships || {};
|
||||||
|
const cartItems = relationships?.cartItems || relationships?.cart_items || [];
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={order?.id}
|
||||||
|
hover
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.3s',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#ffe4d0ff !important',
|
||||||
|
'& td': { color: '#000000ff' },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => handleRowClick(order.id)}
|
||||||
|
>
|
||||||
|
<TableCell>{order?.id || '--'}</TableCell>
|
||||||
|
<TableCell>{attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}</TableCell>
|
||||||
|
<TableCell>{attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}</TableCell>
|
||||||
|
<TableCell>{cartItems.length}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={ async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const cart = order;
|
||||||
|
if (!cart) return;
|
||||||
|
|
||||||
|
const orderData = {
|
||||||
|
data: {
|
||||||
|
type: "order",
|
||||||
|
attributes: {
|
||||||
|
totalPrice: parseFloat(cart.attributes.total_price) || 0,
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
restaurant: {
|
||||||
|
data: {
|
||||||
|
id: restaurantId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderItems: (cart.relationships.cart_items || []).map((item) => ({
|
||||||
|
attributes: {
|
||||||
|
quantity: item.attributes.quantity,
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
product: {
|
||||||
|
data: {
|
||||||
|
id: item.relationships.supplier_product.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
authService.confirmOrder(orderData);
|
||||||
|
|
||||||
|
const result =await authService.confirmOrder(orderData);
|
||||||
|
if (result.success) {
|
||||||
|
setOrdersData(prev => prev.filter(c => c.id !== cart.id));
|
||||||
|
} else {
|
||||||
|
console.error(result.message);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{paginatedOrders.length < itemsPerPage &&
|
||||||
|
Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
|
||||||
|
<TableRow key={`empty-${idx}`} sx={{ height: 53 }}>
|
||||||
|
<TableCell colSpan={4} sx={{ borderBottom: 'none' }} />
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
pt={{ xs: 1, sm: 2 }}
|
||||||
|
sx={{
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderTop: '1px solid #f0f0f0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontSize: { xs: '11px', sm: '14px' }, whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Orders;
|
||||||
@@ -8,27 +8,25 @@ const drawerWidth = 230;
|
|||||||
const Cashier = () => {
|
const Cashier = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const [hasProducts, setHasProducts] = useState(false); // حالة لتتبع وجود المنتجات
|
const [hasProducts, setHasProducts] = useState(false);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
// محاكاة للتحقق من وجود المنتجات (استبدل هذا بمنطقك الفعلي)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// هنا يجب استبدال هذا بمنطق فعلي للتحقق من وجود المنتجات
|
|
||||||
// مثلاً استدعاء API أو التحقق من state
|
|
||||||
const checkProducts = async () => {
|
const checkProducts = async () => {
|
||||||
// محاكاة لاستدعاء API
|
|
||||||
const productsExist = await checkIfProductsExist(); // استبدل هذه الدالة بمنطقك الفعلي
|
const productsExist = await checkIfProductsExist();
|
||||||
setHasProducts(productsExist);
|
setHasProducts(productsExist);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkProducts();
|
checkProducts();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// دالة مساعدة لمحاكاة التحقق من المنتجات (استبدلها بمنطقك الفعلي)
|
|
||||||
const checkIfProductsExist = async () => {
|
const checkIfProductsExist = async () => {
|
||||||
// محاكاة - يمكن أن يكون هذا استدعاء لـ API أو تحقق من state
|
|
||||||
// return true;
|
return false;
|
||||||
return false; // غير هذه القيمة حسب منطقك
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,7 +60,7 @@ const Cashier = () => {
|
|||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
backgroundColor:'#F6F6F6',
|
backgroundColor: '#F6F6F6',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
|
|||||||
136
src/components/Home/CreateNewRestaurant/AppBar.js
Normal file
136
src/components/Home/CreateNewRestaurant/AppBar.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
AppBar,
|
||||||
|
Toolbar,
|
||||||
|
Box,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import LogoutIcon from '@mui/icons-material/Logout';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
const result = await authService.logout();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
navigate('/login');
|
||||||
|
} else {
|
||||||
|
console.error('Logout failed:', result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar
|
||||||
|
sx={{
|
||||||
|
height: { xs: 56, sm: 64, md: 66 },
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
color: 'black',
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderBottom: '1px solid #e0e0e0',
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: theme.zIndex.appBar,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: '24px' },
|
||||||
|
minHeight: { xs: '56px !important', sm: '64px !important' },
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Left: Logo */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src="/image.png"
|
||||||
|
alt="logo"
|
||||||
|
sx={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
objectFit: 'contain',
|
||||||
|
mr: 1.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
color: 'text.primary',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
KITCH
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
color: 'primary.main',
|
||||||
|
ml: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
PLUS
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Right: Log Out Button */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||||
|
<Box
|
||||||
|
onClick={handleLogout}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#fffcf9d5',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
color: 'divider',
|
||||||
|
minWidth: 36,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LogoutIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary="Log Out"
|
||||||
|
primaryTypographyProps={{
|
||||||
|
sx: {
|
||||||
|
color: 'divider',
|
||||||
|
fontSize: '1rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KitchPlusAppBar;
|
||||||
228
src/components/Home/CreateNewRestaurant/CreateRestaurant.js
Normal file
228
src/components/Home/CreateNewRestaurant/CreateRestaurant.js
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery, Snackbar, Alert } from '@mui/material';
|
||||||
|
import AppBar from './AppBar';
|
||||||
|
|
||||||
|
import CloudKitchenProject from '../CreateYourRestaurant/contcet/CloudKitchenProject';
|
||||||
|
import OperationalDetails from '../CreateYourRestaurant/contcet/OperationalDetails';
|
||||||
|
import RequiredEquipments from '../CreateYourRestaurant/contcet/RequiredEquipments';
|
||||||
|
import VisualIdentity from '../CreateYourRestaurant/contcet/VisualIdentity';
|
||||||
|
import Budget from '../CreateYourRestaurant/contcet/Budget';
|
||||||
|
import AdditionalNotes from '../CreateYourRestaurant/contcet/AdditionalNotes';
|
||||||
|
import SideProfile from './SideProfile';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const { createNewRestaurant } = authService;
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const CreateRestaurant = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
const [formData, setFormData] = useState({});
|
||||||
|
const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
|
||||||
|
|
||||||
|
const updateFormData = (newData) => {
|
||||||
|
setFormData(prev => {
|
||||||
|
const updated = { ...prev, ...newData };
|
||||||
|
console.log('Updated formData:', updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseSnackbar = (event, reason) => {
|
||||||
|
if (reason === 'clickaway') return;
|
||||||
|
setSnackbar(prev => ({ ...prev, open: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const result = await createNewRestaurant(formData);
|
||||||
|
if (result.success) {
|
||||||
|
setSnackbar({ open: true, message: 'Restaurant created successfully!', severity: 'success' });
|
||||||
|
} else {
|
||||||
|
setSnackbar({ open: true, message: `Error: ${result.message}`, severity: 'error' });
|
||||||
|
}
|
||||||
|
console.log("Form Data being submitted:", formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
<CloudKitchenProject
|
||||||
|
key="step-0"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<OperationalDetails
|
||||||
|
key="step-1"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<RequiredEquipments
|
||||||
|
key="step-2"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<VisualIdentity
|
||||||
|
key="step-3"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<Budget
|
||||||
|
key="step-4"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<AdditionalNotes
|
||||||
|
key="step-5"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||||
|
setSidebarOpen(true);
|
||||||
|
} else {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [theme.breakpoints.values.md]);
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => {
|
||||||
|
setSidebarOpen(!sidebarOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: '#F6F6F6',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||||
|
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||||
|
transition: theme.transitions.create(['width'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppBar
|
||||||
|
onDrawerToggle={handleDrawerToggle}
|
||||||
|
sidebarOpen={sidebarOpen}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Sidebar profile for desktop */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: '100vh',
|
||||||
|
ml: 3,
|
||||||
|
mb: 2,
|
||||||
|
width: { md: '30%' },
|
||||||
|
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: '100%',
|
||||||
|
pb: 15,
|
||||||
|
pt: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SideProfile
|
||||||
|
currentStepIndex={currentStep}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Step content */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
ml: { xs: 2, md: 3 },
|
||||||
|
flexGrow: 1,
|
||||||
|
height: '100vh',
|
||||||
|
pr: { sm: 2, md: 1 },
|
||||||
|
pt: 3,
|
||||||
|
mb: { sm: 20 },
|
||||||
|
width: { md: '60%' },
|
||||||
|
display: { xs: 'block', sm: 'block', md: 'block' },
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ minHeight: '100%', pb: 18 }}>
|
||||||
|
{steps[currentStep]}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={snackbar.open}
|
||||||
|
autoHideDuration={4000}
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert onClose={handleCloseSnackbar} severity={snackbar.severity}
|
||||||
|
sx={{
|
||||||
|
width: { xs: '40%', sm: '60%', md: '100%' },
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: '500',
|
||||||
|
backgroundColor: '#e57f3f',
|
||||||
|
borderRadius: 6, mb: 6
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{snackbar.message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateRestaurant;
|
||||||
@@ -3,12 +3,12 @@ import { Box, Typography, Stack, Button, useTheme } from '@mui/material';
|
|||||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{ title: 'Cloud Kitchen Hosting', icon: '/images/createProfile/BasicInf.png' },
|
{ title: 'Cloud Kitchen Project', icon: '/images/createProfile/BasicInf.png' },
|
||||||
{ title: 'Restaurant Operations', icon: '/images/icons/rocket.png' },
|
{ title: 'OperationalDetails', icon: '/images/icons/rocket.png' },
|
||||||
{ title: 'Infrastructure & Equipment', icon: '/images/createProfile/equipment.png' },
|
{ title: 'Required Equipments', icon: '/images/createProfile/equipment.png'},
|
||||||
{ title: 'Facilitaion & Cooperation', icon: '/images/createProfile/Facilitaion.png' },
|
{ title: 'Visual Identity', icon: '/images/createProfile/Vector.png' },
|
||||||
{ title: 'Expansion & Future Cooperation', icon: '/images/createProfile/Expansion.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' },
|
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
px: 3,
|
px: 3,
|
||||||
py: 4,
|
py: 4,
|
||||||
display: 'block' ,
|
display: { xs: 'none', md: 'block' },
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -30,14 +30,14 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
>
|
>
|
||||||
{/* العنوان الرئيسي */}
|
{/* العنوان الرئيسي */}
|
||||||
<Typography fontSize="18px" fontWeight={600} mb={1.2}>
|
<Typography fontSize="18px" fontWeight={600} mb={1.2}>
|
||||||
Host Kitchen Flow
|
Create Your Restaurant
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" mb={3}>
|
<Typography variant="body2" color="text.secondary" mb={3}>
|
||||||
Complete this process to register your restaurant on our amazing food platform.
|
Complete this process to register your restaurant on our amazing food platform.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* الخطوات */}
|
{/* الخطوات */}
|
||||||
<Stack spacing={5} sx={{ ml: 1, position: 'relative', pb: 15 }}>
|
<Stack spacing={3} sx={{ ml: 1, position: 'relative', pb: 12 }}>
|
||||||
{steps.map((step, index) => (
|
{steps.map((step, index) => (
|
||||||
<Box key={index} position="relative">
|
<Box key={index} position="relative">
|
||||||
{/* الخط الرأسي بين الدوائر */}
|
{/* الخط الرأسي بين الدوائر */}
|
||||||
@@ -45,10 +45,10 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: {sm:'50px',md:'40px'},
|
top: '28px',
|
||||||
left: '22px',
|
left: '23px',
|
||||||
width: '2px',
|
width: '2px',
|
||||||
height: {sm:'130%',md:'calc(100% - 0px)'},
|
height: 'calc(100% - 5px)',
|
||||||
backgroundColor: '#E0E0E0',
|
backgroundColor: '#E0E0E0',
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
}}
|
}}
|
||||||
@@ -87,7 +87,6 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* الدائرة الأمامية بالأيقونة */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: 40,
|
width: 40,
|
||||||
@@ -106,7 +105,7 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
style={{
|
style={{
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
marginLeft: step.title === 'Infrastructure & Equipment' ? 7 : 0,
|
marginLeft: step.title === 'Required Equipments' ? 7 : 0,
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -134,7 +133,6 @@ const SideProfile = ({ currentStepIndex = 0, onBack }) => {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
right: 24,
|
right: 24,
|
||||||
mt:10
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
import { Box, useTheme, useMediaQuery, Snackbar, Alert } from '@mui/material';
|
||||||
import KitchPlusAppBar from '../AppBar';
|
import KitchPlusAppBar from '../AppBar';
|
||||||
import Sidebar from '../SideHome';
|
import Sidebar from '../SideHome';
|
||||||
import CloudKitchenProject from './contcet/CloudKitchenProject';
|
import CloudKitchenProject from './contcet/CloudKitchenProject';
|
||||||
@@ -10,7 +10,9 @@ import Budget from './contcet/Budget';
|
|||||||
import AdditionalSupport from './contcet/AdditionalSupport';
|
import AdditionalSupport from './contcet/AdditionalSupport';
|
||||||
import AdditionalNotes from './contcet/AdditionalNotes';
|
import AdditionalNotes from './contcet/AdditionalNotes';
|
||||||
import SideProfile from './SideProfile';
|
import SideProfile from './SideProfile';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const { createNewRestaurant } = authService;
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
const CreateRestaurant = () => {
|
const CreateRestaurant = () => {
|
||||||
@@ -19,30 +21,98 @@ const CreateRestaurant = () => {
|
|||||||
const [hasProducts, setHasProducts] = useState(false);
|
const [hasProducts, setHasProducts] = useState(false);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
// ⬇️ إدارة الخطوة الحالية
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
|
// إدارة بيانات النموذج لجميع الخطوات (مشاركة البيانات بين الخطوات)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// دالة لتحديث بيانات النموذج بشكل مرن (دمج الجديد مع القديم)
|
||||||
|
const updateFormData = (newData) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
...newData,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// دالة لإغلاق السناك بار
|
||||||
|
const handleSnackbarClose = (event, reason) => {
|
||||||
|
if (reason === 'clickaway') return;
|
||||||
|
setSnackbarOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await createNewRestaurant(formData);
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
<CloudKitchenProject onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
<CloudKitchenProject
|
||||||
<OperationalDetails onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
key="step-0"
|
||||||
<RequiredEquipments onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
formData={formData}
|
||||||
<VisualIdentity onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
updateFormData={updateFormData}
|
||||||
<Budget onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
<AdditionalSupport onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
<AdditionalNotes onBack={() => setCurrentStep(currentStep - 1)} />,
|
/>,
|
||||||
|
<OperationalDetails
|
||||||
|
key="step-1"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<RequiredEquipments
|
||||||
|
key="step-2"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<VisualIdentity
|
||||||
|
key="step-3"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<Budget
|
||||||
|
key="step-4"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep(prev => Math.min(prev + 1, steps.length - 1))}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
/>,
|
||||||
|
<AdditionalNotes
|
||||||
|
key="step-5"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkProducts = async () => {
|
|
||||||
const productsExist = await checkIfProductsExist();
|
|
||||||
setHasProducts(productsExist);
|
|
||||||
};
|
|
||||||
checkProducts();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkIfProductsExist = async () => {
|
// useEffect(() => {
|
||||||
return false;
|
// const checkProducts = async () => {
|
||||||
};
|
// const productsExist = await checkIfProductsExist();
|
||||||
|
// setHasProducts(productsExist);
|
||||||
|
// };
|
||||||
|
// checkProducts();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// const checkIfProductsExist = async () => {
|
||||||
|
// return false;
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||||
@@ -71,12 +141,14 @@ const CreateRestaurant = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
height: '100vh',
|
display: 'flex',
|
||||||
backgroundColor: '#F6F6F6',
|
height: '100vh',
|
||||||
overflow: 'hidden',
|
backgroundColor: '#F6F6F6',
|
||||||
}}>
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
open={sidebarOpen}
|
open={sidebarOpen}
|
||||||
onClose={handleDrawerToggle}
|
onClose={handleDrawerToggle}
|
||||||
@@ -84,82 +156,102 @@ const CreateRestaurant = () => {
|
|||||||
drawerWidth={drawerWidth}
|
drawerWidth={drawerWidth}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
flexGrow: 1,
|
sx={{
|
||||||
display: 'flex',
|
flexGrow: 1,
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
flexDirection: 'column',
|
||||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
width: { xs: '100%', sm: '100%', md: '100%' },
|
||||||
transition: theme.transitions.create(['width'], {
|
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||||
easing: theme.transitions.easing.sharp,
|
transition: theme.transitions.create(['width'], {
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
easing: theme.transitions.easing.sharp,
|
||||||
}),
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}}>
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<KitchPlusAppBar
|
<KitchPlusAppBar
|
||||||
onDrawerToggle={handleDrawerToggle}
|
onDrawerToggle={handleDrawerToggle}
|
||||||
sidebarOpen={sidebarOpen}
|
sidebarOpen={sidebarOpen}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex', height: '100vh',
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
ml: 3,
|
}}
|
||||||
mb: 2,
|
>
|
||||||
width: { md: '30%' },
|
<Box
|
||||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
sx={{
|
||||||
overflowY: 'auto',
|
height: '100vh',
|
||||||
scrollbarWidth: 'none',
|
ml: 3,
|
||||||
'&::-webkit-scrollbar': {
|
mb: 2,
|
||||||
display: 'none',
|
width: { md: '30%' },
|
||||||
},
|
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||||
}}>
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
<Box sx={{
|
'&::-webkit-scrollbar': {
|
||||||
minHeight: '100%', pb: 15, pt: 3,
|
display: 'none',
|
||||||
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: '100%',
|
||||||
|
pb: 15,
|
||||||
|
pt: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SideProfile
|
<SideProfile
|
||||||
currentStepIndex={currentStep}
|
currentStepIndex={currentStep}
|
||||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
ml: { xs: 2, md: 3 },
|
sx={{
|
||||||
|
ml: { xs: 2, md: 3 },
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
pr: { sm: 2, md: 1 },
|
pr: { sm: 2, md: 1 },
|
||||||
|
pt: 3,
|
||||||
pt: 3,
|
mb: { sm: 20 },
|
||||||
mb: { sm: 20 },
|
width: { md: '60%' },
|
||||||
width: { md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
display: { xs: 'block', sm: 'block', md: 'block' },
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
'&::-webkit-scrollbar': {
|
'&::-webkit-scrollbar': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
minHeight: '100%', pb: 18,
|
<Box
|
||||||
|
sx={{
|
||||||
}}>
|
minHeight: '100%',
|
||||||
|
pb: 18,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{steps[currentStep]}
|
{steps[currentStep]}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={snackbarOpen}
|
||||||
|
autoHideDuration={4000}
|
||||||
|
onClose={handleSnackbarClose}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert onClose={handleSnackbarClose} severity="success" sx={{ width: '100%' }}>
|
||||||
|
عملية إنشاء المطعم تمت بنجاح
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default CreateRestaurant;
|
||||||
export default CreateRestaurant;
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ const steps = [
|
|||||||
{ title: 'Required Equipments', icon: '/images/createProfile/equipment.png'},
|
{ title: 'Required Equipments', icon: '/images/createProfile/equipment.png'},
|
||||||
{ title: 'Visual Identity', icon: '/images/createProfile/Vector.png' },
|
{ title: 'Visual Identity', icon: '/images/createProfile/Vector.png' },
|
||||||
{ title: 'Budget & Expansion', icon: '/images/createProfile/Expansion.png' },
|
{ title: 'Budget & Expansion', icon: '/images/createProfile/Expansion.png' },
|
||||||
{ title: 'Additional Support', icon: '/images/createProfile/hand.png' },
|
|
||||||
{ title: 'Additional Notes & Concerns', icon: '/images/createProfile/Group.png' },
|
{ title: 'Additional Notes & Concerns', icon: '/images/createProfile/Group.png' },
|
||||||
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,151 +1,171 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
Button,
|
Button,
|
||||||
useTheme,
|
useTheme,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import ConfirmationDialog from './ConfirmationDialog';
|
||||||
import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const AdditionalNotes = ({ currentStepIndex = 0, onNext, onBack }) => {
|
const AdditionalNotes = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData, onSubmit }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleOpenModal = () => setOpenModal(true);
|
const navigate = useNavigate();
|
||||||
const handleCloseModal = () => setOpenModal(false);
|
const location = useLocation();
|
||||||
const handleConfirmNext = () => {
|
|
||||||
handleCloseModal();
|
|
||||||
onNext();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const handleOpenModal = () => setOpenModal(true);
|
||||||
<Box
|
const handleCloseModal = () => setOpenModal(false);
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: 740 },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: { xs: 4, md: 11 },
|
|
||||||
pb: 10,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography fontWeight={700} sx={{
|
|
||||||
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
|
||||||
}}>
|
|
||||||
Additional Notes
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
const handleNotesChange = (e) => {
|
||||||
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
updateFormData({ need_help: e.target.value });
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
};
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/*Notes / Concerns Input */}
|
const handleConfirmSubmit = async () => {
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
setLoading(true);
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
try {
|
||||||
Notes / Concerns
|
if (onSubmit) {
|
||||||
</Typography>
|
await onSubmit();
|
||||||
<TextField
|
}
|
||||||
placeholder="Write for us"
|
setOpenModal(false);
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
minRows={7}
|
|
||||||
sx={{
|
|
||||||
'& .MuiInputBase-root': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px',
|
|
||||||
alignItems: 'start',
|
|
||||||
},
|
|
||||||
'& textarea::placeholder': {
|
|
||||||
color: '#969BA7',
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
// التوجيه فقط إذا كانت الصفحة هي /create-restaurant
|
||||||
|
if (location.pathname === '/create-restaurant') {
|
||||||
|
navigate('/restaurant');
|
||||||
|
}
|
||||||
|
// إذا كانت من رابط آخر مثل /create-kitchen لا نفعل شيئًا
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
{/* Buttons */}
|
return (
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box
|
||||||
<Button
|
sx={{
|
||||||
variant="contained"
|
height: { xs: '90%', sm: '100%', md: 740 },
|
||||||
fullWidth
|
backgroundColor: '#FFFFFF',
|
||||||
onClick={handleOpenModal}
|
px: 4,
|
||||||
sx={{
|
pt: { xs: 4, md: 11 },
|
||||||
fontFamily: 'PlusJakartaSans',
|
pb: 10,
|
||||||
fontWeight: 600,
|
display: 'block',
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
borderRadius: 2,
|
||||||
height: { xs: '45px', sm: '52px' },
|
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
||||||
borderRadius: '50px',
|
position: 'relative',
|
||||||
textTransform: 'none',
|
width: { xs: '85%', sm: '90%' },
|
||||||
color: 'white',
|
}}
|
||||||
backgroundColor: theme.palette.primary.main,
|
>
|
||||||
'&:hover': {
|
<Stack spacing={2.5}>
|
||||||
backgroundColor: theme.palette.primary.hover
|
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||||
}
|
Additional Notes
|
||||||
}}
|
</Typography>
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Box sx={{ width: '70%' }}>
|
||||||
variant="outlined"
|
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||||
fullWidth
|
Enter your basic information to proceed to registration of your own restaurant on this platform
|
||||||
onClick={onBack}
|
</Typography>
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' },
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* ✅ Confirmation Modal */}
|
|
||||||
<ConfirmationDialog
|
|
||||||
open={openModal}
|
|
||||||
onClose={handleCloseModal}
|
|
||||||
onConfirm={handleConfirmNext}
|
|
||||||
title="Confirm Submission"
|
|
||||||
description="Are you sure you want to proceed to the next step?"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||||
|
Notes / Concerns
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="Write for us"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
minRows={7}
|
||||||
|
value={formData.need_help || ''}
|
||||||
|
onChange={handleNotesChange}
|
||||||
|
sx={{
|
||||||
|
'& .MuiInputBase-root': {
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '15px',
|
||||||
|
alignItems: 'start',
|
||||||
|
},
|
||||||
|
'& textarea::placeholder': {
|
||||||
|
color: '#969BA7',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ pt: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={handleOpenModal}
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'PlusJakartaSans',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
|
height: { xs: '45px', sm: '52px' },
|
||||||
|
borderRadius: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.hover
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
onClick={onBack}
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
fontFamily: 'PlusJakartaSans',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
|
height: { xs: '45px', sm: '52px' },
|
||||||
|
borderRadius: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<ConfirmationDialog
|
||||||
|
open={openModal}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
onConfirm={handleConfirmSubmit}
|
||||||
|
loading={loading}
|
||||||
|
title="Confirm Submission"
|
||||||
|
description="Are you sure you want to proceed to the next step?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdditionalNotes;
|
export default AdditionalNotes;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
const Budget = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -12,6 +11,10 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
else navigate('/dashboard');
|
else navigate('/dashboard');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (field) => (e) => {
|
||||||
|
updateFormData({ [field]: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -44,20 +47,29 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Estimated Budget Input */}
|
{/* Estimated Budget Input */}
|
||||||
<InputField label="Estimated Budget" placeholder="$4200" theme={theme} />
|
<InputField
|
||||||
|
label="Estimated Budget"
|
||||||
|
placeholder="$4200"
|
||||||
|
theme={theme}
|
||||||
|
value={formData.estimated_budget || ''}
|
||||||
|
onChange={handleInputChange('estimated_budget')}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Expansion Plans Through Cloud Kitchen (No of Branches) Input */}
|
{/* Expansion Plans Through Cloud Kitchen (No of Branches) Input */}
|
||||||
<InputField label="Expansion Plans Through Cloud Kitchen (No of Branches)" placeholder="200" theme={theme} />
|
<InputField
|
||||||
|
label="Expansion Plans Through Cloud Kitchen (No of Branches)"
|
||||||
|
placeholder="200"
|
||||||
|
theme={theme}
|
||||||
|
value={formData.expansion_branches || ''}
|
||||||
|
onChange={handleInputChange('expansion_branches')}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Next Button */}
|
{/* Next Button */}
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -75,7 +87,6 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -88,7 +99,7 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -100,14 +111,12 @@ const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// مكون فرعي لتقليل التكرار في الحقول
|
const InputField = ({ label, placeholder, theme, value, onChange }) => (
|
||||||
const InputField = ({ label, placeholder, theme }) => (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||||
{label}
|
{label}
|
||||||
@@ -116,6 +125,8 @@ const InputField = ({ label, placeholder, theme }) => (
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
|||||||
@@ -1,12 +1,85 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
Box,
|
||||||
|
Typography,
|
||||||
|
Stack,
|
||||||
|
Button,
|
||||||
|
useTheme,
|
||||||
|
TextField,
|
||||||
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
MenuItem,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText
|
||||||
} from '@mui/material';
|
} 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 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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -47,92 +120,150 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{[
|
{/* Restaurant Name */}
|
||||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
{ label: 'Cuisine Type', placeholder: 'Italian, Chinese, etc.' },
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
].map((field, index) => (
|
Restaurant Name
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
</Typography>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<TextField
|
||||||
{field.label}
|
name="name"
|
||||||
</Typography>
|
placeholder="Al-Baik Foods"
|
||||||
<TextField
|
variant="outlined"
|
||||||
placeholder={field.placeholder}
|
fullWidth
|
||||||
variant="outlined"
|
value={formData.name || ''}
|
||||||
fullWidth
|
onChange={handleChange}
|
||||||
sx={{
|
error={!!errors.name}
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
helperText={errors.name}
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
sx={{
|
||||||
'& .MuiOutlinedInput-root': {
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
borderRadius: '10px',
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
transition: '0.3s',
|
'& .MuiOutlinedInput-root': {
|
||||||
'&.Mui-focused fieldset': {
|
borderRadius: '10px',
|
||||||
borderColor: theme.palette.primary.main,
|
transition: '0.3s',
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
'&.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)'
|
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
/>
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
</Box>
|
borderColor: '#3f51b5',
|
||||||
))}
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Cuisine Type */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Cuisine Type
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
name="cuisine_type_id"
|
||||||
|
placeholder="Select Cuisine Type"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formData.cuisine_type_id || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={!!errors.cuisine_type_id}
|
||||||
|
helperText={errors.cuisine_type_id}
|
||||||
|
sx={{
|
||||||
|
'& .MuiSelect-select': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cuisineTypes.length > 0 ? (
|
||||||
|
cuisineTypes.map((type) => (
|
||||||
|
<MenuItem key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<MenuItem disabled>No Cuisine Types Found</MenuItem>
|
||||||
|
)}
|
||||||
|
</TextField>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{}}>
|
{/* Existing Brand */}
|
||||||
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||||
>
|
>
|
||||||
Existing Brand
|
Existing Brand
|
||||||
</Typography>
|
</Typography>
|
||||||
<RadioGroup row name="additionalFacilities">
|
<FormControl error={!!errors.is_existing_brand}>
|
||||||
<FormControlLabel
|
<RadioGroup
|
||||||
value="yes"
|
row
|
||||||
control={
|
name="is_existing_brand"
|
||||||
<Radio
|
value={formData.is_existing_brand ? 'yes' : 'no'}
|
||||||
sx={{
|
onChange={handleChange}
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
>
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
<FormControlLabel
|
||||||
'&.Mui-checked': {
|
value="yes"
|
||||||
color: theme.palette.primary.main
|
control={
|
||||||
}
|
<Radio
|
||||||
}}
|
sx={{
|
||||||
/>
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
}
|
transform: 'scale(0.85)',
|
||||||
label="Yes"
|
'&.Mui-checked': {
|
||||||
/>
|
color: theme.palette.primary.main
|
||||||
<FormControlLabel
|
}
|
||||||
value="no"
|
}}
|
||||||
control={
|
/>
|
||||||
<Radio
|
}
|
||||||
sx={{
|
label="Yes"
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
/>
|
||||||
transform: 'scale(0.85)',
|
<FormControlLabel
|
||||||
'&.Mui-checked': {
|
value="no"
|
||||||
color: theme.palette.primary.main
|
control={
|
||||||
}
|
<Radio
|
||||||
}}
|
sx={{
|
||||||
/>
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
}
|
transform: 'scale(0.85)',
|
||||||
label="No"
|
'&.Mui-checked': {
|
||||||
/>
|
color: theme.palette.primary.main
|
||||||
</RadioGroup>
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="No"
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
{errors.is_existing_brand && (
|
||||||
|
<FormHelperText>{errors.is_existing_brand}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Further Brand Details Input */}
|
{/* Further Brand Details */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||||
Further Brand Details
|
Further Brand Details
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
|
name="brand_details"
|
||||||
placeholder="We need Pizza Machine"
|
placeholder="We need Pizza Machine"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
minRows={3}
|
minRows={3}
|
||||||
|
value={formData.brand_details || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={!!errors.brand_details}
|
||||||
|
helperText={errors.brand_details}
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiInputBase-root': {
|
'& .MuiInputBase-root': {
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@@ -158,122 +289,130 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{[
|
{/* Age Group */}
|
||||||
{ label: 'Age Group', placeholder: '15 - 75' },
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
{ label: 'Location', placeholder: 'Street 123, Jordan' },
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
].map((field, index) => (
|
Age Group
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
</Typography>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<TextField
|
||||||
{field.label}
|
name="age_group"
|
||||||
</Typography>
|
placeholder="15 - 75"
|
||||||
<TextField
|
variant="outlined"
|
||||||
placeholder={field.placeholder}
|
fullWidth
|
||||||
variant="outlined"
|
value={formData.age_group || ''}
|
||||||
fullWidth
|
onChange={handleChange}
|
||||||
sx={{
|
error={!!errors.age_group}
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
helperText={errors.age_group}
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
sx={{
|
||||||
'& .MuiOutlinedInput-root': {
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
borderRadius: '10px',
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
transition: '0.3s',
|
'& .MuiOutlinedInput-root': {
|
||||||
'&.Mui-focused fieldset': {
|
borderRadius: '10px',
|
||||||
borderColor: theme.palette.primary.main,
|
transition: '0.3s',
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
'&.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)'
|
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
/>
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
</Box>
|
borderColor: '#3f51b5',
|
||||||
))}
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Location
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
name="location"
|
||||||
|
placeholder="Street 123, Jordan"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formData.location || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={!!errors.location}
|
||||||
|
helperText={errors.location}
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{}}>
|
{/* Menu Status */}
|
||||||
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||||
>
|
>
|
||||||
Menu Status
|
Menu Status
|
||||||
</Typography>
|
</Typography>
|
||||||
<RadioGroup row name="additionalFacilities">
|
<FormControl error={!!errors.menu_status}>
|
||||||
<FormControlLabel
|
<RadioGroup
|
||||||
value="yes"
|
row
|
||||||
control={
|
name="menu_status"
|
||||||
<Radio
|
value={formData.menu_status ? 'yes' : 'no'}
|
||||||
sx={{
|
onChange={handleChange}
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
>
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
<FormControlLabel
|
||||||
'&.Mui-checked': {
|
value="yes"
|
||||||
color: theme.palette.primary.main
|
control={
|
||||||
}
|
<Radio
|
||||||
}}
|
sx={{
|
||||||
/>
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
}
|
transform: 'scale(0.85)',
|
||||||
label="Yes"
|
'&.Mui-checked': {
|
||||||
/>
|
color: theme.palette.primary.main
|
||||||
<FormControlLabel
|
}
|
||||||
value="no"
|
}}
|
||||||
control={
|
/>
|
||||||
<Radio
|
}
|
||||||
sx={{
|
label="Yes"
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
/>
|
||||||
transform: 'scale(0.85)',
|
<FormControlLabel
|
||||||
'&.Mui-checked': {
|
value="no"
|
||||||
color: theme.palette.primary.main
|
control={
|
||||||
}
|
<Radio
|
||||||
}}
|
sx={{
|
||||||
/>
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
}
|
transform: 'scale(0.85)',
|
||||||
label="No"
|
'&.Mui-checked': {
|
||||||
/>
|
color: theme.palette.primary.main
|
||||||
</RadioGroup>
|
}
|
||||||
|
}}
|
||||||
</Box>
|
/>
|
||||||
|
}
|
||||||
{/* Further Brand Details Input */}
|
label="No"
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
/>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
</RadioGroup>
|
||||||
Need Help
|
{errors.menu_status && (
|
||||||
</Typography>
|
<FormHelperText>{errors.menu_status}</FormHelperText>
|
||||||
<TextField
|
)}
|
||||||
placeholder="Write here..."
|
</FormControl>
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
minRows={3}
|
|
||||||
sx={{
|
|
||||||
'& .MuiInputBase-root': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px',
|
|
||||||
alignItems: 'start',
|
|
||||||
},
|
|
||||||
'& textarea::placeholder': {
|
|
||||||
color: '#969BA7',
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* زر Next */}
|
||||||
<Box sx={{ pt: 2.8 }}>
|
<Box sx={{ pt: 2.8 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -290,33 +429,7 @@ const CloudKitchenProject = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
Divider
|
CircularProgress
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
const ConfirmationDialog = ({ open, onClose, onConfirm, loading }) => {
|
||||||
const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -24,7 +24,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
width: '525px',
|
width: '525px',
|
||||||
height: '300px',
|
height: '350px',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
@@ -69,7 +69,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
{/* الدائرة الأمامية مع الأيقونة */}
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
@@ -92,7 +92,6 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* العنوان بجانب الدائرة */}
|
|
||||||
<DialogTitle sx={{ p: 0 }}>
|
<DialogTitle sx={{ p: 0 }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
@@ -115,7 +114,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: theme.palette.text.secondary
|
color: theme.palette.text.secondary
|
||||||
}}>
|
}}>
|
||||||
Congratulations! you have registered your restaurant successfully on our platform
|
Congratulations! you well register your restaurant successfully on our platform
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
gap: 2,
|
gap: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* الزر الأول - Filled */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -146,9 +145,34 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Done
|
Censel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* الزر الأول - Filled */}
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={onConfirm}
|
||||||
|
disabled={loading}
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.dark,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<CircularProgress size={24} sx={{ color: 'white' }} />
|
||||||
|
) : (
|
||||||
|
'Submit'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,20 +1,71 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField, MenuItem } from '@mui/material';
|
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 theme = useTheme();
|
||||||
const countries = [
|
const [countries, setCountries] = useState([]);
|
||||||
{ code: 'US', name: 'United States' },
|
const [loadingCountries, setLoadingCountries] = useState(false);
|
||||||
{ code: 'GB', name: 'United Kingdom' },
|
const [errors, setErrors] = useState({}); // <-- حالة الأخطاء
|
||||||
{ code: 'FR', name: 'France' },
|
|
||||||
{ code: 'DE', name: 'Germany' },
|
useEffect(() => {
|
||||||
{ code: 'SA', name: 'Saudi Arabia' },
|
const fetchCountries = async () => {
|
||||||
{ code: 'EG', name: 'Egypt' },
|
setLoadingCountries(true);
|
||||||
{ code: 'AE', name: 'United Arab Emirates' },
|
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 = () => {
|
const handleNext = () => {
|
||||||
if (onNext) {
|
if (validate()) {
|
||||||
onNext();
|
onNext();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -41,12 +92,13 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
fontSize: {
|
fontSize: {
|
||||||
xs: '1.8rem',
|
xs: '1.8rem',
|
||||||
sm: '2rem',
|
sm: '2rem',
|
||||||
md: '2.2rem'
|
md: '2.2rem',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Operational Details
|
Operational Details
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
<Box sx={{ width: '70%' }}>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="16px"
|
fontSize="16px"
|
||||||
@@ -54,19 +106,29 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
fontWeight={500}
|
fontWeight={500}
|
||||||
sx={{ pb: 1 }}
|
sx={{ pb: 1 }}
|
||||||
>
|
>
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
Enter your basic information to proceed to registration of your
|
||||||
|
own restaurant on this platform
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Staff MembersInput */}
|
{/* Staff Members Input */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="black"
|
||||||
|
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||||
|
>
|
||||||
Staff Members
|
Staff Members
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
|
name="staff_members"
|
||||||
placeholder="200 (100 Chefs, 100 Cooks)"
|
placeholder="200 (100 Chefs, 100 Cooks)"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData.staff_members || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={!!errors.staff_members}
|
||||||
|
helperText={errors.staff_members}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -75,35 +137,42 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
transition: '0.3s',
|
transition: '0.3s',
|
||||||
'&.Mui-focused fieldset': {
|
'&.Mui-focused fieldset': {
|
||||||
borderColor: theme.palette.primary.main,
|
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': {
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
borderColor: '#3f51b5',
|
borderColor: '#3f51b5',
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
{/* Country Selector */}
|
{/* Country Selector */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="black"
|
||||||
|
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||||
|
>
|
||||||
Country
|
Country
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
name="country_id"
|
||||||
fullWidth
|
fullWidth
|
||||||
defaultValue=""
|
value={formData.country_id || ''}
|
||||||
placeholder="Select your country"
|
onChange={handleChange}
|
||||||
|
disabled={loadingCountries}
|
||||||
|
error={!!errors.country_id}
|
||||||
|
helperText={errors.country_id}
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiSelect-select': {
|
'& .MuiSelect-select': {
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: '15px',
|
fontSize: '15px',
|
||||||
minHeight: '40px',
|
minHeight: '40px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
'& .MuiOutlinedInput-input': { padding: '10.5px 14px' },
|
'& .MuiOutlinedInput-input': { padding: '10.5px 14px' },
|
||||||
'& .MuiOutlinedInput-root': {
|
'& .MuiOutlinedInput-root': {
|
||||||
@@ -111,37 +180,42 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
transition: '0.3s',
|
transition: '0.3s',
|
||||||
'&.Mui-focused fieldset': {
|
'&.Mui-focused fieldset': {
|
||||||
borderColor: theme.palette.primary.main,
|
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': {
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
borderColor: '#3f51b5',
|
borderColor: '#3f51b5',
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<MenuItem value="" disabled hidden>
|
<MenuItem value="" disabled hidden>
|
||||||
Select your country
|
{loadingCountries ? 'Loading...' : 'Select your country'}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{countries.map((country) => (
|
{countries.map((country) => (
|
||||||
<MenuItem key={country.code} value={country.code}>
|
<MenuItem key={country.id} value={country.id}>
|
||||||
{country.name}
|
{country.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Expansion Plan Cities Input */}
|
||||||
{/* Expansion Plan Cities MembersInput */}
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography
|
||||||
Expansion Plan Cities
|
variant="body2"
|
||||||
|
color="black"
|
||||||
|
sx={{ fontWeight: '500', fontSize: '16px' }}
|
||||||
|
>
|
||||||
|
Expansion Plan Cities
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
|
name="expansion_plan_cities" // ← ✅ الاسم الصحيح المتوقع من الـ API
|
||||||
placeholder="Layyah Lahore Islamabad"
|
placeholder="Layyah Lahore Islamabad"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData.expansion_plan_cities || ''}
|
||||||
|
onChange={handleChange}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -150,23 +224,24 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
transition: '0.3s',
|
transition: '0.3s',
|
||||||
'&.Mui-focused fieldset': {
|
'&.Mui-focused fieldset': {
|
||||||
borderColor: theme.palette.primary.main,
|
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': {
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
borderColor: '#3f51b5',
|
borderColor: '#3f51b5',
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
{/* Next Button */}
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Next Button */}
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -184,7 +259,7 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
{/* Back Button */}
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -197,7 +272,7 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -209,7 +284,6 @@ const OperationalDetails = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -11,10 +11,56 @@ import {
|
|||||||
Radio
|
Radio
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack, formData, updateFormData }) => {
|
||||||
const theme = useTheme();
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -56,15 +102,19 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Operational Hours Input */}
|
{/* Equipment Input */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
Equipment
|
Equipment
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder=" Ovens Refrigerators Pizza Machines |"
|
placeholder="Ovens Refrigerators Pizza Machines |"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData.equipment || ''}
|
||||||
|
onChange={handleEquipmentChange}
|
||||||
|
error={!!errors.equipment}
|
||||||
|
helperText={errors.equipment}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -84,22 +134,27 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{}}>
|
{/* Need Specialized Equipment */}
|
||||||
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||||
>
|
>
|
||||||
Need Specialized Equipment
|
Need Specialized Equipment
|
||||||
</Typography>
|
</Typography>
|
||||||
<RadioGroup row name="additionalFacilities">
|
<RadioGroup
|
||||||
|
row
|
||||||
|
name="needSpecializedEquipment"
|
||||||
|
value={formData.needSpecializedEquipment || 'no'}
|
||||||
|
onChange={handleNeedSpecializedChange}
|
||||||
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="yes"
|
value="yes"
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
sx={{
|
sx={{
|
||||||
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
transform: 'scale(0.85)',
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
'&.Mui-checked': {
|
||||||
color: theme.palette.primary.main
|
color: theme.palette.primary.main
|
||||||
}
|
}
|
||||||
@@ -125,53 +180,62 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
label="No"
|
label="No"
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
{errors.needSpecializedEquipment && (
|
||||||
|
<Typography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||||
|
{errors.needSpecializedEquipment}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Specialized Equipments For Food Preparation */}
|
||||||
|
{formData.needSpecializedEquipment === 'yes' && (
|
||||||
{/* Specialized Equipments For Food Preparation Input */}
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
Specialized Equipments For Food Preparation
|
||||||
Specialized Equipments For Food Preparation
|
</Typography>
|
||||||
</Typography>
|
<TextField
|
||||||
<TextField
|
placeholder="Knife Spoon Handle |"
|
||||||
placeholder="Knife Spoon Handle |"
|
variant="outlined"
|
||||||
variant="outlined"
|
fullWidth
|
||||||
fullWidth
|
multiline
|
||||||
multiline
|
minRows={3}
|
||||||
minRows={3}
|
value={formData.specialized_equipment || ''}
|
||||||
sx={{
|
onChange={handleSpecializedEquipmentChange}
|
||||||
'& .MuiInputBase-root': {
|
error={!!errors.specialized_equipment}
|
||||||
fontWeight: 500,
|
helperText={errors.specialized_equipment
|
||||||
fontSize: '15px',
|
}
|
||||||
alignItems: 'start',
|
sx={{
|
||||||
},
|
'& .MuiInputBase-root': {
|
||||||
'& textarea::placeholder': {
|
fontWeight: 500,
|
||||||
color: '#969BA7',
|
fontSize: '15px',
|
||||||
},
|
alignItems: 'start',
|
||||||
'& .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)',
|
|
||||||
},
|
},
|
||||||
},
|
'& textarea::placeholder': {
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
color: '#969BA7',
|
||||||
borderColor: '#3f51b5',
|
},
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
'& .MuiOutlinedInput-root': {
|
||||||
}
|
borderRadius: '10px',
|
||||||
}}
|
transition: '0.3s',
|
||||||
/>
|
'&.Mui-focused fieldset': {
|
||||||
</Box>
|
borderColor: theme.palette.primary.main,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||||
{/* Next Button */}
|
},
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||||
|
borderColor: '#3f51b5',
|
||||||
|
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -189,7 +253,6 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -202,7 +265,7 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -214,7 +277,6 @@ const RequiredEquipments = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,61 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
Button,
|
Button,
|
||||||
useTheme,
|
useTheme,
|
||||||
TextField,
|
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio
|
Radio,
|
||||||
|
Typography as MuiTypography
|
||||||
} from '@mui/material';
|
} 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 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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -54,23 +96,26 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
<Box sx={{}}>
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
||||||
>
|
>
|
||||||
Visual Identity
|
Visual Identity
|
||||||
</Typography>
|
</Typography>
|
||||||
<RadioGroup row name="additionalFacilities">
|
<RadioGroup
|
||||||
|
row
|
||||||
|
name="visualIdentity"
|
||||||
|
value={formData.visualIdentity || ''}
|
||||||
|
onChange={handleVisualIdentityChange}
|
||||||
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="yes"
|
value="yes"
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
sx={{
|
sx={{
|
||||||
|
color: 'rgba(150, 155, 167, 0.6)',
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
transform: 'scale(0.85)',
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
'&.Mui-checked': {
|
||||||
color: theme.palette.primary.main
|
color: theme.palette.primary.main
|
||||||
}
|
}
|
||||||
@@ -96,31 +141,34 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
label="No"
|
label="No"
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
{errors.visualIdentity && (
|
||||||
|
<MuiTypography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||||
|
{errors.visualIdentity}
|
||||||
|
</MuiTypography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* OR Separator */}
|
{/* Upload Logo / Color Scheme */}
|
||||||
<Box>
|
<Box>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
color: 'black',
|
color: 'black',
|
||||||
mb:2 ,
|
mb: 2,
|
||||||
pl: 1, // padding left (اختياري)
|
pl: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Upload Logo / Color Scheme If Any
|
Upload Logo / Color Scheme If Any
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Upload Menu Picture */}
|
|
||||||
<Box
|
<Box
|
||||||
component="label"
|
component="label"
|
||||||
htmlFor="upload-image"
|
htmlFor="upload-image"
|
||||||
sx={{
|
sx={{
|
||||||
border: '2px dashed #ccc',
|
border: errors.logo_path ? '2px solid red' : '2px dashed #ccc',
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
height: '100px',
|
height: '150px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -137,19 +185,28 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Upload Picture
|
{formData.logo ? (formData.logo.name || 'Uploaded Image') : 'Upload Picture'}
|
||||||
<input id="upload-image" type="file" accept="image/*" hidden />
|
<input
|
||||||
|
id="upload-image"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
hidden
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{errors.logo && (
|
||||||
|
<MuiTypography color="error" variant="caption" sx={{ mt: 0.5 }}>
|
||||||
|
{errors.logo}
|
||||||
|
</MuiTypography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
{/* Buttons: Back and Next */}
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -167,7 +224,6 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -180,7 +236,7 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -192,7 +248,6 @@ const VisualIdentity = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
244
src/components/Home/Dashboard/AnalyticsPage.js
Normal file
244
src/components/Home/Dashboard/AnalyticsPage.js
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
import StatisticsCard from './StatisticsCard';
|
||||||
|
// import TablesManager from './TablesManager';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const AnalyticsPage = () => {
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||||
|
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [chartData, setChartData] = useState([]);
|
||||||
|
|
||||||
|
const handleTimeFrameChange = (newTimeFrame) => {
|
||||||
|
setTimeFrame(newTimeFrame);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomDateChange = (event) => {
|
||||||
|
setCustomDate(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchStatistics = async () => {
|
||||||
|
if (!restaurantId) return;
|
||||||
|
|
||||||
|
let period = 'yearly';
|
||||||
|
let labels = [];
|
||||||
|
let mappedData = [];
|
||||||
|
|
||||||
|
switch (timeFrame) {
|
||||||
|
case '24h': // يومي
|
||||||
|
period = 'daily';
|
||||||
|
labels = Array.from({ length: 24 }, (_, i) => `${i.toString().padStart(2, '0')}:00`);
|
||||||
|
try {
|
||||||
|
const response = await authService.getOrderStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label || item.date || '',
|
||||||
|
orders_total: item.orders_total || 0,
|
||||||
|
items_total: item.items_total || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '30d': // شهري
|
||||||
|
period = 'monthly';
|
||||||
|
labels = Array.from({ length: 30 }, (_, i) => dayjs().startOf('month').add(i, 'day').format('DD'));
|
||||||
|
try {
|
||||||
|
const response = await authService.getOrderStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label ? dayjs(item.label).format('DD') : item.date ? dayjs(item.date).format('DD') : '',
|
||||||
|
orders_total: item.orders_total || 0,
|
||||||
|
items_total: item.items_total || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '12m': // سنوي
|
||||||
|
default:
|
||||||
|
period = 'yearly';
|
||||||
|
labels = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||||
|
try {
|
||||||
|
const response = await authService.getOrderStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label || item.date || '',
|
||||||
|
orders_total: item.orders_total || 0,
|
||||||
|
items_total: item.items_total || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
orders_total: 0,
|
||||||
|
items_total: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setChartData(mappedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStatistics();
|
||||||
|
}, [timeFrame, restaurantId, customDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
pr={{ xs: 1, sm: 2 }}
|
||||||
|
maxWidth="100%"
|
||||||
|
overflowX="hidden">
|
||||||
|
{/* Header Buttons */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection={{ xs: 'column', sm: 'row' }}
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
gap={2}
|
||||||
|
mb={1.5}
|
||||||
|
>
|
||||||
|
<ButtonGroup
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||||
|
'& .MuiButton-root': {
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '13px',
|
||||||
|
padding: '6px 12px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#555',
|
||||||
|
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||||
|
},
|
||||||
|
'& .MuiButton-contained': {
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
|
||||||
|
<Button
|
||||||
|
key={value}
|
||||||
|
variant={timeFrame === value ? 'contained' : 'text'}
|
||||||
|
onClick={() => handleTimeFrameChange(value)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{/* Date Picker + Today Button */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
gap={1}
|
||||||
|
alignItems="center"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: 2,
|
||||||
|
p: '4px 8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={customDate}
|
||||||
|
onChange={handleCustomDateChange}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? '100%' : 150,
|
||||||
|
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setCustomDate(dayjs().format('YYYY-MM-DD'))}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
textTransform: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
fontSize: isMobile ? 12 : 13,
|
||||||
|
'&:hover': { backgroundColor: 'rgba(255, 117, 34, 0.15)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* StatisticsCard */}
|
||||||
|
<Box>
|
||||||
|
<StatisticsCard
|
||||||
|
title="Analytics Overview"
|
||||||
|
subtitle="Orders Statistics"
|
||||||
|
data={chartData}
|
||||||
|
dataKeys={[
|
||||||
|
{ key: 'orders_total', name: 'Orders', color: '#E46A11' },
|
||||||
|
{ key: 'items_total', name: 'Items', color: '#0182FC' },
|
||||||
|
]}
|
||||||
|
xDataKey="label"
|
||||||
|
valueFormatter={(value) => value}
|
||||||
|
timeFrame={timeFrame}
|
||||||
|
onTimeFrameChange={handleTimeFrameChange}
|
||||||
|
height={320}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalyticsPage;
|
||||||
82
src/components/Home/Dashboard/AnalyticsPageOccupancy.js
Normal file
82
src/components/Home/Dashboard/AnalyticsPageOccupancy.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
import OrderStatusCard from './OrderStatusCard';
|
||||||
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const AnalyticsPageOccupancy = () => {
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||||
|
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [occupancyRate, setOccupancyRate] = useState(0);
|
||||||
|
|
||||||
|
const fetchOccupancyRate = async () => {
|
||||||
|
if (!restaurantId) return;
|
||||||
|
|
||||||
|
let period = 'yearly';
|
||||||
|
switch (timeFrame) {
|
||||||
|
case '24h':
|
||||||
|
period = 'daily';
|
||||||
|
break;
|
||||||
|
case '30d':
|
||||||
|
period = 'monthly';
|
||||||
|
break;
|
||||||
|
case '12m':
|
||||||
|
default:
|
||||||
|
period = 'yearly';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await authService.getOccupancyRate(
|
||||||
|
restaurantId,
|
||||||
|
period,
|
||||||
|
customDate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// occupancy_rate جايه كنص مثل "11.83%" → نحولها لرقم
|
||||||
|
const rateValue = parseFloat(response.data.replace('%', '').trim());
|
||||||
|
setOccupancyRate(rateValue);
|
||||||
|
} else {
|
||||||
|
setOccupancyRate(0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setOccupancyRate(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchOccupancyRate();
|
||||||
|
}, [timeFrame, restaurantId, customDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box pr={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
|
||||||
|
{/* Occupancy Card */}
|
||||||
|
<Box>
|
||||||
|
<OrderStatusCard
|
||||||
|
title="Occupancy Rate"
|
||||||
|
period={
|
||||||
|
timeFrame === '24h'
|
||||||
|
? 'Today'
|
||||||
|
: timeFrame === '30d'
|
||||||
|
? 'This Month'
|
||||||
|
: 'This Year'
|
||||||
|
}
|
||||||
|
progress={occupancyRate} // نسبة الإشغال
|
||||||
|
description={`Current occupancy rate is ${occupancyRate}% for selected period.`}
|
||||||
|
timeFrame={timeFrame}
|
||||||
|
setTimeFrame={setTimeFrame}
|
||||||
|
customDate={customDate}
|
||||||
|
setCustomDate={setCustomDate}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalyticsPageOccupancy;
|
||||||
241
src/components/Home/Dashboard/AnalyticsPagebill.js
Normal file
241
src/components/Home/Dashboard/AnalyticsPagebill.js
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
import StatisticsCard from './StatisticsCard';
|
||||||
|
// import TablesManager from './TablesManager';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const AnalyticsPagebill = () => {
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
const [timeFrame, setTimeFrame] = useState('12m'); // '12m', '30d', '24h'
|
||||||
|
const [customDate, setCustomDate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [chartData, setChartData] = useState([]);
|
||||||
|
|
||||||
|
const handleTimeFrameChange = (newTimeFrame) => {
|
||||||
|
setTimeFrame(newTimeFrame);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomDateChange = (event) => {
|
||||||
|
setCustomDate(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchStatistics = async () => {
|
||||||
|
if (!restaurantId) return;
|
||||||
|
|
||||||
|
let period = 'yearly';
|
||||||
|
let labels = [];
|
||||||
|
let mappedData = [];
|
||||||
|
|
||||||
|
switch (timeFrame) {
|
||||||
|
case '24h': // يومي
|
||||||
|
period = 'daily';
|
||||||
|
labels = Array.from({ length: 24 }, (_, i) => `${i.toString().padStart(2, '0')}:00`);
|
||||||
|
try {
|
||||||
|
const response = await authService.getBillStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label || item.date || '',
|
||||||
|
bills_total: item.bills_total || 0,
|
||||||
|
total_price: item.total_price || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '30d': // شهري
|
||||||
|
period = 'monthly';
|
||||||
|
labels = Array.from({ length: 30 }, (_, i) => dayjs().startOf('month').add(i, 'day').format('DD'));
|
||||||
|
try {
|
||||||
|
const response = await authService.getBillStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label ? dayjs(item.label).format('DD') : item.date ? dayjs(item.date).format('DD') : '',
|
||||||
|
bills_total: item.bills_total || 0,
|
||||||
|
total_price: item.total_price || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '12m': // سنوي
|
||||||
|
default:
|
||||||
|
period = 'yearly';
|
||||||
|
labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
try {
|
||||||
|
const response = await authService.getBillStatistics(restaurantId, period, customDate);
|
||||||
|
if (response.success && response.data.length > 0) {
|
||||||
|
mappedData = response.data.map(item => ({
|
||||||
|
label: item.label || item.date || '',
|
||||||
|
bills_total: item.bills_total || 0,
|
||||||
|
total_price: item.total_price || 0
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
mappedData = labels.map(label => ({
|
||||||
|
label,
|
||||||
|
bills_total: 0,
|
||||||
|
total_price: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setChartData(mappedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStatistics();
|
||||||
|
}, [timeFrame, restaurantId, customDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={{ xs: 1, sm: 2 }} maxWidth="100%" overflowX="hidden">
|
||||||
|
{/* Header Buttons */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection={{ xs: 'column', sm: 'row' }}
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
gap={2}
|
||||||
|
mb={1}
|
||||||
|
>
|
||||||
|
<ButtonGroup
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||||
|
'& .MuiButton-root': {
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '13px',
|
||||||
|
padding: '6px 12px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#555',
|
||||||
|
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||||
|
},
|
||||||
|
'& .MuiButton-contained': {
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
|
||||||
|
<Button
|
||||||
|
key={value}
|
||||||
|
variant={timeFrame === value ? 'contained' : 'text'}
|
||||||
|
onClick={() => handleTimeFrameChange(value)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{/* Date Picker + Today Button */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
gap={1}
|
||||||
|
alignItems="center"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: 2,
|
||||||
|
p: '4px 8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={customDate}
|
||||||
|
onChange={handleCustomDateChange}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? '100%' : 150,
|
||||||
|
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setCustomDate(dayjs().format('YYYY-MM-DD'))}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
textTransform: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
fontSize: isMobile ? 12 : 13,
|
||||||
|
'&:hover': { backgroundColor: 'rgba(255, 117, 34, 0.15)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* StatisticsCard */}
|
||||||
|
<Box>
|
||||||
|
<StatisticsCard
|
||||||
|
title="Analytics Overview"
|
||||||
|
subtitle="Bills Statistics"
|
||||||
|
data={chartData}
|
||||||
|
dataKeys={[
|
||||||
|
{ key: 'bills_total', name: 'Bills', color: '#11e4c1ff' },
|
||||||
|
{ key: 'total_price', name: 'Total price', color: '#fcce01ff' },
|
||||||
|
]}
|
||||||
|
xDataKey="label"
|
||||||
|
valueFormatter={(value) => value}
|
||||||
|
timeFrame={timeFrame}
|
||||||
|
onTimeFrameChange={handleTimeFrameChange}
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalyticsPagebill;
|
||||||
@@ -11,23 +11,23 @@ const Dashboard = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const [hasProducts, setHasProducts] = useState(false);
|
const [hasProducts, setHasProducts] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
// const [isLoading, setIsLoading] = useState(true);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
// محاكاة التحقق من المنتجات
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkProducts = async () => {
|
const checkProducts = async () => {
|
||||||
setIsLoading(true);
|
// setIsLoading(true);
|
||||||
const productsExist = await checkIfProductsExist(); // استبدل بمنطقك
|
const productsExist = await checkIfProductsExist();
|
||||||
setHasProducts(productsExist);
|
setHasProducts(productsExist);
|
||||||
setIsLoading(false);
|
// setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkProducts();
|
checkProducts();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const checkIfProductsExist = async () => {
|
const checkIfProductsExist = async () => {
|
||||||
return new Promise((resolve) => setTimeout(() => resolve(true), 1500)); // محاكاة تأخير
|
return new Promise((resolve) => setTimeout(() => resolve(true), 1500));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -76,8 +76,8 @@ const Dashboard = () => {
|
|||||||
<Box sx={{
|
<Box sx={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
width: sidebarOpen ? 'calc(100% - 20px)' : '100%',
|
width: sidebarOpen ? 'calc(100% - 20px)' : '100%',
|
||||||
pt: { xs: 0.5, sm: 1 },
|
// pt: { xs: 0.5, sm: 1 },
|
||||||
mt: { xs: 1, sm: 2 },
|
// mt: { xs: 1, sm: 2 },
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
pl: { xs: 0, sm: 2 },
|
pl: { xs: 0, sm: 2 },
|
||||||
pr: { xs: 1, sm: 2 },
|
pr: { xs: 1, sm: 2 },
|
||||||
@@ -89,15 +89,18 @@ const Dashboard = () => {
|
|||||||
duration: theme.transitions.duration.leavingScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}),
|
}),
|
||||||
}}>
|
}}>
|
||||||
{isLoading ? (
|
{/* {isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Skeleton variant="rectangular" height={50} sx={{ mb: 2 }} />
|
<Skeleton variant="rectangular" height={50} sx={{ mb: 2 }} />
|
||||||
<Skeleton variant="rectangular" height={200} sx={{ mb: 2 }} />
|
<Skeleton variant="rectangular" height={200} sx={{ mb: 2 }} />
|
||||||
<Skeleton variant="rectangular" height={300} sx={{ mb: 2 }} />
|
<Skeleton variant="rectangular" height={300} sx={{ mb: 2 }} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
hasProducts ? <DashbordContect /> : <NoProdectDash />
|
hasProducts ?
|
||||||
)}
|
<DashbordContect />
|
||||||
|
: <NoProdectDash />
|
||||||
|
)} */}
|
||||||
|
<DashbordContect />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
|||||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import OrderStatusCard from './OrderStatusCard';
|
import AnalyticsPage from './AnalyticsPage';
|
||||||
import StatisticsCard from './StatisticsCard';
|
import AnalyticsPageOccupancy from './AnalyticsPageOccupancy';
|
||||||
import RecentActivity from './RecentActivity';
|
import AnalyticsPagebill from './AnalyticsPagebill';
|
||||||
|
|
||||||
const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) => (
|
const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) => (
|
||||||
<Box
|
<Box
|
||||||
@@ -52,111 +52,6 @@ const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) =
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const StatusCard = ({
|
|
||||||
icon,
|
|
||||||
statusText,
|
|
||||||
statusColor,
|
|
||||||
iconColor,
|
|
||||||
outerColor,
|
|
||||||
innerColor,
|
|
||||||
height = {
|
|
||||||
xs: 'calc(140px + 0.5vh)',
|
|
||||||
sm: 'calc(150px + 0.5vh)',
|
|
||||||
md: 'calc(162px + 0.5vh)'
|
|
||||||
},
|
|
||||||
width = {
|
|
||||||
xs: 'min(90%, 600px)', // تقليل من 90% إلى 85% والحد الأقصى من 300px إلى 280px
|
|
||||||
sm: 'clamp(220px, 23vw, 280px)', // تقليل جميع القيم
|
|
||||||
md: 'clamp(220px, 19vw, 300px)'
|
|
||||||
},
|
|
||||||
transition = 'width 0.3s ease',
|
|
||||||
iconSpacing = { xs: 6, sm: 8, md: 12 },
|
|
||||||
extraButton,
|
|
||||||
statusButtonWidth,
|
|
||||||
title = 'Point Of Sale',
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
p: 0,
|
|
||||||
pb: 2,
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: 'none',
|
|
||||||
minWidth: { xs: 180, sm: 220 }
|
|
||||||
}}>
|
|
||||||
<CardContent>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: iconSpacing }}>
|
|
||||||
<IconCircle bgColor={innerColor} outerColor={outerColor}>
|
|
||||||
{React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
|
|
||||||
</IconCircle>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#E7F4EE',
|
|
||||||
color: statusColor,
|
|
||||||
textTransform: 'none',
|
|
||||||
boxShadow: 'none',
|
|
||||||
borderRadius: '100px',
|
|
||||||
width: statusButtonWidth || 'auto',
|
|
||||||
fontSize: { xs: '0.7rem', sm: '0.8rem', md: '0.9rem' },
|
|
||||||
px: { xs: 1, sm: 1.5 },
|
|
||||||
py: 0.5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{statusText}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
gutterBottom
|
|
||||||
mt={1.5}
|
|
||||||
sx={{
|
|
||||||
color: '#667085',
|
|
||||||
fontSize: { xs: '14px', sm: '15px', md: '16px' },
|
|
||||||
fontWeight: 500
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
|
||||||
{extraButton ? extraButton : <Box />}
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#F7F7F7',
|
|
||||||
width: { xs: '26px', sm: '28px', md: '30px' },
|
|
||||||
height: { xs: '26px', sm: '28px', md: '30px' },
|
|
||||||
minWidth: '0',
|
|
||||||
padding: 0,
|
|
||||||
borderRadius: '12px',
|
|
||||||
boxShadow: 'none',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#e0e0e0',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowOutwardIcon
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
width: { xs: '16px', sm: '18px', md: '20px' },
|
|
||||||
height: { xs: '16px', sm: '18px', md: '20px' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DashboardContect = () => {
|
const DashboardContect = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -172,7 +67,7 @@ const DashboardContect = () => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||||
mb: 3,
|
mb: 1,
|
||||||
pl: 1,
|
pl: 1,
|
||||||
pr: { xs: 1, sm: 3 },
|
pr: { xs: 1, sm: 3 },
|
||||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||||
@@ -192,7 +87,7 @@ const DashboardContect = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Buttons */}
|
{/* Buttons */}
|
||||||
<Box sx={{
|
{/* <Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: { xs: 1, sm: 2 },
|
gap: { xs: 1, sm: 2 },
|
||||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||||
@@ -260,11 +155,11 @@ const DashboardContect = () => {
|
|||||||
>
|
>
|
||||||
Check Inventory
|
Check Inventory
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box> */}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Status Cards */}
|
{/* Status Cards */}
|
||||||
<Box
|
{/* <Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -333,7 +228,7 @@ const DashboardContect = () => {
|
|||||||
iconSpacing={{ xs: 3, sm: 5, md: 7 }}
|
iconSpacing={{ xs: 3, sm: 5, md: 7 }}
|
||||||
title="Delivery"
|
title="Delivery"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box> */}
|
||||||
|
|
||||||
{/* OrderStatusCard/ StatisticsCard */}
|
{/* OrderStatusCard/ StatisticsCard */}
|
||||||
<Box
|
<Box
|
||||||
@@ -343,12 +238,8 @@ const DashboardContect = () => {
|
|||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
flexDirection: { xs: 'column', sm: 'row' },
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
gap: { xs: 1, sm: 1, md: 7 }, // gap مرن
|
gap: { xs: 1, sm: 1, md: 7 }, // gap مرن
|
||||||
mb: 1,
|
mr: { xs: 4, sm: 3, md: 0 },
|
||||||
pl: 1,
|
|
||||||
pr: 1,
|
|
||||||
mt: 2,
|
|
||||||
ml: { xs: 2, sm: 0 },
|
ml: { xs: 2, sm: 0 },
|
||||||
mr: 2,
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -357,12 +248,13 @@ const DashboardContect = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
width: { xs: '100%', sm: '35%', md: '30%' },
|
width: { xs: '100%', sm: '35%', md: '30%' },
|
||||||
minWidth: 220,
|
minWidth: 20,
|
||||||
ml: { xs: 0, sm: -1 },
|
ml: { xs: 0, sm: -1 },
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
mt:8
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OrderStatusCard />
|
<AnalyticsPageOccupancy />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* StatisticsCard */}
|
{/* StatisticsCard */}
|
||||||
@@ -370,11 +262,11 @@ const DashboardContect = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
ml: { xs: 0, sm: 2 },
|
// ml: { xs: 0, sm: 2 },
|
||||||
// backgroundColor:'red'
|
// backgroundColor:'red'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StatisticsCard />
|
<AnalyticsPage />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -382,11 +274,11 @@ const DashboardContect = () => {
|
|||||||
{/* RecentActivity */}
|
{/* RecentActivity */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mr: { xs: 4, sm: 3 ,md:0 },
|
mr: { xs: 4, sm: 3, md: 0 },
|
||||||
ml: { xs: 2, sm: 0 },
|
ml: { xs: 2, sm: 0 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecentActivity />
|
<AnalyticsPagebill />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const NoProdectDash = () => {
|
|||||||
fontSize: { xs: '14px', md: '18px' },
|
fontSize: { xs: '14px', md: '18px' },
|
||||||
mb: 2,
|
mb: 2,
|
||||||
color: '#5F6868',
|
color: '#5F6868',
|
||||||
whiteSpace: 'pre-line' // هذا يسمح بكسر السطر عند المسافات
|
whiteSpace: 'pre-line' // يسمح بكسر السطر عند المسافات
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box component="span" sx={{ fontWeight: 600, fontSize: '18px' }}>Sorry!</Box>{' '}
|
<Box component="span" sx={{ fontWeight: 600, fontSize: '18px' }}>Sorry!</Box>{' '}
|
||||||
|
|||||||
@@ -1,260 +1,241 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
Divider,
|
useTheme,
|
||||||
Chip,
|
IconButton,
|
||||||
useTheme,
|
useMediaQuery,
|
||||||
IconButton,
|
Menu,
|
||||||
useMediaQuery
|
MenuItem,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
TrendingUp as TrendingUpIcon,
|
TrendingUp as TrendingUpIcon,
|
||||||
TrendingDown as TrendingDownIcon,
|
TrendingDown as TrendingDownIcon,
|
||||||
CheckCircle as CheckCircleIcon,
|
|
||||||
Cancel as CancelIcon,
|
|
||||||
Pending as PendingIcon
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import {
|
import {
|
||||||
CircularProgressbar,
|
CircularProgressbar,
|
||||||
buildStyles
|
buildStyles
|
||||||
} from 'react-circular-progressbar';
|
} from 'react-circular-progressbar';
|
||||||
import 'react-circular-progressbar/dist/styles.css';
|
import 'react-circular-progressbar/dist/styles.css';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import { ResponsiveContainer } from 'recharts';
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import dayjs from 'dayjs';
|
||||||
// import { Box, IconButton, Paper, Typography, useTheme, useMediaQuery } from '@mui/material';
|
|
||||||
import {
|
|
||||||
|
|
||||||
ResponsiveContainer,
|
|
||||||
|
|
||||||
} from 'recharts';
|
|
||||||
|
|
||||||
|
|
||||||
const OrderStatusCard = ({
|
const OrderStatusCard = ({
|
||||||
title = 'Order Status',
|
title = 'Order Status',
|
||||||
period = 'This Quarter',
|
period = 'This Quarter',
|
||||||
progress = 70.5,
|
progress = 70.5,
|
||||||
progressChange = '+10%',
|
description = 'You succeeded in earning $240 today, it’s higher than yesterday',
|
||||||
progressDirection = 'up', // or 'down'
|
timeFrame,
|
||||||
description = 'You succeeded in earning $240 today, it’s higher than yesterday',
|
setTimeFrame,
|
||||||
completed = '$20k',
|
customDate,
|
||||||
canceled = '$16k',
|
setCustomDate
|
||||||
pending = '$1.5k'
|
|
||||||
}) => {
|
}) => {
|
||||||
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 open = Boolean(anchorEl);
|
||||||
const progressIcon = progressDirection === 'up' ? <TrendingUpIcon fontSize="small" /> : <TrendingDownIcon fontSize="small" />;
|
const handleMenuOpen = (event) => {
|
||||||
const highlightedDescription = description.replace(
|
setAnchorEl(event.currentTarget);
|
||||||
/(\$\d+(?:\.\d+)?[kKmM]?)/g,
|
};
|
||||||
'<span style="color:#000;font-weight:500;">$1</span>'
|
const handleMenuClose = () => {
|
||||||
);
|
setAnchorEl(null);
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
};
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
|
||||||
|
|
||||||
|
return (
|
||||||
return (
|
<Card
|
||||||
<Card
|
sx={{
|
||||||
sx={{
|
width: { xs: '96%', sm: '94%', md: 360 },
|
||||||
width: { xs: '96%', sm: '94%', md: 360 },
|
height: '400',
|
||||||
height: 'auto',
|
borderRadius: 2,
|
||||||
borderRadius: 2,
|
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
p: 1,
|
||||||
pl: { xs: 1, sm: 1 },
|
position: 'relative'
|
||||||
pr: { xs: 1, sm: 1 },
|
}}
|
||||||
pb: { xs: 1, sm: 1 },
|
>
|
||||||
pt: { xs: 0, sm: 0 },
|
<ResponsiveContainer width="100%" height={isMobile ? '75vh' : isTablet ? '59vh' : 385}>
|
||||||
|
|
||||||
position: 'relative' // إضافة هذه الخاصية
|
{/* زر القائمة العلوية */}
|
||||||
}}
|
<IconButton
|
||||||
|
aria-label="more-actions"
|
||||||
|
onClick={handleMenuOpen}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
color: '#667085'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ResponsiveContainer width="100%" height={isMobile ? '75vh' : isTablet ? '59vh' : 385}>
|
<MoreVertIcon />
|
||||||
{/* زر الإجراءات في الزاوية العلوية اليمنى */}
|
</IconButton>
|
||||||
<IconButton
|
|
||||||
aria-label="more-actions"
|
{/* القائمة المنسدلة */}
|
||||||
sx={{
|
<Menu
|
||||||
position: 'absolute',
|
anchorEl={anchorEl}
|
||||||
top: 8,
|
open={open}
|
||||||
right: 8,
|
onClose={handleMenuClose}
|
||||||
color: '#667085'
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
}}
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ p: 2, minWidth: 250 }}>
|
||||||
|
{/* Header Buttons (Timeframe) */}
|
||||||
|
<ButtonGroup
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0',
|
||||||
|
mb: 2,
|
||||||
|
width: '100%',
|
||||||
|
'& .MuiButton-root': {
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '13px',
|
||||||
|
padding: '6px 12px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#555',
|
||||||
|
'&:hover': { backgroundColor: '#f5f5f5' }
|
||||||
|
},
|
||||||
|
'& .MuiButton-contained': {
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[{ label: '12 Months', value: '12m' }, { label: '30 Days', value: '30d' }, { label: '24 Hours', value: '24h' }].map(({ label, value }) => (
|
||||||
|
<Button
|
||||||
|
key={value}
|
||||||
|
variant={timeFrame === value ? 'contained' : 'text'}
|
||||||
|
onClick={() => { setTimeFrame(value); handleMenuClose(); }}
|
||||||
>
|
>
|
||||||
<MoreVertIcon />
|
{label}
|
||||||
</IconButton>
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
<CardContent>
|
{/* Date Picker + Today Button */}
|
||||||
{/* Header */}
|
<Box
|
||||||
<Box
|
display="flex"
|
||||||
sx={{
|
gap={1}
|
||||||
display: 'flex',
|
alignItems="center"
|
||||||
flexDirection: 'column',
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: 2,
|
||||||
|
p: '4px 8px',
|
||||||
|
boxShadow: 'inset 0 0 0 1px #e0e0e0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={customDate}
|
||||||
|
onChange={(e) => setCustomDate(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? '100%' : 150,
|
||||||
|
'& .MuiInputBase-input': { fontSize: isMobile ? 12 : 13, padding: '6px 8px' },
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { border: 'none' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setCustomDate(dayjs().format('YYYY-MM-DD'))}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'rgba(255, 117, 34, 0.08)',
|
||||||
|
color: '#ff5722',
|
||||||
|
textTransform: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
fontSize: isMobile ? 12 : 13,
|
||||||
|
'&:hover': { backgroundColor: 'rgba(255, 117, 34, 0.15)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
ml: 0 // هامش يسار لإفساح المجال للأيقونة
|
{/* المحتوى الأساسي للكارت */}
|
||||||
}}
|
<CardContent>
|
||||||
>
|
<Box sx={{ display: 'flex', flexDirection: 'column', ml: 0 }}>
|
||||||
<Typography
|
<Typography variant="h6" sx={{ fontSize: { xs: '16px', sm: '18px' }, fontWeight: 500 }}>
|
||||||
variant="h6"
|
{title}
|
||||||
sx={{
|
</Typography>
|
||||||
fontSize: { xs: '16px', sm: '18px' },
|
<Typography sx={{ fontSize: { xs: '12px', sm: '14px' }, fontWeight: 500, color: '#667085', mb: 3 }}>
|
||||||
fontWeight: 500
|
{period}
|
||||||
}}
|
</Typography>
|
||||||
>
|
</Box>
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: { xs: '12px', sm: '14px' },
|
|
||||||
fontWeight: 500,
|
|
||||||
color: '#667085'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{period}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Progress Circle */}
|
{/* دائرة التقدم */}
|
||||||
<Box sx={{ height: 120, mb: 2, position: 'relative' }}>
|
<Box sx={{ height: 120, mb: 2, position: 'relative' }}>
|
||||||
{/* تغيير النسبة تحت الدائرة */}
|
<Box sx={{ position: 'relative', width: '100%', maxWidth: 350, mx: 'auto' }}>
|
||||||
<Box
|
<CircularProgressbar
|
||||||
sx={{
|
value={100}
|
||||||
position: 'absolute',
|
circleRatio={0.5}
|
||||||
top: '80%',
|
strokeWidth={5}
|
||||||
left: '50%',
|
styles={buildStyles({
|
||||||
transform: 'translate(-50%, -50%)',
|
rotation: 0.75,
|
||||||
textAlign: 'center',
|
strokeLinecap: 'round',
|
||||||
}}
|
trailColor: '#eee',
|
||||||
>
|
pathColor: '#eee',
|
||||||
<Chip
|
})}
|
||||||
label={progressChange}
|
/>
|
||||||
size="small"
|
<Box sx={{ position: 'absolute', top: -1.5, left: 0, width: '100%', height: '100%' }}>
|
||||||
icon={progressIcon}
|
<CircularProgressbar
|
||||||
sx={{
|
value={progress}
|
||||||
height: 22,
|
circleRatio={0.5}
|
||||||
fontSize: '0.75rem',
|
strokeWidth={6}
|
||||||
color: progressDirection === 'up' ? '#087443' : '#B42318',
|
styles={buildStyles({
|
||||||
backgroundColor: progressDirection === 'up' ? '#ECFDF3' : '#FEF3F2',
|
rotation: 0.75,
|
||||||
'.MuiChip-icon': {
|
strokeLinecap: 'round',
|
||||||
color: progressDirection === 'up' ? '#087443' : '#B42318',
|
trailColor: 'transparent',
|
||||||
},
|
pathColor: theme.palette.primary.main,
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* دائرة التقدم */}
|
{/* النص داخل الدائرة */}
|
||||||
<Box sx={{ position: 'relative', width: '100%', maxWidth: 250, mx: 'auto' }}>
|
<Box
|
||||||
<CircularProgressbar
|
sx={{
|
||||||
value={100}
|
position: 'absolute',
|
||||||
circleRatio={0.5}
|
top: '36%',
|
||||||
strokeWidth={5}
|
left: '50%',
|
||||||
styles={buildStyles({
|
transform: 'translate(-50%, -50%)',
|
||||||
rotation: 0.75,
|
fontSize: { xs: '20px', sm: '26px' },
|
||||||
strokeLinecap: 'round',
|
fontWeight: 500,
|
||||||
trailColor: '#eee',
|
color: theme.palette.text.primary,
|
||||||
pathColor: '#eee',
|
}}
|
||||||
})}
|
>
|
||||||
/>
|
{`${progress}%`}
|
||||||
<Box sx={{ position: 'absolute', top: -1.5, left: 0, width: '100%', height: '100%' }}>
|
</Box>
|
||||||
<CircularProgressbar
|
</Box>
|
||||||
value={progress}
|
</Box>
|
||||||
circleRatio={0.5}
|
|
||||||
strokeWidth={6}
|
|
||||||
styles={buildStyles({
|
|
||||||
rotation: 0.75,
|
|
||||||
strokeLinecap: 'round',
|
|
||||||
trailColor: 'transparent',
|
|
||||||
pathColor: theme.palette.primary.main,
|
|
||||||
textColor: 'transparent',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* النص داخل الدائرة */}
|
{/* الوصف */}
|
||||||
<Box
|
<Typography
|
||||||
sx={{
|
variant="body2"
|
||||||
position: 'absolute',
|
sx={{
|
||||||
top: '22%',
|
mb: 1,
|
||||||
left: '50%',
|
mt: 10,
|
||||||
transform: 'translate(-50%, -50%)',
|
color: '#667085',
|
||||||
fontSize: { xs: '18px', sm: '24px' },
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
fontWeight: 500,
|
}}
|
||||||
color: theme.palette.text.primary,
|
>
|
||||||
}}
|
{description}
|
||||||
>
|
</Typography>
|
||||||
{`${progress}%`}
|
</CardContent>
|
||||||
</Box>
|
</ResponsiveContainer>
|
||||||
</Box>
|
</Card>
|
||||||
</Box>
|
);
|
||||||
|
|
||||||
{/* الوصف */}
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{
|
|
||||||
mb: 1,
|
|
||||||
mt: 5,
|
|
||||||
color: '#667085',
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
}}
|
|
||||||
dangerouslySetInnerHTML={{ __html: highlightedDescription }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Divider sx={{ my: 2 }} />
|
|
||||||
|
|
||||||
{/* الحقول الثلاثة (Completed / Canceled / Pending) */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: { xs: 1, sm: 2 },
|
|
||||||
flexDirection: { xs: 'column', sm: 'row' },
|
|
||||||
ml: -1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* لكل قسم: */}
|
|
||||||
{[{
|
|
||||||
label: 'Completed',
|
|
||||||
icon: <ArrowUpwardIcon color="success" fontSize="small" sx={{ mt: 0.2, ml: -1 }} />,
|
|
||||||
value: completed
|
|
||||||
}, {
|
|
||||||
label: 'Canceled',
|
|
||||||
icon: <ArrowDownwardIcon color="error" fontSize="small" sx={{ mt: 0.6, ml: -1 }} />,
|
|
||||||
value: canceled
|
|
||||||
}, {
|
|
||||||
label: 'Pending',
|
|
||||||
icon: <ArrowDownwardIcon color="warning" fontSize="small" sx={{ mt: 0.6, ml: -1 }} />,
|
|
||||||
value: pending
|
|
||||||
}].map(({ label, icon, value }, i) => (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
flex: 1,
|
|
||||||
mb: { xs: 1, sm: 0 }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ color: '#667085', fontSize: '12px', fontWeight: 500, mb: 1 }}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
||||||
{icon}
|
|
||||||
<Typography sx={{ color: '#333843', fontSize: { xs: '14px', sm: '16px',md:'24px'}, fontWeight: 500 }}>
|
|
||||||
{value}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrderStatusCard;
|
export default OrderStatusCard;
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Chip
|
Chip
|
||||||
} from '@mui/material';
|
} 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 = () => {
|
const RecentActivity = () => {
|
||||||
// Sample data
|
// Sample data
|
||||||
|
|||||||
@@ -1,60 +1,92 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, IconButton, Paper, Typography, useTheme, useMediaQuery } from '@mui/material';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
LineChart,
|
Box,
|
||||||
Line,
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
Button
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
AreaChart,
|
||||||
|
Area,
|
||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Area,
|
CartesianGrid
|
||||||
AreaChart,
|
|
||||||
CartesianGrid,
|
|
||||||
defs,
|
|
||||||
linearGradient,
|
|
||||||
stop
|
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
// import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
|
||||||
const data = [
|
const StatisticsCard = ({
|
||||||
{ month: 'Jan', revenue: 4000, sales: 2400 },
|
title = "Statistics",
|
||||||
{ month: 'Feb', revenue: 3000, sales: 1398 },
|
subtitle = "Delivery Times",
|
||||||
{ month: 'Mar', revenue: 2000, sales: 9800 },
|
data = [],
|
||||||
{ month: 'Apr', revenue: 2780, sales: 3908 },
|
dataKeys = [
|
||||||
{ month: 'May', revenue: 1890, sales: 4800 },
|
{ key: 'revenue', name: 'Revenue', color: '#E46A11' },
|
||||||
{ month: 'Jun', revenue: 2390, sales: 3800 },
|
{ key: 'sales', name: 'Sales', color: '#0182FC' }
|
||||||
{ month: 'Jul', revenue: 3490, sales: 4300 },
|
],
|
||||||
{ month: 'Aug', revenue: 5000, sales: 4000 },
|
xDataKey = 'month',
|
||||||
{ month: 'Sep', revenue: 4700, sales: 4200 },
|
valueFormatter = (value) => value,
|
||||||
{ month: 'Oct', revenue: 5200, sales: 4600 },
|
timeFrame = 'month',
|
||||||
{ month: 'Nov', revenue: 4800, sales: 4400 },
|
onTimeFrameChange,
|
||||||
{ month: 'Dec', revenue: 5300, sales: 4800 },
|
height // 👈 جديد
|
||||||
];
|
}) => {
|
||||||
|
|
||||||
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 theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
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 (
|
return (
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ borderRadius: 2, width: '100%' }}>
|
||||||
<Paper sx={{
|
<Paper sx={{
|
||||||
p: { xs: 1.5, sm: 2 },
|
p: { xs: 1.5, sm: 2 },
|
||||||
mb: { xs: 2, sm: 3 },
|
mb: { xs: 2, sm: 2 },
|
||||||
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.1)',
|
||||||
position: 'relative',
|
borderRadius: 2,
|
||||||
borderRadius: 2 ,
|
position: 'relative'
|
||||||
|
|
||||||
}}>
|
}}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="more-actions"
|
aria-label="more-actions"
|
||||||
@@ -64,128 +96,115 @@ const StatisticsCard = () => {
|
|||||||
right: { xs: 4, sm: 8 },
|
right: { xs: 4, sm: 8 },
|
||||||
color: '#667085'
|
color: '#667085'
|
||||||
}}
|
}}
|
||||||
|
onClick={handleMenuOpen}
|
||||||
>
|
>
|
||||||
<MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} />
|
{/* <MoreVertIcon fontSize={isMobile ? 'small' : 'medium'} /> */}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
>
|
||||||
|
<MenuItem>
|
||||||
|
<TextField
|
||||||
|
type="date"
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
size="small"
|
||||||
|
sx={{ width: 150 }}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem>
|
||||||
|
<Button variant="outlined" size="small" onClick={handleTodayClick}>
|
||||||
|
Today
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
justifyContent: 'space-between',
|
||||||
mb: { xs: 1, sm: 2 },
|
alignItems: 'center',
|
||||||
pr: { xs: 3, sm: 4 }
|
mb: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="h6" sx={{
|
<Box>
|
||||||
fontSize: { xs: '15px', sm: '18px' },
|
<Typography variant="h6" sx={{ fontSize: { xs: '15px', sm: '18px' }, fontWeight: 500 }}>
|
||||||
fontWeight: 500
|
{title}
|
||||||
}}>
|
</Typography>
|
||||||
Statistics
|
<Typography sx={{ fontSize: { xs: '11px', sm: '14px' }, fontWeight: 500, color: '#667085' }}>
|
||||||
</Typography>
|
{subtitle}
|
||||||
<Typography sx={{
|
</Typography>
|
||||||
fontSize: { xs: '11px', sm: '14px' },
|
</Box>
|
||||||
fontWeight: 500,
|
|
||||||
color: '#667085'
|
|
||||||
}}>
|
|
||||||
Delivery Times
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<ResponsiveContainer width="100%" height={isMobile ? 250 : isTablet ? 290 : 295}>
|
{/* Chart */}
|
||||||
<AreaChart data={data} margin={{
|
<ResponsiveContainer
|
||||||
top: isMobile ? -30 : -45,
|
width="100%"
|
||||||
right: isMobile ? 15 : 30,
|
height={height || (isMobile ? 350 : isTablet ? 390 : 395)} // 👈 أولوية للـ prop
|
||||||
left: isMobile ? 10 : 20,
|
>
|
||||||
bottom: isMobile ? 10 : 20
|
|
||||||
|
<AreaChart data={data} margin={{
|
||||||
|
top: isMobile ? -30 : -45,
|
||||||
|
right: isMobile ? 15 : 30,
|
||||||
|
left: isMobile ? 10 : 20,
|
||||||
|
bottom: isMobile ? 10 : 20
|
||||||
}}>
|
}}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
|
{dataKeys.map(({ key, color }) => (
|
||||||
<stop offset="0%" stopColor={theme.palette.primary.main} stopOpacity={0.8} />
|
<linearGradient key={key} id={`color-${key}`} x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="100%" stopColor={theme.palette.primary.main} stopOpacity={0} />
|
<stop offset="0%" stopColor={color} stopOpacity={0.8} />
|
||||||
</linearGradient>
|
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
||||||
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
</linearGradient>
|
||||||
<stop offset="0%" stopColor="#81C2FF" stopOpacity={0.8} />
|
))}
|
||||||
<stop offset="100%" stopColor="#81C2FF" stopOpacity={0} />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<CartesianGrid stroke="#eee" strokeDasharray="0 0" vertical={false} />
|
<CartesianGrid stroke="#eee" strokeDasharray="0 0" vertical={false} />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="month"
|
dataKey={xDataKey}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tickMargin={isMobile ? 8 : 15}
|
tickMargin={isMobile ? 8 : 15}
|
||||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
tick={{
|
||||||
|
fontSize: isMobile ? 11 : 12,
|
||||||
|
angle: -25,
|
||||||
|
textAnchor: 'end'
|
||||||
|
}}
|
||||||
|
interval={0}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
tickFormatter={formatCurrency}
|
ticks={ticksArray}
|
||||||
|
tickFormatter={valueFormatter}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tickMargin={isMobile ? 8 : 15}
|
tickMargin={isMobile ? 8 : 15}
|
||||||
tick={{ fontSize: isMobile ? 11 : 12 }}
|
tick={{ fontSize: isMobile ? 11 : 12 }}
|
||||||
/>
|
/>
|
||||||
<Tooltip />
|
|
||||||
|
<Tooltip formatter={valueFormatter} />
|
||||||
<Legend
|
<Legend
|
||||||
verticalAlign="top"
|
verticalAlign="top"
|
||||||
height={isMobile ? 30 : 36}
|
height={isMobile ? 30 : 36}
|
||||||
iconType="circle"
|
iconType="circle"
|
||||||
iconSize={isMobile ? 8 : 10}
|
iconSize={isMobile ? 8 : 10}
|
||||||
wrapperStyle={{
|
wrapperStyle={{ paddingBottom: isMobile ? '15px' : '20px' }}
|
||||||
paddingBottom: isMobile ? '15px' : '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Area
|
|
||||||
name="Revenue"
|
|
||||||
type="monotone"
|
|
||||||
dataKey="revenue"
|
|
||||||
stroke='#E46A11'
|
|
||||||
strokeWidth={isMobile ? 2 : 3}
|
|
||||||
fill="url(#colorRevenue)"
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
type="monotone"
|
|
||||||
name="Sales"
|
|
||||||
dataKey="sales"
|
|
||||||
stroke="#0182FC"
|
|
||||||
strokeWidth={isMobile ? 2 : 3}
|
|
||||||
fill="url(#colorSales)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="revenue"
|
|
||||||
stroke={theme.palette.primary.main}
|
|
||||||
strokeWidth={isMobile ? 2 : 3}
|
|
||||||
dot={{
|
|
||||||
r: isMobile ? 4 : 6,
|
|
||||||
fill: theme.palette.primary.main,
|
|
||||||
stroke: '#fff',
|
|
||||||
strokeWidth: 2
|
|
||||||
}}
|
|
||||||
activeDot={{
|
|
||||||
r: isMobile ? 6 : 8,
|
|
||||||
fill: theme.palette.primary.main,
|
|
||||||
stroke: '#fff',
|
|
||||||
strokeWidth: 2
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="sales"
|
|
||||||
stroke="#0182FC"
|
|
||||||
strokeWidth={isMobile ? 2 : 3}
|
|
||||||
dot={{
|
|
||||||
r: isMobile ? 4 : 6,
|
|
||||||
fill: "#0182FC",
|
|
||||||
stroke: '#fff',
|
|
||||||
strokeWidth: 2
|
|
||||||
}}
|
|
||||||
activeDot={{
|
|
||||||
r: isMobile ? 6 : 8,
|
|
||||||
fill: "#0182FC",
|
|
||||||
stroke: '#fff',
|
|
||||||
strokeWidth: 2
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
{dataKeys.map(({ key, name, color }) => (
|
||||||
|
<Area
|
||||||
|
key={key}
|
||||||
|
name={name}
|
||||||
|
type="monotone"
|
||||||
|
dataKey={key}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={isMobile ? 2 : 3}
|
||||||
|
fill={`url(#color-${key})`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -193,4 +212,19 @@ const StatisticsCard = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StatisticsCard;
|
StatisticsCard.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
data: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
dataKeys: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
key: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
color: PropTypes.string.isRequired
|
||||||
|
})),
|
||||||
|
xDataKey: PropTypes.string,
|
||||||
|
valueFormatter: PropTypes.func,
|
||||||
|
timeFrame: PropTypes.oneOf(['day', 'week', 'month', 'year']),
|
||||||
|
onTimeFrameChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatisticsCard;
|
||||||
|
|||||||
114
src/components/Home/Employ/Employ.js
Normal file
114
src/components/Home/Employ/Employ.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import KitchPlusAppBar from '../AppBar';
|
||||||
|
import Sidebar from '../SideHome';
|
||||||
|
import Waiter from './contcet/waiter';
|
||||||
|
import Cooker from './contcet/cooker';
|
||||||
|
import Accountant from './contcet/Accountant';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const Employ = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const isMdUp = useMediaQuery(theme.breakpoints.up('md'));
|
||||||
|
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (window.innerWidth >= theme.breakpoints.values.md) {
|
||||||
|
setSidebarOpen(true);
|
||||||
|
} else {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [theme.breakpoints.values.md]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: '#F6F6F6',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
open={sidebarOpen}
|
||||||
|
onClose={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
isMobile={isMobile}
|
||||||
|
drawerWidth={drawerWidth}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
marginLeft: {
|
||||||
|
xs: 0,
|
||||||
|
sm: sidebarOpen ? `${drawerWidth}px` : 0,
|
||||||
|
md: 0,
|
||||||
|
},
|
||||||
|
transition: theme.transitions.create(['width', 'margin'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KitchPlusAppBar
|
||||||
|
onDrawerToggle={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
sidebarOpen={sidebarOpen}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||||
|
pt: { xs: 2, md: 3 },
|
||||||
|
pl: { xs: 1, sm: 3, md: 3 },
|
||||||
|
pb: { xs: 1, md: 4 },
|
||||||
|
pr: {
|
||||||
|
xs: 2,
|
||||||
|
md: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
transition: theme.transitions.create(['margin'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* المحتوى: Waiter و Cooker */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
width: { xs: '90%', sm: '95%', md: '100%' },
|
||||||
|
mt: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Waiter restaurantId={restaurantId} />
|
||||||
|
<Cooker restaurantId={restaurantId} />
|
||||||
|
<Accountant restaurantId={restaurantId} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Employ;
|
||||||
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
} from '@mui/material';
|
||||||
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import AddEmployModal from './AddEmployModal';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import IOSSwitch from './IOSSwitch';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||||
|
|
||||||
|
const Accountant = ({ restaurantId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const [switchStates, setSwitchStates] = useState({});
|
||||||
|
const [shiftFilter, setShiftFilter] = useState('all');
|
||||||
|
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||||
|
const [Accountants, setAccountants] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||||
|
const [AccountantDetails, setAccountantDetails] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
// حالات الحذف
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
const [selectedAccountant, setSelectedAccountant] = useState(null);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAccountants = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.getAllAccountants(restaurantId);
|
||||||
|
if (response.accountants) {
|
||||||
|
setAccountants(response.accountants);
|
||||||
|
|
||||||
|
// تهيئة switchStates حسب قيمة active
|
||||||
|
const initialStates = {};
|
||||||
|
response.accountants.forEach(accountants => {
|
||||||
|
initialStates[accountants.id] = accountants.active === 1; // أو Boolean(waiter.active)
|
||||||
|
});
|
||||||
|
setSwitchStates(initialStates);
|
||||||
|
} else {
|
||||||
|
setAccountants([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch waiters:", error);
|
||||||
|
setAccountants([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAccountants();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
const handleSwitchChange = async (id, email) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.toggleAccountStatus(email, "Accountant");
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setSwitchStates((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[id]: response.data,
|
||||||
|
}));
|
||||||
|
// alert(response.message);
|
||||||
|
} else {
|
||||||
|
alert("Failed: " + response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleOpenModal = () => setModalOpen(true);
|
||||||
|
const handleCloseModal = () => setModalOpen(false);
|
||||||
|
|
||||||
|
const handleConfirm = async (formData) => {
|
||||||
|
try {
|
||||||
|
const AccountantData = {
|
||||||
|
...formData,
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authService.registerAccountant(AccountantData);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
alert('Accountant registered successfully!');
|
||||||
|
setModalOpen(false);
|
||||||
|
|
||||||
|
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
|
||||||
|
const newAccountant = response.Accountant || AccountantData;
|
||||||
|
// تأكد أن الـ API يعيد الـ Accountant المضاف، أو استخدم البيانات المحلية
|
||||||
|
setAccountants((prev) => [newAccountant, ...prev]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (response.errors) {
|
||||||
|
const errorsList = Object.entries(response.errors)
|
||||||
|
.map(([field, msgs]) => {
|
||||||
|
if (Array.isArray(msgs)) {
|
||||||
|
return `${field}: ${msgs.join(', ')}`;
|
||||||
|
} else {
|
||||||
|
return `${field}: ${msgs}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
alert(`Failed to register Accountant due to validation errors:\n${errorsList}`);
|
||||||
|
} else {
|
||||||
|
alert('Failed to register Accountant: ' + response.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('An error occurred: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getShiftChipProps = (shiftType) => {
|
||||||
|
switch (shiftType?.toLowerCase()) {
|
||||||
|
case 'morning':
|
||||||
|
return {
|
||||||
|
label: 'Morning',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E7F4EE',
|
||||||
|
color: '#0D894F',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'evening':
|
||||||
|
return {
|
||||||
|
label: 'Evening',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#FDF1E8',
|
||||||
|
color: '#E46A11',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
label: shiftType,
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E0E0E0',
|
||||||
|
color: '#424242',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredAccountants =
|
||||||
|
shiftFilter === 'all'
|
||||||
|
? Accountants
|
||||||
|
: Accountants.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||||
|
|
||||||
|
const displayedAccountants = showAll ? filteredAccountants : filteredAccountants.slice(0, 3);
|
||||||
|
|
||||||
|
const handleFilterClick = (event) => {
|
||||||
|
setFilterAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterClose = () => {
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterSelect = (value) => {
|
||||||
|
setShiftFilter(value);
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// فتح مودال الحذف
|
||||||
|
const handleDeleteClick = (Accountant) => {
|
||||||
|
setSelectedAccountant(Accountant);
|
||||||
|
setConfirmDeleteOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تأكيد الحذف
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
if (!selectedAccountant) return;
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.deleteAccountant(selectedAccountant.id);
|
||||||
|
|
||||||
|
// هنا تحقق من وجود message بدل success
|
||||||
|
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||||
|
// alert(response.message);
|
||||||
|
setAccountants((prev) => prev.filter((w) => w.id !== selectedAccountant.id));
|
||||||
|
} else {
|
||||||
|
alert("Failed to delete Accountant: " + (response.message || "Unknown error"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("An error occurred: " + error.message);
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
setSelectedAccountant(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenDetails = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.getAccountantById(id);
|
||||||
|
if (response.accountant) {
|
||||||
|
setAccountantDetails(response.accountant);
|
||||||
|
setDetailsModalOpen(true);
|
||||||
|
} else {
|
||||||
|
alert("Failed to fetch Accountant details");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '100%', sm: '93.5%' },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'stretch', sm: 'center' },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '18px', sm: '20px' },
|
||||||
|
mb: { xs: 1, sm: 0 },
|
||||||
|
textAlign: { xs: 'center', sm: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Accountant
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Accountants.length > 3 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 90,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{showAll ? 'See Less' : 'See All'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
color: '#667085',
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||||
|
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||||
|
fontWeight: 600,
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
minWidth: 'unset',
|
||||||
|
}}
|
||||||
|
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||||
|
onClick={handleFilterClick}
|
||||||
|
>
|
||||||
|
{isMobile ? '' : 'Filters'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleOpenModal}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '100%', sm: '135px' },
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: { xs: 'unset', sm: '135px' },
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Add Accountant
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{
|
||||||
|
minWidth: { sm: 550, md: 650 },
|
||||||
|
tableLayout: 'auto',
|
||||||
|
}}
|
||||||
|
aria-label="recent activity table"
|
||||||
|
size={isMobile ? 'small' : 'medium'}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||||
|
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Email
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||||
|
Shift Type
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Actions
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
Loading...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : displayedAccountants.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
No activities found.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
displayedAccountants.map((Accountant) => (
|
||||||
|
<TableRow key={Accountant.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#296adaff",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 6 },
|
||||||
|
wordBreak: "break-word",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": { color: "#71a5ffff" },
|
||||||
|
}}
|
||||||
|
onClick={() => handleOpenDetails(Accountant.id)}
|
||||||
|
>
|
||||||
|
{Accountant.name}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#4F5867',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 3 },
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Accountant.email}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Chip {...getShiftChipProps(Accountant.shift_type)} />
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||||
|
<IOSSwitch
|
||||||
|
checked={!!switchStates[Accountant.id]}
|
||||||
|
onChange={() => handleSwitchChange(Accountant.id, Accountant.email)}
|
||||||
|
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||||
|
/>
|
||||||
|
<DeleteForeverIcon
|
||||||
|
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||||
|
onClick={() => handleDeleteClick(Accountant)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<AddEmployModal
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
title="Add Accountant"
|
||||||
|
buttonText="Add Accountant"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={filterAnchorEl}
|
||||||
|
open={Boolean(filterAnchorEl)}
|
||||||
|
onClose={handleFilterClose}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
px: 1,
|
||||||
|
position: 'relative',
|
||||||
|
width: '120px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['all', 'morning', 'evening'].map((shift) => (
|
||||||
|
<MenuItem
|
||||||
|
key={shift}
|
||||||
|
selected={shiftFilter === shift}
|
||||||
|
onClick={() => handleFilterSelect(shift)}
|
||||||
|
sx={{
|
||||||
|
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||||
|
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#f0f0f0ff',
|
||||||
|
transition: 'background-color 150ms ease-in-out',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* مودال تأكيد الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Are you sure you want to delete "{selectedAccountant?.name}"? This action cannot be undone.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteConfirm}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<EmployeeDetailsModal
|
||||||
|
open={detailsModalOpen}
|
||||||
|
onClose={() => setDetailsModalOpen(false)}
|
||||||
|
employee={AccountantDetails}
|
||||||
|
type="Accountant"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Accountant;
|
||||||
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
useTheme,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
MenuItem,
|
||||||
|
} from '@mui/material';
|
||||||
|
import VisibilityOutlined from '@mui/icons-material/VisibilityOutlined';
|
||||||
|
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||||
|
|
||||||
|
const AddUserModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
title = 'Add User',
|
||||||
|
buttonText = 'Add User',
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [formValues, setFormValues] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
password_confirmation: '',
|
||||||
|
shift_type: '',
|
||||||
|
working_hours: '',
|
||||||
|
monthly_salary: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
|
const handleTogglePassword = () => setShowPassword((prev) => !prev);
|
||||||
|
const handleToggleConfirmPassword = () => setShowConfirmPassword((prev) => !prev);
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
|
setFormValues((prev) => ({ ...prev, [key]: value }));
|
||||||
|
setErrors((prev) => ({ ...prev, [key]: null })); // مسح الخطأ عند التعديل
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
let tempErrors = {};
|
||||||
|
|
||||||
|
if (!formValues.name.trim()) tempErrors.name = 'Name is required';
|
||||||
|
|
||||||
|
if (!formValues.email.trim()) tempErrors.email = 'Email is required';
|
||||||
|
else {
|
||||||
|
// Regex بسيط للتحقق من صحة الإيميل
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(formValues.email))
|
||||||
|
tempErrors.email = 'Invalid email format';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formValues.password) {
|
||||||
|
tempErrors.password = 'Password is required';
|
||||||
|
} else if (formValues.password.length < 6) {
|
||||||
|
tempErrors.password = 'Password must be at least 6 characters long';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formValues.password_confirmation)
|
||||||
|
tempErrors.password_confirmation = 'Confirm password is required';
|
||||||
|
|
||||||
|
if (
|
||||||
|
formValues.password &&
|
||||||
|
formValues.password_confirmation &&
|
||||||
|
formValues.password !== formValues.password_confirmation
|
||||||
|
)
|
||||||
|
tempErrors.password_confirmation = 'Passwords do not match';
|
||||||
|
|
||||||
|
if (!formValues.shift_type) tempErrors.shift_type = 'Shift type is required';
|
||||||
|
|
||||||
|
if (!formValues.working_hours) tempErrors.working_hours = 'Working hours is required';
|
||||||
|
|
||||||
|
if (!formValues.monthly_salary) tempErrors.monthly_salary = 'Monthly salary is required';
|
||||||
|
|
||||||
|
setErrors(tempErrors);
|
||||||
|
|
||||||
|
return Object.keys(tempErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (validate()) {
|
||||||
|
onConfirm(formValues);
|
||||||
|
|
||||||
|
// إعادة تعيين الحقول بعد النجاح
|
||||||
|
setFormValues({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
password_confirmation: '',
|
||||||
|
shift_type: '',
|
||||||
|
working_hours: '',
|
||||||
|
monthly_salary: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
setErrors({}); // تصفير الأخطاء أيضًا
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle sx={{ fontWeight: 600, fontSize: '20px' }}>{title}</DialogTitle>
|
||||||
|
<DialogContent
|
||||||
|
dividers
|
||||||
|
sx={{
|
||||||
|
overflow: 'auto', // يسمح بالتمرير
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none', // يخفي الـ scrollbar على Webkit (Chrome, Edge, Safari)
|
||||||
|
},
|
||||||
|
scrollbarWidth: 'none', // يخفي scrollbar على Firefox
|
||||||
|
msOverflowStyle: 'none', // يخفي scrollbar على IE 10+
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Name */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Name
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="John Doe"
|
||||||
|
type="text"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formValues.name}
|
||||||
|
onChange={(e) => handleChange('name', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.name}
|
||||||
|
helperText={errors.name}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Email
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="example@example.com"
|
||||||
|
type="email"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formValues.email}
|
||||||
|
onChange={(e) => handleChange('email', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.email}
|
||||||
|
helperText={errors.email}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Password
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
placeholder="Enter password"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={formValues.password}
|
||||||
|
onChange={(e) => handleChange('password', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.password}
|
||||||
|
helperText={errors.password}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleTogglePassword} edge="end">
|
||||||
|
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Confirm Password */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Confirm Password
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
|
placeholder="Confirm password"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={formValues.password_confirmation}
|
||||||
|
onChange={(e) => handleChange('password_confirmation', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.password_confirmation}
|
||||||
|
helperText={errors.password_confirmation}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleToggleConfirmPassword} edge="end">
|
||||||
|
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Shift Type */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Shift Type
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={formValues.shift_type}
|
||||||
|
onChange={(e) => handleChange('shift_type', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.shift_type}
|
||||||
|
helperText={errors.shift_type}
|
||||||
|
placeholder="Select shift type"
|
||||||
|
>
|
||||||
|
<MenuItem value="morning">Morning</MenuItem>
|
||||||
|
<MenuItem value="evening">Evening</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Working Hours */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Working Hours
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="12"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formValues.working_hours}
|
||||||
|
onChange={(e) => handleChange('working_hours', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.working_hours}
|
||||||
|
helperText={errors.working_hours}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Monthly Salary */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
|
Monthly Salary
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
placeholder="500"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={formValues.monthly_salary}
|
||||||
|
onChange={(e) => handleChange('monthly_salary', e.target.value)}
|
||||||
|
sx={fieldStyle(theme)}
|
||||||
|
error={!!errors.monthly_salary}
|
||||||
|
helperText={errors.monthly_salary}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions sx={{ pr: 3, pb: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: '135px',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: '135px',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldStyle = (theme) => ({
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& label': { fontWeight: 600, fontSize: '15px', color: 'black' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: theme.palette.grey[400],
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
'& input::-ms-reveal, & input::-ms-clear': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AddUserModal;
|
||||||
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
const EmployeeDetailsModal = ({ open, onClose, employee, type }) => {
|
||||||
|
if (!employee) return null;
|
||||||
|
|
||||||
|
// دالة لتحويل الحالة الرقمية إلى نصية
|
||||||
|
const getStatusText = (active) => (active ? "Active" : "Inactive");
|
||||||
|
|
||||||
|
// عنوان المودال حسب نوع الموظف
|
||||||
|
const getTitle = () => {
|
||||||
|
switch (type?.toLowerCase()) {
|
||||||
|
case "waiter":
|
||||||
|
return "Waiter Details";
|
||||||
|
case "cooker":
|
||||||
|
return "Cooker Details";
|
||||||
|
case "accountant":
|
||||||
|
return "Accountant Details";
|
||||||
|
default:
|
||||||
|
return "Employee Details";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>{getTitle()}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
{employee.name && <Typography><b>Name:</b> {employee.name}</Typography>}
|
||||||
|
{employee.email && <Typography><b>Email:</b> {employee.email}</Typography>}
|
||||||
|
{employee.shift_type && <Typography><b>Shift:</b> {employee.shift_type}</Typography>}
|
||||||
|
{employee.working_hours !== undefined && (
|
||||||
|
<Typography><b>Working Hours:</b> {employee.working_hours}</Typography>
|
||||||
|
)}
|
||||||
|
{employee.monthly_salary !== undefined && (
|
||||||
|
<Typography><b>Monthly Salary:</b> {employee.monthly_salary}</Typography>
|
||||||
|
)}
|
||||||
|
{employee.active !== undefined && (
|
||||||
|
<Typography><b>Status:</b> {getStatusText(employee.active)}</Typography>
|
||||||
|
)}
|
||||||
|
{employee.code && <Typography><b>Code:</b> {employee.code}</Typography>}
|
||||||
|
{employee.expires_at && <Typography><b>Expires At:</b> {employee.expires_at}</Typography>}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ textTransform: "none", minWidth: { md: 120 } }}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmployeeDetailsModal;
|
||||||
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
|
|
||||||
|
const IOSSwitch = styled((props) => (
|
||||||
|
<Switch focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
width: 42,
|
||||||
|
height: 26,
|
||||||
|
padding: 0,
|
||||||
|
'& .MuiSwitch-switchBase': {
|
||||||
|
padding: 0,
|
||||||
|
margin: 2,
|
||||||
|
transitionDuration: '300ms',
|
||||||
|
'&.Mui-checked': {
|
||||||
|
transform: 'translateX(16px)',
|
||||||
|
color: '#fff',
|
||||||
|
'& + .MuiSwitch-track': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
opacity: 1,
|
||||||
|
border: 0,
|
||||||
|
},
|
||||||
|
'&.Mui-disabled + .MuiSwitch-track': {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&.Mui-focusVisible .MuiSwitch-thumb': {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
border: '6px solid #fff',
|
||||||
|
},
|
||||||
|
'&.Mui-disabled .MuiSwitch-thumb': {
|
||||||
|
color:
|
||||||
|
theme.palette.mode === 'light'
|
||||||
|
? theme.palette.grey[100]
|
||||||
|
: theme.palette.grey[600],
|
||||||
|
},
|
||||||
|
'&.Mui-disabled + .MuiSwitch-track': {
|
||||||
|
opacity: theme.palette.mode === 'light' ? 0.7 : 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-thumb': {
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
borderRadius: 11,
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-track': {
|
||||||
|
borderRadius: 26 / 2,
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.mode === 'light' ? '#E9E9EA' : 'rgba(255,255,255,0.35)',
|
||||||
|
opacity: 1,
|
||||||
|
transition: theme.transitions.create(['background-color'], {
|
||||||
|
duration: 500,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default IOSSwitch;
|
||||||
526
src/components/Home/Employ/contcet/cooker.js
Normal file
526
src/components/Home/Employ/contcet/cooker.js
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
} from '@mui/material';
|
||||||
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import AddEmployModal from './AddEmployModal';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import IOSSwitch from './IOSSwitch';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||||
|
|
||||||
|
const Cooker = ({ restaurantId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const [switchStates, setSwitchStates] = useState({});
|
||||||
|
const [shiftFilter, setShiftFilter] = useState('all');
|
||||||
|
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||||
|
const [Cookers, setCookers] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||||
|
const [CookerDetails, setCookerDetails] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
// حالات الحذف
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
const [selectedCooker, setSelectedCooker] = useState(null);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
// استدعاء API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCookers = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.getAllCookers(restaurantId);
|
||||||
|
if (response.cookers) {
|
||||||
|
setCookers(response.cookers);
|
||||||
|
|
||||||
|
const initialStates = {};
|
||||||
|
response.cookers.forEach(cookers => {
|
||||||
|
initialStates[cookers.id] = cookers.active === 1; // أو Boolean(waiter.active)
|
||||||
|
});
|
||||||
|
setSwitchStates(initialStates);
|
||||||
|
} else {
|
||||||
|
setCookers([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch Cookers:", error);
|
||||||
|
setCookers([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCookers();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
const handleSwitchChange = async (id, email) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.toggleAccountStatus(email, "Cooker");
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setSwitchStates((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[id]: response.data,
|
||||||
|
}));
|
||||||
|
// alert(response.message);
|
||||||
|
} else {
|
||||||
|
alert("Failed: " + response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenModal = () => setModalOpen(true);
|
||||||
|
const handleCloseModal = () => setModalOpen(false);
|
||||||
|
|
||||||
|
const handleConfirm = async (formData) => {
|
||||||
|
try {
|
||||||
|
const CookerData = {
|
||||||
|
...formData,
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authService.registerCooker(CookerData);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
alert('Cooker registered successfully!');
|
||||||
|
setModalOpen(false);
|
||||||
|
|
||||||
|
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
|
||||||
|
const newCooker = response.Cooker || CookerData;
|
||||||
|
// تأكد أن الـ API يعيد الـ Cooker المضاف، أو استخدم البيانات المحلية
|
||||||
|
setCookers((prev) => [newCooker, ...prev]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (response.errors) {
|
||||||
|
const errorsList = Object.entries(response.errors)
|
||||||
|
.map(([field, msgs]) => {
|
||||||
|
if (Array.isArray(msgs)) {
|
||||||
|
return `${field}: ${msgs.join(', ')}`;
|
||||||
|
} else {
|
||||||
|
return `${field}: ${msgs}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
alert(`Failed to register Cooker due to validation errors:\n${errorsList}`);
|
||||||
|
} else {
|
||||||
|
alert('Failed to register Cooker: ' + response.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('An error occurred: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getShiftChipProps = (shiftType) => {
|
||||||
|
switch (shiftType?.toLowerCase()) {
|
||||||
|
case 'morning':
|
||||||
|
return {
|
||||||
|
label: 'Morning',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E7F4EE',
|
||||||
|
color: '#0D894F',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'evening':
|
||||||
|
return {
|
||||||
|
label: 'Evening',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#FDF1E8',
|
||||||
|
color: '#E46A11',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
label: shiftType,
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E0E0E0',
|
||||||
|
color: '#424242',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredCookers =
|
||||||
|
shiftFilter === 'all'
|
||||||
|
? Cookers
|
||||||
|
: Cookers.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||||
|
|
||||||
|
const displayedCookers = showAll ? filteredCookers : filteredCookers.slice(0, 3);
|
||||||
|
|
||||||
|
const handleFilterClick = (event) => {
|
||||||
|
setFilterAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterClose = () => {
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterSelect = (value) => {
|
||||||
|
setShiftFilter(value);
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// فتح مودال الحذف
|
||||||
|
const handleDeleteClick = (Cooker) => {
|
||||||
|
setSelectedCooker(Cooker);
|
||||||
|
setConfirmDeleteOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تأكيد الحذف
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
if (!selectedCooker) return;
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.deleteCooker(selectedCooker.id);
|
||||||
|
|
||||||
|
// هنا تحقق من وجود message بدل success
|
||||||
|
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||||
|
// alert(response.message);
|
||||||
|
setCookers((prev) => prev.filter((w) => w.id !== selectedCooker.id));
|
||||||
|
} else {
|
||||||
|
alert("Failed to delete Cooker: " + (response.message || "Unknown error"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("An error occurred: " + error.message);
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
setSelectedCooker(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenDetails = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.getCookerById(id);
|
||||||
|
if (response.cooker) {
|
||||||
|
setCookerDetails(response.cooker);
|
||||||
|
setDetailsModalOpen(true);
|
||||||
|
} else {
|
||||||
|
alert("Failed to fetch Cooker details");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '100%', sm: '93.5%' },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'stretch', sm: 'center' },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '18px', sm: '20px' },
|
||||||
|
mb: { xs: 1, sm: 0 },
|
||||||
|
textAlign: { xs: 'center', sm: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cooker
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Cookers.length > 3 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 90,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{showAll ? 'See Less' : 'See All'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
color: '#667085',
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||||
|
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||||
|
fontWeight: 600,
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
minWidth: 'unset',
|
||||||
|
}}
|
||||||
|
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||||
|
onClick={handleFilterClick}
|
||||||
|
>
|
||||||
|
{isMobile ? '' : 'Filters'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleOpenModal}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '100%', sm: '135px' },
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: { xs: 'unset', sm: '135px' },
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Add Cooker
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{
|
||||||
|
minWidth: { sm: 550, md: 650 },
|
||||||
|
tableLayout: 'auto',
|
||||||
|
}}
|
||||||
|
aria-label="recent activity table"
|
||||||
|
size={isMobile ? 'small' : 'medium'}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||||
|
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Email
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||||
|
Shift Type
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Actions
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
Loading...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : displayedCookers.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
No activities found.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
displayedCookers.map((Cooker) => (
|
||||||
|
<TableRow key={Cooker.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#296adaff",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 6 },
|
||||||
|
wordBreak: "break-word",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": { color: "#71a5ffff" },
|
||||||
|
}}
|
||||||
|
onClick={() => handleOpenDetails(Cooker.id)}
|
||||||
|
>
|
||||||
|
{Cooker.name}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#4F5867',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 3 },
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Cooker.email}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Chip {...getShiftChipProps(Cooker.shift_type)} />
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||||
|
<IOSSwitch
|
||||||
|
checked={!!switchStates[Cooker.id]}
|
||||||
|
onChange={() => handleSwitchChange(Cooker.id, Cooker.email)}
|
||||||
|
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteForeverIcon
|
||||||
|
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||||
|
onClick={() => handleDeleteClick(Cooker)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<AddEmployModal
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
title="Add Cooker"
|
||||||
|
buttonText="Add Cooker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={filterAnchorEl}
|
||||||
|
open={Boolean(filterAnchorEl)}
|
||||||
|
onClose={handleFilterClose}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
px: 1,
|
||||||
|
position: 'relative',
|
||||||
|
width: '120px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['all', 'morning', 'evening'].map((shift) => (
|
||||||
|
<MenuItem
|
||||||
|
key={shift}
|
||||||
|
selected={shiftFilter === shift}
|
||||||
|
onClick={() => handleFilterSelect(shift)}
|
||||||
|
sx={{
|
||||||
|
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||||
|
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#f0f0f0ff',
|
||||||
|
transition: 'background-color 150ms ease-in-out',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* مودال تأكيد الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Are you sure you want to delete "{selectedCooker?.name}"? This action cannot be undone.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteConfirm}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<EmployeeDetailsModal
|
||||||
|
open={detailsModalOpen}
|
||||||
|
onClose={() => setDetailsModalOpen(false)}
|
||||||
|
employee={CookerDetails}
|
||||||
|
type="Cooker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Cooker;
|
||||||
519
src/components/Home/Employ/contcet/waiter.js
Normal file
519
src/components/Home/Employ/contcet/waiter.js
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
} from '@mui/material';
|
||||||
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import AddEmployModal from './AddEmployModal';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import IOSSwitch from './IOSSwitch';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||||
|
|
||||||
|
const Waiter = ({ restaurantId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const [switchStates, setSwitchStates] = useState({});
|
||||||
|
const [shiftFilter, setShiftFilter] = useState('all');
|
||||||
|
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||||
|
const [waiters, setWaiters] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||||
|
const [waiterDetails, setWaiterDetails] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
// حالات الحذف
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
const [selectedWaiter, setSelectedWaiter] = useState(null);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
// استدعاء API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchWaiters = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.getAllWaiters(restaurantId);
|
||||||
|
if (response.waiters) {
|
||||||
|
setWaiters(response.waiters);
|
||||||
|
|
||||||
|
// تهيئة switchStates حسب قيمة active
|
||||||
|
const initialStates = {};
|
||||||
|
response.waiters.forEach(waiter => {
|
||||||
|
initialStates[waiter.id] = waiter.active === 1; // أو Boolean(waiter.active)
|
||||||
|
});
|
||||||
|
setSwitchStates(initialStates);
|
||||||
|
} else {
|
||||||
|
setWaiters([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch waiters:", error);
|
||||||
|
setWaiters([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWaiters();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSwitchChange = async (id, email) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.toggleAccountStatus(email, "Waiter");
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setSwitchStates((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[id]: response.data,
|
||||||
|
}));
|
||||||
|
// alert(response.message);
|
||||||
|
} else {
|
||||||
|
alert("Failed: " + response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleOpenModal = () => setModalOpen(true);
|
||||||
|
const handleCloseModal = () => setModalOpen(false);
|
||||||
|
|
||||||
|
const handleConfirm = async (formData) => {
|
||||||
|
try {
|
||||||
|
const waiterData = {
|
||||||
|
...formData,
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authService.registerWaiter(waiterData);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
alert('Waiter registered successfully!');
|
||||||
|
setModalOpen(false);
|
||||||
|
// إعادة التحميل بعد الإضافة
|
||||||
|
const updated = await authService.getAllWaiters(restaurantId);
|
||||||
|
if (updated.waiters) setWaiters(updated.waiters);
|
||||||
|
} else {
|
||||||
|
if (response.errors) {
|
||||||
|
const errorsList = Object.entries(response.errors)
|
||||||
|
.map(([field, msgs]) => `${field}: ${msgs.join(', ')}`)
|
||||||
|
.join('\n');
|
||||||
|
alert(`Failed to register waiter due to validation errors:\n${errorsList}`);
|
||||||
|
} else {
|
||||||
|
alert('Failed to register waiter: ' + response.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('An error occurred: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShiftChipProps = (shiftType) => {
|
||||||
|
switch (shiftType?.toLowerCase()) {
|
||||||
|
case 'morning':
|
||||||
|
return {
|
||||||
|
label: 'Morning',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E7F4EE',
|
||||||
|
color: '#0D894F',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'evening':
|
||||||
|
return {
|
||||||
|
label: 'Evening',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#FDF1E8',
|
||||||
|
color: '#E46A11',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
label: shiftType,
|
||||||
|
sx: {
|
||||||
|
backgroundColor: '#E0E0E0',
|
||||||
|
color: '#424242',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
minWidth: 80,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredWaiters =
|
||||||
|
shiftFilter === 'all'
|
||||||
|
? waiters
|
||||||
|
: waiters.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||||
|
|
||||||
|
const displayedWaiters = showAll ? filteredWaiters : filteredWaiters.slice(0, 3);
|
||||||
|
|
||||||
|
const handleFilterClick = (event) => {
|
||||||
|
setFilterAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterClose = () => {
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterSelect = (value) => {
|
||||||
|
setShiftFilter(value);
|
||||||
|
setFilterAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// فتح مودال الحذف
|
||||||
|
const handleDeleteClick = (waiter) => {
|
||||||
|
setSelectedWaiter(waiter);
|
||||||
|
setConfirmDeleteOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تأكيد الحذف
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
if (!selectedWaiter) return;
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const response = await authService.deleteWaiter(selectedWaiter.id);
|
||||||
|
|
||||||
|
// هنا تحقق من وجود message بدل success
|
||||||
|
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||||
|
// alert(response.message);
|
||||||
|
setWaiters((prev) => prev.filter((w) => w.id !== selectedWaiter.id));
|
||||||
|
} else {
|
||||||
|
alert("Failed to delete waiter: " + (response.message || "Unknown error"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("An error occurred: " + error.message);
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
setSelectedWaiter(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenDetails = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.getWaiterById(id);
|
||||||
|
if (response.waiter) {
|
||||||
|
setWaiterDetails(response.waiter);
|
||||||
|
setDetailsModalOpen(true);
|
||||||
|
} else {
|
||||||
|
alert("Failed to fetch waiter details");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error: " + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '100%', sm: '93.5%' },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'stretch', sm: 'center' },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '18px', sm: '20px' },
|
||||||
|
mb: { xs: 1, sm: 0 },
|
||||||
|
textAlign: { xs: 'center', sm: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Waiter
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{waiters.length > 3 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 90,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{showAll ? 'See Less' : 'See All'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
color: '#667085',
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||||
|
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||||
|
fontWeight: 600,
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
minWidth: 'unset',
|
||||||
|
}}
|
||||||
|
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||||
|
onClick={handleFilterClick}
|
||||||
|
>
|
||||||
|
{isMobile ? '' : 'Filters'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleOpenModal}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '100%', sm: '135px' },
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: { xs: 'unset', sm: '135px' },
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Add Waiter
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{
|
||||||
|
minWidth: { sm: 550, md: 650 },
|
||||||
|
tableLayout: 'auto',
|
||||||
|
}}
|
||||||
|
aria-label="recent activity table"
|
||||||
|
size={isMobile ? 'small' : 'medium'}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||||
|
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Email
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||||
|
Shift Type
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
Actions
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
Loading...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : displayedWaiters.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||||
|
No activities found.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
displayedWaiters.map((waiter) => (
|
||||||
|
<TableRow key={waiter.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#296adaff",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 6 },
|
||||||
|
wordBreak: "break-word",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": { color: "#71a5ffff" },
|
||||||
|
}}
|
||||||
|
onClick={() => handleOpenDetails(waiter.id)}
|
||||||
|
>
|
||||||
|
{waiter.name}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#4F5867',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 500,
|
||||||
|
pl: { xs: 1, sm: 3 },
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{waiter.email}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Chip {...getShiftChipProps(waiter.shift_type)} />
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||||
|
<IOSSwitch
|
||||||
|
checked={!!switchStates[waiter.id]}
|
||||||
|
onChange={() => handleSwitchChange(waiter.id, waiter.email)}
|
||||||
|
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteForeverIcon
|
||||||
|
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||||
|
onClick={() => handleDeleteClick(waiter)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<AddEmployModal
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
title="Add Waiter"
|
||||||
|
buttonText="Add Waiter"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={filterAnchorEl}
|
||||||
|
open={Boolean(filterAnchorEl)}
|
||||||
|
onClose={handleFilterClose}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
px: 1,
|
||||||
|
position: 'relative',
|
||||||
|
width: '120px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['all', 'morning', 'evening'].map((shift) => (
|
||||||
|
<MenuItem
|
||||||
|
key={shift}
|
||||||
|
selected={shiftFilter === shift}
|
||||||
|
onClick={() => handleFilterSelect(shift)}
|
||||||
|
sx={{
|
||||||
|
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||||
|
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#f0f0f0ff',
|
||||||
|
transition: 'background-color 150ms ease-in-out',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* مودال تأكيد الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Are you sure you want to delete "{selectedWaiter?.name}"? This action cannot be undone.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteConfirm}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<EmployeeDetailsModal
|
||||||
|
open={detailsModalOpen}
|
||||||
|
onClose={() => setDetailsModalOpen(false)}
|
||||||
|
employee={waiterDetails} // بدل waiter
|
||||||
|
type="waiter"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Waiter;
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
|
||||||
import KitchPlusAppBar from '../AppBar';
|
|
||||||
import Sidebar from '../SideHome';
|
|
||||||
import PostSubmission from './contcet/PostSubmission';
|
|
||||||
import CloudKitchenHosting from './contcet/CloudKitchenHosting';
|
|
||||||
import Infrastructure from './contcet/Infrastructure';
|
|
||||||
import RstaurantOperations from './contcet/RstaurantOperations';
|
|
||||||
import Facilitation from './contcet/Facilitation';
|
|
||||||
import Expansion from './contcet/Expansion';
|
|
||||||
|
|
||||||
import SideProfile from './SideProfile';
|
|
||||||
|
|
||||||
const drawerWidth = 230;
|
|
||||||
|
|
||||||
const HostKitchen = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
const [hasProducts, setHasProducts] = useState(false);
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
|
||||||
|
|
||||||
// ⬇️ إدارة الخطوة الحالية
|
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
|
||||||
|
|
||||||
const steps = [
|
|
||||||
<CloudKitchenHosting onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
|
||||||
<RstaurantOperations onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
|
||||||
<Infrastructure onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
|
||||||
<Facilitation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
|
||||||
<Expansion onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkProducts = async () => {
|
|
||||||
const productsExist = await checkIfProductsExist();
|
|
||||||
setHasProducts(productsExist);
|
|
||||||
};
|
|
||||||
checkProducts();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkIfProductsExist = async () => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
|
||||||
setSidebarOpen(true);
|
|
||||||
} else {
|
|
||||||
setSidebarOpen(false);
|
|
||||||
}
|
|
||||||
}, [theme.breakpoints.values.md]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
|
||||||
setSidebarOpen(true);
|
|
||||||
} else {
|
|
||||||
setSidebarOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleResize();
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
|
||||||
}, [theme.breakpoints.values.md]);
|
|
||||||
|
|
||||||
const handleDrawerToggle = () => {
|
|
||||||
setSidebarOpen(!sidebarOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
height: '100vh',
|
|
||||||
backgroundColor: '#F6F6F6',
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}>
|
|
||||||
<Sidebar
|
|
||||||
open={sidebarOpen}
|
|
||||||
onClose={handleDrawerToggle}
|
|
||||||
isMobile={isMobile}
|
|
||||||
drawerWidth={drawerWidth}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
|
||||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
|
||||||
transition: theme.transitions.create(['width'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
}}>
|
|
||||||
<KitchPlusAppBar
|
|
||||||
onDrawerToggle={handleDrawerToggle}
|
|
||||||
sidebarOpen={sidebarOpen}
|
|
||||||
isMobile={isMobile}
|
|
||||||
/>
|
|
||||||
{/* <Box sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
width: sidebarOpen ? 'calc(100% - 20px)' : '100%',
|
|
||||||
pt: { xs: 0.5, sm: 1 },
|
|
||||||
mt: { xs: 1, sm: 2 },
|
|
||||||
overflowY: 'auto',
|
|
||||||
pl: { xs: 0, sm: 2 },
|
|
||||||
pr: { xs: 1, sm: 2 },
|
|
||||||
pb: { xs: 1, sm: 2 },
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': { display: 'none' },
|
|
||||||
transition: theme.transitions.create(['margin'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
}}>
|
|
||||||
<PostSubmission />
|
|
||||||
</Box> */}
|
|
||||||
<Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex', height: '100vh',
|
|
||||||
overflowY: 'auto',
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
height: '100vh',
|
|
||||||
ml: 3,
|
|
||||||
mb: 2,
|
|
||||||
width: { xs: '30%', md: '30%' },
|
|
||||||
display: { xs: 'none', sm: 'block', md: 'block' },
|
|
||||||
// overflowY: 'auto',
|
|
||||||
// scrollbarWidth: 'none',
|
|
||||||
// '&::-webkit-scrollbar': {
|
|
||||||
// display: 'none',
|
|
||||||
// },
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
minHeight: '100%', pb: 15, pt: 3,
|
|
||||||
|
|
||||||
}}>
|
|
||||||
<SideProfile
|
|
||||||
currentStepIndex={currentStep}
|
|
||||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
ml: { xs: 1, md: 3 },
|
|
||||||
flexGrow: 1,
|
|
||||||
height: '100vh',
|
|
||||||
pr: { sm: 2, md: 1 },
|
|
||||||
pt: 3,
|
|
||||||
mb: { sm: 20 },
|
|
||||||
width: { xs: '50%', md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
|
||||||
// overflowY: 'auto',
|
|
||||||
// scrollbarWidth: 'none',
|
|
||||||
// '&::-webkit-scrollbar': {
|
|
||||||
// display: 'none',
|
|
||||||
// },
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
minHeight: '100%', pb: 18,
|
|
||||||
|
|
||||||
}}>
|
|
||||||
{steps[currentStep]}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HostKitchen;
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
|
||||||
|
|
||||||
const CloudKitchenHosting = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: 5,
|
|
||||||
pb: 10,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography
|
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cloud Kitchen Hosting
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ width: '80%' }}>
|
|
||||||
<Typography
|
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
|
||||||
{[
|
|
||||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
|
||||||
{ label: 'Restaurant Location', placeholder: 'Street 123, Jordan' },
|
|
||||||
{ label: 'Availabel Space', placeholder: '300 sq. feet' },
|
|
||||||
{ label: 'Number of Employees', placeholder: '200' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
|
||||||
{field.label}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={onNext}
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CloudKitchenHosting;
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import {
|
|
||||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
|
||||||
RadioGroup,
|
|
||||||
FormControlLabel,
|
|
||||||
} from '@mui/material';
|
|
||||||
import ConfirmationDialog from './ConfirmationDialog'; // ✅ استدعاء المودال المنفصل
|
|
||||||
|
|
||||||
const Expansion = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
|
||||||
|
|
||||||
const handleOpenModal = () => setOpenModal(true);
|
|
||||||
const handleCloseModal = () => setOpenModal(false);
|
|
||||||
const handleConfirmNext = () => {
|
|
||||||
handleCloseModal();
|
|
||||||
onNext();
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: 5,
|
|
||||||
pb: 23.2,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography
|
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Expansion & Future Cooperation
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ width: '80%' }}>
|
|
||||||
<Typography
|
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1.5, color: '#191635' }}
|
|
||||||
>
|
|
||||||
Hosting Multiple Cloud Kitchens
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup row name="additionalFacilities">
|
|
||||||
<FormControlLabel
|
|
||||||
value="yes"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Yes"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="no"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
|
||||||
transform: 'scale(0.85)',
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="No"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
|
||||||
{[
|
|
||||||
{ label: 'Future Kitchen Plans', placeholder: '20' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 3 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
|
||||||
{field.label}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1.5, mt: 2, color: '#191635' }}
|
|
||||||
>
|
|
||||||
Partnership & Support
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup row name="additionalFacilities">
|
|
||||||
<FormControlLabel
|
|
||||||
value="yes"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Yes"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="no"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
|
||||||
transform: 'scale(0.85)',
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="No"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={handleOpenModal}
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* ✅ Confirmation Modal */}
|
|
||||||
<ConfirmationDialog
|
|
||||||
open={openModal}
|
|
||||||
onClose={handleCloseModal}
|
|
||||||
onConfirm={handleConfirmNext}
|
|
||||||
title="Confirm Submission"
|
|
||||||
description="Are you sure you want to proceed to the next step?"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Expansion;
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
|
||||||
RadioGroup,
|
|
||||||
FormControlLabel,
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
const Facilitation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: 5,
|
|
||||||
pb: 5.8,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography
|
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Facilitation & Cooperation
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ width: '80%' }}>
|
|
||||||
<Typography
|
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, color: '#191635' }}
|
|
||||||
>
|
|
||||||
Additional Services Provided
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup row name="additionalFacilities">
|
|
||||||
<FormControlLabel
|
|
||||||
value="yes"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Yes"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="no"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
|
||||||
transform: 'scale(0.85)',
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="No"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Additional Services Input */}
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
|
||||||
Additional Services
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={`1. Marketing\n2. Logistics\n3. Staff Training`}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
minRows={4}
|
|
||||||
sx={{
|
|
||||||
'& .MuiInputBase-root': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px',
|
|
||||||
alignItems: 'start',
|
|
||||||
},
|
|
||||||
'& textarea::placeholder': {
|
|
||||||
color: '#969BA7',
|
|
||||||
whiteSpace: 'pre-line', // لعرض الأسطر بشكل صحيح
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{}}>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, color: '#191635' }}
|
|
||||||
>
|
|
||||||
Ventilation and Cooling System
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup row name="additionalFacilities">
|
|
||||||
<FormControlLabel
|
|
||||||
value="yes"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Yes"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="no"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
|
||||||
transform: 'scale(0.85)',
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="No"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
|
||||||
{[
|
|
||||||
{ label: 'Working Hours', placeholder: '8 hours' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
|
||||||
{field.label}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{ pt: 1 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={onNext}
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Facilitation;
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Box, Typography, Stack, Button, useTheme, TextField, Radio,
|
|
||||||
RadioGroup,
|
|
||||||
FormControlLabel,
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
const Infrastructure = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: 5,
|
|
||||||
pb: 11,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography
|
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Infrastructure & Equipments
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ width: '80%' }}>
|
|
||||||
<Typography
|
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
|
||||||
{[
|
|
||||||
{ label: 'Current Equipment', placeholder: 'oven, refrgerators' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
|
||||||
{field.label}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{}}>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: 500, fontSize: '16px', mb: 1, mt: 2, color: '#191635' }}
|
|
||||||
>
|
|
||||||
Additional Facilities Needed
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup row name="additionalFacilities">
|
|
||||||
<FormControlLabel
|
|
||||||
value="yes"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)', // شفافية اللون
|
|
||||||
transform: 'scale(0.85)', // تقليل حجم الزر لتقليل "سُمك الحواف"
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Yes"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="no"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(150, 155, 167, 0.6)',
|
|
||||||
transform: 'scale(0.85)',
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="No"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Description Input */}
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px' }}>
|
|
||||||
Description
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder="We need Pizza Machine"
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
minRows={5}
|
|
||||||
sx={{
|
|
||||||
'& .MuiInputBase-root': {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '15px',
|
|
||||||
alignItems: 'start',
|
|
||||||
},
|
|
||||||
'& textarea::placeholder': {
|
|
||||||
color: '#969BA7',
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2.8 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={onNext}
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'none', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Infrastructure;
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Button, Card, CardContent, Stack, Typography, useMediaQuery } from '@mui/material';
|
|
||||||
import AirportShuttleIcon from '@mui/icons-material/AirportShuttle';
|
|
||||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
|
||||||
import Inventory2Icon from '@mui/icons-material/Inventory2';
|
|
||||||
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
|
||||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
|
||||||
import { useTheme } from '@mui/material/styles';
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
|
||||||
|
|
||||||
|
|
||||||
const IconCircle = ({ children, bgColor = '#DEDEFA', outerColor = '#EFEFFD' }) => (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: 40, sm: 45, md: 50 },
|
|
||||||
height: { xs: 43, sm: 48, md: 53 },
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: { xs: 32, sm: 36, md: 40 },
|
|
||||||
height: { xs: 35, sm: 39, md: 43 },
|
|
||||||
borderRadius: '50%',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
backgroundColor: outerColor,
|
|
||||||
zIndex: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: { xs: 24, sm: 27, md: 30 },
|
|
||||||
height: { xs: 27, sm: 30, md: 33 },
|
|
||||||
backgroundColor: bgColor,
|
|
||||||
borderRadius: '50%',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const StatusCard = ({
|
|
||||||
icon,
|
|
||||||
statusText,
|
|
||||||
statusColor,
|
|
||||||
iconColor,
|
|
||||||
outerColor,
|
|
||||||
innerColor,
|
|
||||||
height = {
|
|
||||||
xs: 'calc(140px + 0.5vh)',
|
|
||||||
sm: 'calc(150px + 0.5vh)',
|
|
||||||
md: 'calc(162px + 0.5vh)'
|
|
||||||
},
|
|
||||||
width = {
|
|
||||||
xs: 'min(90%, 600px)', // تقليل من 90% إلى 85% والحد الأقصى من 300px إلى 280px
|
|
||||||
sm: 'clamp(220px, 23vw, 280px)', // تقليل جميع القيم
|
|
||||||
md: 'clamp(220px, 19vw, 300px)'
|
|
||||||
},
|
|
||||||
transition = 'width 0.3s ease',
|
|
||||||
iconSpacing = { xs: 6, sm: 8, md: 12 },
|
|
||||||
extraButton,
|
|
||||||
statusButtonWidth,
|
|
||||||
title = 'Point Of Sale',
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
p: 0,
|
|
||||||
pb: 2,
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: 'none',
|
|
||||||
minWidth: { xs: 180, sm: 220 }
|
|
||||||
}}>
|
|
||||||
<CardContent>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: iconSpacing }}>
|
|
||||||
<IconCircle bgColor={innerColor} outerColor={outerColor}>
|
|
||||||
{React.cloneElement(icon, { sx: { color: iconColor, fontSize: { xs: 16, sm: 18, md: 20 } } })}
|
|
||||||
</IconCircle>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#E7F4EE',
|
|
||||||
color: statusColor,
|
|
||||||
textTransform: 'none',
|
|
||||||
boxShadow: 'none',
|
|
||||||
borderRadius: '100px',
|
|
||||||
width: statusButtonWidth || 'auto',
|
|
||||||
fontSize: { xs: '0.7rem', sm: '0.8rem', md: '0.9rem' },
|
|
||||||
px: { xs: 1, sm: 1.5 },
|
|
||||||
py: 0.5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{statusText}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
gutterBottom
|
|
||||||
mt={1.5}
|
|
||||||
sx={{
|
|
||||||
color: '#667085',
|
|
||||||
fontSize: { xs: '14px', sm: '15px', md: '16px' },
|
|
||||||
fontWeight: 500
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
|
||||||
{extraButton ? extraButton : <Box />}
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#F7F7F7',
|
|
||||||
width: { xs: '26px', sm: '28px', md: '30px' },
|
|
||||||
height: { xs: '26px', sm: '28px', md: '30px' },
|
|
||||||
minWidth: '0',
|
|
||||||
padding: 0,
|
|
||||||
borderRadius: '12px',
|
|
||||||
boxShadow: 'none',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#e0e0e0',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowOutwardIcon
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
width: { xs: '16px', sm: '18px', md: '20px' },
|
|
||||||
height: { xs: '16px', sm: '18px', md: '20px' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PostSubmission = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Header Section */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
|
||||||
mb: 3,
|
|
||||||
pl: 1,
|
|
||||||
pr: { xs: 1, sm: 3 },
|
|
||||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
|
||||||
gap: { xs: 2, sm: 0 }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Title */}
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
sx={{
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
|
||||||
color: '#121212'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Post Submission
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{/* Buttons */}
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: { xs: 1, sm: 2 },
|
|
||||||
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
|
||||||
width: { xs: '100%', sm: 'auto' },
|
|
||||||
justifyContent: { xs: 'space-between', sm: 'space-between', md: 'flex-end' }
|
|
||||||
}}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
boxShadow: 'none',
|
|
||||||
borderRadius: '8px',
|
|
||||||
height: '40px',
|
|
||||||
width: { xs: '48%', sm: '130px', md: '100px' },
|
|
||||||
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
|
||||||
fontWeight: 600,
|
|
||||||
p: 0,
|
|
||||||
m: 0,
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
minWidth: 'unset'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
{!isSmallScreen && 'Interested'}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PostSubmission;
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
|
||||||
|
|
||||||
const CloudKitchenHosting = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: { xs: '90%', sm: '100%', md: '92.5%' },
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
px: 4,
|
|
||||||
pt: 5,
|
|
||||||
pb: 10,
|
|
||||||
display: 'block',
|
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: '0px 1px 4px rgba(0,0,0,0.05)',
|
|
||||||
position: 'relative',
|
|
||||||
width: { xs: '85%', sm: '90%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2.5}>
|
|
||||||
<Typography
|
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Rstaurant Operations
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ width: '80%' }}>
|
|
||||||
<Typography
|
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your basic information to proceed to registration of your own restaurant on this platform
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
|
||||||
{[
|
|
||||||
{ label: 'Monthly Sales Value', placeholder: '$400' },
|
|
||||||
{ label: 'Operating Size (Customers/Day)', placeholder: '20' },
|
|
||||||
{ label: 'Peak Hours', placeholder:'Breakfast' },
|
|
||||||
{ label: 'Preferred Cuisine to Host', placeholder: 'Fast Food' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
|
||||||
{field.label}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: '10px',
|
|
||||||
transition: '0.3s',
|
|
||||||
'&.Mui-focused fieldset': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
|
||||||
borderColor: '#3f51b5',
|
|
||||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={onNext}
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
onClick={onBack}
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
fontFamily: 'PlusJakartaSans',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
|
||||||
height: { xs: '45px', sm: '52px' },
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CloudKitchenHosting;
|
|
||||||
@@ -2,8 +2,8 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
import KitchPlusAppBar from '../AppBar';
|
import KitchPlusAppBar from '../AppBar';
|
||||||
import Sidebar from '../SideHome';
|
import Sidebar from '../SideHome';
|
||||||
import ProductEntry from './ProductEntry';
|
import ProductEntry from './contect/ProductEntry';
|
||||||
import TableView from './TableView';
|
import TableView from './contect/TableView';
|
||||||
|
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
@@ -53,9 +53,9 @@ const Inventory = () => {
|
|||||||
status: newProduct.consumptionStatus ? 'Published' : 'Draft',
|
status: newProduct.consumptionStatus ? 'Published' : 'Draft',
|
||||||
expiration: newProduct.expirationDate
|
expiration: newProduct.expirationDate
|
||||||
? `${Math.ceil(
|
? `${Math.ceil(
|
||||||
(new Date(newProduct.expirationDate) - new Date()) /
|
(new Date(newProduct.expirationDate) - new Date()) /
|
||||||
(1000 * 60 * 60 * 24)
|
(1000 * 60 * 60 * 24)
|
||||||
)} Days Left`
|
)} Days Left`
|
||||||
: 'N/A',
|
: 'N/A',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const initialProducts = [
|
|||||||
|
|
||||||
const InventoryTablePage = () => {
|
const InventoryTablePage = () => {
|
||||||
const [products, setProducts] = useState(initialProducts);
|
const [products, setProducts] = useState(initialProducts);
|
||||||
|
|
||||||
const handleAddNewProduct = () => {
|
const handleAddNewProduct = () => {
|
||||||
alert('Navigate to Add Product page or open form here');
|
alert('Navigate to Add Product page or open form here');
|
||||||
};
|
};
|
||||||
@@ -416,9 +416,9 @@ const ProductEntry = ({ onAdd, onShowTable }) => {
|
|||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: '#FFECE0',
|
||||||
color: '#fff',
|
color: theme.palette.primary.main,
|
||||||
borderColor: theme.palette.primary.dark,
|
borderColor: '#FFECE0',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
206
src/components/Home/Meal/Meal.js
Normal file
206
src/components/Home/Meal/Meal.js
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery, Typography } from '@mui/material';
|
||||||
|
import KitchPlusAppBar from '../AppBar';
|
||||||
|
import Sidebar from '../SideHome';
|
||||||
|
import AccountSettings from './contect/AccountSettings';
|
||||||
|
import MealsByCateg from './contect/MealsByCateg';
|
||||||
|
import AllMeals from './contect/AllMeals';
|
||||||
|
import ProductDetail from './contect/ProductDetail';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const Meal = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
|
||||||
|
const [showAllPopular, setShowAllPopular] = useState(false);
|
||||||
|
const [categories, setCategories] = useState([]);
|
||||||
|
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
|
||||||
|
const [categoryName, setCategoryName] = useState('');
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState(null);
|
||||||
|
const [meals, setMeals] = useState([]);
|
||||||
|
const [loadingMeals, setLoadingMeals] = useState(false);
|
||||||
|
|
||||||
|
// جلب الفئات عند التحميل
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
if (!restaurantId) return;
|
||||||
|
const result = await authService.getCategoriesByRestaurant(restaurantId);
|
||||||
|
if (result.success) setCategories(result.data);
|
||||||
|
else setCategories([]);
|
||||||
|
};
|
||||||
|
fetchCategories();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
// تحديث اسم الفئة عند تغيير selectedCategoryId أو categories
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedCategoryId && categories.length > 0) {
|
||||||
|
const selected = categories.find(c => c.id === selectedCategoryId);
|
||||||
|
setCategoryName(selected ? selected.name : '');
|
||||||
|
} else {
|
||||||
|
setCategoryName('');
|
||||||
|
}
|
||||||
|
}, [selectedCategoryId, categories]);
|
||||||
|
|
||||||
|
// جلب الوجبات عند اختيار الفئة
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchMeals = async () => {
|
||||||
|
if (selectedCategoryId && restaurantId) {
|
||||||
|
setLoadingMeals(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getMealsByCategory(restaurantId, selectedCategoryId);
|
||||||
|
if (result.success) {
|
||||||
|
const selectedCategory = categories.find(c => c.id === selectedCategoryId);
|
||||||
|
const mealsWithImages = result.data.map(meal => ({
|
||||||
|
id: meal.id,
|
||||||
|
name: meal.name,
|
||||||
|
price: meal.price,
|
||||||
|
unit: meal.unit || '/pcs',
|
||||||
|
image: meal.photo || '/images/default-product.png',
|
||||||
|
category: selectedCategory ? selectedCategory.name : '', // اسم الفئة
|
||||||
|
category_id: meal.category_id,
|
||||||
|
restaurant_id: meal.restaurant_id,
|
||||||
|
description: meal.description || [],
|
||||||
|
additions: meal.additions || []
|
||||||
|
}));
|
||||||
|
setMeals(mealsWithImages);
|
||||||
|
} else setMeals([]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setMeals([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingMeals(false);
|
||||||
|
}
|
||||||
|
} else setMeals([]);
|
||||||
|
};
|
||||||
|
fetchMeals();
|
||||||
|
}, [selectedCategoryId, restaurantId, categories]);
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||||
|
const toggleShowAllPopular = () => setShowAllPopular(prev => !prev);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||||
|
<Sidebar
|
||||||
|
open={sidebarOpen}
|
||||||
|
onClose={handleDrawerToggle}
|
||||||
|
isMobile={isMobile}
|
||||||
|
drawerWidth={drawerWidth}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '10%',
|
||||||
|
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
||||||
|
transition: theme.transitions.create(['width'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KitchPlusAppBar
|
||||||
|
onDrawerToggle={handleDrawerToggle}
|
||||||
|
sidebarOpen={sidebarOpen}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||||
|
pt: { xs: 2, sm: 3 },
|
||||||
|
overflowY: 'auto',
|
||||||
|
pl: { xs: 1, sm: 2 },
|
||||||
|
pr: { xs: 1, sm: 2 },
|
||||||
|
md: 10,
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedProduct ? (
|
||||||
|
<ProductDetail
|
||||||
|
product={selectedProduct}
|
||||||
|
onBack={() => setSelectedProduct(null)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<AccountSettings
|
||||||
|
selectedCategory={selectedCategoryId}
|
||||||
|
setSelectedCategory={setSelectedCategoryId}
|
||||||
|
restaurantId={restaurantId}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedCategoryId ? (
|
||||||
|
<Box sx={{ mt: 3, md: 10, }}>
|
||||||
|
|
||||||
|
<MealsByCateg
|
||||||
|
categoryId={selectedCategoryId}
|
||||||
|
restaurantId={restaurantId}
|
||||||
|
categoryName={categoryName}
|
||||||
|
onBack={() => setSelectedCategoryId(null)}
|
||||||
|
onProductClick={(meal) => {
|
||||||
|
const category = categories.find(c => c.id === meal.category_id);
|
||||||
|
setSelectedProduct({
|
||||||
|
...meal,
|
||||||
|
category: category ? category.name : '',
|
||||||
|
category_id: meal.category_id,
|
||||||
|
restaurant_id: restaurantId
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
) : !showAllPopular ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
|
alignItems: 'stretch',
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexBasis: { xs: '100%', md: '100%' },
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
pb: { xs: 4, md: 4 },
|
||||||
|
pr: { xs: 1, md: 0 },
|
||||||
|
mb: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AllMeals
|
||||||
|
restaurantId={restaurantId}
|
||||||
|
categories={categories}
|
||||||
|
showAll={showAllPopular}
|
||||||
|
onToggleShowAll={toggleShowAllPopular}
|
||||||
|
onProductClick={(meal) => {
|
||||||
|
const category = categories.find(c => c.id === meal.category_id);
|
||||||
|
setSelectedProduct({
|
||||||
|
...meal,
|
||||||
|
category: category ? category.name : '',
|
||||||
|
category_id: meal.category_id,
|
||||||
|
restaurant_id: restaurantId
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Meal;
|
||||||
330
src/components/Home/Meal/contect/AccountSettings.js
Normal file
330
src/components/Home/Meal/contect/AccountSettings.js
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
Skeleton,
|
||||||
|
} from '@mui/material';
|
||||||
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
|
import CategoryScrollList from './CategoryScrollList';
|
||||||
|
import AddCategory from './AddCategory';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
|
||||||
|
const AccountSettings = ({ selectedCategory, setSelectedCategory, restaurantId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true); // <-- حالة التحميل
|
||||||
|
const [selectedCategoryForEdit, setSelectedCategoryForEdit] = useState(null);
|
||||||
|
const [contextMenu, setContextMenu] = useState(null);
|
||||||
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
const scrollRef = useRef();
|
||||||
|
|
||||||
|
// تحميل الفئات عند فتح الصفحة
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
setLoading(true); // بدء التحميل
|
||||||
|
const result = await authService.getCategoriesByRestaurant(restaurantId);
|
||||||
|
if (result.success) setCategories(result.data);
|
||||||
|
else console.error(result.message);
|
||||||
|
setLoading(false); // انتهاء التحميل
|
||||||
|
};
|
||||||
|
if (restaurantId) fetchCategories();
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedCategory) localStorage.setItem('selectedCategoryId', selectedCategory);
|
||||||
|
}, [selectedCategory]);
|
||||||
|
|
||||||
|
const scroll = (offset) => {
|
||||||
|
if (scrollRef.current) scrollRef.current.scrollLeft += offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCategory = async (payload) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.addCategory(payload);
|
||||||
|
setCategories((prev) => [...prev, response.category]);
|
||||||
|
setOpenAddModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطأ أثناء الإضافة:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateCategory = async (payload) => {
|
||||||
|
try {
|
||||||
|
const response = await authService.updateCategory(selectedCategoryForEdit.id, payload);
|
||||||
|
setCategories((prev) =>
|
||||||
|
prev.map((cat) => (cat.id === selectedCategoryForEdit.id ? response.category : cat))
|
||||||
|
);
|
||||||
|
setOpenAddModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطأ أثناء التعديل:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteCategory = async () => {
|
||||||
|
try {
|
||||||
|
await authService.deleteCategory(selectedCategoryForEdit.id);
|
||||||
|
setCategories((prev) =>
|
||||||
|
prev.filter((cat) => cat.id !== selectedCategoryForEdit.id)
|
||||||
|
);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
setSelectedCategoryForEdit(null);
|
||||||
|
|
||||||
|
if (selectedCategory === selectedCategoryForEdit?.id) {
|
||||||
|
setSelectedCategory(null);
|
||||||
|
localStorage.removeItem('selectedCategoryId');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطأ أثناء الحذف:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!openAddModal) setSelectedCategoryForEdit(null);
|
||||||
|
}, [openAddModal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 3 },
|
||||||
|
pt: { xs: 2, sm: 1.5 },
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
maxWidth: { xs: '90%', md: '100%' },
|
||||||
|
borderRadius: 2,
|
||||||
|
maxHeight: { xs: '293px', sm: '30%', md: 140 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* رأس القسم */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||||
|
mb: 3,
|
||||||
|
pl: 1,
|
||||||
|
pr: { xs: 1, sm: 0 },
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: { xs: '20px', sm: '22px', md: '24px' },
|
||||||
|
color: '#121212',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Categories
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: { xs: 1, sm: 2 },
|
||||||
|
flexWrap: { xs: 'wrap', sm: 'nowrap' },
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
justifyContent: { xs: 'space-between', sm: 'flex-end' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '40px',
|
||||||
|
width: { xs: '100%', sm: '130px', md: '150px' },
|
||||||
|
fontSize: { xs: '12px', sm: '14px', md: '16px' },
|
||||||
|
fontWeight: 700,
|
||||||
|
minWidth: 'unset',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedCategoryForEdit(null);
|
||||||
|
setOpenAddModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Category
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={() => scroll(-200)}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: '#fff',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
'&:hover': { backgroundColor: '#ffddbfff' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIosNewIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={() => scroll(200)}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: '#fff',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
'&:hover': { backgroundColor: '#ffddbfff' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowForwardIosIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* قائمة الفئات */}
|
||||||
|
{/* قائمة الفئات */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '90%', sm: '95%', md: '100%' },
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 3,
|
||||||
|
pl: 1,
|
||||||
|
mt: 5,
|
||||||
|
pr: { xs: 1, sm: 4 },
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflowX: 'auto',
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
ref={scrollRef}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
// Skeleton Loader
|
||||||
|
Array.from({ length: 5 }).map((_, index) => (
|
||||||
|
<Skeleton
|
||||||
|
key={index}
|
||||||
|
variant="rectangular"
|
||||||
|
width={120}
|
||||||
|
height={60}
|
||||||
|
sx={{ borderRadius: 2 }}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : categories.length === 0 ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '60px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
No categories available
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<CategoryScrollList
|
||||||
|
categories={categories}
|
||||||
|
selectedCategory={selectedCategory}
|
||||||
|
setSelectedCategory={setSelectedCategory}
|
||||||
|
setSelectedCategoryForEdit={setSelectedCategoryForEdit}
|
||||||
|
setContextMenu={setContextMenu}
|
||||||
|
scrollRef={scrollRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* مودال الإضافة / التعديل */}
|
||||||
|
<Modal
|
||||||
|
open={openAddModal}
|
||||||
|
onClose={() => setOpenAddModal(false)}
|
||||||
|
aria-labelledby="add-category-modal"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 2,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 2,
|
||||||
|
maxWidth: '90vw',
|
||||||
|
width: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddCategory
|
||||||
|
onAdd={selectedCategoryForEdit ? handleUpdateCategory : handleAddCategory}
|
||||||
|
editingCategory={selectedCategoryForEdit}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* قائمة السياق */}
|
||||||
|
<Menu
|
||||||
|
open={contextMenu !== null}
|
||||||
|
onClose={() => setContextMenu(null)}
|
||||||
|
anchorReference="anchorPosition"
|
||||||
|
anchorPosition={
|
||||||
|
contextMenu !== null
|
||||||
|
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
sx={{ zIndex: 2000 }}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setOpenAddModal(true);
|
||||||
|
setContextMenu(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Category
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setConfirmDeleteOpen(true);
|
||||||
|
setContextMenu(null);
|
||||||
|
}}
|
||||||
|
sx={{ color: 'red' }}
|
||||||
|
>
|
||||||
|
Delete Category
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* تأكيد الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>Are you sure you want to delete this category?</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button onClick={handleDeleteCategory} color="error" variant="contained">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountSettings;
|
||||||
102
src/components/Home/Meal/contect/AddCategory.js
Normal file
102
src/components/Home/Meal/contect/AddCategory.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||||
|
import { useRestaurant } from '../../../../contexts/RestaurantContext';
|
||||||
|
|
||||||
|
const AddCategory = ({ onAdd, editingCategory }) => {
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
const [productName, setProductName] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [price, setPrice] = useState('');
|
||||||
|
const [discount, setDiscount] = useState('');
|
||||||
|
const [productImage, setProductImage] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editingCategory) {
|
||||||
|
setProductName(editingCategory.name || '');
|
||||||
|
setDescription(editingCategory.description || '');
|
||||||
|
setPrice(editingCategory.price || '');
|
||||||
|
setDiscount(editingCategory.discount || '');
|
||||||
|
setProductImage(editingCategory.icon || null);
|
||||||
|
}
|
||||||
|
}, [editingCategory]);
|
||||||
|
|
||||||
|
const handleImageUpload = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => setProductImage(reader.result);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!productName) {
|
||||||
|
alert("Please enter category name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!restaurantId) {
|
||||||
|
alert("Restaurant ID not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: productName,
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
discount,
|
||||||
|
icon: productImage,
|
||||||
|
};
|
||||||
|
|
||||||
|
// فقط أرسل البيانات للمكون الأب
|
||||||
|
if (onAdd) onAdd(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={2} maxWidth={400} mx="auto">
|
||||||
|
<Typography variant="body2" color="black" sx={{ fontWeight: 600, fontSize: '24px' }}>
|
||||||
|
Category Name
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter category name"
|
||||||
|
value={productName}
|
||||||
|
onChange={(e) => setProductName(e.target.value)}
|
||||||
|
margin="normal"
|
||||||
|
sx={{
|
||||||
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
transition: '0.3s',
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: '#FF914D',
|
||||||
|
boxShadow: '0 0 0 2px rgba(255,145,77,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
backgroundColor: '#FF914D',
|
||||||
|
'&:hover': { backgroundColor: '#e57f3c' },
|
||||||
|
borderRadius: 2,
|
||||||
|
color: '#fff',
|
||||||
|
textTransform: 'none',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
}}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{editingCategory ? 'Update Category' : 'Add Category'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCategory;
|
||||||
251
src/components/Home/Meal/contect/AddMeal.js
Normal file
251
src/components/Home/Meal/contect/AddMeal.js
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
} from '@mui/material';
|
||||||
|
import AddAPhotoOutlinedIcon from '@mui/icons-material/AddAPhotoOutlined';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE_MB = 2;
|
||||||
|
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
|
||||||
|
const AddMeal = ({ open, onClose, onAdd, selectedCategory, restaurantId }) => {
|
||||||
|
const [mealName, setMealName] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [additions, setAdditions] = useState(['']);
|
||||||
|
const [price, setPrice] = useState('');
|
||||||
|
const [mealImage, setMealImage] = useState(null);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
setMealName('');
|
||||||
|
setDescription('');
|
||||||
|
setAdditions(['']);
|
||||||
|
setPrice('');
|
||||||
|
setMealImage(null);
|
||||||
|
setErrors({});
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleImageUpload = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||||
|
setErrors(prev => ({ ...prev, image: 'Allowed types: jpg, jpeg, png' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
|
||||||
|
setErrors(prev => ({ ...prev, image: `Image must be smaller than ${MAX_FILE_SIZE_MB} MB` }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMealImage(file);
|
||||||
|
setErrors(prev => ({ ...prev, image: '' }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAddition = () => setAdditions([...additions, '']);
|
||||||
|
const handleRemoveAddition = (index) => setAdditions(additions.filter((_, i) => i !== index));
|
||||||
|
const handleChangeAddition = (index, value) => {
|
||||||
|
const newAdditions = [...additions];
|
||||||
|
newAdditions[index] = value;
|
||||||
|
setAdditions(newAdditions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
const newErrors = {};
|
||||||
|
if (!mealName.trim()) newErrors.name = 'Meal name is required';
|
||||||
|
if (!description.trim()) newErrors.description = 'Description is required';
|
||||||
|
if (!price || isNaN(price) || Number(price) <= 0) newErrors.price = 'Valid price is required';
|
||||||
|
if (!mealImage) newErrors.image = 'Meal image is required';
|
||||||
|
if (!selectedCategory) newErrors.category = 'Category must be selected';
|
||||||
|
additions.forEach((add, i) => {
|
||||||
|
if (!add.trim()) newErrors[`addition_${i}`] = 'Cannot be empty';
|
||||||
|
});
|
||||||
|
setErrors(newErrors);
|
||||||
|
return Object.keys(newErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
const mealData = {
|
||||||
|
name: mealName.trim(),
|
||||||
|
category_id: selectedCategory,
|
||||||
|
price,
|
||||||
|
restaurant_id: restaurantId,
|
||||||
|
photo: mealImage,
|
||||||
|
description: [description.trim()],
|
||||||
|
additions: additions.filter(a => a.trim() !== ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await authService.addMeal(mealData);
|
||||||
|
|
||||||
|
// عرض رسالة Snackbar أو alert
|
||||||
|
alert(result.message);
|
||||||
|
|
||||||
|
// تمرير الوجبة الجديدة للمكون الأب لتحديث القائمة
|
||||||
|
if (result.data && onAdd) {
|
||||||
|
const newMeal = {
|
||||||
|
id: result.data.id,
|
||||||
|
name: result.data.name,
|
||||||
|
price: result.data.price,
|
||||||
|
description: result.data.description || [],
|
||||||
|
additions: result.data.additions || [],
|
||||||
|
unit: result.data.unit || "/pcs",
|
||||||
|
photo: result.data.photo || "/images/default-product.png",
|
||||||
|
category_id: result.data.category_id || selectedCategory,
|
||||||
|
};
|
||||||
|
onAdd(newMeal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// إغلاق المودال بعد الإضافة
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Add meal error:', error);
|
||||||
|
alert(error.message || "Something went wrong");
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle sx={{ fontWeight: 600, fontSize: '20px' }}>Add Meal</DialogTitle>
|
||||||
|
<DialogContent
|
||||||
|
dividers
|
||||||
|
sx={{
|
||||||
|
overflow: 'auto',
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
msOverflowStyle: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Meal Image */}
|
||||||
|
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Meal Image</Typography>
|
||||||
|
<Box
|
||||||
|
component="label"
|
||||||
|
htmlFor="upload-image"
|
||||||
|
sx={{
|
||||||
|
border: '2px dashed #ccc',
|
||||||
|
borderRadius: '10px',
|
||||||
|
height: '150px',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#969BA7',
|
||||||
|
backgroundColor: '#FAFAFA',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '18px',
|
||||||
|
textAlign: 'center',
|
||||||
|
mb: 2,
|
||||||
|
'&:hover': { borderColor: '#FF914D', backgroundColor: '#fffaf5' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{mealImage ? (
|
||||||
|
<Box sx={{ position: 'relative', width: '100%', height: '100%', borderRadius: 2, overflow: 'hidden' }}>
|
||||||
|
<img src={mealImage instanceof File ? URL.createObjectURL(mealImage) : mealImage} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||||
|
<Button onClick={() => setMealImage(null)} sx={{ position: 'absolute', top: 4, right: 4, minWidth: 0, padding: '2px 6px', fontSize: 12 }}>Remove</Button>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Upload Meal Picture
|
||||||
|
<AddAPhotoOutlinedIcon sx={{ fontSize: 32, color: '#9e9e9e', mt: 1 }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<input id="upload-image" type="file" accept="image/*" hidden onChange={handleImageUpload} />
|
||||||
|
</Box>
|
||||||
|
{errors.image && <Typography color="error" sx={{ mb: 1 }}>{errors.image}</Typography>}
|
||||||
|
|
||||||
|
{/* Meal Name */}
|
||||||
|
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Meal Name</Typography>
|
||||||
|
<TextField fullWidth placeholder="Meal Name" value={mealName} onChange={e => setMealName(e.target.value)} error={!!errors.name} helperText={errors.name} sx={{ mb: 2 }} />
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Description</Typography>
|
||||||
|
<TextField fullWidth placeholder="Description" value={description} onChange={e => setDescription(e.target.value)} error={!!errors.description} helperText={errors.description} sx={{ mb: 2 }} />
|
||||||
|
|
||||||
|
{/* Additions */}
|
||||||
|
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Additions</Typography>
|
||||||
|
{additions.map((add, i) => (
|
||||||
|
<Box key={i} sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder={`Addition ${i + 1}`}
|
||||||
|
value={add}
|
||||||
|
onChange={e => handleChangeAddition(i, e.target.value)}
|
||||||
|
error={!!errors[`addition_${i}`]}
|
||||||
|
helperText={errors[`addition_${i}`]}
|
||||||
|
/>
|
||||||
|
<IconButton onClick={() => handleRemoveAddition(i)} color="error"><DeleteIcon /></IconButton>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Button variant="text" startIcon={<AddIcon />} onClick={handleAddAddition} sx={{ mb: 2 ,textTransform:'none'}}>Add Another</Button>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<Typography variant="body2" fontWeight={500} sx={{ mb: 1 }}>Price</Typography>
|
||||||
|
<TextField fullWidth type="number" placeholder="Price" value={price} onChange={e => setPrice(e.target.value)} error={!!errors.price} helperText={errors.price} sx={{ mb: 2 }} />
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions sx={{ pr: 3, pb: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: '135px',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
height: '40px',
|
||||||
|
width: '135px',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Add Meal
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddMeal;
|
||||||
131
src/components/Home/Meal/contect/AllMeals.js
Normal file
131
src/components/Home/Meal/contect/AllMeals.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Skeleton, useTheme } from "@mui/material";
|
||||||
|
import MealCard from "./MealCard";
|
||||||
|
import authService from "../../../../services/authService";
|
||||||
|
import SimplePagination from "../../SimplePagination";
|
||||||
|
|
||||||
|
const AllMeals = ({ restaurantId, categories = [], onProductClick }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pagination, setPagination] = useState(null);
|
||||||
|
|
||||||
|
const itemsPerPage = 6;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!restaurantId) return;
|
||||||
|
|
||||||
|
const fetchMeals = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const result = await authService.getAllMealsByRestaurant(restaurantId, page);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const mealsWithImages = result.data.map(meal => {
|
||||||
|
const category = categories.find(c => c.id === meal.category_id);
|
||||||
|
return {
|
||||||
|
id: meal.id,
|
||||||
|
name: meal.name,
|
||||||
|
price: meal.price,
|
||||||
|
description: meal.description || [],
|
||||||
|
additions: meal.additions || [],
|
||||||
|
unit: meal.unit || "/pcs",
|
||||||
|
photo: meal.photo || meal.photo_url || "/images/default-product.png",
|
||||||
|
category_id: meal.category_id,
|
||||||
|
category: category ? category.name : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setProducts(mealsWithImages);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
} else {
|
||||||
|
setProducts([]);
|
||||||
|
setPagination(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setError("Failed to fetch meals.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMeals();
|
||||||
|
}, [restaurantId, page, categories]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mb: 0,
|
||||||
|
mr: { xs: 2, sm: 2, md: 0 },
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 3 },
|
||||||
|
pt: { xs: 2, sm: 1.5 },
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: "10px",
|
||||||
|
padding: "20px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
|
overflowY: "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<Typography variant="h6" fontWeight={600}>All Meals</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||||
|
{Array.from({ length: itemsPerPage }).map((_, index) => (
|
||||||
|
<Box key={index} sx={{ width: "calc(33.33% - 16px)" }}>
|
||||||
|
<Skeleton variant="rectangular" width="100%" height={180} sx={{ borderRadius: 2 }} />
|
||||||
|
<Skeleton variant="text" width="80%" sx={{ mt: 1 }} />
|
||||||
|
<Skeleton variant="text" width="40%" />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : error ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
|
||||||
|
<Typography variant="body1" color="error">{error}</Typography>
|
||||||
|
</Box>
|
||||||
|
) : products.length === 0 ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "150px" }}>
|
||||||
|
<Typography variant="body1" color="text.secondary">There is no meal available</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||||
|
{products.map(product => (
|
||||||
|
<Box key={product.id} onClick={() => onProductClick(product)} >
|
||||||
|
<MealCard
|
||||||
|
meal={product}
|
||||||
|
onClick={() => onProductClick(product)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pagination Footer */}
|
||||||
|
{pagination && !loading && (
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2} sx={{ borderTop: "1px solid #f0f0f0", pt: 2 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Showing {(page - 1) * itemsPerPage + 1} to {Math.min(page * itemsPerPage, products.length)} of {pagination.total} meals
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<SimplePagination
|
||||||
|
currentPage={page}
|
||||||
|
pageCount={pagination.last_page}
|
||||||
|
onChange={(newPage) => setPage(newPage)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AllMeals;
|
||||||
76
src/components/Home/Meal/contect/CategoryScrollList.js
Normal file
76
src/components/Home/Meal/contect/CategoryScrollList.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
const CategoryScrollList = ({
|
||||||
|
categories,
|
||||||
|
selectedCategory,
|
||||||
|
setSelectedCategory,
|
||||||
|
setSelectedCategoryForEdit,
|
||||||
|
setContextMenu,
|
||||||
|
scrollRef,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={scrollRef}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflowX: 'auto',
|
||||||
|
scrollBehavior: 'smooth',
|
||||||
|
pl: 1,
|
||||||
|
pr: { xs: 1, sm: 0 },
|
||||||
|
mb: 3,
|
||||||
|
mx: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{categories.map((cat) => {
|
||||||
|
const isSelected = selectedCategory === cat.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={cat.id}
|
||||||
|
onClick={() => setSelectedCategory(cat.id)}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedCategoryForEdit(cat);
|
||||||
|
setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
p: 1,
|
||||||
|
borderRadius: '12px',
|
||||||
|
backgroundColor: isSelected ? theme.palette.primary.main : '#F9F9FC',
|
||||||
|
border: isSelected ? `0px solid ${theme.palette.primary.main}` : '0px solid #ddd',
|
||||||
|
textAlign: 'center',
|
||||||
|
minWidth: '70px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
flexShrink: 0,
|
||||||
|
transition: '0.3s',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: isSelected ? theme.palette.primary.dark : '#EDEDED',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
fontWeight={500}
|
||||||
|
sx={{ color: isSelected ? '#fff' : '#121212' }}
|
||||||
|
>
|
||||||
|
{cat.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryScrollList;
|
||||||
138
src/components/Home/Meal/contect/MealCard.js
Normal file
138
src/components/Home/Meal/contect/MealCard.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardMedia,
|
||||||
|
IconButton,
|
||||||
|
Chip,
|
||||||
|
} from "@mui/material";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
|
||||||
|
const MealCard = ({ meal, onClick, onAdd }) => {
|
||||||
|
const { name, price, unit, photo, image, description, additions } = meal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
onClick={onClick}
|
||||||
|
sx={{
|
||||||
|
width: { xs: "160px", md: "160px" },
|
||||||
|
height: 250,
|
||||||
|
borderRadius: "20px",
|
||||||
|
p: "15px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
backgroundColor: "#FCFCFC",
|
||||||
|
boxShadow: "none",
|
||||||
|
border: "1px solid #f0f0f0",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "transform 0.2s ease-in-out",
|
||||||
|
"&:hover": { transform: "scale(1.02)" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* صورة الوجبة */}
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
image={photo || "/images/default-product.png"}
|
||||||
|
alt={name}
|
||||||
|
sx={{
|
||||||
|
width: "120px",
|
||||||
|
height: "120px",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: "12px",
|
||||||
|
// mb: 1,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* تفاصيل الوجبة */}
|
||||||
|
<CardContent sx={{ p: 0, width: "100%", textAlign: "center" }}>
|
||||||
|
<Typography variant="body1" fontWeight={600} fontSize={16} noWrap>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* السعر */}
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{ color: "#FF914D", fontSize: "15px", mt: 0.5 }}
|
||||||
|
>
|
||||||
|
${price}{" "}
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{unit || "/pcs"}
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* الوصف (أول عنصر فقط للتصغير) */}
|
||||||
|
{description && description.length > 0 && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
noWrap
|
||||||
|
sx={{ display: "block", mt: 0.5 }}
|
||||||
|
>
|
||||||
|
{description[0]}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* الإضافات (أول 2 فقط) */}
|
||||||
|
{/* {additions && additions.length > 0 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 0.5,
|
||||||
|
mt: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{additions.slice(0, 2).map((add, i) => (
|
||||||
|
<Chip
|
||||||
|
key={i}
|
||||||
|
label={add}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
fontSize: "10px",
|
||||||
|
height: 18,
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{/* زر الإضافة */}
|
||||||
|
{/* <IconButton
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onAdd) onAdd(meal);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#FF8551",
|
||||||
|
color: "#fff",
|
||||||
|
mt: 1,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
"&:hover": { backgroundColor: "#ff7043" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon fontSize="small" />
|
||||||
|
</IconButton> */}
|
||||||
|
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MealCard;
|
||||||
202
src/components/Home/Meal/contect/MealsByCateg.js
Normal file
202
src/components/Home/Meal/contect/MealsByCateg.js
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { Box, Typography, Skeleton, useTheme, Button } from "@mui/material";
|
||||||
|
import MealCard from "./MealCard";
|
||||||
|
import authService from "../../../../services/authService";
|
||||||
|
import AddMeal from "./AddMeal";
|
||||||
|
import SimplePagination from "../../SimplePagination";
|
||||||
|
|
||||||
|
const MealsByCateg = ({
|
||||||
|
restaurantId,
|
||||||
|
categoryId,
|
||||||
|
onProductClick,
|
||||||
|
onAddMeal,
|
||||||
|
categoryName,
|
||||||
|
onBack,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pagination, setPagination] = useState(null);
|
||||||
|
|
||||||
|
const itemsPerPage = 6;
|
||||||
|
|
||||||
|
// استخدام useCallback لمنع إنشاء الدالة عند كل إعادة render
|
||||||
|
const fetchMeals = useCallback(async () => {
|
||||||
|
if (!restaurantId || !categoryId) return;
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const result = await authService.getMealsByCategory(
|
||||||
|
restaurantId,
|
||||||
|
categoryId,
|
||||||
|
page
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const mealsWithImages = result.data.map((meal) => ({
|
||||||
|
id: meal.id,
|
||||||
|
name: meal.name,
|
||||||
|
price: meal.price,
|
||||||
|
description: meal.description || [],
|
||||||
|
additions: meal.additions || [],
|
||||||
|
unit: meal.unit || "/pcs",
|
||||||
|
photo: meal.photo || "/images/default-product.png",
|
||||||
|
category_id: meal.category_id || categoryId,
|
||||||
|
}));
|
||||||
|
setProducts(mealsWithImages);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
} else {
|
||||||
|
setProducts([]);
|
||||||
|
setPagination(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setError("Failed to fetch meals.");
|
||||||
|
setProducts([]);
|
||||||
|
setPagination(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [restaurantId, categoryId, page]);
|
||||||
|
|
||||||
|
// جلب البيانات عند تغيير restaurantId أو categoryId أو page
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMeals();
|
||||||
|
}, [fetchMeals]);
|
||||||
|
|
||||||
|
// إضافة وجبة جديدة وتحديث القائمة فورًا
|
||||||
|
// في MealsByCateg
|
||||||
|
const handleAddProduct = async (meal) => {
|
||||||
|
setOpenModal(false); // اغلاق مودال الإضافة
|
||||||
|
setPage(1); // العودة للصفحة الأولى
|
||||||
|
const result = await fetchMeals(); // جلب الوجبات بعد الإضافة
|
||||||
|
if (onAddMeal) onAddMeal(meal); // تمرير الوجبة للمكون الأب
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mb: 2,
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 3 },
|
||||||
|
pt: { xs: 2, sm: 1.5 },
|
||||||
|
maxWidth: { xs: "90%", md: "100%" },
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: "10px",
|
||||||
|
padding: "20px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
|
overflowY: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* العنوان + أزرار */}
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<Typography variant="h6" fontWeight={600}>
|
||||||
|
{categoryName || "Meals"}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setOpenModal(true)}
|
||||||
|
sx={{
|
||||||
|
textTransform: "none",
|
||||||
|
color: "#fff",
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: "8px",
|
||||||
|
height: "40px",
|
||||||
|
width: { xs: "100%", sm: "130px", md: "150px" },
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Product
|
||||||
|
</Button>
|
||||||
|
<Button variant="text" color="primary" sx={{ ml: 1 }} onClick={onBack}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* حالة التحميل أو الخطأ */}
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||||
|
{Array.from({ length: itemsPerPage }).map((_, index) => (
|
||||||
|
<Box key={index} sx={{ flex: "1 1 calc(33.33% - 16px)" }}>
|
||||||
|
<Skeleton variant="rectangular" width="100%" height={180} sx={{ borderRadius: 2 }} />
|
||||||
|
<Skeleton variant="text" width="80%" sx={{ mt: 1 }} />
|
||||||
|
<Skeleton variant="text" width="40%" />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : error ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
|
||||||
|
<Typography variant="body1" color="error">{error}</Typography>
|
||||||
|
</Box>
|
||||||
|
) : products.length === 0 ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "150px" }}>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
There is no meal available
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||||
|
{products.map((product) => (
|
||||||
|
<Box
|
||||||
|
key={product.id}
|
||||||
|
onClick={() =>
|
||||||
|
onProductClick({
|
||||||
|
...product,
|
||||||
|
category: categoryName,
|
||||||
|
category_id: categoryId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MealCard
|
||||||
|
meal={product}
|
||||||
|
onClick={() =>
|
||||||
|
onProductClick({
|
||||||
|
...product,
|
||||||
|
category: categoryName,
|
||||||
|
category_id: categoryId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onAdd={onAddMeal}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pagination Footer */}
|
||||||
|
{pagination && !loading && pagination.last_page > 1 && (
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2} sx={{ borderTop: "1px solid #f0f0f0", pt: 2 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Showing {(page - 1) * itemsPerPage + 1} to{" "}
|
||||||
|
{Math.min(page * itemsPerPage, pagination.total)} of {pagination.total} meals
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<SimplePagination
|
||||||
|
currentPage={page}
|
||||||
|
pageCount={pagination.last_page}
|
||||||
|
onChange={(newPage) => setPage(newPage)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add Meal Modal */}
|
||||||
|
<AddMeal
|
||||||
|
open={openModal}
|
||||||
|
onClose={() => setOpenModal(false)}
|
||||||
|
onAdd={handleAddProduct}
|
||||||
|
selectedCategory={categoryId}
|
||||||
|
restaurantId={restaurantId}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MealsByCateg;
|
||||||
551
src/components/Home/Meal/contect/ProductDetail.js
Normal file
551
src/components/Home/Meal/contect/ProductDetail.js
Normal file
@@ -0,0 +1,551 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Chip,
|
||||||
|
LinearProgress,
|
||||||
|
useTheme,
|
||||||
|
IconButton,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
FormControl,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
FormHelperText,
|
||||||
|
} from "@mui/material";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
|
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||||
|
import authService from "../../../../services/authService";
|
||||||
|
|
||||||
|
const ProductDetail = ({ product, onBack }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: "",
|
||||||
|
category_id: "",
|
||||||
|
restaurant_id: "",
|
||||||
|
description: [],
|
||||||
|
additions: [],
|
||||||
|
price: "",
|
||||||
|
photoFile: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [newDescription, setNewDescription] = useState("");
|
||||||
|
const [newAddition, setNewAddition] = useState("");
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
const [categories, setCategories] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (product) {
|
||||||
|
setFormData({
|
||||||
|
name: product.name || "",
|
||||||
|
category_id: product.category_id || "",
|
||||||
|
restaurant_id: product.restaurant_id || "",
|
||||||
|
description: product.description || [],
|
||||||
|
additions: product.additions || [],
|
||||||
|
price: product.price || "",
|
||||||
|
photoFile: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
const result = await authService.getCategoriesByRestaurant(product?.restaurant_id);
|
||||||
|
if (result.success) setCategories(result.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching categories:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (product?.restaurant_id) fetchCategories();
|
||||||
|
}, [product]);
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
const newErrors = {};
|
||||||
|
if (!formData.name.trim()) newErrors.name = "Name is required";
|
||||||
|
if (!formData.category_id) newErrors.category_id = "Category is required";
|
||||||
|
if (!formData.price || isNaN(formData.price) || formData.price <= 0) {
|
||||||
|
newErrors.price = "Valid price is required";
|
||||||
|
}
|
||||||
|
if (formData.description.length === 0) newErrors.description = "At least one description is required";
|
||||||
|
if (formData.additions.length === 0) newErrors.additions = "At least one addition is required";
|
||||||
|
|
||||||
|
setErrors(newErrors);
|
||||||
|
return Object.keys(newErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
if (errors[name]) setErrors((prev) => ({ ...prev, [name]: "" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
setErrors((prev) => ({ ...prev, photoFile: "Please select an image file" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
setErrors((prev) => ({ ...prev, photoFile: "File size should be less than 5MB" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData((prev) => ({ ...prev, photoFile: file }));
|
||||||
|
setErrors((prev) => ({ ...prev, photoFile: "" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddDescription = () => {
|
||||||
|
if (newDescription.trim() !== "") {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
description: [...prev.description, newDescription.trim()],
|
||||||
|
}));
|
||||||
|
setNewDescription("");
|
||||||
|
setErrors((prev) => ({ ...prev, description: "" }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveDescription = (idx) => {
|
||||||
|
const newDesc = formData.description.filter((_, i) => i !== idx);
|
||||||
|
setFormData((prev) => ({ ...prev, description: newDesc }));
|
||||||
|
if (newDesc.length === 0) setErrors((prev) => ({ ...prev, description: "At least one description is required" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAddition = () => {
|
||||||
|
if (newAddition.trim() !== "") {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
additions: [...prev.additions, newAddition.trim()],
|
||||||
|
}));
|
||||||
|
setNewAddition("");
|
||||||
|
setErrors((prev) => ({ ...prev, additions: "" }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveAddition = (idx) => {
|
||||||
|
const newAdds = formData.additions.filter((_, i) => i !== idx);
|
||||||
|
setFormData((prev) => ({ ...prev, additions: newAdds }));
|
||||||
|
if (newAdds.length === 0) setErrors((prev) => ({ ...prev, additions: "At least one addition is required" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditDescription = (idx, value) => {
|
||||||
|
const newDesc = [...formData.description];
|
||||||
|
newDesc[idx] = value;
|
||||||
|
setFormData((prev) => ({ ...prev, description: newDesc }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditAddition = (idx, value) => {
|
||||||
|
const newAdds = [...formData.additions];
|
||||||
|
newAdds[idx] = value;
|
||||||
|
setFormData((prev) => ({ ...prev, additions: newAdds }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!validateForm()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsSaving(true);
|
||||||
|
const updateData = {
|
||||||
|
name: formData.name.trim(),
|
||||||
|
category_id: formData.category_id,
|
||||||
|
restaurant_id: formData.restaurant_id,
|
||||||
|
description: formData.description,
|
||||||
|
additions: formData.additions,
|
||||||
|
price: formData.price,
|
||||||
|
photo: formData.photoFile ? [formData.photoFile] : [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await authService.updateMeal(product.id, updateData);
|
||||||
|
|
||||||
|
// ✅ حدّث state بآخر نسخة من السيرفر
|
||||||
|
setFormData({
|
||||||
|
name: res.meal.name || "",
|
||||||
|
category_id: res.meal.category_id || "",
|
||||||
|
restaurant_id: res.meal.restaurant_id || "",
|
||||||
|
description: res.meal.description || [],
|
||||||
|
additions: res.meal.additions || [],
|
||||||
|
price: res.meal.price || "",
|
||||||
|
photoFile: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsEditing(false);
|
||||||
|
alert("Meal updated successfully!");
|
||||||
|
|
||||||
|
// ✅ إغلاق المكون بعد النجاح
|
||||||
|
if (onBack) {
|
||||||
|
onBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Error updating meal: " + (err.message || "Something went wrong"));
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteMeal = async () => {
|
||||||
|
try {
|
||||||
|
setIsSaving(true);
|
||||||
|
await authService.deleteMeal(product.id, product.restaurant_id, product.category_id);
|
||||||
|
onBack();
|
||||||
|
// alert("Meal deleted successfully!");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Error deleting meal: " + (err.message || "Something went wrong"));
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", backgroundColor: "#fff", borderRadius: "8px", p: 3, mx: "auto", gap: 3 }}>
|
||||||
|
{/* صندوق الصورة */}
|
||||||
|
<Box sx={{ width: "50%", display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
<Box sx={{ position: "relative", width: "100%", height: 450 }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={onBack}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 102,
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
"&:hover": { backgroundColor: "#f0f0f0" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={formData.photoFile ? URL.createObjectURL(formData.photoFile) : product.image || product.photo}
|
||||||
|
alt={product.name}
|
||||||
|
sx={{
|
||||||
|
width: "80%",
|
||||||
|
height: "80%",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: "8px",
|
||||||
|
p: 5,
|
||||||
|
border: errors.photoFile ? "2px solid red" : "none"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{isEditing && (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||||
|
<Button
|
||||||
|
component="label"
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<CloudUploadIcon />}
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
borderColor: errors.photoFile ? 'red' : theme.palette.primary.main,
|
||||||
|
color: errors.photoFile ? 'red' : theme.palette.primary.main
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change Image
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
hidden
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
{errors.photoFile && <FormHelperText error>{errors.photoFile}</FormHelperText>}
|
||||||
|
{formData.photoFile && <Typography variant="caption" color="textSecondary">Selected: {formData.photoFile.name}</Typography>}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* التفاصيل */}
|
||||||
|
<Box sx={{ width: "50%", display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
{/* الاسم */}
|
||||||
|
<Box>
|
||||||
|
{isEditing ? (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Meal Name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
error={!!errors.name}
|
||||||
|
helperText={errors.name}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography variant="h5" sx={{mt:1}} fontWeight={700}>{product.name}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* الفئة */}
|
||||||
|
<Box>
|
||||||
|
{isEditing ? (
|
||||||
|
<FormControl fullWidth error={!!errors.category_id} size="small">
|
||||||
|
<Select
|
||||||
|
name="category_id"
|
||||||
|
value={formData.category_id || ""}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
|
||||||
|
{!formData.category_id && (
|
||||||
|
<MenuItem value="">
|
||||||
|
<em>Select Category</em>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{categories.map((category) => (
|
||||||
|
<MenuItem key={category.id} value={category.id}>
|
||||||
|
{category.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{errors.category_id && (
|
||||||
|
<FormHelperText>{errors.category_id}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
) : (
|
||||||
|
<Typography variant="body2" sx={{ color: "#777" }}>
|
||||||
|
{product.category ||
|
||||||
|
categories.find((c) => c.id === (product.category_id || formData.category_id))?.name ||
|
||||||
|
`Category #${product.category_id || formData.category_id}`}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* السعر */}
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>Price</Typography>
|
||||||
|
{isEditing ? (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="0.00"
|
||||||
|
name="price"
|
||||||
|
type="number"
|
||||||
|
value={formData.price}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
error={!!errors.price}
|
||||||
|
helperText={errors.price}
|
||||||
|
inputProps={{ min: 0, step: 0.01 }}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography variant="h6" sx={{ color: theme.palette.primary.main, fontWeight: 700 }}>
|
||||||
|
${Number(product.price).toFixed(2)} {product.unit || "/pcs"}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* الوصف */}
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>
|
||||||
|
Description {isEditing && "*"}
|
||||||
|
</Typography>
|
||||||
|
{isEditing ? (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||||
|
{formData.description.map((desc, idx) => (
|
||||||
|
<Box key={idx} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
value={desc}
|
||||||
|
onChange={(e) => handleEditDescription(idx, e.target.value)}
|
||||||
|
placeholder={`Description ${idx + 1}`}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{errors.description && <FormHelperText error>{errors.description}</FormHelperText>}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
|
||||||
|
{formData.description.map((desc, idx) => <Typography key={idx} variant="body2">• {desc}</Typography>)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* الإضافات */}
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body2" fontWeight={500} fontSize={18} sx={{ mb: 1 }}>
|
||||||
|
Additions {isEditing && "*"}
|
||||||
|
</Typography>
|
||||||
|
{isEditing ? (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||||
|
{formData.additions.map((add, idx) => (
|
||||||
|
<Box key={idx} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
value={add}
|
||||||
|
onChange={(e) => handleEditAddition(idx, e.target.value)}
|
||||||
|
placeholder={`Addition ${idx + 1}`}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<IconButton color="error" onClick={() => handleRemoveAddition(idx)} size="small">
|
||||||
|
<ClearIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Add new addition"
|
||||||
|
value={newAddition}
|
||||||
|
onChange={(e) => setNewAddition(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<IconButton color="primary" onClick={handleAddAddition} disabled={!newAddition.trim()}>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
{errors.additions && <FormHelperText error>{errors.additions}</FormHelperText>}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
|
||||||
|
{formData.additions.map((add, idx) => <Chip key={idx} label={add} size="small" variant="outlined" />)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* شريط تقدم */}
|
||||||
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={100}
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
height: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: "#f0f0f0",
|
||||||
|
"& .MuiLinearProgress-bar": { backgroundColor: theme.palette.primary.main },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{backgroundColor:'#fff2e0ff' , height:100 , width:500 , borderRadius: '8px',alignItems:'center' }}>
|
||||||
|
{/* أزرار التحكم */}
|
||||||
|
<Box sx={{ display: "flex", gap: 2, p:4}}>
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textTransform: 'none',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '45px',
|
||||||
|
color: '#fff'
|
||||||
|
}}
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
{isSaving ? "Saving..." : "Save Changes"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textTransform: 'none',
|
||||||
|
borderColor: "gray",
|
||||||
|
color: "gray",
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '45px',
|
||||||
|
backgroundColor:'#fff'
|
||||||
|
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditing(false);
|
||||||
|
setFormData({
|
||||||
|
name: product.name || "",
|
||||||
|
category_id: product.category_id || "",
|
||||||
|
restaurant_id: product.restaurant_id || "",
|
||||||
|
description: product.description || [],
|
||||||
|
additions: product.additions || [],
|
||||||
|
price: product.price || "",
|
||||||
|
photoFile: null,
|
||||||
|
});
|
||||||
|
setErrors({});
|
||||||
|
}}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textTransform: 'none',
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '45px',
|
||||||
|
backgroundColor:'#fff'
|
||||||
|
}}
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
>
|
||||||
|
Edit Meal
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textTransform: 'none',
|
||||||
|
borderColor: "error.main",
|
||||||
|
color: "error.main",
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '45px',
|
||||||
|
backgroundColor:'#fff'
|
||||||
|
}}
|
||||||
|
onClick={() => setConfirmDeleteOpen(true)}
|
||||||
|
>
|
||||||
|
Delete Meal
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* مودال تأكيد الحذف */}
|
||||||
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Are you sure you want to delete "{product.name}"? This action cannot be undone.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteMeal}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
{isSaving ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductDetail;
|
||||||
74
src/components/Home/Order/Order.js
Normal file
74
src/components/Home/Order/Order.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import KitchPlusAppBar from '../AppBar';
|
||||||
|
import Sidebar from '../SideHome';
|
||||||
|
import Orders from './contect/Orders';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const Order = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [theme.breakpoints.values.md]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const admin = authService.getAdminData();
|
||||||
|
console.log('Admin Info:', admin);
|
||||||
|
|
||||||
|
const adminId = authService.getAdminId();
|
||||||
|
console.log('Admin ID:', adminId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const admin = authService.getAdminData();
|
||||||
|
const adminId = authService.getAdminId();
|
||||||
|
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', height: '100vh', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||||
|
<Sidebar open={sidebarOpen} onClose={handleDrawerToggle} isMobile={isMobile} drawerWidth={drawerWidth} />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
transition: theme.transitions.create(['width'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KitchPlusAppBar onDrawerToggle={handleDrawerToggle} sidebarOpen={sidebarOpen} isMobile={isMobile} />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 6,
|
||||||
|
width: { xs: '90%', sm: '95%', md: '96%' },
|
||||||
|
pt: { xs: 2, sm: 3 },
|
||||||
|
pl: { xs: 2, sm: 3 },
|
||||||
|
pb: { xs: 2, sm: 4 },
|
||||||
|
pr: { xs: 2, sm: 3 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Orders adminId={adminId} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Order;
|
||||||
170
src/components/Home/Order/contect/CartView.js
Normal file
170
src/components/Home/Order/contect/CartView.js
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React, { useContext, useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, LinearProgress, IconButton } from "@mui/material";
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import { CartContext } from "../../../../contexts/CartContextR";
|
||||||
|
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||||
|
|
||||||
|
const CartView = ({ onClose, onCartCreated, adminId }) => {
|
||||||
|
const { cart, clearCart, createNewCart } = useContext(CartContext);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { showSnackbar } = useSnackbar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Admin ID from props in:CartView', adminId);
|
||||||
|
}, [adminId]);
|
||||||
|
|
||||||
|
const totalPrice = cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0);
|
||||||
|
|
||||||
|
const handleSendCart = async () => {
|
||||||
|
if (!cart.length) return;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// تحقق من أن adminId موجود ضمن قائمة صالحة (يمكنك تعديلها حسب بياناتك)
|
||||||
|
const validAdminIds = [1, 2]; // IDs موجودة في DB
|
||||||
|
if (!validAdminIds.includes(adminId)) {
|
||||||
|
console.error("Invalid admin ID");
|
||||||
|
showSnackbar("Selected admin is not valid.", "error");
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// تحقق من أن جميع المنتجات موجودة في قاعدة البيانات
|
||||||
|
const validProductIds = [1, 2, 3, 4]; // IDs المنتجات الموجودة
|
||||||
|
for (let item of cart) {
|
||||||
|
if (!validProductIds.includes(item.id)) {
|
||||||
|
console.error(`Invalid product ID: ${item.id}`);
|
||||||
|
// alert(`Product with ID ${item.id} does not exist.`);
|
||||||
|
showSnackbar(`Product with ID ${item.id} does not exist.`, "error");
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تجهيز البيانات حسب شكل الـ backend
|
||||||
|
const cartData = {
|
||||||
|
data: {
|
||||||
|
type: "cart",
|
||||||
|
attributes: {
|
||||||
|
totalPrice: cart.reduce((sum, item) => sum + (item.totalPrice || 0), 0),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
admin: {
|
||||||
|
data: { id: adminId },
|
||||||
|
},
|
||||||
|
cartItems: cart.map(item => ({
|
||||||
|
attributes: { quantity: item.quantity },
|
||||||
|
relationships: { product: { data: { id: item.id } } },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newCart = await createNewCart(cartData);
|
||||||
|
|
||||||
|
if (newCart && newCart.success) {
|
||||||
|
onCartCreated(newCart.data);
|
||||||
|
clearCart();
|
||||||
|
onClose();
|
||||||
|
showSnackbar("Cart sent successfully!", "success");
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create cart:", newCart.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error sending cart:", error);
|
||||||
|
showSnackbar("Failed to create cart.", "error");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (loading) return <LinearProgress />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: "100%", sm: "93.5%" },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: 2,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* السهم للعودة */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
|
<IconButton onClick={onClose} size="small" sx={{ mr: 1 }}>
|
||||||
|
<ArrowBackIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 600, fontSize: { xs: "18px", sm: "20px" } }}>
|
||||||
|
Current Cart
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{cart.length === 0 ? (
|
||||||
|
<Typography sx={{ mt: 2, textAlign: "center" }}>No items in cart.</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{cart.map(item => (
|
||||||
|
<Box
|
||||||
|
key={item.id}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 1,
|
||||||
|
border: "1px solid #e0e0e0",
|
||||||
|
backgroundColor: "#fafafa",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography fontWeight={600}>{item.name}</Typography>
|
||||||
|
<Typography variant="body2">Unit: {item.unit}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ textAlign: { xs: "left", sm: "right" } }}>
|
||||||
|
<Typography variant="body2">Quantity: {item.quantity}</Typography>
|
||||||
|
<Typography variant="body2">Total: ${item.totalPrice}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1 }}>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||||
|
Total Price: ${totalPrice.toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cart.length > 0 && (
|
||||||
|
<Box sx={{ display: "flex", gap: 1, mt: 2, flexWrap: { xs: "wrap", sm: "nowrap" }, justifyContent: { xs: "center", sm: "flex-start" } }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSendCart}
|
||||||
|
sx={{ color: 'white', borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '200px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '200px' } }}
|
||||||
|
>
|
||||||
|
Send Cart to Server
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={clearCart}
|
||||||
|
sx={{ borderRadius: '8px', fontWeight: 600, fontSize: '14px', height: '40px', width: { xs: '100%', sm: '150px' }, textTransform: 'none', minWidth: { xs: 'unset', sm: '150px' } }}
|
||||||
|
>
|
||||||
|
Clear Cart
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CartView;
|
||||||
223
src/components/Home/Order/contect/OrderDetails.js
Normal file
223
src/components/Home/Order/contect/OrderDetails.js
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
CircularProgress
|
||||||
|
} from '@mui/material';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import { useSnackbar } from "../../../../contexts/SnackbarContext";
|
||||||
|
|
||||||
|
const CartDetails = ({ cart, onClose, onUpdated, onDeleted }) => {
|
||||||
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [totalPrice, setTotalPrice] = useState(cart?.attributes?.total_price || "");
|
||||||
|
const [cartItems, setCartItems] = useState(cart.relationships?.cartItems || cart.relationships?.cart_items || []);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
const { showSnackbar } = useSnackbar();
|
||||||
|
if (!cart) return null;
|
||||||
|
|
||||||
|
const handleSaveAll = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const payload = {
|
||||||
|
data: {
|
||||||
|
type: "cart",
|
||||||
|
attributes: {
|
||||||
|
totalPrice: Number(totalPrice),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
cartItems: cartItems.map(item => ({
|
||||||
|
attributes: {
|
||||||
|
quantity: Number(item.attributes.quantity),
|
||||||
|
},
|
||||||
|
relationships: {
|
||||||
|
product: {
|
||||||
|
data: {
|
||||||
|
id: item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await authService.updateCart(cart.id, payload);
|
||||||
|
if (result.success) {
|
||||||
|
onUpdated(result.data);
|
||||||
|
setEditMode(false);
|
||||||
|
} else {
|
||||||
|
// alert(result.message || 'Failed to update cart');
|
||||||
|
showSnackbar(result.message || "Failed to update cart", "error");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// alert('An error occurred while updating the cart');
|
||||||
|
showSnackbar("An error occurred while updating the cart", "error");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteCart = async () => {
|
||||||
|
setDeleteLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.deleteCart(cart.id);
|
||||||
|
if (result.success) {
|
||||||
|
onDeleted(cart.id);
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
// alert(result.message || 'Failed to delete cart');
|
||||||
|
showSnackbar(result.message || "Failed to update cart", "error");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// alert('An error occurred while deleting the cart');
|
||||||
|
showSnackbar("An error occurred while deleting the cart", "error");
|
||||||
|
} finally {
|
||||||
|
setDeleteLoading(false);
|
||||||
|
setOpenDeleteDialog(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeQuantity = (itemId, newQuantity) => {
|
||||||
|
setCartItems(prev =>
|
||||||
|
prev.map(item =>
|
||||||
|
item.id === itemId
|
||||||
|
? { ...item, attributes: { ...item.attributes, quantity: newQuantity } }
|
||||||
|
: item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Paper sx={{ p: 3, mt: 2, borderRadius: 2, border: '1px solid #e0e0e0' }}>
|
||||||
|
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
|
||||||
|
Cart #{cart.id} Details
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{editMode ? (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
label="Total Price"
|
||||||
|
type="number"
|
||||||
|
value={totalPrice}
|
||||||
|
onChange={(e) => setTotalPrice(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||||
|
<List>
|
||||||
|
{cartItems.map(item => (
|
||||||
|
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={`Item ID: ${item.id} | Product: ${item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}`}
|
||||||
|
secondary={
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
label="Quantity"
|
||||||
|
value={item.attributes.quantity}
|
||||||
|
onChange={(e) => handleChangeQuantity(item.id, Number(e.target.value))}
|
||||||
|
sx={{ width: '120px' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
|
<Button variant="contained" onClick={handleSaveAll} disabled={loading}>
|
||||||
|
{loading ? "Saving..." : "Save All"}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" onClick={() => setEditMode(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography>Total Price: {cart.attributes.total_price || cart.attributes.totalPrice}</Typography>
|
||||||
|
<Typography>
|
||||||
|
Created At: {new Date(cart.attributes.createdAt).toLocaleString()}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ mt: 2, fontWeight: 500 }}>Items:</Typography>
|
||||||
|
<List>
|
||||||
|
{cartItems.map(item => (
|
||||||
|
<ListItem key={item.id} sx={{ pl: 0 }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={`Item ID: ${item.id}`}
|
||||||
|
secondary={`Quantity: ${item.attributes.quantity} | Product: ${item.relationships?.supplier_product?.id ||
|
||||||
|
item.relationships?.product?.data?.id
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
|
<Button variant="contained" onClick={() => setEditMode(true)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={() => setOpenDeleteDialog(true)}
|
||||||
|
sx={{ ml: 'auto' }}
|
||||||
|
>
|
||||||
|
Delete Cart
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" onClick={onClose}>
|
||||||
|
Back to Orders
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={openDeleteDialog}
|
||||||
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
|
>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Are you sure you want to delete this cart? This action cannot be undone.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenDeleteDialog(false)} disabled={deleteLoading}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteCart}
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={deleteLoading}
|
||||||
|
>
|
||||||
|
{deleteLoading ? <CircularProgress size={24} /> : 'Delete'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CartDetails;
|
||||||
326
src/components/Home/Order/contect/Orders.js
Normal file
326
src/components/Home/Order/contect/Orders.js
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useMediaQuery,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
CircularProgress,
|
||||||
|
Button
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
|
import authService from '../../../../services/authService';
|
||||||
|
import CartDetails from './OrderDetails';
|
||||||
|
import CartView from './CartView';
|
||||||
|
|
||||||
|
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const handlePrev = () => { if (currentPage > 1) onChange(currentPage - 1); };
|
||||||
|
const handleNext = () => { if (currentPage < pageCount) onChange(currentPage + 1); };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handlePrev}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#FFECE0',
|
||||||
|
'&:hover': { backgroundColor: '#FFD6B5' },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
'&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIosNewIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: '#fff',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
userSelect: 'none',
|
||||||
|
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentPage}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={currentPage >= pageCount}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#FFECE0',
|
||||||
|
'&:hover': { backgroundColor: '#FFD6B5' },
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
'&.Mui-disabled': { color: '#ccc', backgroundColor: '#FFF5E6' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowForwardIosIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Orders = ({ adminId }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Admin ID from props:', adminId);
|
||||||
|
}, [adminId]);
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [ordersData, setOrdersData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selectedCart, setSelectedCart] = useState(null);
|
||||||
|
const [cartLoading, setCartLoading] = useState(false);
|
||||||
|
const [showCartView, setShowCartView] = useState(false); // ← التحكم بعرض CartView هنا
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const itemsPerPage = 6;
|
||||||
|
const [orders, setOrders] = useState([]);
|
||||||
|
|
||||||
|
const handleAddOrder = (newOrder) => {
|
||||||
|
setOrders((prevOrders) => [...prevOrders, newOrder]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchOrders = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getCart();
|
||||||
|
if (result.success) {
|
||||||
|
setOrdersData(Array.isArray(result.data) ? result.data : []);
|
||||||
|
} else {
|
||||||
|
console.error(result.message);
|
||||||
|
setOrdersData([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch orders:', error);
|
||||||
|
setOrdersData([]);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchOrders();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRowClick = async (cartId) => {
|
||||||
|
setCartLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await authService.getCartById(cartId);
|
||||||
|
if (result.success) setSelectedCart(result.data);
|
||||||
|
else console.error(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch cart details:', error);
|
||||||
|
}
|
||||||
|
setCartLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCartUpdated = (updatedCart) => {
|
||||||
|
setOrdersData(prev =>
|
||||||
|
prev.map(cart => cart.id === updatedCart.id ? updatedCart : cart)
|
||||||
|
);
|
||||||
|
setSelectedCart(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseCartDetails = () => { setSelectedCart(null); };
|
||||||
|
|
||||||
|
const pageCount = Math.ceil(ordersData.length / itemsPerPage);
|
||||||
|
const paginatedOrders = ordersData.slice(
|
||||||
|
(currentPage - 1) * itemsPerPage,
|
||||||
|
currentPage * itemsPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cartLoading) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCart) {
|
||||||
|
return (
|
||||||
|
<CartDetails
|
||||||
|
cart={selectedCart}
|
||||||
|
onClose={handleCloseCartDetails}
|
||||||
|
onUpdated={handleCartUpdated}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// عرض CartView بدل الطلبات إذا تم الضغط على الزر
|
||||||
|
// داخل Orders.js
|
||||||
|
if (showCartView) {
|
||||||
|
return (
|
||||||
|
<CartView
|
||||||
|
onClose={() => setShowCartView(false)}
|
||||||
|
onSend={handleAddOrder}
|
||||||
|
onCartCreated={(newCart) => {
|
||||||
|
setOrdersData(prev => [newCart, ...prev]); // ضف الكارت الجديد مباشرة
|
||||||
|
setShowCartView(false);
|
||||||
|
}}
|
||||||
|
adminId={adminId} // ← أرسل الـ adminId هنا
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: '100%', sm: '93.5%' },
|
||||||
|
p: { xs: 2, sm: 3 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: { xs: 'stretch', sm: 'center' },
|
||||||
|
mb: 2,
|
||||||
|
gap: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: { xs: '18px', sm: '20px' },
|
||||||
|
mb: { xs: 1, sm: 0 },
|
||||||
|
textAlign: { xs: 'center', sm: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Order
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* جدول الطلبات */}
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: 2,
|
||||||
|
minWidth: 320,
|
||||||
|
height: '380px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: 'auto' }}
|
||||||
|
aria-label="orders table"
|
||||||
|
size={isMobile ? 'small' : 'medium'}
|
||||||
|
>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||||
|
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Cart ID</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Total Price</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Created At</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F' }}>Items Count</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{loading
|
||||||
|
? Array.from({ length: itemsPerPage }).map((_, idx) => (
|
||||||
|
<TableRow key={`skeleton-${idx}`}>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
<TableCell><Skeleton variant="text" /></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{paginatedOrders.map(order => {
|
||||||
|
const attributes = order?.attributes || {};
|
||||||
|
const relationships = order?.relationships || {};
|
||||||
|
const cartItems = relationships?.cartItems || relationships?.cart_items || [];
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={order?.id}
|
||||||
|
hover
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.3s',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#ffe4d0ff !important',
|
||||||
|
'& td': { color: '#000000ff' },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => handleRowClick(order.id)}
|
||||||
|
>
|
||||||
|
<TableCell>{order?.id || '--'}</TableCell>
|
||||||
|
<TableCell>{attributes?.totalPrice ?? attributes?.total_price ?? '0.00'}</TableCell>
|
||||||
|
<TableCell>{attributes?.createdAt ? new Date(attributes.createdAt).toLocaleString() : '--'}</TableCell>
|
||||||
|
<TableCell>{cartItems.length}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{paginatedOrders.length < itemsPerPage &&
|
||||||
|
Array.from({ length: itemsPerPage - paginatedOrders.length }).map((_, idx) => (
|
||||||
|
<TableRow key={`empty-${idx}`} sx={{ height: 53 }}>
|
||||||
|
<TableCell colSpan={4} sx={{ borderBottom: 'none' }} />
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
pt={{ xs: 1, sm: 2 }}
|
||||||
|
sx={{
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderTop: '1px solid #f0f0f0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontSize: { xs: '11px', sm: '14px' }, whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
Showing {(currentPage - 1) * itemsPerPage + 1} - {Math.min(currentPage * itemsPerPage, ordersData.length)} of {ordersData.length}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Orders;
|
||||||
136
src/components/Home/ResList/AppBar.js
Normal file
136
src/components/Home/ResList/AppBar.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
AppBar,
|
||||||
|
Toolbar,
|
||||||
|
Box,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import LogoutIcon from '@mui/icons-material/Logout';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const KitchPlusAppBar = ({ onDrawerToggle, sidebarOpen, isMobile }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
// دالة تسجيل الخروج المعدلة
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
const result = await authService.logout();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
navigate('/login');
|
||||||
|
} else {
|
||||||
|
console.error('Logout failed:', result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar
|
||||||
|
sx={{
|
||||||
|
height: { xs: 56, sm: 64, md: 66 },
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
color: 'black',
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderBottom: '1px solid #e0e0e0',
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: theme.zIndex.appBar,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: '24px' },
|
||||||
|
minHeight: { xs: '56px !important', sm: '64px !important' },
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Left: Logo */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src="/image.png"
|
||||||
|
alt="logo"
|
||||||
|
sx={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
objectFit: 'contain',
|
||||||
|
mr: 1.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
color: 'text.primary',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
KITCH
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
color: 'primary.main',
|
||||||
|
ml: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
PLUS
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Right: Log Out Button */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.8, sm: 1, md: 1.5 } }}>
|
||||||
|
<Box
|
||||||
|
onClick={handleLogout}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#fffcf9d5',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
color: 'divider',
|
||||||
|
minWidth: 36,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LogoutIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary="Log Out"
|
||||||
|
primaryTypographyProps={{
|
||||||
|
sx: {
|
||||||
|
color: 'divider',
|
||||||
|
fontSize: '1rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KitchPlusAppBar;
|
||||||
60
src/components/Home/ResList/RestaurantDetailsModal.js
Normal file
60
src/components/Home/ResList/RestaurantDetailsModal.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
const RestaurantDetailsModal = ({ open, onClose, restaurant }) => {
|
||||||
|
if (!restaurant) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>{restaurant.name}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
{restaurant.cuisine_type && (
|
||||||
|
<Typography><b>Cuisine Type:</b> {restaurant.cuisine_type.name}</Typography>
|
||||||
|
)}
|
||||||
|
<Typography><b>Location:</b> {restaurant.location}</Typography>
|
||||||
|
<Typography><b>Brand Details:</b> {restaurant.brand_details || 'N/A'}</Typography>
|
||||||
|
<Typography><b>Age Group:</b> {restaurant.age_group}</Typography>
|
||||||
|
<Typography><b>Menu Status:</b> {restaurant.menu_status ? 'Active' : 'Inactive'}</Typography>
|
||||||
|
<Typography><b>Need Help:</b> {restaurant.need_help || 'N/A'}</Typography>
|
||||||
|
{restaurant.operational_details && (
|
||||||
|
<>
|
||||||
|
<Typography><b>Staff Members:</b> {restaurant.operational_details.staff_members}</Typography>
|
||||||
|
<Typography><b>Equipment:</b> {restaurant.operational_details.equipment}</Typography>
|
||||||
|
<Typography><b>Specialized Equipment:</b> {restaurant.operational_details.specialized_equipment}</Typography>
|
||||||
|
<Typography><b>Expansion Cities:</b> {restaurant.operational_details.expansion_plan_cities}</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{restaurant.budget_expansion && (
|
||||||
|
<>
|
||||||
|
<Typography><b>Estimated Budget:</b> {restaurant.budget_expansion.estimated_budget}</Typography>
|
||||||
|
<Typography><b>Expansion Branches:</b> {restaurant.budget_expansion.expansion_branches}</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{restaurant.created_by && (
|
||||||
|
<Typography><b>Created By:</b> {restaurant.created_by.name}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ textTransform: "none", minWidth: 120 }}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RestaurantDetailsModal;
|
||||||
76
src/components/Home/ResList/RestaurantProfile.js
Normal file
76
src/components/Home/ResList/RestaurantProfile.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import KitchPlusAppBar from './AppBar';
|
||||||
|
import RestaurantSelection from './RestaurantSelection';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
|
||||||
|
const RestaurantProfile = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const [restaurants, setRestaurants] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchRestaurants = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await authService.getRestaurants();
|
||||||
|
if (result.success) {
|
||||||
|
const mappedRestaurants = result.data.map((res) => ({
|
||||||
|
id: res.id,
|
||||||
|
name: res.name,
|
||||||
|
location: res.location || '',
|
||||||
|
image: res.image_url,
|
||||||
|
address: res.address || '',
|
||||||
|
}));
|
||||||
|
setRestaurants(mappedRestaurants);
|
||||||
|
} else {
|
||||||
|
alert(result.message || 'خطأ في جلب المطاعم');
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchRestaurants();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => {};
|
||||||
|
const handleCreateRestaurant = () => {
|
||||||
|
alert('Redirect to restaurant creation page or open a form!');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: '#F6F6F6',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KitchPlusAppBar
|
||||||
|
onDrawerToggle={handleDrawerToggle}
|
||||||
|
sidebarOpen={false}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
msOverflowStyle: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<RestaurantSelection
|
||||||
|
restaurants={restaurants}
|
||||||
|
onCreateRestaurant={handleCreateRestaurant}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RestaurantProfile;
|
||||||
186
src/components/Home/ResList/RestaurantSelection.js
Normal file
186
src/components/Home/ResList/RestaurantSelection.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
IconButton,
|
||||||
|
Skeleton
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import AddToPhotosIcon from '@mui/icons-material/AddToPhotos';
|
||||||
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
import RestaurantDetailsModal from './RestaurantDetailsModal';
|
||||||
|
import authService from '../../../services/authService';
|
||||||
|
const RestaurantSelection = ({ restaurants = [], loading = false }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmall = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const navigate = useNavigate(); // استخدم navigate للتوجيه
|
||||||
|
const { setRestaurantId } = useRestaurant();
|
||||||
|
|
||||||
|
const [selectedRestaurant, setSelectedRestaurant] = useState(null);
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleSelectRestaurant = (id) => {
|
||||||
|
setRestaurantId(id);
|
||||||
|
navigate('/dashboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchRestaurantDetails = async (id) => {
|
||||||
|
try {
|
||||||
|
const result = await authService.getRestaurantById(id);
|
||||||
|
if (result.success) {
|
||||||
|
setSelectedRestaurant(result.data);
|
||||||
|
setModalOpen(true);
|
||||||
|
} else {
|
||||||
|
alert(result.message || 'Failed to fetch restaurant details');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching restaurant details:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ دالة التوجيه عند الضغط على زر الإنشاء
|
||||||
|
const handleCreateClick = () => {
|
||||||
|
navigate('/create-restaurant'); // توجه مباشرة إلى صفحة الإنشاء
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box sx={{ py: 8, backgroundColor: '#FAFAFA', minHeight: '100vh' }}>
|
||||||
|
{/* زر إنشاء مطعم */}
|
||||||
|
<Grid container spacing={1} justifyContent={isSmall ? 'center' : 'flex-start'} pl={isSmall ? 0 : 2}>
|
||||||
|
<Box
|
||||||
|
onClick={handleCreateClick}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: 3,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 250,
|
||||||
|
maxHeight: 186,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'transform 0.3s',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
ml: { xs: 0, sm: 8, md: 28 },
|
||||||
|
'&:hover': { transform: 'translateY(-5px)' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: 52, height: 52, borderRadius: '50%', backgroundColor: '#F6F6F6', display: 'flex', alignItems: 'center', justifyContent: 'center', mb: 1 }}>
|
||||||
|
<AddToPhotosIcon sx={{ fontSize: 30, color: theme.palette.primary.main }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography fontWeight="600" variant="subtitle1" fontSize="1.25rem">
|
||||||
|
Create New Restaurant
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Start from scratch and build your restaurant profile
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* قائمة المطاعم */}
|
||||||
|
<Box sx={{ mt: 5, ml: { xs: 0, sm: 10, md: 30 } }}>
|
||||||
|
<Typography variant="h6" fontWeight="bold" mb={3} textAlign={isSmall ? 'center' : 'flex-start'}>
|
||||||
|
Existing Restaurants
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{restaurants.length === 0 && !loading ? (
|
||||||
|
<Typography textAlign={isSmall ? 'center' : 'flex-start'} color="text.secondary" fontSize="1rem">
|
||||||
|
No restaurants found. Create one to get started.
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={4} justifyContent={isSmall ? 'center' : 'flex-start'}>
|
||||||
|
{(loading ? Array.from(new Array(3)) : restaurants).map((restaurant, index) => (
|
||||||
|
<Grid key={restaurant?.id || index}>
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton variant="rectangular" width={230} height={162} sx={{ borderRadius: 3 }} />
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: 3,
|
||||||
|
p: 3,
|
||||||
|
width: '230px',
|
||||||
|
height: '90%',
|
||||||
|
maxHeight: '162px',
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': { transform: 'translateY(-3px)' },
|
||||||
|
mx: 'auto',
|
||||||
|
}}
|
||||||
|
onClick={() => handleSelectRestaurant(restaurant.id)}
|
||||||
|
>
|
||||||
|
{/* أيقونة فتح المودال */}
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fetchRestaurantDetails(restaurant.id);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(255,255,255,1)',
|
||||||
|
transform: 'scale(1.1)',
|
||||||
|
transition: 'transform 0.2s'
|
||||||
|
},
|
||||||
|
zIndex: 2,
|
||||||
|
padding: 0.5
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<InfoIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
{/* صورة المطعم */}
|
||||||
|
<Box
|
||||||
|
sx={{ width: 110, height: 100, overflow: 'hidden', border: '2px solid #e0e0e0', mb: 1.5, mx: 'auto' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={restaurant.image || '/images/default-restaurant.png'}
|
||||||
|
alt={restaurant.name}
|
||||||
|
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* باقي الكارت */}
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Typography fontWeight="600" textAlign="center" fontSize="1.25rem" mb={0.5}>
|
||||||
|
{restaurant.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" fontSize="0.9rem" textAlign="center" mb={1}>
|
||||||
|
{restaurant.location || ' '}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* مودال المطعم */}
|
||||||
|
<RestaurantDetailsModal
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={() => setModalOpen(false)}
|
||||||
|
restaurant={selectedRestaurant}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RestaurantSelection;
|
||||||
@@ -1,165 +1,159 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, useTheme, useMediaQuery } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import KitchPlusAppBar from '../AppBar';
|
|
||||||
import Sidebar from '../SideHome';
|
|
||||||
import LoginRestaurant from './contcet/LoginRestaurant';
|
|
||||||
import BasicInformation from './contcet/BasicInformation';
|
import BasicInformation from './contcet/BasicInformation';
|
||||||
import ContactInformation from './contcet/ContactInformation';
|
import ContactInformation from './contcet/ContactInformation';
|
||||||
import BusinessHours from './contcet/BusinessHours';
|
import BusinessHours from './contcet/BusinessHours';
|
||||||
import UploadMenu from './contcet/UploadMenu';
|
|
||||||
import Equipment from './contcet/Equipment';
|
import Equipment from './contcet/Equipment';
|
||||||
import TypeOfRestaurant from './contcet/TypeOfRestaurant';
|
import TypeOfRestaurant from './contcet/TypeOfRestaurant';
|
||||||
import OperationalCapacity from './contcet/OperationalCapacity';
|
import OperationalCapacity from './contcet/OperationalCapacity';
|
||||||
import UploadPhotos from './contcet/UploadPhotos';
|
|
||||||
import SideProfile from './SideProfile';
|
import SideProfile from './SideProfile';
|
||||||
|
import AccountProfile from '../Settings/AccountProfile';
|
||||||
const drawerWidth = 230;
|
import authService from '../../../services/authService';
|
||||||
|
import { useRestaurant } from '../../../contexts/RestaurantContext';
|
||||||
|
import ConfirmationDialog from './contcet/ConfirmationDialog';
|
||||||
|
|
||||||
const RestaurantProfile = () => {
|
const RestaurantProfile = () => {
|
||||||
const theme = useTheme();
|
const { restaurantId } = useRestaurant();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
const [hasProducts, setHasProducts] = useState(false);
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
|
||||||
|
|
||||||
// ⬇️ إدارة الخطوة الحالية
|
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
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 = [
|
const steps = [
|
||||||
<BasicInformation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
<BasicInformation
|
||||||
<ContactInformation onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
key="step-0"
|
||||||
<BusinessHours onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
formData={formData}
|
||||||
<UploadMenu onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
updateFormData={updateFormData}
|
||||||
<Equipment onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
onNext={() => setCurrentStep((p) => p + 1)}
|
||||||
<OperationalCapacity onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
/>,
|
||||||
<TypeOfRestaurant onNext={() => setCurrentStep(currentStep + 1)} onBack={() => setCurrentStep(currentStep - 1)} />,
|
<ContactInformation
|
||||||
<UploadPhotos onBack={() => setCurrentStep(currentStep - 1)} />,
|
key="step-1"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep((p) => p + 1)}
|
||||||
|
onBack={() => setCurrentStep((p) => p - 1)}
|
||||||
|
/>,
|
||||||
|
<BusinessHours
|
||||||
|
key="step-2"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep((p) => p + 1)}
|
||||||
|
onBack={() => setCurrentStep((p) => p - 1)}
|
||||||
|
/>,
|
||||||
|
<Equipment
|
||||||
|
key="step-3"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep((p) => p + 1)}
|
||||||
|
onBack={() => setCurrentStep((p) => p - 1)}
|
||||||
|
/>,
|
||||||
|
<OperationalCapacity
|
||||||
|
key="step-4"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onNext={() => setCurrentStep((p) => p + 1)}
|
||||||
|
onBack={() => setCurrentStep((p) => p - 1)}
|
||||||
|
/>,
|
||||||
|
<TypeOfRestaurant
|
||||||
|
key="step-5"
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onBack={() => setCurrentStep((p) => p - 1)}
|
||||||
|
onRegister={handleSubmit}
|
||||||
|
/>
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkProducts = async () => {
|
|
||||||
const productsExist = await checkIfProductsExist();
|
|
||||||
setHasProducts(productsExist);
|
|
||||||
};
|
|
||||||
checkProducts();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkIfProductsExist = async () => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
|
||||||
setSidebarOpen(true);
|
|
||||||
} else {
|
|
||||||
setSidebarOpen(false);
|
|
||||||
}
|
|
||||||
}, [theme.breakpoints.values.md]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (window.innerWidth >= theme.breakpoints.values.md) {
|
|
||||||
setSidebarOpen(true);
|
|
||||||
} else {
|
|
||||||
setSidebarOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleResize();
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
|
||||||
}, [theme.breakpoints.values.md]);
|
|
||||||
|
|
||||||
const handleDrawerToggle = () => {
|
|
||||||
setSidebarOpen(!sidebarOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{ display: 'flex', height: '100%', backgroundColor: '#F6F6F6', overflow: 'hidden' }}>
|
||||||
display: 'flex',
|
|
||||||
height: '100vh',
|
{/* إظهار SideProfile فقط إذا لم يتم التحويل إلى AccountProfile */}
|
||||||
backgroundColor: '#F6F6F6',
|
{!showSetting && (
|
||||||
overflow: 'hidden',
|
<Box
|
||||||
}}>
|
sx={{
|
||||||
<Sidebar
|
height: '100%',
|
||||||
open={sidebarOpen}
|
width: { md: '30%' },
|
||||||
onClose={handleDrawerToggle}
|
display: { xs: 'none', sm: 'none', md: 'block' },
|
||||||
isMobile={isMobile}
|
overflowY: 'auto',
|
||||||
drawerWidth={drawerWidth}
|
scrollbarWidth: 'none',
|
||||||
/>
|
'&::-webkit-scrollbar': { display: 'none' }
|
||||||
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
flexGrow: 1,
|
<Box sx={{ minHeight: '100%', pb: 15, pt: 1 }}>
|
||||||
display: 'flex',
|
<SideProfile
|
||||||
flexDirection: 'column',
|
currentStepIndex={currentStep}
|
||||||
width: { xs: '100%', sm: '100%', md: '100%' },
|
onBack={() => setCurrentStep((prev) => Math.max(prev - 1, 0))}
|
||||||
marginLeft: { xs: 0, sm: sidebarOpen ? `${drawerWidth}px` : 0, md: 0 },
|
/>
|
||||||
transition: theme.transitions.create(['width'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
}}>
|
|
||||||
<KitchPlusAppBar
|
|
||||||
onDrawerToggle={handleDrawerToggle}
|
|
||||||
sidebarOpen={sidebarOpen}
|
|
||||||
isMobile={isMobile}
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex', height: '100vh',
|
|
||||||
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
height: '100vh',
|
|
||||||
ml: 3,
|
|
||||||
mb: 2,
|
|
||||||
width: { md: '30%' },
|
|
||||||
display: { xs: 'none', sm: 'none', md: 'block' },
|
|
||||||
overflowY: 'auto',
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
minHeight: '100%', pb: 15, pt: 3,
|
|
||||||
|
|
||||||
}}>
|
|
||||||
<SideProfile
|
|
||||||
currentStepIndex={currentStep}
|
|
||||||
onBack={() => setCurrentStep(prev => Math.max(prev - 1, 0))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
ml: { xs: 2, md: 3 },
|
|
||||||
|
|
||||||
flexGrow: 1,
|
|
||||||
height: '100vh',
|
|
||||||
pr: { sm: 2, md: 1 },
|
|
||||||
|
|
||||||
pt: 3,
|
|
||||||
mb: { sm: 20 },
|
|
||||||
width: { md: '60%' }, display: { xs: 'block', sm: 'block', md: 'block' },
|
|
||||||
overflowY: 'auto',
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
minHeight: '100%', pb: 18,
|
|
||||||
|
|
||||||
}}>
|
|
||||||
{steps[currentStep]}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
ml: { xs: 2, md: 3 },
|
||||||
|
flexGrow: 1,
|
||||||
|
height: '100%',
|
||||||
|
pr: { sm: 2, md: 1 },
|
||||||
|
pt: 1,
|
||||||
|
mb: { sm: 20 },
|
||||||
|
width: { md: '60%' },
|
||||||
|
display: 'block',
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ minHeight: '100%', pb: 18 }}>
|
||||||
|
{showSetting ? <AccountProfile /> : steps[currentStep]}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* مودال بدون أزرار */}
|
||||||
|
<ConfirmationDialog open={modalOpen} onClose={() => {}} hideButtons={true} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ const steps = [
|
|||||||
{ title: 'Basic Information', icon: '/images/createProfile/BasicInf.png' },
|
{ title: 'Basic Information', icon: '/images/createProfile/BasicInf.png' },
|
||||||
{ title: 'Contact Information', icon: '/images/createProfile/ContactInf.png' },
|
{ title: 'Contact Information', icon: '/images/createProfile/ContactInf.png' },
|
||||||
{ title: 'Business Hours', icon: '/images/createProfile/BusinessHours.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: 'Available Equipment', icon: '/images/createProfile/equipment.png' },
|
||||||
{ title: 'Operational Capacity', icon: '/images/icons/rocket.png' },
|
{ title: 'Operational Capacity', icon: '/images/icons/rocket.png' },
|
||||||
{ title: 'Type of Restaurant', icon: '/images/createProfile/TypeOfRestaurant.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' },
|
{ title: 'Submit & Confirmation', icon: '/images/createProfile/Confirmation.png' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
124
src/components/Home/RestaurantProfile/all.js
Normal file
124
src/components/Home/RestaurantProfile/all.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
|
import { CircularProgress, Box, useTheme, useMediaQuery, Skeleton } from "@mui/material";
|
||||||
|
import RestaurantProfile from "./RestaurantProfile";
|
||||||
|
import Setting from "../Settings/AccountProfile";
|
||||||
|
import authService from "../../../services/authService";
|
||||||
|
import { useRestaurant } from "../../../contexts/RestaurantContext";
|
||||||
|
import KitchPlusAppBar from "../AppBar";
|
||||||
|
import Sidebar from "../SideHome"
|
||||||
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
const RestaurantWrapper = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
const { restaurantId } = useRestaurant();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [hasProfile, setHasProfile] = useState(false);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||||
|
|
||||||
|
const fetchProfile = useCallback(async () => {
|
||||||
|
if (!restaurantId) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const profile = await authService.getRestaurantProfile(restaurantId);
|
||||||
|
setHasProfile(profile && Object.keys(profile).length > 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking restaurant profile:", error);
|
||||||
|
setHasProfile(false);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [restaurantId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProfile();
|
||||||
|
}, [fetchProfile]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () =>
|
||||||
|
setSidebarOpen(window.innerWidth >= theme.breakpoints.values.md);
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, [theme.breakpoints.values.md]);
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => setSidebarOpen(!sidebarOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
height: "100vh",
|
||||||
|
backgroundColor: "#F6F6F6",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* البار الجانبي */}
|
||||||
|
<Sidebar
|
||||||
|
open={sidebarOpen}
|
||||||
|
onClose={handleDrawerToggle}
|
||||||
|
isMobile={isMobile}
|
||||||
|
drawerWidth={drawerWidth}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* المحتوى الرئيسي */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginLeft: {
|
||||||
|
xs: 0,
|
||||||
|
sm: sidebarOpen ? `${drawerWidth}px` : 0,
|
||||||
|
md: 0,
|
||||||
|
},
|
||||||
|
transition: theme.transitions.create(["width"], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* البار العلوي */}
|
||||||
|
<KitchPlusAppBar
|
||||||
|
onDrawerToggle={handleDrawerToggle}
|
||||||
|
sidebarOpen={sidebarOpen}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* المحتوى حسب وجود البروفايل */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
"&::-webkit-scrollbar": { display: "none" },
|
||||||
|
pt: { xs: 2, sm: 3, md: 3 },
|
||||||
|
pl: { xs: 2, sm: 3, md: 3 },
|
||||||
|
pr: { xs: 2, sm: 3, md: 3 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
// Skeleton placeholder
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
<Skeleton variant="rectangular" height={40} width="40%" />
|
||||||
|
<Skeleton variant="rectangular" height={40} width="60%" />
|
||||||
|
<Skeleton variant="rectangular" height={200} width="100%" />
|
||||||
|
<Skeleton variant="rectangular" height={40} width="80%" />
|
||||||
|
</Box>
|
||||||
|
) : hasProfile ? (
|
||||||
|
<Setting />
|
||||||
|
) : (
|
||||||
|
<RestaurantProfile refreshProfile={fetchProfile} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RestaurantWrapper;
|
||||||
@@ -1,17 +1,49 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
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 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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: { xs: '90%', sm: '100%', md: 740 },
|
height: { xs: '90%', sm: '100%', md: 740 },
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
px: 4,
|
px: 4,
|
||||||
pt: {xs:4,md: 4.5},
|
pt: { xs: 4, md: 4.5 },
|
||||||
|
|
||||||
pb: 10,
|
pb: 10,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
@@ -20,19 +52,16 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
width: { xs: '85%', sm: '90%' },
|
width: { xs: '85%', sm: '90%' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2.5}>
|
<Stack spacing={1.5}>
|
||||||
<Typography
|
<Typography
|
||||||
fontWeight={700}
|
fontWeight={700}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: {
|
fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' }
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Basic Information
|
Basic Information
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
<Box sx={{ width: '70%' }}>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="16px"
|
fontSize="16px"
|
||||||
@@ -44,23 +73,21 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Inputs (Restaurant Name, Type, Address, City, Postal Code) */}
|
{fields.map((fieldObj, index) => (
|
||||||
{[
|
<Box key={index} sx={{ display: 'flex', flexDirection: 'column'}}>
|
||||||
{ label: 'Restaurant Name', placeholder: 'Al-Baik Foods' },
|
|
||||||
{ label: 'Restaurant Type', placeholder: 'Fast Food' },
|
|
||||||
{ label: 'Address', placeholder: 'Ward # 13' },
|
|
||||||
{ label: 'City', placeholder: 'Jordan' },
|
|
||||||
{ label: 'Postal Code', placeholder: '31200' },
|
|
||||||
].map((field, index) => (
|
|
||||||
<Box key={index} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||||
{field.label}
|
{fieldObj.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder={field.placeholder}
|
placeholder={fieldObj.placeholder}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData[fieldObj.field] || ''}
|
||||||
|
onChange={(e) => handleChange(fieldObj.field, e.target.value)}
|
||||||
|
error={!!errors[fieldObj.field]}
|
||||||
|
helperText={errors[fieldObj.field] || ' '}
|
||||||
sx={{
|
sx={{
|
||||||
|
pb:-1,
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
'& .MuiOutlinedInput-root': {
|
'& .MuiOutlinedInput-root': {
|
||||||
@@ -80,12 +107,11 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNextClick}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -103,7 +129,6 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -116,7 +141,7 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -128,7 +153,6 @@ const BasicInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -11,28 +11,62 @@ import {
|
|||||||
FormControlLabel
|
FormControlLabel
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
const BusinessHours = ({ formData, updateFormData, onNext, onBack }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [selectedDays, setSelectedDays] = useState([]);
|
const [selectedDays, setSelectedDays] = useState(formData.closed_days || []);
|
||||||
|
const [operationHours, setOperationHours] = useState(formData.Operation_hour || '');
|
||||||
const handleCheckboxChange = (day) => {
|
const [errors, setErrors] = useState({});
|
||||||
setSelectedDays((prev) =>
|
|
||||||
prev.includes(day)
|
|
||||||
? prev.filter((d) => d !== day)
|
|
||||||
: [...prev, day]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: { xs: '90%', sm: '100%', md: 740 },
|
height: { xs: '90%', sm: '100%', md: 740 },
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
px: 4,
|
px: 4,
|
||||||
pt: {xs:4,md:4.5},
|
pt: { xs: 4, md: 4.5 },
|
||||||
pb: 10,
|
pb: 10,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
@@ -41,27 +75,13 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
width: { xs: '85%', sm: '90%' },
|
width: { xs: '85%', sm: '90%' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2.5}>
|
<Stack spacing={1.5}>
|
||||||
<Typography
|
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Business Hours
|
Business Hours
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
<Box sx={{ width: '70%' }}>
|
||||||
<Typography
|
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your business hours to proceed to registration of your own restaurant on this platform
|
Enter your business hours to proceed to registration of your own restaurant on this platform
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -72,9 +92,13 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Operational Hours
|
Operational Hours
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="16 hours"
|
placeholder="9:00 AM - 11:00 PM"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={operationHours}
|
||||||
|
onChange={(e) => setOperationHours(e.target.value)}
|
||||||
|
error={!!errors.operationHours}
|
||||||
|
helperText={errors.operationHours || ' '}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -96,22 +120,11 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
|
|
||||||
{/* Checkboxes for Days of the Week */}
|
{/* Checkboxes for Days of the Week */}
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Typography
|
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}>
|
||||||
variant="body2"
|
|
||||||
color="black"
|
|
||||||
sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}
|
|
||||||
>
|
|
||||||
Closed Days
|
Closed Days
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormGroup
|
<FormGroup row sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
row
|
{days.map(day => {
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{days.map((day) => {
|
|
||||||
const isChecked = selectedDays.includes(day);
|
const isChecked = selectedDays.includes(day);
|
||||||
return (
|
return (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
@@ -123,82 +136,69 @@ const BusinessHours = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
transform: 'scale(1.5)',
|
transform: 'scale(1.5)',
|
||||||
color: '#F0EDED',
|
color: '#F0EDED',
|
||||||
'&.Mui-checked': {
|
'&.Mui-checked': { color: '#FF914D' },
|
||||||
color: '#FF914D',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography sx={{ color: isChecked ? '#e57f3f' : '#969BA7', fontWeight: 500 }}>
|
||||||
sx={{
|
|
||||||
color: isChecked ? '#e57f3f' : '#969BA7',
|
|
||||||
fontWeight: 500
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{day}
|
{day}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
sx={{
|
sx={{ m: 0, width: { xs: '45%', sm: '30%', md: '22%' } }}
|
||||||
m: 0,
|
|
||||||
width: { xs: '45%', sm: '30%', md: '22%' },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
{errors.closedDays && (
|
||||||
|
<Typography color="error" fontSize="0.875rem" sx={{ mt: 0.5 }}>
|
||||||
|
{errors.closedDays}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Next Button */}
|
{/* Next & Back Buttons */}
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
<Box sx={{ pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNextClick}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||||
backgroundColor: theme.palette.primary.hover,
|
}}
|
||||||
},
|
>
|
||||||
}}
|
Next
|
||||||
>
|
</Button>
|
||||||
Next
|
|
||||||
</Button>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
{/* زر Back تحت زر Next */}
|
fullWidth
|
||||||
<Button
|
onClick={onBack}
|
||||||
variant="outlined"
|
sx={{
|
||||||
fullWidth
|
mt: 2,
|
||||||
onClick={onBack}
|
fontFamily: 'PlusJakartaSans',
|
||||||
sx={{
|
fontWeight: 600,
|
||||||
mt: 2,
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
fontFamily: 'PlusJakartaSans',
|
height: { xs: '45px', sm: '52px' },
|
||||||
fontWeight: 600,
|
borderRadius: '50px',
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
textTransform: 'none',
|
||||||
height: { xs: '45px', sm: '52px' },
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderRadius: '50px',
|
borderColor: theme.palette.primary.main,
|
||||||
textTransform: 'none',
|
color: theme.palette.primary.main,
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
'&:hover': { backgroundColor: theme.palette.primary.light, borderColor: theme.palette.primary.main },
|
||||||
borderColor: theme.palette.primary.main,
|
}}
|
||||||
color: theme.palette.primary.main,
|
>
|
||||||
'&:hover': {
|
Back
|
||||||
backgroundColor: theme.palette.primary.light,
|
</Button>
|
||||||
borderColor: theme.palette.primary.main,
|
</Box>
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,22 +4,19 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
Divider
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
|
||||||
const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
const ConfirmationDialog = ({ open, onClose }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
|
|
||||||
fullWidth
|
fullWidth
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
@@ -33,148 +30,80 @@ const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|
||||||
p: 3,
|
p: 3,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
}}>
|
}}>
|
||||||
<Box
|
<Box sx={{
|
||||||
sx={{
|
position: 'relative',
|
||||||
display: 'flex',
|
width: 100,
|
||||||
alignItems: 'center',
|
height: 100,
|
||||||
justifyContent: 'center',
|
display: 'flex',
|
||||||
gap: 2,
|
alignItems: 'center',
|
||||||
mb: 3,
|
justifyContent: 'center',
|
||||||
ml:-5,
|
mb: 3
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{/* الدوائر */}
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
position: 'relative',
|
position: 'absolute',
|
||||||
width: 100,
|
backgroundColor: '#F5F8FF',
|
||||||
height: 100,
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '50%',
|
||||||
|
zIndex: 0,
|
||||||
|
}} />
|
||||||
|
<Box sx={{
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#E9EFFF',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
zIndex: 1,
|
||||||
}}>
|
}}>
|
||||||
{/* الدائرة الخلفية */}
|
<img
|
||||||
<Box sx={{
|
src='/images/createProfile/Successful.png'
|
||||||
position: 'absolute',
|
style={{
|
||||||
backgroundColor: '#F5F8FF',
|
width: 40,
|
||||||
width: '100%',
|
height: 40,
|
||||||
height: '100%',
|
objectFit: 'contain',
|
||||||
borderRadius: '50%',
|
|
||||||
zIndex: 0,
|
|
||||||
}} />
|
|
||||||
|
|
||||||
{/* الدائرة الأمامية مع الأيقونة */}
|
|
||||||
<Box sx={{
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: '#E9EFFF',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
zIndex: 1,
|
|
||||||
}}>
|
|
||||||
<img
|
|
||||||
src='/images/createProfile/Successful.png'
|
|
||||||
style={{
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
objectFit: 'contain',
|
|
||||||
}}
|
|
||||||
alt="Success icon"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* العنوان بجانب الدائرة */}
|
|
||||||
<DialogTitle sx={{ p: 0 }}>
|
|
||||||
<Typography
|
|
||||||
variant="h4"
|
|
||||||
component="div"
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '28px',
|
|
||||||
lineHeight: '1.2'
|
|
||||||
}}
|
}}
|
||||||
>
|
alt="Success icon"
|
||||||
Register successful!
|
/>
|
||||||
</Typography>
|
</Box>
|
||||||
</DialogTitle>
|
|
||||||
</Box>
|
</Box>
|
||||||
<DialogContent sx={{ p: 0 }}>
|
|
||||||
<DialogContentText sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
mb: 0,
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 500,
|
|
||||||
color: theme.palette.text.secondary
|
|
||||||
}}>
|
|
||||||
Congratulations! you have registered your restaurant successfully on our platform
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions
|
|
||||||
sx={{
|
|
||||||
p: 0,
|
|
||||||
px: 3,
|
|
||||||
pt: 2,
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* الزر الأول - Filled */}
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={onClose}
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '16px',
|
|
||||||
height: '48px',
|
|
||||||
borderRadius: '50px',
|
|
||||||
textTransform: 'none',
|
|
||||||
color: 'white',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.palette.primary.dark,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View Profile
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* الزر الثاني - Outlined + أيقونة Edit */}
|
<DialogTitle sx={{ p: 0 }}>
|
||||||
<Button
|
<Typography
|
||||||
variant="outlined"
|
variant="h4"
|
||||||
fullWidth
|
component="div"
|
||||||
onClick={onClose}
|
sx={{
|
||||||
startIcon={<EditIcon sx={{ color: '#00081D' }} />}
|
fontWeight: 600,
|
||||||
sx={{
|
fontSize: '28px',
|
||||||
fontWeight: 600,
|
lineHeight: '1.2',
|
||||||
fontSize: '16px',
|
textAlign: 'center'
|
||||||
height: '48px',
|
}}
|
||||||
borderRadius: '50px',
|
>
|
||||||
textTransform: 'none',
|
Register successful!
|
||||||
borderColor: '#E6E7EA',
|
</Typography>
|
||||||
color: '#00081D',
|
</DialogTitle>
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#FAFAFA',
|
<DialogContent sx={{ p: 0, mt: 2 }}>
|
||||||
borderColor: '#E6E7EA',
|
<DialogContentText sx={{
|
||||||
},
|
textAlign: 'center',
|
||||||
}}
|
fontSize: '16px',
|
||||||
>
|
fontWeight: 500,
|
||||||
Edit Profile
|
color: theme.palette.text.secondary
|
||||||
</Button>
|
}}>
|
||||||
</DialogActions>
|
Congratulations! You have registered your restaurant successfully on our platform
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfirmationDialog;
|
export default ConfirmationDialog;
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
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 theme = useTheme();
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleChange = (field, value) => {
|
||||||
if (onNext) {
|
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();
|
onNext();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -25,26 +49,13 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
width: { xs: '85%', sm: '90%' },
|
width: { xs: '85%', sm: '90%' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2.5}>
|
<Stack spacing={1.5}>
|
||||||
<Typography
|
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||||
fontWeight={700}
|
|
||||||
sx={{
|
|
||||||
fontSize: {
|
|
||||||
xs: '1.8rem',
|
|
||||||
sm: '2rem',
|
|
||||||
md: '2.2rem'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Contact Information
|
Contact Information
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
<Box sx={{ width: '70%' }}>
|
||||||
<Typography
|
<Typography fontSize="16px" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your contact information to proceed to registration of your own restaurant on this platform
|
Enter your contact information to proceed to registration of your own restaurant on this platform
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -58,6 +69,10 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
placeholder="03055376864"
|
placeholder="03055376864"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData.Phone || ''}
|
||||||
|
onChange={(e) => handleChange('Phone', e.target.value)}
|
||||||
|
error={!!errors.Phone}
|
||||||
|
helperText={errors.Phone || ' '}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -84,9 +99,13 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="example@example.com"
|
placeholder="example@example.com"
|
||||||
type="email"
|
type="Email"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={formData.Email || ''}
|
||||||
|
onChange={(e) => handleChange('Email', e.target.value)}
|
||||||
|
error={!!errors.Email}
|
||||||
|
helperText={errors.Email || ' '}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
@@ -105,56 +124,49 @@ const ContactInformation = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Next Button */}
|
|
||||||
|
|
||||||
<Box sx={{ pt: 2 }}>
|
{/* Next & Back Buttons */}
|
||||||
<Button
|
<Box sx={{ pt: 2 }}>
|
||||||
variant="contained"
|
<Button
|
||||||
fullWidth
|
variant="contained"
|
||||||
onClick={onNext}
|
fullWidth
|
||||||
sx={{
|
onClick={handleNextClick}
|
||||||
fontFamily: 'PlusJakartaSans',
|
sx={{
|
||||||
fontWeight: 600,
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
fontWeight: 600,
|
||||||
height: { xs: '45px', sm: '52px' },
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
borderRadius: '50px',
|
height: { xs: '45px', sm: '52px' },
|
||||||
textTransform: 'none',
|
borderRadius: '50px',
|
||||||
color: 'white',
|
textTransform: 'none',
|
||||||
backgroundColor: theme.palette.primary.main,
|
color: 'white',
|
||||||
'&:hover': {
|
backgroundColor: theme.palette.primary.main,
|
||||||
backgroundColor: theme.palette.primary.hover,
|
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||||
},
|
}}
|
||||||
}}
|
>
|
||||||
>
|
Next
|
||||||
Next
|
</Button>
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
<Button
|
||||||
<Button
|
variant="outlined"
|
||||||
variant="outlined"
|
fullWidth
|
||||||
fullWidth
|
onClick={onBack}
|
||||||
onClick={onBack}
|
sx={{
|
||||||
sx={{
|
mt: 2,
|
||||||
mt: 2,
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontWeight: 600,
|
||||||
fontWeight: 600,
|
fontSize: { xs: '14px', sm: '16px' },
|
||||||
fontSize: { xs: '14px', sm: '16px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
height: { xs: '45px', sm: '52px' },
|
borderRadius: '50px',
|
||||||
borderRadius: '50px',
|
textTransform: 'none',
|
||||||
textTransform: 'none',
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
borderColor: theme.palette.primary.main,
|
||||||
borderColor: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
'&:hover': { backgroundColor: theme.palette.primary.light, borderColor: theme.palette.primary.main },
|
||||||
'&:hover': {
|
}}
|
||||||
backgroundColor: theme.palette.primary.light,
|
>
|
||||||
borderColor: theme.palette.primary.main,
|
Back
|
||||||
},
|
</Button>
|
||||||
}}
|
</Box>
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,66 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/material';
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Stack,
|
||||||
|
Button,
|
||||||
|
useTheme,
|
||||||
|
TextField,
|
||||||
|
Modal,
|
||||||
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
const Equipment = ({ formData, updateFormData, onNext, onBack }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleNext = () => {
|
const [equipment, setEquipment] = useState(formData.equipment || {
|
||||||
if (onNext) onNext();
|
oven: '',
|
||||||
else navigate('/dashboard');
|
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 (
|
return (
|
||||||
@@ -27,30 +78,29 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2.5}>
|
<Stack spacing={1}>
|
||||||
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
<Typography fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||||
Equipment
|
Equipment
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ width: '70%' }}>
|
<Box sx={{ width: '70%' }}>
|
||||||
<Typography
|
<Typography fontSize="16px" color="text.secondary" fontWeight={500}>
|
||||||
fontSize="16px"
|
|
||||||
color="text.secondary"
|
|
||||||
fontWeight={500}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
>
|
|
||||||
Enter your kitchen equipment details to complete your restaurant registration.
|
Enter your kitchen equipment details to complete your restaurant registration.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Oven Input */}
|
{/* Dynamic Equipment Fields */}
|
||||||
<InputField label="Oven" placeholder="5" theme={theme} />
|
{Object.keys(equipment).map((key) => (
|
||||||
|
<InputField
|
||||||
{/* Grill Input */}
|
key={key}
|
||||||
<InputField label="Grill" placeholder="20" theme={theme} />
|
label={key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
|
placeholder="0"
|
||||||
{/* Refrigerators Input */}
|
theme={theme}
|
||||||
<InputField label="Refrigerators" placeholder="200" theme={theme} />
|
value={equipment[key]}
|
||||||
|
onChange={(val) => handleInputChange(key, val)}
|
||||||
|
error={errors[key]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Add More Equipment Button */}
|
{/* Add More Equipment Button */}
|
||||||
<Box>
|
<Box>
|
||||||
@@ -65,23 +115,20 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': { backgroundColor: 'transparent' }
|
||||||
backgroundColor: 'transparent',
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onClick={() => console.log('Add More Equipment')}
|
onClick={() => setModalOpen(true)}
|
||||||
>
|
>
|
||||||
Add More
|
Add More
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Next Button */}
|
{/* Next Button */}
|
||||||
|
<Box>
|
||||||
<Box sx={{ pt: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={onNext}
|
onClick={handleNextClick}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'PlusJakartaSans',
|
fontFamily: 'PlusJakartaSans',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -91,15 +138,12 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': { backgroundColor: theme.palette.primary.hover },
|
||||||
backgroundColor: theme.palette.primary.hover,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* زر Back تحت زر Next */}
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -112,7 +156,7 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
height: { xs: '45px', sm: '52px' },
|
height: { xs: '45px', sm: '52px' },
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
display: { xs: 'block', sm: 'block', md: 'none' }, // يظهر فقط في xs و sm
|
display: { xs: 'block', sm: 'block', md: 'none' },
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@@ -124,14 +168,61 @@ const Equipment = ({ currentStepIndex = 0, onNext, onBack }) => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* Modal for Adding Equipment */}
|
||||||
|
<Modal open={modalOpen} onClose={() => setModalOpen(false)}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 400,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 2,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||||
|
Add New Equipment
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Equipment Name"
|
||||||
|
value={newEquipment.name}
|
||||||
|
onChange={(e) => setNewEquipment({ ...newEquipment, name: e.target.value })}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Quantity"
|
||||||
|
type="number"
|
||||||
|
value={newEquipment.quantity}
|
||||||
|
onChange={(e) => setNewEquipment({ ...newEquipment, quantity: e.target.value })}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={handleAddEquipment}
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: '50px',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// مكون فرعي لتقليل التكرار في الحقول
|
const InputField = ({ label, placeholder, theme, value, onChange, error }) => (
|
||||||
const InputField = ({ label, placeholder, theme }) => (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '16px', color: 'black' }}>
|
||||||
{label}
|
{label}
|
||||||
@@ -140,6 +231,10 @@ const InputField = ({ label, placeholder, theme }) => (
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
error={!!error}
|
||||||
|
helperText={error || ' '}
|
||||||
sx={{
|
sx={{
|
||||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||||
'& input::placeholder': { color: '#969BA7' },
|
'& input::placeholder': { color: '#969BA7' },
|
||||||
|
|||||||
لم تُعرض بعض الملفات لأن الكثير من الملفات تغيرت في هذا الاختلاف إظهار المزيد
المرجع في مشكلة جديدة
حظر مستخدم