Trương Tấn Cường commited on
Commit
ec8aeac
·
unverified ·
2 Parent(s): a0dabed fdbdf19

Merge pull request #44 from PBL6-team-CATS/feature/frontend_update

Browse files
frontend/.gitignore CHANGED
@@ -24,4 +24,5 @@ yarn-error.log*
24
 
25
  .env
26
  note_dev.txt
27
- object.json
 
 
24
 
25
  .env
26
  note_dev.txt
27
+ object.json
28
+ .json
frontend/public/about-us.jpg ADDED
frontend/public/desserts.jpg ADDED
frontend/public/dishes.jpg ADDED
frontend/public/drinks.jpg ADDED
frontend/public/header.jpg ADDED
frontend/src/index.js CHANGED
@@ -1,8 +1,9 @@
1
  import React from 'react';
2
  import ReactDOM from 'react-dom/client';
3
  import { createBrowserRouter, RouterProvider } from 'react-router-dom';
4
- import './styles/index.css';
5
  import 'bootstrap/dist/css/bootstrap.min.css';
 
 
6
  import ErrorPage from './pages/ErrorPage';
7
  import HomePage from './pages/HomePage';
8
  import reportWebVitals from './reportWebVitals';
@@ -13,48 +14,103 @@ import MenuPage from './pages/MenuPage';
13
  import CartPage from './pages/CartPage';
14
  import UserInfoPage from './pages/UserInfoPage';
15
 
 
 
 
 
 
 
 
 
 
16
  const router = createBrowserRouter([
17
- {
18
- path: "/",
19
- element: <HomePage />,
20
- errorElement: <ErrorPage />,
21
- },
22
- {
23
- path: "/login",
24
- element: <LoginPage />,
25
- errorElement: <ErrorPage />
26
- },
27
- {
28
- path: "/register",
29
- element: <RegisterPage/>,
30
- errorElement: <ErrorPage/>
31
- },
32
- {
33
- path: "/news",
34
- element: <NewsPage/>,
35
- errorElement: <ErrorPage/>
36
- },
37
- {
38
- path: "/menu",
39
- element: <MenuPage/>
40
- },
41
- {
42
- path: "/cart",
43
- element: <CartPage/>,
44
- errorElement: <ErrorPage/>
45
- },
46
- {
47
- path: "/userinfo",
48
- element: <UserInfoPage/>,
49
- errorElement: <ErrorPage/>
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  ]);
52
 
53
  const root = ReactDOM.createRoot(document.getElementById('root'));
54
  root.render(
55
- <React.StrictMode>
56
- <RouterProvider router={router}/>
57
- </React.StrictMode>
58
  );
59
 
60
  // If you want to start measuring performance in your app, pass a function
 
1
  import React from 'react';
2
  import ReactDOM from 'react-dom/client';
3
  import { createBrowserRouter, RouterProvider } from 'react-router-dom';
 
4
  import 'bootstrap/dist/css/bootstrap.min.css';
5
+ import './styles/index.css';
6
+ import './styles/styles.css';
7
  import ErrorPage from './pages/ErrorPage';
8
  import HomePage from './pages/HomePage';
9
  import reportWebVitals from './reportWebVitals';
 
14
  import CartPage from './pages/CartPage';
15
  import UserInfoPage from './pages/UserInfoPage';
16
 
17
+ import AdminSummaryPage from './pages/AdminSummaryPage';
18
+ import AdminFeedPage from './pages/AdminFeedPage';
19
+ import AdminMenuPage from './pages/AdminMenuPage';
20
+ import AdminStaffPage from './pages/AdminStaffPage';
21
+ import AdminOrderPage from './pages/AdminOrderPage';
22
+ import AdminSchedulePage from './pages/AdminSchedulePage';
23
+ import AdminLoginPage from './pages/AdminLoginPage';
24
+ import AdminUserInfoPage from './pages/AdminUserInfoPage';
25
+
26
  const router = createBrowserRouter([
27
+ {
28
+ path: "/",
29
+ element: <HomePage />,
30
+ errorElement: <ErrorPage />,
31
+ },
32
+ {
33
+ path: "/login",
34
+ element: <LoginPage />,
35
+ errorElement: <ErrorPage />
36
+ },
37
+ {
38
+ path: "/register",
39
+ element: <RegisterPage />,
40
+ errorElement: <ErrorPage />
41
+ },
42
+ {
43
+ path: "/news",
44
+ element: <NewsPage />,
45
+ errorElement: <ErrorPage />
46
+ },
47
+ {
48
+ path: "/menu",
49
+ element: <MenuPage />
50
+ },
51
+ {
52
+ path: "/cart",
53
+ element: <CartPage />,
54
+ errorElement: <ErrorPage />
55
+ },
56
+ {
57
+ path: "/userinfo",
58
+ element: <UserInfoPage />,
59
+ errorElement: <ErrorPage />
60
+ },
61
+ // admin section
62
+ {
63
+ path: "/admin",
64
+ element: <AdminSummaryPage />,
65
+ errorElement: <ErrorPage />
66
+ },
67
+ {
68
+ path: "/admin-summary",
69
+ element: <AdminSummaryPage />,
70
+ errorElement: <ErrorPage />
71
+ },
72
+ {
73
+ path: "/admin-feed",
74
+ element: <AdminFeedPage />,
75
+ errorElement: <ErrorPage />
76
+ },
77
+ {
78
+ path: "/admin-schedule",
79
+ element: <AdminSchedulePage />,
80
+ errorElement: <ErrorPage />
81
+ },
82
+ {
83
+ path: "/admin-menu",
84
+ element: <AdminMenuPage />,
85
+ errorElement: <ErrorPage />
86
+ },
87
+ {
88
+ path: "/admin-staff",
89
+ element: <AdminStaffPage />,
90
+ errorElement: <ErrorPage />
91
+ },
92
+ {
93
+ path: "/admin-orders",
94
+ element: <AdminOrderPage />,
95
+ errorElement: <ErrorPage />
96
+ },
97
+ {
98
+ path: "/admin-login",
99
+ element: <AdminLoginPage />,
100
+ errorElement: <ErrorPage />
101
+ },
102
+ {
103
+ path:"/admin-info",
104
+ element: <AdminUserInfoPage/>,
105
+ errorElement: <ErrorPage/>
106
+ }
107
  ]);
108
 
109
  const root = ReactDOM.createRoot(document.getElementById('root'));
110
  root.render(
111
+ <React.StrictMode>
112
+ <RouterProvider router={router} />
113
+ </React.StrictMode>
114
  );
115
 
116
  // If you want to start measuring performance in your app, pass a function
frontend/src/molecules/AboutUsSection.js CHANGED
@@ -1,21 +1,46 @@
1
  import { Container, Row, Col } from "react-bootstrap";
2
 
3
- export default function AboutUsSection () {
4
  return (<>
5
- <Container id="about-us" className="my-5">
6
- <h1 className="my-4 text-cen">CATS Shop
7
- <small> - We code for fun </small>
8
- </h1>
 
 
 
 
 
 
 
 
 
9
 
10
- <Row className="align-items-center">
11
- <Col md={8}>
12
- <img className="img-fluid" src="/cats-logo.png" alt="" style={{width: "100%", height: "auto"}}></img>
13
- </Col>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- <Col md={4} className="d-flex justify-content-center align-items-center">
16
- Chào mừng mọi người đến với Cats Shop, phương châm của chúng tôi là code at the sink and sleep at the sea.
17
- </Col>
18
- </Row>
19
- </Container>
20
  </>)
21
  }
 
1
  import { Container, Row, Col } from "react-bootstrap";
2
 
3
+ export default function AboutUsSection() {
4
  return (<>
5
+ <Container id="about-us" className="mb-5" style={{
6
+ maxWidth: "100%",
7
+ backgroundImage: "linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('/about-us.jpg')", // Thay đường dẫn ảnh nền
8
+ backgroundSize: 'cover',
9
+ backgroundPosition: 'center',
10
+ backgroundAttachment: 'fixed',
11
+ width: '100%',
12
+ padding: '50px 0',
13
+ }}>
14
+ <div className="d-flex justify-content-center align-items-center" style={{
15
+ maxWidth: "90%",
16
+ minHeight: "50vh"
17
+ }} >
18
 
19
+ <Row className="align-items-center">
20
+ {/* <Col md={6}>
21
+ <img className="img-fluid" src="/cats-logo.png" alt="" style={{ width: "100%", height: "auto" }}></img>
22
+ </Col> */}
23
+ <Col md={3}></Col>
24
+ <Col md={6} className="align-items-center">
25
+ <h1 style={{
26
+ fontWeight: 'bold',
27
+ // fontFamily: 'Arial, sans-serif', /* Chọn font chữ */
28
+ 'fontSize': '72px', /* Kích thước chữ cho h1 */
29
+ }}
30
+ className='mb-3'>CATS Shop
31
+ </h1>
32
+ <br />
33
+ <p style={{
34
+ // fontFamily: 'Arial, sans-serif', /* Chọn font chữ */
35
+ 'fontSize': '36px', /* Kích thước chữ cho h1 */
36
+ }}>
37
+ Chào mừng mọi người đến với Cats Shop, phương châm của chúng tôi là code at the sink and sleep at the sea.
38
+ </p>
39
+ </Col>
40
+ <Col md={3}></Col>
41
+ </Row>
42
+ </div>
43
 
44
+ </Container>
 
 
 
 
45
  </>)
46
  }
frontend/src/molecules/AdminNavBar.js CHANGED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {Container, Nav, Navbar, Button, Stack} from 'react-bootstrap'
2
+ import { useNavigate } from 'react-router-dom';
3
+ import DataStorage from '../organisms/DataStorage';
4
+
5
+ export default function AdminNavbar() {
6
+
7
+ const navigate = useNavigate();
8
+
9
+ function handleLogout() {
10
+ DataStorage.set('isLoggedInAdmin','false');
11
+ DataStorage.remove('accessTokenAdmin');
12
+ DataStorage.remove('roleAdmin');
13
+ DataStorage.remove('usernameAdmin');
14
+ DataStorage.remove('expiryDateAdmin');
15
+ navigate('/');
16
+ }
17
+
18
+ let username = DataStorage.get('usernameAdmin');
19
+ let isLoggedIn = DataStorage.get('isLoggedInAdmin');
20
+
21
+ let userContent;
22
+ if (isLoggedIn === 'true') {
23
+ userContent = <>
24
+ <Stack direction='horizontal' gap={2}>
25
+ <Button href="/admin-info" variant='primary'>
26
+ Xin chào, ADMIN {username}
27
+ </Button>
28
+ <Button onClick={handleLogout} variant='outline-primary'>
29
+ Đăng xuất
30
+ </Button>
31
+ </Stack>
32
+ </>
33
+ } else {
34
+ userContent = <>
35
+ <Stack direction='horizontal' gap={2}>
36
+ <Button href="/admin-login" variant='primary'>
37
+ Đăng nhập
38
+ </Button>
39
+ </Stack>
40
+ </>
41
+ }
42
+ return (
43
+ <Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' >
44
+ <Container>
45
+ <Navbar.Brand href="/admin">
46
+ <img
47
+ alt=""
48
+ src="/cats-logo.png"
49
+ width="30"
50
+ height="30"
51
+ className="d-inline-block align-top"
52
+ />{' '}
53
+ CATS-Shop
54
+ </Navbar.Brand>
55
+ <Navbar.Toggle aria-controls="basic-navbar-nav" />
56
+ <Navbar.Collapse id="basic-navbar-nav">
57
+ <Nav className="me-auto text-center">
58
+ {/* These are the navigators */}
59
+ <Nav.Link disabled={!isLoggedIn} href="/admin-summary">Dashboard</Nav.Link>
60
+ <Nav.Link disabled={!isLoggedIn} href="/admin-feed">Bài đăng</Nav.Link>
61
+ <Nav.Link disabled={!isLoggedIn} href="/admin-menu">Thực đơn</Nav.Link>
62
+ <Nav.Link disabled={!isLoggedIn} href="/admin-staff">Nhân viên</Nav.Link>
63
+ <Nav.Link disabled={!isLoggedIn} href="/admin-schedule">Lịch làm việc</Nav.Link>
64
+ <Nav.Link disabled={!isLoggedIn} href="/admin-orders">Đơn hàng</Nav.Link>
65
+ </Nav>
66
+ {userContent}
67
+ </Navbar.Collapse>
68
+ </Container>
69
+ </Navbar>
70
+ )
71
+ }
frontend/src/molecules/ContactSection.js CHANGED
@@ -2,10 +2,11 @@ import { Container, Row, Col } from "react-bootstrap";
2
 
3
  export default function ContactSection() {
4
  return (
5
- <Container id="contact" className="my-5 align-items-center">
6
  <h1 className="text-center mb-5">Thông tin liên hệ</h1>
7
- <Row md={3}>
8
  {/* Cột 1: Số điện thoại liên hệ */}
 
9
  <Col>
10
  <h5>Liên hệ</h5>
11
  <p>Số điện thoại: 0123-456-789</p>
@@ -26,6 +27,7 @@ export default function ContactSection() {
26
  </a>
27
  </p>
28
  </Col>
 
29
  </Row>
30
  </Container>
31
  );
 
2
 
3
  export default function ContactSection() {
4
  return (
5
+ <Container fluid id="contact" className="my-5 align-items-center">
6
  <h1 className="text-center mb-5">Thông tin liên hệ</h1>
7
+ <Row>
8
  {/* Cột 1: Số điện thoại liên hệ */}
9
+ <Col md={2}></Col>
10
  <Col>
11
  <h5>Liên hệ</h5>
12
  <p>Số điện thoại: 0123-456-789</p>
 
27
  </a>
28
  </p>
29
  </Col>
30
+ <Col md={1}></Col>
31
  </Row>
32
  </Container>
33
  );
frontend/src/molecules/Navbar.js CHANGED
@@ -1,4 +1,4 @@
1
- import {Container, Nav, Navbar, Button, Stack} from 'react-bootstrap'
2
  import { useNavigate } from 'react-router-dom';
3
  import DataStorage from '../organisms/DataStorage';
4
 
@@ -7,7 +7,7 @@ export default function ANavbar() {
7
  const navigate = useNavigate();
8
 
9
  function handleLogout() {
10
- DataStorage.set('isLoggedIn','false');
11
  DataStorage.remove('accessToken');
12
  DataStorage.remove('role');
13
  DataStorage.remove('username');
@@ -49,17 +49,21 @@ export default function ANavbar() {
49
  }
50
 
51
  return (
52
- <Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' >
53
- <Container>
54
  <Navbar.Brand href="/">
55
- <img
56
- alt=""
57
- src="/cats-logo.png"
58
- width="30"
59
- height="30"
60
- className="d-inline-block align-top"
61
- />{' '}
62
- CATS-Shop
 
 
 
 
63
  </Navbar.Brand>
64
  <Navbar.Toggle aria-controls="basic-navbar-nav" />
65
  <Navbar.Collapse id="basic-navbar-nav">
 
1
+ import { Container, Nav, Navbar, Button, Stack } from 'react-bootstrap'
2
  import { useNavigate } from 'react-router-dom';
3
  import DataStorage from '../organisms/DataStorage';
4
 
 
7
  const navigate = useNavigate();
8
 
9
  function handleLogout() {
10
+ DataStorage.set('isLoggedIn', 'false');
11
  DataStorage.remove('accessToken');
12
  DataStorage.remove('role');
13
  DataStorage.remove('username');
 
49
  }
50
 
51
  return (
52
+ <Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' style={{ minHeight: '6vh' }}>
53
+ <Container fluid style={{ maxWidth: "90%" }}>
54
  <Navbar.Brand href="/">
55
+ <div style={{ fontWeight: 'bold' }}>
56
+ <span className='mr-1'>
57
+ <img
58
+ alt=""
59
+ src="/cats-logo.png"
60
+ width="30"
61
+ height="30"
62
+ className="d-inline-block align-top"
63
+ />
64
+ </span>
65
+ {" "}
66
+ CATS - Shop</div>
67
  </Navbar.Brand>
68
  <Navbar.Toggle aria-controls="basic-navbar-nav" />
69
  <Navbar.Collapse id="basic-navbar-nav">
frontend/src/organisms/CacheStorage.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Cookies from 'js-cookie';
2
+
3
+ export default class CacheStorage {
4
+ static storageMethod = process.env.REACT_APP_CACHE_METHOD;
5
+
6
+ // Get data
7
+ static get(key) {
8
+ if (this.storageMethod === 'session') {
9
+ return sessionStorage.getItem(key);
10
+ } else if (this.storageMethod === 'cookie') {
11
+ return Cookies.get(key);
12
+ }
13
+ return null;
14
+ }
15
+
16
+ // Set data
17
+ static set(key, value, param = { expiryDate: null }) {
18
+ if (this.storageMethod === 'session') {
19
+ sessionStorage.setItem(key, value);
20
+ } else if (this.storageMethod === 'cookie') {
21
+ if (param.expiryDate) {
22
+ const expiryDate = new Date(param.expiryDate * 1000);
23
+ Cookies.set(key, value, { expires: expiryDate })
24
+ } else if (Cookies.get('expiryDate')) {
25
+ const expiryDate = new Date(Cookies.get('expiryDate') * 1000);
26
+ Cookies.set(key, value, { expires: expiryDate });
27
+ } else Cookies.set(key, value, { expires: 7 })// Expires in 7 by default
28
+ }
29
+ }
30
+
31
+ // Remove data
32
+ static remove(key) {
33
+ if (this.storageMethod === 'session') {
34
+ sessionStorage.removeItem(key);
35
+ } else if (this.storageMethod === 'cookie') {
36
+ Cookies.remove(key);
37
+ }
38
+ }
39
+
40
+ // Get all data
41
+ static getAll() {
42
+ const data = {};
43
+ if (this.storageMethod === 'session') {
44
+ for (let i = 0; i < sessionStorage.length; i++) {
45
+ const key = sessionStorage.key(i);
46
+ data[key] = sessionStorage.getItem(key);
47
+ }
48
+ } else if (this.storageMethod === 'cookie') {
49
+ const cookies = Cookies.get();
50
+ Object.keys(cookies).forEach(key => {
51
+ data[key] = cookies[key];
52
+ });
53
+ }
54
+ return data;
55
+ }
56
+
57
+ // Clear all data
58
+ static clearAll() {
59
+ if (this.storageMethod === 'session') {
60
+ sessionStorage.clear();
61
+ } else if (this.storageMethod === 'cookie') {
62
+ const cookies = Cookies.get();
63
+ Object.keys(cookies).forEach(key => {
64
+ Cookies.remove(key);
65
+ });
66
+ }
67
+ }
68
+
69
+ // Check if a key exists
70
+ static hasKey(key) {
71
+ if (this.storageMethod === 'session') {
72
+ return sessionStorage.getItem(key) !== null;
73
+ } else if (this.storageMethod === 'cookie') {
74
+ return Cookies.get(key) !== undefined;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ // Get all keys
80
+ static getKeys() {
81
+ const keys = [];
82
+ if (this.storageMethod === 'session') {
83
+ for (let i = 0; i < sessionStorage.length; i++) {
84
+ keys.push(sessionStorage.key(i));
85
+ }
86
+ } else if (this.storageMethod === 'cookie') {
87
+ keys.push(...Object.keys(Cookies.get()));
88
+ }
89
+ return keys;
90
+ }
91
+ }
frontend/src/organisms/MenuSection.js CHANGED
@@ -1,58 +1,151 @@
1
- import { useState } from 'react';
2
  import { Container, Carousel, Row, Col, Button } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
 
 
 
 
 
 
 
 
4
 
5
  function MenuSection() {
6
- const [index, setIndex] = useState(0);
7
-
8
- const handleSelect = (selectedIndex) => {
9
- setIndex(selectedIndex);
10
- };
11
-
12
- const menuItems = [
13
- { name: 'Món 1', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
14
- { name: 'Món 2', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
15
- { name: 'Món 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
16
- ];
17
-
18
- const countCarouselSlides = 4;
19
-
20
- return (
21
- <Container id="menu" className="text-center justify-content-center align-items-center my-5">
22
- <h1 className='mb-5'>Menu</h1>
23
- <Carousel activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
24
- {Array.from({ length: countCarouselSlides }).map((_, slideIndex) => (
25
- <Carousel.Item key={slideIndex}>
26
- <div className="d-flex justify-content-center align-items-center" style={{ width: '100%' }}>
27
- <img
28
- className="img-fluid"
29
- src="/placeholder4.jpg"
30
- alt="a placeholder"
31
- style={{ maxHeight: '100%', maxWidth: '100%', objectFit: 'contain' }}
32
- />
33
- </div>
34
-
35
- <Carousel.Caption>
36
- <div className='my-5'>
37
- <Row md={3} className="g-4">
38
- {menuItems.map((item, idx) => (
39
- <Col key={idx}>
40
- <MenuItem
41
- dishName={item.name}
42
- description={item.description}
43
- imageSrc={item.imageSrc}
44
- />
45
- </Col>
46
- ))}
47
- </Row>
48
- <Button as='a' href='/menu' className='mt-5'> Xem thêm các món </Button>
49
- </div>
50
- </Carousel.Caption>
51
- </Carousel.Item>
52
- ))}
53
- </Carousel>
54
- </Container>
55
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  export default MenuSection;
 
1
+ import { useState, useEffect } from 'react';
2
  import { Container, Carousel, Row, Col, Button } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
+ import CacheStorage from './CacheStorage';
5
+ import axios from 'axios';
6
+
7
+ const carouselImages = [
8
+ { itemType: 1, imageUrl: '/dishes.jpg' },
9
+ { itemType: 2, imageUrl: '/drinks.jpg' },
10
+ { itemType: 3, imageUrl: '/desserts.jpg' },
11
+ ];
12
 
13
  function MenuSection() {
14
+
15
+ const [carouselMaxHeight, setHeight] = useState('700px');
16
+ const [index, setIndex] = useState(0);
17
+ const [menuItems, setMenuItems] = useState({}); // Đối tượng lưu danh sách món theo từng itemType
18
+ const [loading, setLoading] = useState(true);
19
+
20
+ const handleSelect = (selectedIndex) => {
21
+ setIndex(selectedIndex);
22
+ };
23
+
24
+ // const menuItems = [
25
+ // { name: 'Món 1', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
26
+ // { name: 'Món 2', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
27
+ // { name: 'Món 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
28
+ // ];
29
+
30
+
31
+ const categoryMapper = {
32
+ 1: 'Món chính',
33
+ 2: 'Đồ uống',
34
+ 3: 'Tráng miệng',
35
+ };
36
+
37
+ useEffect(() => {
38
+ const fetchMenuItems = async () => {
39
+ try {
40
+ const menuItemsByType = {};
41
+
42
+ for (const carousel of carouselImages) {
43
+ // Gọi API để lấy món theo itemType
44
+ const response = await axios.get(process.env.REACT_APP_API_URL + `/menu-items?limit=3&filter.item_type=${carousel.itemType}`);
45
+ // Lưu danh sách món theo itemType
46
+ menuItemsByType[carousel.itemType] = response.data.data;
47
+ }
48
+ console.log(menuItemsByType);
49
+ setMenuItems(menuItemsByType);
50
+ setLoading(false);
51
+ CacheStorage.set('homeMenuItems',JSON.stringify(menuItemsByType));
52
+ console.log('fetched from API');
53
+ } catch (error) {
54
+ console.error('Error fetching menu items:', error);
55
+ setLoading(false);
56
+ }
57
+ };
58
+ if (CacheStorage.get('homeMenuItems')) {
59
+ setMenuItems(JSON.parse(CacheStorage.get('homeMenuItems')));
60
+ setLoading(false);
61
+ console.log('fetched from cache');
62
+ } else {
63
+ fetchMenuItems();
64
+ }
65
+ }, []);
66
+
67
+ useEffect(() => {
68
+ const handleResize = () => {
69
+ if (window.innerWidth < 992) { // md
70
+ setHeight('1500px'); // Không giới hạn chiều cao
71
+ } else {
72
+ setHeight('700px'); // Giới hạn chiều cao cho md trở lên
73
+ }
74
+ };
75
+
76
+ // Đăng ký event resize
77
+ window.addEventListener('resize', handleResize);
78
+
79
+ // Gọi lần đầu để xác định chiều cao ban đầu
80
+ handleResize();
81
+
82
+ // Cleanup
83
+ return () => {
84
+ window.removeEventListener('resize', handleResize);
85
+ };
86
+ }, []);
87
+
88
+
89
+ let menuContent;
90
+
91
+ if (loading) {
92
+ menuContent = (<p>Đang tải dữ liệu</p>);
93
+ } else {
94
+ menuContent = (<Carousel activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
95
+ {carouselImages.map((carousel, index) => (
96
+ <Carousel.Item key={index}>
97
+ <div className="d-flex justify-content-center align-items-center" style={{ width: '100%' }}>
98
+ <img
99
+ className="img-fluid"
100
+ src={carousel.imageUrl}
101
+ alt={`Carousel ${carousel.itemType}`}
102
+ style={{
103
+ height: carouselMaxHeight,
104
+ width: '100vw',
105
+ objectPosition: 'center',
106
+ objectFit: 'cover',
107
+ }}
108
+ />
109
+ <div
110
+ style={{
111
+ position: 'absolute',
112
+ top: 0,
113
+ left: 0,
114
+ width: '100%',
115
+ height: '100%',
116
+ backgroundColor: 'rgba(0, 0, 0, 0.5)', // Màu tối với độ trong suốt
117
+ }}
118
+ ></div>
119
+ </div>
120
+
121
+ <Carousel.Caption>
122
+ <div className='my-5'>
123
+ <h1 style={{ color: "var(--text-color)" }} className='mb-5'>{categoryMapper[carousel.itemType]}</h1>
124
+ <Row className="g-4">
125
+ {Array.from(menuItems[carousel.itemType]).map((item, idx) => (
126
+ <Col sm={12} lg={4} key={idx}>
127
+ <MenuItem
128
+ dishName={item.item_name}
129
+ description={item.description}
130
+ imageSrc={item.image_url}
131
+ />
132
+ </Col>
133
+ ))}
134
+ </Row>
135
+ <Button as='a' href='/menu' className='mt-5'> Xem thêm các món </Button>
136
+ </div>
137
+ </Carousel.Caption>
138
+ </Carousel.Item>
139
+ ))}
140
+ </Carousel>);
141
+ }
142
+
143
+ return (
144
+ <Container id="menu" className="text-center justify-content-center align-items-center my-5" style={{ maxWidth: "100%" }}>
145
+ <h1 className='mb-5'>Menu</h1>
146
+ {menuContent}
147
+ </Container>
148
+ );
149
  }
150
 
151
  export default MenuSection;
frontend/src/organisms/NewsSection.js CHANGED
@@ -1,30 +1,62 @@
1
  import { Container, Col, Row } from 'react-bootstrap';
2
  import NewsItem from '../molecules/NewsItem';
 
 
 
3
 
4
  function NewsSection() {
5
 
6
- const newsFeeds = [
7
- { title: 'Feed 1', text: 'This is the first feed', imageSrc: '/placeholder1.jpg', feedHref: '' },
8
- { title: 'Feed 2', text: 'This is the second feed', imageSrc: '/placeholder1.jpg', feedHref: '' },
9
- { title: 'Feed 3', text: 'This is the third feed', imageSrc: '/placeholder1.jpg', feedHref: ''}
10
- ];
11
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  return (
13
- <Container id="news" className="text-center justify-content-center align-items-center my-5">
14
  <h1 className='mb-5'>Tin tức</h1>
15
- <Row xs={1} md={2} xl={3} className="g-4">
16
- {Array.from(newsFeeds).map((feed, idx) => (
17
- <Col key={idx}>
18
- <NewsItem title={feed.title}
19
- text={feed.text}
20
- imageSrc={feed.imageSrc}
21
- // feedHref={feed.feedHref}
22
- feedHref="/news" //demo
23
- >
24
- </NewsItem>
25
- </Col>
26
- ))}
27
- </Row>
28
  </Container>
29
  );
30
  }
 
1
  import { Container, Col, Row } from 'react-bootstrap';
2
  import NewsItem from '../molecules/NewsItem';
3
+ import CacheStorage from './CacheStorage';
4
+ import axios from 'axios';
5
+ import { useState, useEffect } from 'react';
6
 
7
  function NewsSection() {
8
 
9
+ const [feeds, setFeeds] = useState([]); // Lưu danh sách bài đăng
10
+ const [loading, setLoading] = useState(true); // Trạng thái tải dữ liệu
11
+
12
+ useEffect(() => {
13
+ const fetchFeeds = async () => {
14
+ try {
15
+ const response = await axios.get(process.env.REACT_APP_API_URL + '/feeds?limit=3');
16
+ setFeeds(response.data.data);
17
+ setLoading(false);
18
+ CacheStorage.set('feeds',JSON.stringify(response.data.data));
19
+ console.log(response.data);
20
+ } catch (error) {
21
+ console.error('Error fetching branches:', error);
22
+ setLoading(false);
23
+ }
24
+ }
25
+
26
+ if (CacheStorage.get('feeds')) {
27
+ setFeeds(JSON.parse(CacheStorage.get('feeds')));
28
+ setLoading(false);
29
+ console.log(JSON.parse(CacheStorage.get('feeds')));
30
+ console.log('fetched from cache');
31
+ } else {
32
+ fetchFeeds();
33
+ }
34
+ }, []);
35
+
36
+ let feedContent;
37
+
38
+ if (loading) {
39
+ feedContent = (<p>Đang tải các bài đăng...</p>);
40
+ } else {
41
+ feedContent = (<Row xs={1} md={2} xl={3} className="g-4">
42
+ {Array.from(feeds).map((feed, idx) => (
43
+ <Col key={idx}>
44
+ <NewsItem title={feed.title}
45
+ text={feed.description}
46
+ imageSrc={feed.image_url}
47
+ // feedHref={feed.feedHref}
48
+ feedHref={'/news?id='+feed.id} //demo
49
+ >
50
+ </NewsItem>
51
+ </Col>
52
+ ))}
53
+ </Row>);
54
+ }
55
+
56
  return (
57
+ <Container fluid id="news" className="text-center justify-content-center align-items-center my-5" style={{maxWidth:"90%"}}>
58
  <h1 className='mb-5'>Tin tức</h1>
59
+ {feedContent}
 
 
 
 
 
 
 
 
 
 
 
 
60
  </Container>
61
  );
62
  }
frontend/src/organisms/StoreSection.js CHANGED
@@ -1,36 +1,71 @@
1
  import { Container, Col, Row } from 'react-bootstrap';
2
  import StoreItem from '../molecules/StoreItem';
 
 
 
3
 
4
  function StoreSection() {
5
-
6
- const stores = [
7
- { storeName: 'Store 1', address: 'Address 1', imageSrc: '/placeholder2.jpg' },
8
- { storeName: 'Store 2', address: 'Address 2', imageSrc: '/placeholder2.jpg' },
9
- { storeName: 'Store 3', address: 'Address 3', imageSrc: '/placeholder2.jpg' },
10
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  return (
13
- <Container id="store" className="text-center justify-content-center align-items-center my-5">
14
  <h1 className='mb-5'>Các chi nhánh</h1>
15
  <Row className="align-items-center">
16
- <Col xs={12} md={4} className="d-flex justify-content-center align-items-center">
17
- Đây các chi nhánh đang mở, chưa mở, sắp mở thể sẽ không mở
18
  </Col>
19
 
20
- <Col xs={12} md={8}>
21
- <Container>
22
- <Row xs={1} md={2} xl={3} className="g-4">
23
- {Array.from(stores).map((store, idx) => (
24
- <Col key={idx}>
25
- <StoreItem storeName={store.storeName}
26
- address={store.address}
27
- imageSrc={store.imageSrc}>
28
- </StoreItem>
29
- </Col>
30
- ))}
31
- </Row>
32
- </Container>
33
- </Col>
34
  </Row>
35
  </Container>
36
  );
 
1
  import { Container, Col, Row } from 'react-bootstrap';
2
  import StoreItem from '../molecules/StoreItem';
3
+ import CacheStorage from './CacheStorage';
4
+ import axios from 'axios';
5
+ import { useState, useEffect } from 'react';
6
 
7
  function StoreSection() {
8
+
9
+ // const stores = [
10
+ // { storeName: 'Store 1', address: 'Address 1', imageSrc: '/placeholder2.jpg' },
11
+ // { storeName: 'Store 2', address: 'Address 2', imageSrc: '/placeholder2.jpg' },
12
+ // { storeName: 'Store 3', address: 'Address 3', imageSrc: '/placeholder2.jpg' },
13
+ // ];
14
+
15
+ const [stores, setStores] = useState([]); // Lưu danh sách chi nhánh
16
+ const [loading, setLoading] = useState(true); // Trạng thái tải dữ liệu
17
+
18
+ useEffect(() => {
19
+ // Gọi API lấy danh sách chi nhánh
20
+ const fetchBranches = async () => {
21
+ try {
22
+ const response = await axios.get(process.env.REACT_APP_API_URL + '/branchs'); // Thay 'API_ENDPOINT' bằng URL của API
23
+ setStores(response.data); // Lưu dữ liệu vào state
24
+ setLoading(false); // Đặt loading thành false khi hoàn tất
25
+ CacheStorage.set('stores',JSON.stringify(Object(response.data)));
26
+ } catch (error) {
27
+ console.error('Error fetching branches:', error);
28
+ setLoading(false); // Đặt loading thành false nếu lỗi
29
+ }
30
+ };
31
+ if (CacheStorage.get('stores')) {
32
+ setStores(JSON.parse(CacheStorage.get('stores')));
33
+ setLoading(false);
34
+ } else {
35
+ fetchBranches();
36
+ }
37
+ }, []); // Chỉ chạy một lần khi component được mount
38
+
39
+ let storesContent;
40
+
41
+ if (loading) {
42
+ storesContent = (<p>Đang tải danh sách chi nhánh...</p>); // Hiển thị thông báo khi đang tải dữ liệu
43
+ } else {
44
+ storesContent = (<Col xs={12} md={9}>
45
+ <Container fluid>
46
+ <Row xs={1} md={2} xl={3} className="g-4">
47
+ {Array.from(stores).map((store, idx) => (
48
+ <Col key={idx}>
49
+ <StoreItem storeName={store.name}
50
+ address={store.location}
51
+ imageSrc={store.image_url}>
52
+ </StoreItem>
53
+ </Col>
54
+ ))}
55
+ </Row>
56
+ </Container>
57
+ </Col>)
58
+ }
59
 
60
  return (
61
+ <Container fluid id="store" className="text-center justify-content-center align-items-center my-5" style={{ maxWidth: "90%" }}>
62
  <h1 className='mb-5'>Các chi nhánh</h1>
63
  <Row className="align-items-center">
64
+ <Col xs={12} md={3} className="d-flex justify-content-center align-items-center">
65
+ hệ thống chuỗi nhà hàng nổi tiếng toàn quốc, chúng tôi đang hoạt động các cơ sở sau
66
  </Col>
67
 
68
+ {storesContent}
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  </Row>
70
  </Container>
71
  );
frontend/src/pages/AdminFeedPage.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from "react-bootstrap";
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+
4
+ export default function AdminFeedPage() {
5
+
6
+ return (
7
+ <AdminTemplate content={
8
+ (
9
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{ minHeight: '80vh' }}>
10
+ <Row>
11
+ <Col xs={12}>
12
+ <h1>This is a demo feed page</h1>
13
+ </Col>
14
+ <Col xs={12}>
15
+ <h3>In the future, we hope to view list of feed, add, edit or remove feeds.</h3>
16
+ </Col>
17
+ </Row>
18
+ </Container>
19
+ )
20
+ } />
21
+ );
22
+ }
frontend/src/pages/AdminHomePage.js DELETED
File without changes
frontend/src/pages/AdminLoginPage.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { Alert, Container, Form, Row, Col, Button, Card } from "react-bootstrap";
3
+ import AdminTemplate from "../templates/AdminTemplate";
4
+ import { useNavigate } from "react-router-dom";
5
+ import axios from 'axios';
6
+ import jwtDecoder from "../organisms/jwtDecoder";
7
+ import DataStorage from "../organisms/DataStorage";
8
+
9
+ export default function AdminLoginPage() {
10
+
11
+ const domain = process.env.REACT_APP_API_URL;
12
+
13
+ const [username, setUsername] = useState('');
14
+ const [password, setPassword] = useState('');
15
+ const [error, setError] = useState('');
16
+
17
+ const navigate = useNavigate();
18
+
19
+ const handleSubmit = (e) => {
20
+ e.preventDefault();
21
+ // Validate password and confirm password match
22
+ if (password.length === 0) {
23
+ setError('Hãy nhập mật khẩu');
24
+ } else if (username.trim().length === 0) {
25
+ setError('Tên đăng nhập không thể trống')
26
+ } else {
27
+ setError('');
28
+
29
+ let data = {
30
+ 'username': username,
31
+ 'password': password
32
+ }
33
+
34
+ axios.post(domain + '/authentication/login', data)
35
+ .then((response) => {
36
+ if (response.status === 200) {
37
+ // setError(JSON.stringify(jwtDecoder(response.data.access_token)));
38
+ const decodedToken = jwtDecoder(response.data.access_token);
39
+ const role = decodedToken.payload.roles;
40
+
41
+ if (role !== 'ADMIN' && role !== 'AREA_MANAGER' && role !== 'BRANCH_MANAGER') {
42
+ setError('TÀI KHOẢN KHÔNG HỢP LỆ');
43
+ } else {
44
+ const full_name = decodedToken.payload.username;
45
+ const expiryTime = decodedToken.payload.exp;
46
+ DataStorage.set('expiryDateAdmin', expiryTime, { expiryDate: expiryTime });
47
+ DataStorage.set('usernameAdmin', full_name);
48
+ DataStorage.set('roleAdmin', role);
49
+ DataStorage.set('isLoggedInAdmin', 'true');
50
+ DataStorage.set('accessTokenAdmin', response.data.access_token);
51
+ navigate('/admin');
52
+ }
53
+ } else {
54
+ setError(JSON.stringify(response));
55
+ }
56
+ })
57
+ .catch((error) => {
58
+ // if (error.status === 400) {
59
+ // setError('Lỗi đăng nhập, vui lòng kiểm tra lại tài khoản và mật khẩu');
60
+ // } else if (error.status === 500) {
61
+ // setError('Server đang tạm gặp vấn đề');
62
+ // }
63
+ setError(JSON.stringify(error));
64
+ })
65
+ }
66
+ };
67
+
68
+ return (
69
+ <AdminTemplate content={
70
+ (<Container fluid className="d-flex justify-content-center align-items-center mt-5"
71
+ style={{
72
+ maxWidth: "100%",
73
+ minHeight: "70vh",
74
+ }}>
75
+ <Row style={{ maxWidth: "90vw" }}>
76
+ {/* <Col xs={1} md={2}></Col> */}
77
+ <Col xs={10} md={8}>
78
+ <Card style={{ width: '35vw' }} className='justify-content-center card-nospan'>
79
+ <Card.Header>
80
+ <Card.Title className='mt-1 text-center'>Đăng nhập quyền quản lý</Card.Title>
81
+ </Card.Header>
82
+ <Card.Body>
83
+ <Form onSubmit={handleSubmit} >
84
+ {error && <Alert variant="danger">{error}</Alert>}
85
+
86
+ <Form.Group controlId="username" className='mb-3'>
87
+ <Form.Label>Administration credential</Form.Label>
88
+ <Form.Control
89
+ type="text"
90
+ placeholder="Administrator email"
91
+ onChange={(e) => setUsername(e.target.value)}
92
+ />
93
+ </Form.Group>
94
+
95
+ <Form.Group controlId="password" className='mb-4'>
96
+ <Form.Label>Mật khẩu</Form.Label>
97
+ <Form.Control
98
+ type="password"
99
+ placeholder="Mật khẩu"
100
+ onChange={(e) => setPassword(e.target.value)}
101
+ />
102
+ </Form.Group>
103
+ <div className='d-flex justify-content-center align-items-center'>
104
+ {/* <Button as='a' variant='outline-primary' href="/register" className='m-2'>Đăng ký</Button> */}
105
+ <Button type="submit" className='m-2'>Đăng nhập</Button>
106
+ {/* <Button as='a' variant='outline-primary' href="/forgot-password" className='m-2'>Quên mật khẩu</Button> */}
107
+ </div>
108
+
109
+ </Form>
110
+ </Card.Body>
111
+ </Card>
112
+
113
+ </Col>
114
+ {/* <Col xs={1} md={2}></Col> */}
115
+ </Row>
116
+ </Container>)
117
+ }>
118
+ </AdminTemplate>
119
+ );
120
+ }
frontend/src/pages/AdminMenuPage.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from 'react-bootstrap';
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+
4
+ export default function AdminMenuPage() {
5
+ return (
6
+ <AdminTemplate content={
7
+ (
8
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{ minHeight: '80vh' }}>
9
+ <Row>
10
+ <Col xs={12}>
11
+ <h1>This is a demo menu page</h1>
12
+ </Col>
13
+ <Col xs={12}>
14
+ <h3>In the future, the menu should be retrieve from master-list, and branch admins can decide which item is available at this branch</h3>
15
+ </Col>
16
+ </Row>
17
+ </Container>
18
+ )
19
+ } />
20
+ );
21
+ }
frontend/src/pages/AdminOrderPage.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from "react-bootstrap";
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+
4
+ export default function AdminOrderPage() {
5
+ return (
6
+ <AdminTemplate content={
7
+ (
8
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{ minHeight: '80vh' }}>
9
+ <Row>
10
+ <Col xs={12}>
11
+ <h1>This is a demo orders page</h1>
12
+ </Col>
13
+ <Col xs={12}>
14
+ <h3>In the future, we hope to view orders daily/monthly on this page, with search and filters</h3>
15
+ </Col>
16
+ </Row>
17
+ </Container>
18
+ )
19
+ } />
20
+ );
21
+ }
frontend/src/pages/AdminSchedulePage.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from "react-bootstrap";
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+
4
+ export default function AdminSchedulePage() {
5
+ return (
6
+ <AdminTemplate content={
7
+ (
8
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{ minHeight: '80vh' }}>
9
+ <Row>
10
+ <Col xs={12}>
11
+ <h1>This is a demo schedule page</h1>
12
+ </Col>
13
+ <Col xs={12}>
14
+ <h3>In the future, we will view monthly work schedules from this page</h3>
15
+ </Col>
16
+ </Row>
17
+ </Container>
18
+ )
19
+ } />
20
+ );
21
+ }
frontend/src/pages/AdminStaffPage.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from "react-bootstrap";
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+
4
+ export default function AdminStaffPage() {
5
+ return (
6
+ <AdminTemplate content={
7
+ (
8
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{minHeight: '80vh' }}>
9
+ <Row>
10
+ <Col xs={12}>
11
+ <h1>This is a demo staff page</h1>
12
+ </Col>
13
+ <Col xs={12}>
14
+ <h3>In the future, we are about to view staffs info, add, edit, and remove staffs in this page</h3>
15
+ </Col>
16
+ </Row>
17
+ </Container>
18
+ )
19
+ } />
20
+ );
21
+ }
frontend/src/pages/AdminSummaryPage.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Container, Row, Col } from "react-bootstrap";
2
+ import AdminTemplate from "../templates/AdminTemplate";
3
+ import { useNavigate } from "react-router-dom";
4
+ import { useEffect } from "react";
5
+ import DataStorage from "../organisms/DataStorage";
6
+
7
+ export default function AdminSummaryPage() {
8
+
9
+ const navigate = useNavigate();
10
+
11
+ useEffect(() => {
12
+ if (!DataStorage.get('isLoggedInAdmin')) {
13
+ navigate('/admin-login');
14
+ }
15
+ }, [navigate]);
16
+
17
+ return (
18
+ <AdminTemplate content={
19
+ (
20
+ <Container className='d-flex text-center align-items-center justify-content-center' style={{ minHeight: '80vh' }}>
21
+ <Row>
22
+ <Col xs={12}>
23
+ <h1>This is a demo summary page</h1>
24
+ </Col>
25
+ <Col xs={12}>
26
+ <h3>In the future, we hope to connect to PowerBI API and show the dashboard</h3>
27
+ </Col>
28
+ </Row>
29
+ </Container>
30
+ )
31
+ } />
32
+ );
33
+ }
frontend/src/pages/AdminUserInfoPage.js ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import AdminTemplate from "../templates/AdminTemplate";
2
+ import { Container, Form, Row, Col, Card, Alert, Button, Image } from "react-bootstrap";
3
+ import React, { useState, useEffect } from "react";
4
+ import validator from "validator";
5
+ import axios from "axios";
6
+ import DataStorage from "../organisms/DataStorage";
7
+
8
+ export default function AdminUserInfoPage() {
9
+
10
+ // fetch data:
11
+
12
+ const [error, setErrors] = useState("");
13
+ const [initialData, setInitialData] = useState(null);
14
+ const [formData, setFormData] = useState({
15
+ avatar: "/default_avatar.jpg",
16
+ full_name: "",
17
+ phone_number: "",
18
+ address: "",
19
+ email: "",
20
+ password: "",
21
+ confirmPassword: "",
22
+ });
23
+
24
+ useEffect(() => {
25
+ axios
26
+ .get(process.env.REACT_APP_API_URL + '/authentication/profile', {
27
+ headers: {
28
+ Authorization: `Bearer ${DataStorage.get('accessToken')}`,
29
+ },
30
+ })
31
+ .then((response) => {
32
+ if (response.status === 401) {
33
+ // phiên hết hạn
34
+ setErrors(JSON.stringify(response));
35
+ } else {
36
+ const { avatar, full_name, address, phone_number, email } = response.data;
37
+ const fetchedData = {
38
+ avatar: avatar || "/default_avatar.jpg",
39
+ full_name: full_name || "",
40
+ phone_number: phone_number || "",
41
+ address: address || "",
42
+ email: email || "",
43
+ password: "",
44
+ confirmPassword: "",
45
+ };
46
+
47
+ if (fetchedData.full_name !== DataStorage.get('username')) DataStorage.set('username', fetchedData.full_name)
48
+ setInitialData(fetchedData);
49
+ setFormData(fetchedData);
50
+ }
51
+ })
52
+ .catch((error) => {
53
+ setErrors(JSON.stringify(error));
54
+ });
55
+ }, []);
56
+
57
+ // console.log('formdata', formData);
58
+ // console.log('initialdata', initialData);
59
+ // State để hiển thị lỗi khi validation
60
+
61
+ const [isChanged, setChanged] = useState(false);
62
+
63
+ const handleChange = (e) => {
64
+ const { name, value } = e.target;
65
+ setFormData({ ...formData, [name]: value });
66
+ };
67
+
68
+ // Hàm xử lý thay đổi avatar
69
+ const handleAvatarChange = (e) => {
70
+ const file = e.target.files[0];
71
+ if (file) {
72
+ const reader = new FileReader();
73
+ reader.onloadend = () => {
74
+ setFormData((prevData) => ({
75
+ ...prevData,
76
+ avatar: reader.result // Cập nhật ảnh đại diện
77
+ }));
78
+ };
79
+ reader.readAsDataURL(file);
80
+ setChanged(true);
81
+ }
82
+ };
83
+
84
+ useEffect(() => {
85
+ const hasChanges = () => {
86
+ if (initialData) {
87
+ for (let key in formData) {
88
+ if (formData[key] !== initialData[key]) {
89
+ return true;
90
+ }
91
+ }
92
+ return false;
93
+ } else {
94
+ return false;
95
+ }
96
+ };
97
+ setChanged(hasChanges());
98
+ }, [formData, initialData]);
99
+
100
+ const handleSubmit = async (e) => {
101
+ e.preventDefault();
102
+ setChanged(false); // prevent another click
103
+ if (formData.phone_number.trim() !== "" && !validator.isMobilePhone(formData.phone_number, 'vi-VN')) setErrors("Số điện thoại không hợp lệ");
104
+ else if (formData.email.trim() !== "" && !validator.isEmail(formData.email)) setErrors("Email không hợp lệ");
105
+ else if (formData.password !== "" && formData.confirmPassword !== formData.password) setErrors("Mật khẩu không khớp");
106
+ else {
107
+
108
+ // let avatarBase64 = null;
109
+
110
+ // if (formData.avatar) {
111
+ // // Chuyển ảnh đại diện thành chuỗi Base64
112
+ // avatarBase64 = await new Promise((resolve, reject) => {
113
+ // const reader = new FileReader();
114
+ // reader.onloadend = () => resolve(reader.result.split(",")[1]); // Chỉ lấy phần Base64
115
+ // reader.onerror = (error) => reject(error);
116
+ // reader.readAsDataURL(formData.avatar);
117
+ // });
118
+ // }
119
+
120
+ let data = {};
121
+
122
+ // if (avatarBase64) data.avatar = avatarBase64;
123
+ if (formData.full_name.trim() !== "" && formData.full_name.trim() !== initialData.full_name) data.full_name = formData.full_name.trim();
124
+ if (formData.phone_number.trim() !== "" && formData.phone_number.trim() !== initialData.phone_number) data.phone_number = formData.phone_number.trim();
125
+ if (formData.address.trim() !== "" && formData.address.trim() !== initialData.address) data.address = formData.address.trim();
126
+ if (formData.email.trim() !== "" && formData.email.trim() !== initialData.email) data.email = formData.email.trim();
127
+ if (formData.password.trim() !== "") data.hash_password = formData.password;
128
+
129
+ // gửi request ở đây
130
+ axios.post(process.env.REACT_APP_API_URL + '/users/updateUser', data, {
131
+ headers: {
132
+ Authorization: `Bearer ${DataStorage.get('accessToken')}`,
133
+ }
134
+ }).then((response) => {
135
+ if (response.status === 200 || response.status === 201) {
136
+ window.location.reload(); // cập nhật lại thông tin
137
+ } else {
138
+ setErrors(JSON.stringify(response));
139
+ }
140
+ }).catch((error) => setErrors(JSON.stringify(error)));
141
+ }
142
+ };
143
+
144
+ return <AdminTemplate content={
145
+ (
146
+ <Container fluid className="d-flex align-items-center justify-content-center mt-5">
147
+ <Row>
148
+ <Col xs={10} md={8}>
149
+ <Card style={{ width: '30rem' }} className='justify-content-center'>
150
+ <Card.Header>
151
+ <Card.Title className='mt-1 text-center'>Thông tin khách hàng</Card.Title>
152
+ </Card.Header>
153
+ <Card.Body>
154
+
155
+ <Form onSubmit={handleSubmit}>
156
+ {error && <Alert variant="danger">{error}</Alert>}
157
+ <Row className="mb-3">
158
+ <Col xs={12} md={6} className="text-center">
159
+ {/*Image*/}
160
+ <Image
161
+ src={formData.avatar} // Hiển thị ảnh đại diện từ formData
162
+ alt="User Avatar"
163
+ roundedCircle
164
+ width={120}
165
+ height={120}
166
+ />
167
+ <Form.Group controlId="formAvatar">
168
+ <Form.Label>Ảnh đại diện</Form.Label>
169
+ <Form.Control type="file" accept="image/*" onChange={handleAvatarChange} />
170
+ </Form.Group>
171
+ </Col>
172
+ <Col xs={12} md={6}>
173
+ <Form.Group controlId="formUsername">
174
+ <Form.Label>Họ tên người dùng</Form.Label>
175
+ <Form.Control
176
+ type="text"
177
+ name="full_name"
178
+ placeholder="Họ tên người dùng"
179
+ value={formData.full_name}
180
+ onChange={handleChange}
181
+ />
182
+ </Form.Group>
183
+ </Col>
184
+ </Row>
185
+
186
+ <Form.Group controlId="formPhone" className="mb-3">
187
+ <Form.Label>Số điện thoại</Form.Label>
188
+ <Form.Control
189
+ type="text"
190
+ name="phone_number"
191
+ placeholder="Số điện thoại"
192
+ value={formData.phone_number}
193
+ onChange={handleChange}
194
+ />
195
+ </Form.Group>
196
+
197
+ <Form.Group controlId="formAddress" className="mb-3">
198
+ <Form.Label>Địa chỉ giao hàng mặc định</Form.Label>
199
+ <Form.Control
200
+ type="text"
201
+ name="address"
202
+ placeholder="Địa chỉ giao hàng"
203
+ value={formData.address}
204
+ onChange={handleChange}
205
+ />
206
+ </Form.Group>
207
+
208
+ <Form.Group controlId="formEmail" className="mb-3">
209
+ <Form.Label>Email</Form.Label>
210
+ <Form.Control
211
+ type="text"
212
+ name="email"
213
+ placeholder="Email"
214
+ value={formData.email}
215
+ onChange={handleChange}
216
+ />
217
+ </Form.Group>
218
+
219
+ <Form.Group controlId="formPassword" className="mb-3">
220
+ <Form.Label>Password</Form.Label>
221
+ <Form.Control
222
+ type="password"
223
+ name="password"
224
+ placeholder="********"
225
+ value={formData.password}
226
+ onChange={handleChange}
227
+ />
228
+ </Form.Group>
229
+
230
+ <Form.Group controlId="formConfirmPassword" className="mb-3">
231
+ <Form.Label>Nhập lại Password</Form.Label>
232
+ <Form.Control
233
+ type="password"
234
+ name="confirmPassword"
235
+ placeholder="********"
236
+ value={formData.confirmPassword}
237
+ onChange={handleChange}
238
+ />
239
+ </Form.Group>
240
+
241
+ <Button variant="primary" type="submit" disabled={!isChanged}>
242
+ Cập nhật
243
+ </Button>
244
+ </Form>
245
+ </Card.Body>
246
+ </Card>
247
+
248
+ </Col>
249
+ </Row>
250
+
251
+
252
+ </Container>
253
+ )
254
+ } />
255
+ }
frontend/src/pages/CartPage.js CHANGED
@@ -12,29 +12,27 @@ export default function CartPage() {
12
  const cart = JSON.parse(DataStorage.get('cart')) || {};
13
 
14
  // Chuyển cart thành mảng chứa các món có số lượng > 0
15
- const items = Object.entries(cart)
16
- .filter(([name, amount]) => amount > 0)
17
- .map(([name, amount]) => ({
18
- name,
19
- imageSrc: '/placeholder3.jpg',
20
- price: 100, // Thêm đơn giá của món ăn (giả sử ở đây là 100 cho mỗi món)
21
- amount
22
- }));
23
-
24
  setCartItems(items);
25
  }, []);
26
 
27
-
28
  return (
29
  <BasicTemplate content={
30
  (
31
- <Container className="d-flex align-items-center justify-content-center my-5" style={{ 'min-height': '70vh' }}>
32
  {cartItems.length > 0 ? (
33
  <div className="text-center">
34
  <h2 className="text-center mb-4">Giỏ hàng của bạn</h2>
35
  <Row className="g-3">
36
  {cartItems.map((item, idx) => (
37
- <Col md={12} key={idx} className="my-3">
38
  <Card className="shadow-sm" style={{ display: 'flex', flexDirection: 'row' }}>
39
  <Card.Img
40
  variant="left"
@@ -42,22 +40,30 @@ export default function CartPage() {
42
  style={{ width: '150px', objectFit: 'cover' }}
43
  />
44
  <Card.Body>
45
- <Row xs={4} className="align-items-center justify-content-center">
46
- <Card.Title as='Col'>{item.name}</Card.Title>
47
- <Card.Text as='Col'>Đơn giá: {item.price} VND</Card.Text>
48
- <Card.Text as='Col'>Số lượng: {item.amount}</Card.Text>
49
- <Card.Text as='Col'>
50
- Tổng cộng: {item.price * item.amount} VND
51
- </Card.Text>
 
 
 
 
 
 
52
  </Row>
53
  </Card.Body>
54
  </Card>
55
  </Col>
56
  ))}
57
  </Row>
58
- <Button as='a' href='/payment' className='my-3'>
59
  Thanh toán
60
  </Button>
 
 
61
  </div>
62
  ) : (
63
  <div className="text-center">
 
12
  const cart = JSON.parse(DataStorage.get('cart')) || {};
13
 
14
  // Chuyển cart thành mảng chứa các món có số lượng > 0
15
+ const items = Object.entries(cart).map(([_, item]) => ({
16
+ name: item.name,
17
+ amount: item.amount,
18
+ imageSrc: item.imageSrc,
19
+ price: item.price
20
+ }));
21
+
22
+ console.log(items);
 
23
  setCartItems(items);
24
  }, []);
25
 
 
26
  return (
27
  <BasicTemplate content={
28
  (
29
+ <Container className="d-flex align-items-center justify-content-center my-5" style={{ minHeight: '70vh' }}>
30
  {cartItems.length > 0 ? (
31
  <div className="text-center">
32
  <h2 className="text-center mb-4">Giỏ hàng của bạn</h2>
33
  <Row className="g-3">
34
  {cartItems.map((item, idx) => (
35
+ <Col md={12} key={idx} className="m-3">
36
  <Card className="shadow-sm" style={{ display: 'flex', flexDirection: 'row' }}>
37
  <Card.Img
38
  variant="left"
 
40
  style={{ width: '150px', objectFit: 'cover' }}
41
  />
42
  <Card.Body>
43
+ <Row xs={4} >
44
+ <Col className="d-flex align-items-center justify-content-center">
45
+ <Card.Title>{item.name}</Card.Title>
46
+ </Col>
47
+ <Col className="d-flex align-items-center justify-content-center">
48
+ <Card.Text>Đơn giá: {item.price} VND</Card.Text>
49
+ </Col>
50
+ <Col className="d-flex align-items-center justify-content-center">
51
+ <Card.Text>Số lượng: {item.amount}</Card.Text>
52
+ </Col>
53
+ <Col className="d-flex align-items-center justify-content-center">
54
+ <Card.Text>Tổng cộng: {item.price * item.amount} VND</Card.Text>
55
+ </Col>
56
  </Row>
57
  </Card.Body>
58
  </Card>
59
  </Col>
60
  ))}
61
  </Row>
62
+ <Button as='a' href='/payment' className='m-3'>
63
  Thanh toán
64
  </Button>
65
+ <Button as='a' href='/menu' className="m-3">
66
+ Quay lại menu</Button>
67
  </div>
68
  ) : (
69
  <div className="text-center">
frontend/src/pages/LoginPage.js CHANGED
@@ -51,11 +51,12 @@ export default function LoginPage() {
51
  }
52
  })
53
  .catch((error) => {
54
- if (error.status === 400) {
55
- setError('Lỗi đăng nhập, vui lòng kiểm tra lại tài khoản và mật khẩu');
56
- } else if (error.status === 500) {
57
- setError('Server đang tạm gặp vấn đề');
58
- }
 
59
  })
60
 
61
  }
@@ -63,11 +64,21 @@ export default function LoginPage() {
63
 
64
  return (
65
  <BasicTemplate content={
66
- (<Container fluid className="d-flex justify-content-center mt-5">
67
- <Row>
68
- <Col xs={1} md={2}></Col>
 
 
 
 
 
 
 
 
 
 
69
  <Col xs={10} md={8}>
70
- <Card style={{ width: '30rem' }} className='justify-content-center'>
71
  <Card.Header>
72
  <Card.Title className='mt-1 text-center'>Đăng nhập</Card.Title>
73
  </Card.Header>
@@ -76,15 +87,15 @@ export default function LoginPage() {
76
  {error && <Alert variant="danger">{error}</Alert>}
77
 
78
  <Form.Group controlId="username" className='mb-3'>
79
- <Form.Label>Tên đăng nhập</Form.Label>
80
  <Form.Control
81
  type="text"
82
- placeholder="Tên đăng nhập"
83
  onChange={(e) => setUsername(e.target.value)}
84
  />
85
  </Form.Group>
86
 
87
- <Form.Group controlId="password" className='mb-3'>
88
  <Form.Label>Mật khẩu</Form.Label>
89
  <Form.Control
90
  type="password"
@@ -92,10 +103,10 @@ export default function LoginPage() {
92
  onChange={(e) => setPassword(e.target.value)}
93
  />
94
  </Form.Group>
95
- <div className='d-flex justify-content-between align-items-center'>
96
- <a href="/register" className='me-2'>Đăng ký</a>
97
- <Button type="submit" className='me-2'>Đăng nhập</Button>
98
- <a href="/forgot-password" className='me-2'>Quên mật khẩu</a>
99
  </div>
100
 
101
  </Form>
@@ -103,7 +114,7 @@ export default function LoginPage() {
103
  </Card>
104
 
105
  </Col>
106
- <Col xs={1} md={2}></Col>
107
  </Row>
108
  </Container>)
109
  }>
 
51
  }
52
  })
53
  .catch((error) => {
54
+ // if (error.status === 400) {
55
+ // setError('Lỗi đăng nhập, vui lòng kiểm tra lại tài khoản và mật khẩu');
56
+ // } else if (error.status === 500) {
57
+ // setError('Server đang tạm gặp vấn đề');
58
+ // }
59
+ setError(JSON.stringify(error));
60
  })
61
 
62
  }
 
64
 
65
  return (
66
  <BasicTemplate content={
67
+ (<Container fluid className="d-flex justify-content-center align-items-center mt-5"
68
+ style={{
69
+ maxWidth: "100%",
70
+ minHeight: "70vh",
71
+ backgroundImage: "linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('/about-us.jpg')", // Thay đường dẫn ảnh nền
72
+ backgroundSize: 'cover',
73
+ backgroundPosition: 'center',
74
+ backgroundAttachment: 'fixed',
75
+ width: '100%',
76
+ padding: '50px 0',
77
+ }}>
78
+ <Row style={{maxWidth:"90vw"}}>
79
+ {/* <Col xs={1} md={2}></Col> */}
80
  <Col xs={10} md={8}>
81
+ <Card style={{ width: '35vw' }} className='justify-content-center card-nospan'>
82
  <Card.Header>
83
  <Card.Title className='mt-1 text-center'>Đăng nhập</Card.Title>
84
  </Card.Header>
 
87
  {error && <Alert variant="danger">{error}</Alert>}
88
 
89
  <Form.Group controlId="username" className='mb-3'>
90
+ <Form.Label>Email/Số điện thoại</Form.Label>
91
  <Form.Control
92
  type="text"
93
+ placeholder="Email/số điện thoại"
94
  onChange={(e) => setUsername(e.target.value)}
95
  />
96
  </Form.Group>
97
 
98
+ <Form.Group controlId="password" className='mb-4'>
99
  <Form.Label>Mật khẩu</Form.Label>
100
  <Form.Control
101
  type="password"
 
103
  onChange={(e) => setPassword(e.target.value)}
104
  />
105
  </Form.Group>
106
+ <div className='d-flex justify-content-center align-items-center'>
107
+ <Button as='a' variant='outline-primary' href="/register" className='m-2'>Đăng ký</Button>
108
+ <Button type="submit" className='m-2'>Đăng nhập</Button>
109
+ <Button as='a' variant='outline-primary' href="/forgot-password" className='m-2'>Quên mật khẩu</Button>
110
  </div>
111
 
112
  </Form>
 
114
  </Card>
115
 
116
  </Col>
117
+ {/* <Col xs={1} md={2}></Col> */}
118
  </Row>
119
  </Container>)
120
  }>
frontend/src/pages/MenuPage.js CHANGED
@@ -1,26 +1,43 @@
1
- import { useState } from 'react';
2
  import { Modal, Button, Container, Row, Col, Tab, Tabs, Form, InputGroup } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
  import BasicTemplate from '../templates/BasicTemplate';
5
  import DataStorage from '../organisms/DataStorage';
 
 
 
 
 
 
 
 
6
 
7
  function MenuPage() {
8
- const [key, setKey] = useState('cat1');
9
  const [selectedDish, setSelectedDish] = useState(null);
10
  const [show, setShow] = useState(false);
11
  const [cartAmount, setCartAmount] = useState(0);
 
 
12
 
13
  function handleClose() {
14
  const cart = JSON.parse(DataStorage.get('cart')) || {}; // Đảm bảo cart không null
15
- cart[selectedDish.name] = cartAmount;
16
- const filteredCart = Object.fromEntries(
17
- Object.entries(cart).filter(([key, value]) => value > 0)
18
- );
19
- DataStorage.set('cart', JSON.stringify(filteredCart));
20
- // console.log(JSON.stringify(filteredCart));
21
- setShow(false);
22
- }
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  function notLoggedInClose() {
25
  setShow(false);
26
  }
@@ -36,7 +53,7 @@ function MenuPage() {
36
  const cart = JSON.parse(DataStorage.get('cart')) || {};
37
 
38
  // Kiểm tra số lượng món ăn trong cart
39
- const amount = cart[dish.name] !== undefined ? cart[dish.name] : 0;
40
  setCartAmount(amount); // Cập nhật số lượng
41
  setShow(true); // Hiển thị modal
42
  }
@@ -52,27 +69,35 @@ function MenuPage() {
52
  }
53
  }
54
 
55
- const menuItems1 = [
56
- { name: 'Món 1 thể loại 1', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
57
- { name: 'Món 2 thể loại 1', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
58
- { name: 'Món 3 thể loại 1', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
59
- ];
60
-
61
- const menuItems2 = [
62
- { name: 'Món 1 thể loại 2', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
63
- { name: 'Món 2 thể loại 2', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
64
- { name: 'Món 3 thể loại 2', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' },
65
- { name: 'Món 3 thể loại 2', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' },
66
- ];
67
-
68
- const menuItems3 = [
69
- { name: 'Món 1 thể loại 3', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
70
- { name: 'Món 2 thể loại 3', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
71
- { name: 'Món 3 thể loại 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' },
72
- { name: 'Món 4 thể loại 3', description: 'Mô tả món 1', imageSrc: '/placeholder3.jpg' },
73
- { name: 'Món 5 thể loại 3', description: 'Mô tả món 2', imageSrc: '/placeholder3.jpg' },
74
- { name: 'Món 6 thể loại 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
75
- ];
 
 
 
 
 
 
 
 
76
 
77
  let modalContent;
78
 
@@ -86,37 +111,40 @@ function MenuPage() {
86
  Vui lòng đăng nhập để xem chi tiết món và đặt hàng
87
  </Modal.Body>
88
  <Modal.Footer>
89
- <Button variant="primary" as='a' href='/login'>
90
- Đăng nhập
91
- </Button>
92
- <Button variant="secondary" onClick={notLoggedInClose}>
93
- Đóng
94
- </Button>
95
- </Modal.Footer>
96
  </Modal>
97
  )
98
  }
99
  else {
100
  modalContent = (<Modal show={show} onHide={handleClose} className='text-center'>
101
  <Modal.Header closeButton className='text-center'>
102
- <Modal.Title >{selectedDish?.name}</Modal.Title> {/* Dish name in the title */}
103
  </Modal.Header>
104
  <Modal.Body>
105
- <img src={selectedDish?.imageSrc} alt={selectedDish?.name} style={{ width: '100%' }} /> {/* Dish image */}
106
  <p>{selectedDish?.description}</p> {/* Dish description */}
107
 
108
  <Row className='mb-5'>
109
  <Col md={2}></Col>
110
  <Col md={8}>
111
  <Form.Label>Số lượng</Form.Label>
112
- <InputGroup as='row' mb={4} className='mb-3'>
113
- <InputGroup.Text as='button' onClick={setDecrease}>-</InputGroup.Text>
114
- <Form.Control
115
- value={cartAmount}
116
- aria-label="Amount"
117
- onChange={(e) => setCartAmount(e.target.value)} />
118
- <InputGroup.Text as='button' onClick={setIncrease}>+</InputGroup.Text>
119
- </InputGroup></Col>
 
 
 
120
  <Col md={2}></Col>
121
  </Row>
122
 
@@ -129,8 +157,41 @@ function MenuPage() {
129
  </Modal>);
130
  }
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  return <BasicTemplate content={(
133
- <Container fluid className='my-5'>
134
  <>
135
  {modalContent}
136
  </>
@@ -138,65 +199,7 @@ function MenuPage() {
138
  <Row>
139
  <Col xs={1} md={2}></Col>
140
  <Col xs={10} md={8}>
141
- <Tabs
142
- id="controlled-tab-example"
143
- activeKey={key}
144
- onSelect={(k) => setKey(k)}
145
- className="mb-3"
146
- >
147
- <Tab eventKey="cat1" title="Thể loại 1">
148
- <Container fluid className='my-5'>
149
- <Row md={3} className="g-4">
150
- {menuItems1.map((item, idx) => (
151
- <Col key={idx} >
152
- <div onClick={() => handleShow(item)}>
153
- <MenuItem
154
- dishName={item.name}
155
- description={item.description}
156
- imageSrc={item.imageSrc}
157
- />
158
- </div>
159
-
160
- </Col>
161
- ))}
162
- </Row>
163
- </Container>
164
- </Tab>
165
- <Tab eventKey="cat2" title="Thể loại 2">
166
- <Container fluid className='my-5'>
167
- <Row md={3} className="g-4">
168
- {menuItems2.map((item, idx) => (
169
- <Col key={idx}>
170
- <div onClick={() => handleShow(item)}>
171
- <MenuItem
172
- dishName={item.name}
173
- description={item.description}
174
- imageSrc={item.imageSrc}
175
- />
176
- </div>
177
- </Col>
178
- ))}
179
- </Row>
180
- </Container>
181
- </Tab>
182
- <Tab eventKey="cat3" title="Thể loại 3">
183
- <Container fluid className='my-5'>
184
- <Row md={3} className="g-4">
185
- {menuItems3.map((item, idx) => (
186
- <Col key={idx}>
187
- <div onClick={() => handleShow(item)}>
188
- <MenuItem
189
- dishName={item.name}
190
- description={item.description}
191
- imageSrc={item.imageSrc}
192
- />
193
- </div>
194
- </Col>
195
- ))}
196
- </Row>
197
- </Container>
198
- </Tab>
199
- </Tabs>
200
  </Col>
201
  <Col xs={1} md={2}></Col>
202
  </Row>
 
1
+ import { useState, useEffect } from 'react';
2
  import { Modal, Button, Container, Row, Col, Tab, Tabs, Form, InputGroup } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
  import BasicTemplate from '../templates/BasicTemplate';
5
  import DataStorage from '../organisms/DataStorage';
6
+ import CacheStorage from '../organisms/CacheStorage';
7
+ import axios from 'axios';
8
+
9
+ const categoryMapper = [
10
+ { itemType: 1, category: 'Món chính' },
11
+ { itemType: 2, category: 'Đồ uống' },
12
+ { itemType: 3, category: 'Tráng miệng' },
13
+ ];
14
 
15
  function MenuPage() {
16
+ const [key, setKey] = useState(0);
17
  const [selectedDish, setSelectedDish] = useState(null);
18
  const [show, setShow] = useState(false);
19
  const [cartAmount, setCartAmount] = useState(0);
20
+ const [loading, setLoading] = useState(true);
21
+ const [menuItems, setMenuItems] = useState({});
22
 
23
  function handleClose() {
24
  const cart = JSON.parse(DataStorage.get('cart')) || {}; // Đảm bảo cart không null
 
 
 
 
 
 
 
 
25
 
26
+ cart[selectedDish.id] = {
27
+ 'name':selectedDish.item_name,
28
+ 'amount': cartAmount,
29
+ 'imageSrc': selectedDish.image_url,
30
+ 'price': selectedDish.price
31
+ };
32
+ for (let id in cart) {
33
+ if (cart[id].amount === 0) {
34
+ delete cart[id];
35
+ }
36
+ DataStorage.set('cart', JSON.stringify(cart));
37
+ // console.log(JSON.stringify(filteredCart));
38
+ setShow(false);
39
+ }
40
+ }
41
  function notLoggedInClose() {
42
  setShow(false);
43
  }
 
53
  const cart = JSON.parse(DataStorage.get('cart')) || {};
54
 
55
  // Kiểm tra số lượng món ăn trong cart
56
+ const amount = cart[dish.id] !== undefined ? cart[dish.id] : 0;
57
  setCartAmount(amount); // Cập nhật số lượng
58
  setShow(true); // Hiển thị modal
59
  }
 
69
  }
70
  }
71
 
72
+ useEffect(() => {
73
+ const fetchMenuItems = async () => {
74
+ try {
75
+ const menuItemsByType = {};
76
+
77
+ for (const item of categoryMapper) {
78
+ // Gọi API để lấy món theo itemType
79
+ const response = await axios.get(process.env.REACT_APP_API_URL + `/menu-items?filter.item_type=${item.itemType}`);
80
+ // Lưu danh sách món theo itemType
81
+ menuItemsByType[item.itemType] = response.data.data;
82
+ }
83
+ console.log(menuItemsByType);
84
+ setMenuItems(menuItemsByType);
85
+ setLoading(false);
86
+ CacheStorage.set('menuItems',JSON.stringify(menuItemsByType));
87
+ } catch (error) {
88
+ console.error('Error fetching menu items:', error);
89
+ setLoading(false);
90
+ }
91
+ };
92
+
93
+ if (CacheStorage.get('menuItems')) {
94
+ setMenuItems(JSON.parse(CacheStorage.get('menuItems')));
95
+ setLoading(false);
96
+ } else {
97
+ fetchMenuItems();
98
+ }
99
+
100
+ }, []);
101
 
102
  let modalContent;
103
 
 
111
  Vui lòng đăng nhập để xem chi tiết món và đặt hàng
112
  </Modal.Body>
113
  <Modal.Footer>
114
+ <Button variant="primary" as='a' href='/login'>
115
+ Đăng nhập
116
+ </Button>
117
+ <Button variant="secondary" onClick={notLoggedInClose}>
118
+ Đóng
119
+ </Button>
120
+ </Modal.Footer>
121
  </Modal>
122
  )
123
  }
124
  else {
125
  modalContent = (<Modal show={show} onHide={handleClose} className='text-center'>
126
  <Modal.Header closeButton className='text-center'>
127
+ <Modal.Title >{selectedDish?.item_name}</Modal.Title> {/* Dish name in the title */}
128
  </Modal.Header>
129
  <Modal.Body>
130
+ <img src={selectedDish?.image_url} alt={selectedDish?.item_name} style={{ width: '100%' }} /> {/* Dish image */}
131
  <p>{selectedDish?.description}</p> {/* Dish description */}
132
 
133
  <Row className='mb-5'>
134
  <Col md={2}></Col>
135
  <Col md={8}>
136
  <Form.Label>Số lượng</Form.Label>
137
+ <Row>
138
+ <InputGroup mb={4} className='mb-3'>
139
+ <InputGroup.Text as='button' onClick={setDecrease}>-</InputGroup.Text>
140
+ <Form.Control
141
+ value={cartAmount}
142
+ aria-label="Amount"
143
+ onChange={(e) => setCartAmount(e.target.value)} />
144
+ <InputGroup.Text as='button' onClick={setIncrease}>+</InputGroup.Text>
145
+ </InputGroup>
146
+ </Row>
147
+ </Col>
148
  <Col md={2}></Col>
149
  </Row>
150
 
 
157
  </Modal>);
158
  }
159
 
160
+ let menuContent;
161
+
162
+ if (loading) {
163
+ menuContent = (<p>Đang tải thực đơn...</p>);
164
+ } else {
165
+ menuContent = (<Tabs
166
+ id="controlled-tab-example"
167
+ activeKey={key}
168
+ onSelect={(k) => setKey(k)}
169
+ className="mb-3 custom-tab"
170
+ >
171
+ {categoryMapper.map((category, index) => (
172
+ <Tab eventKey={index} title={category.category}>
173
+ <Container fluid className='my-5'>
174
+ <Row md={3} className="g-4">
175
+ {menuItems[category.itemType].map((item, idx) => (
176
+ <Col key={item.id}>
177
+ <div onClick={() => handleShow(item)} className="text-center">
178
+ <MenuItem
179
+ dishName={item.item_name}
180
+ description={item.description}
181
+ imageSrc={item.image_url}
182
+ />
183
+ </div>
184
+ </Col>
185
+ ))}
186
+ </Row>
187
+ </Container>
188
+ </Tab>
189
+ ))}
190
+ </Tabs>);
191
+ }
192
+
193
  return <BasicTemplate content={(
194
+ <Container fluid className='my-5' style={{ minHeight: '70vh' }}>
195
  <>
196
  {modalContent}
197
  </>
 
199
  <Row>
200
  <Col xs={1} md={2}></Col>
201
  <Col xs={10} md={8}>
202
+ {menuContent}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  </Col>
204
  <Col xs={1} md={2}></Col>
205
  </Row>
frontend/src/pages/NewsPage.js CHANGED
@@ -1,12 +1,22 @@
1
  import {Container, Row, Col} from "react-bootstrap";
2
  import BasicTemplate from "../templates/BasicTemplate";
 
 
3
 
4
- export default function NewsPage({newsTitle, newsContent, newsImageSrc}) {
5
 
6
- newsTitle = "This is a demo newstitle"
7
- newsContent = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
8
- newsImageSrc = "/placeholder3.jpg"
 
 
9
 
 
 
 
 
 
 
10
  return (
11
  <BasicTemplate content={
12
  (<Container id="about-us" className="my-5">
 
1
  import {Container, Row, Col} from "react-bootstrap";
2
  import BasicTemplate from "../templates/BasicTemplate";
3
+ import { useSearchParams } from 'react-router-dom';
4
+ import CacheStorage from "../organisms/CacheStorage";
5
 
6
+ export default function NewsPage() {
7
 
8
+
9
+ const [searchParams] = useSearchParams();
10
+ const newsId = Number(searchParams.get('id'));
11
+
12
+ const feedDetail = Array.from(JSON.parse(CacheStorage.get('feeds'))).filter((item) => item.id === newsId);
13
 
14
+
15
+
16
+ const newsTitle = feedDetail[0].title;
17
+ const newsContent = feedDetail[0].description;
18
+ const newsImageSrc = feedDetail[0].image_url;
19
+
20
  return (
21
  <BasicTemplate content={
22
  (<Container id="about-us" className="my-5">
frontend/src/pages/RegisterPage.js CHANGED
@@ -3,6 +3,9 @@ import validator from 'validator';
3
  import { useNavigate } from 'react-router-dom';
4
  import { Container, Form, Button, Alert, Row, Col, Card } from 'react-bootstrap';
5
  import BasicTemplate from '../templates/BasicTemplate';
 
 
 
6
 
7
  const RegisterPage = () => {
8
  const [full_name, setFullname] = useState('');
@@ -14,13 +17,15 @@ const RegisterPage = () => {
14
 
15
  const navigator = useNavigate();
16
 
 
 
17
  const handleSubmit = (e) => {
18
  e.preventDefault();
19
  // Validate password and confirm password match
20
  if (full_name.length === 0) {
21
  setError('Họ và tên không thể để trống');
22
  } else if (!validator.isMobilePhone(phone_number, 'vi-VN')) {
23
- setError('Số điện thoại không hợp lệ');
24
  } else if (!validator.isEmail(email)) {
25
  setError('Email không hợp lệ');
26
  } else if (password.length < 8) {
@@ -30,18 +35,61 @@ const RegisterPage = () => {
30
  } else {
31
  setError('');
32
  // gọi API đăng ký ở đây
33
- console.log('Đăng thành công:', {full_name, password });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  navigator('/');
35
  }
36
  };
37
 
38
  return (
39
  <BasicTemplate content={
40
- (<Container fluid className="d-flex justify-content-center mt-5">
41
- <Row>
42
- <Col xs={1} md={2}></Col>
 
 
 
 
 
 
 
 
 
 
43
  <Col xs={10} md={8}>
44
- <Card style={{ width: '30rem' }} className='justify-content-center'>
45
  <Card.Header>
46
  <Card.Title className='mt-1 text-center'>Đăng ký</Card.Title>
47
  </Card.Header>
@@ -57,7 +105,7 @@ const RegisterPage = () => {
57
  type="text"
58
  placeholder="Họ và tên"
59
  onChange={(e) => setFullname(e.target.value)}
60
-
61
  />
62
  </Form.Group>
63
 
@@ -68,7 +116,7 @@ const RegisterPage = () => {
68
  type="text"
69
  placeholder="Số điện thoại"
70
  onChange={(e) => setPhoneNumber(e.target.value)}
71
-
72
  />
73
  </Form.Group>
74
 
@@ -79,7 +127,7 @@ const RegisterPage = () => {
79
  type="text"
80
  placeholder="Email"
81
  onChange={(e) => setEmail(e.target.value)}
82
-
83
  />
84
  </Form.Group>
85
 
@@ -90,24 +138,24 @@ const RegisterPage = () => {
90
  type="password"
91
  placeholder="Nhập mật khẩu"
92
  onChange={(e) => setPassword(e.target.value)}
93
-
94
  />
95
  </Form.Group>
96
 
97
- <Form.Group controlId="confirm_password" className='mb-3'>
98
  <Form.Label>Xác nhận mật khẩu</Form.Label>
99
  <Form.Control
100
  type="password"
101
  placeholder="Xác nhận mật khẩu"
102
  onChange={(e) => setConfirmPassword(e.target.value)}
103
-
104
  />
105
  </Form.Group>
106
- <div className='d-flex justify-content-between align-items-center'>
107
- <Button variant="primary" type="submit" className='me-2'>
108
  Đăng ký
109
  </Button>
110
- <a href="/login" className='me-2'>Đăng nhập</a>
111
 
112
  </div>
113
 
@@ -116,7 +164,7 @@ const RegisterPage = () => {
116
  </Card>
117
 
118
  </Col>
119
- <Col xs={1} md={2}></Col>
120
  </Row>
121
  </Container>
122
  )
 
3
  import { useNavigate } from 'react-router-dom';
4
  import { Container, Form, Button, Alert, Row, Col, Card } from 'react-bootstrap';
5
  import BasicTemplate from '../templates/BasicTemplate';
6
+ import axios from 'axios';
7
+ import DataStorage from '../organisms/DataStorage';
8
+ import jwtDecoder from '../organisms/jwtDecoder';
9
 
10
  const RegisterPage = () => {
11
  const [full_name, setFullname] = useState('');
 
17
 
18
  const navigator = useNavigate();
19
 
20
+ const domain = process.env.REACT_APP_API_URL;
21
+
22
  const handleSubmit = (e) => {
23
  e.preventDefault();
24
  // Validate password and confirm password match
25
  if (full_name.length === 0) {
26
  setError('Họ và tên không thể để trống');
27
  } else if (!validator.isMobilePhone(phone_number, 'vi-VN')) {
28
+ setError('Số điện thoại không hợp lệ');
29
  } else if (!validator.isEmail(email)) {
30
  setError('Email không hợp lệ');
31
  } else if (password.length < 8) {
 
35
  } else {
36
  setError('');
37
  // gọi API đăng ký ở đây
38
+ let data = {
39
+ "email": email,
40
+ "phone_number": phone_number,
41
+ "full_name": full_name,
42
+ "password": password
43
+ }
44
+
45
+ axios.post(domain + '/authentication/signup', data)
46
+ .then((response) => {
47
+ if (response.status === 200 || response.status === 201) {
48
+ const decodedToken = jwtDecoder(response.data.access_token);
49
+ const full_name = decodedToken.payload.username;
50
+ const role = decodedToken.payload.roles;
51
+ const expiryTime = decodedToken.payload.exp;
52
+ DataStorage.set('expiryDate', expiryTime, { expiryDate: expiryTime });
53
+ DataStorage.set('username', full_name);
54
+ DataStorage.set('role', role);
55
+ DataStorage.set('isLoggedIn', 'true');
56
+ DataStorage.set('accessToken', response.data.access_token);
57
+ DataStorage.set('cart', '{}');
58
+ navigator('/');
59
+ } else {
60
+ setError(JSON.stringify(response));
61
+ }
62
+ })
63
+ .catch((error) => {
64
+ if (error.status === 400) {
65
+ setError('Lỗi đăng ký');
66
+ } else if (error.status === 500) {
67
+ setError('Server đang tạm gặp vấn đề');
68
+ }
69
+ })
70
+
71
+ console.log('Đăng ký thành công:', { full_name, password });
72
  navigator('/');
73
  }
74
  };
75
 
76
  return (
77
  <BasicTemplate content={
78
+ (<Container fluid className="d-flex justify-content-center align-items-center mt-5"
79
+ style={{
80
+ maxWidth: "100%",
81
+ minHeight: "70vh",
82
+ backgroundImage: "linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('/about-us.jpg')", // Thay đường dẫn ảnh nền
83
+ backgroundSize: 'cover',
84
+ backgroundPosition: 'center',
85
+ backgroundAttachment: 'fixed',
86
+ width: '100%',
87
+ padding: '50px 0',
88
+ }}>
89
+ <Row style={{maxWidth:"90vw"}}>
90
+ {/* <Col xs={1} md={2}></Col> */}
91
  <Col xs={10} md={8}>
92
+ <Card style={{ width: '35vw' }} className='d-flex justify-content-center card-nospan'>
93
  <Card.Header>
94
  <Card.Title className='mt-1 text-center'>Đăng ký</Card.Title>
95
  </Card.Header>
 
105
  type="text"
106
  placeholder="Họ và tên"
107
  onChange={(e) => setFullname(e.target.value)}
108
+
109
  />
110
  </Form.Group>
111
 
 
116
  type="text"
117
  placeholder="Số điện thoại"
118
  onChange={(e) => setPhoneNumber(e.target.value)}
119
+
120
  />
121
  </Form.Group>
122
 
 
127
  type="text"
128
  placeholder="Email"
129
  onChange={(e) => setEmail(e.target.value)}
130
+
131
  />
132
  </Form.Group>
133
 
 
138
  type="password"
139
  placeholder="Nhập mật khẩu"
140
  onChange={(e) => setPassword(e.target.value)}
141
+
142
  />
143
  </Form.Group>
144
 
145
+ <Form.Group controlId="confirm_password" className='mb-5'>
146
  <Form.Label>Xác nhận mật khẩu</Form.Label>
147
  <Form.Control
148
  type="password"
149
  placeholder="Xác nhận mật khẩu"
150
  onChange={(e) => setConfirmPassword(e.target.value)}
151
+
152
  />
153
  </Form.Group>
154
+ <div className='d-flex justify-content-center align-items-center text-center'>
155
+ <Button variant="primary" type="submit" className='mx-4'>
156
  Đăng ký
157
  </Button>
158
+ <Button as='a' variant='outline-primary' href="/login" className='mx-4'>Đăng nhập</Button>
159
 
160
  </div>
161
 
 
164
  </Card>
165
 
166
  </Col>
167
+ {/* <Col xs={1} md={2}></Col> */}
168
  </Row>
169
  </Container>
170
  )
frontend/src/styles/styles.css CHANGED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Thay đổi màu sắc chính của trang */
2
+ :root {
3
+ --primary-color: #300604;
4
+ /* Màu đỏ ấm, có thể chỉnh sửa */
5
+ --secondary-color: #b17219;
6
+ /* Màu cam nhạt */
7
+ --background-color: #222020;
8
+ /* Màu nền nhẹ nhàng */
9
+ --text-color: #fbf5f5;
10
+ --text-color-disabled: #706161;
11
+ /* Màu chữ chính */
12
+ --container-background-color: rgba(44, 41, 41, 0.5);
13
+ --fading-background-color: linear-gradient(to bottom,
14
+ rgba(0, 0, 0, 0) 0%,
15
+ /* Bắt đầu trong suốt */
16
+ rgba(44, 41, 41, 0.8) 30%,
17
+ /* Đậm dần */
18
+ rgba(44, 41, 41, 0.8) 70%,
19
+ /* Vùng màu đậm */
20
+ rgba(0, 0, 0, 0) 100%
21
+ /* Trở lại trong suốt */
22
+ );
23
+ --card-background-color: rgba(65, 52, 53, 0.8);
24
+ --modal-background-color: rgb(32, 29, 29);
25
+ }
26
+
27
+ /* Đặt nền cho toàn bộ trang */
28
+ body {
29
+ color: var(--text-color);
30
+ background-image: linear-gradient(rgba(20, 20, 20, 0.8), rgba(20, 20, 20, 0.95)), url('../../public/header.jpg');
31
+ background-color: var(--background-color);
32
+ /* Màu nền */
33
+ /* Hoặc sử dụng hình ảnh nền */
34
+ background-size: 95% auto;
35
+ /* Để hình ảnh phủ đầy toàn bộ trang */
36
+ background-repeat: no-repeat;
37
+ /* Để hình ảnh không lặp lại */
38
+ background-attachment: fixed;
39
+ /* Để nền cố định khi cuộn trang */
40
+ background-position: center;
41
+ /* Căn giữa hình ảnh */
42
+ }
43
+
44
+
45
+ /* Container */
46
+ .container {
47
+ padding-top: 20px;
48
+ padding-bottom: 20px;
49
+ background-color: var(--fading-background-color);
50
+ }
51
+
52
+ .custom-tab .nav-link {
53
+ background-color: var(--primary-color);
54
+ /* Màu nền cho các nút tab không được chọn */
55
+ color: var(--text-color);
56
+ /* Màu chữ cho các nút tab không được chọn */
57
+ }
58
+
59
+ .custom-tab .nav-link.active {
60
+ background-color: var(--secondary-color);
61
+ /* Màu nền cho tab đang được chọn */
62
+ color: var(--text-color);
63
+ /* Màu chữ cho tab đang được chọn */
64
+ }
65
+
66
+ .custom-tab .nav-link:hover {
67
+ background-color: var(--card-background-color);
68
+ /* Màu nền cho tab khi hover */
69
+ color: var(--text-color);
70
+ /* Màu chữ khi hover */
71
+ }
72
+
73
+ /* Navbar */
74
+ .navbar {
75
+ background-color: var(--primary-color) !important;
76
+ font-size: 1.1rem;
77
+ }
78
+
79
+ .navbar-brand,
80
+ .nav-link {
81
+ color: #fff !important;
82
+ font-weight: normal;
83
+ transition: color 0.3s;
84
+ }
85
+
86
+ .nav-link.disabled {
87
+ color: var(--text-color-disabled) !important;
88
+ }
89
+
90
+ .nav-link:hover {
91
+ color: var(--secondary-color) !important;
92
+ }
93
+
94
+ .navbar-toggler {
95
+ border-color: rgba(255, 255, 255, 0.5);
96
+ }
97
+
98
+ .navbar-light .navbar-toggler-icon {
99
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
100
+ }
101
+
102
+ /* Button */
103
+ .btn-primary {
104
+ background-color: var(--secondary-color);
105
+ border-color: var(--secondary-color);
106
+ color: #fff;
107
+ font-weight: bold;
108
+ transition: background-color 0.3s, color 0.3s;
109
+ min-width: 7vw;
110
+ }
111
+
112
+ .btn-primary:hover {
113
+ background-color: #e99e40;
114
+ border-color: #e99e40;
115
+ }
116
+
117
+ /* .btn-outline-primary {
118
+ color: var(--primary-color);
119
+ border-color: var(--primary-color);
120
+ font-weight: bold;
121
+ }
122
+
123
+ .btn-outline-primary:hover {
124
+ background-color: var(--primary-color);
125
+ color: #fff;
126
+ } */
127
+
128
+ /* Style cho nút secondary */
129
+ .btn-outline-primary {
130
+ background-color: rgba(from var(--background-color) r g b, 0.8);
131
+ /* Màu nền trắng */
132
+ color: var(--text-color);
133
+ /* Màu chữ đỏ */
134
+ font-weight: bold;
135
+ border-color: var(--secondary-color);
136
+ /* Màu viền đỏ */
137
+ min-width: 7vw;
138
+ }
139
+
140
+ .btn-outline-primary:hover {
141
+ background-color: #c2bdaf;
142
+ /* Màu nền nhạt hơn khi hover */
143
+ color: var(--primary-color);
144
+ /* Màu chữ đỏ khi hover */
145
+ border-color: var(--secondary-color);
146
+ /* Giữ màu viền đỏ khi hover */
147
+ }
148
+
149
+ /* Card */
150
+ .card {
151
+ background-color: var(--card-background-color);
152
+ border: none;
153
+ border-radius: 10px;
154
+ box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.3);
155
+ transition: transform 0.3s;
156
+ }
157
+
158
+ .card:hover {
159
+ transform: scale(1.02);
160
+ }
161
+
162
+ .card-img-top {
163
+ border-top-left-radius: 10px;
164
+ border-top-right-radius: 10px;
165
+ height: 200px;
166
+ /* Tùy chỉnh chiều cao ảnh */
167
+ object-fit: cover;
168
+ }
169
+
170
+ .card-title {
171
+ font-size: 1.25rem;
172
+ font-weight: bold;
173
+ color: var(--text-color);
174
+ }
175
+
176
+ .card-text {
177
+ color: var(--text-color);
178
+ }
179
+
180
+ .card-body {
181
+ min-height: 150px;
182
+ }
183
+
184
+ .card-footer {
185
+ background-color: #fff;
186
+ border-top: none;
187
+ text-align: center;
188
+ }
189
+
190
+ .card-nospan:hover {
191
+ transform: scale(1.00);
192
+ }
193
+
194
+ /* Container cho các phần */
195
+ .menu-section {
196
+ padding: 40px 0;
197
+ background-color: #fff;
198
+ }
199
+
200
+ .form-label {
201
+ color: var(--text-color);
202
+ }
203
+
204
+ a {
205
+ color: var(--text-color);
206
+ text-decoration: none;
207
+ }
208
+
209
+ a:hover {
210
+ color: orange;
211
+ /* Màu sắc khi di chuột qua */
212
+ }
213
+
214
+ /* Khi liên kết được nhấn */
215
+ a:active {
216
+ color: red;
217
+ /* Màu sắc khi nhấn */
218
+ }
219
+
220
+ /* Khi liên kết đã được truy cập */
221
+
222
+ .header-section {
223
+ padding: 60px 0;
224
+ background-image: url('../../public/header.jpg');
225
+ /* Thêm hình nền nếu có */
226
+ background-size: cover;
227
+ background-position: center;
228
+ /* color: #fff; */
229
+ text-align: center;
230
+ }
231
+
232
+ .header-section h1 {
233
+ font-size: 2.5rem;
234
+ font-weight: bold;
235
+ }
236
+
237
+ .header-section p {
238
+ font-size: 1.2rem;
239
+ margin-top: 10px;
240
+ max-width: 600px;
241
+ margin: auto;
242
+ color: #f1f1f1;
243
+ }
244
+
245
+ .modal-content {
246
+ background-color: var(--modal-background-color);
247
+ }
frontend/src/templates/AdminTemplate.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // pages/HomePage.js
2
+ // import React, { useState } from 'react';
3
+ import AdminNavbar from '../molecules/AdminNavBar';
4
+ function AdminTemplate ({content}) {
5
+ // const [isLoggedIn, setIsLoggedIn] = useState(false);
6
+ // const [user, setUser] = useState({ name: 'User' });
7
+
8
+ return (
9
+ <>
10
+ <AdminNavbar></AdminNavbar>
11
+ {content}
12
+ </>
13
+ );
14
+ };
15
+
16
+ export default AdminTemplate;
frontend/test.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "xoixomai",
4
+ "item_name": "Xôi xoài",
5
+ "image_url": "https://cdn.mediamart.vn/images/news/mo-lam-xoi-xoai-thai-lan-thom-beo-hp-dn-chun-huong-v_2107a3bc.jpg",
6
+ "item_type": 3,
7
+ "description": "Xôi xoài thái thơm ngon với nước cốt dừa",
8
+ "price": 35000,
9
+ "create_at": "2024-11-02T13:42:02.009Z"
10
+ },
11
+ {
12
+ "id": "trada",
13
+ "item_name": "Trà đá",
14
+ "image_url": "https://media-cdn-v2.laodong.vn/Storage/NewsPortal/2019/7/25/746291/Tra-Da.jpg",
15
+ "item_type": 2,
16
+ "description": "Trà đá giải khát, thích hợp cho mọi món ăn",
17
+ "price": 5000,
18
+ "create_at": "2024-11-02T13:42:02.009Z"
19
+ },
20
+ {
21
+ "id": "trachanh",
22
+ "item_name": "Trà chanh",
23
+ "image_url": "https://www.bartender.edu.vn/wp-content/uploads/2020/07/cach-pha-tra-chanh-khong-bi-dang.jpg",
24
+ "item_type": 2,
25
+ "description": "Trà chanh tươi mát với chút vị chua dịu",
26
+ "price": 10000,
27
+ "create_at": "2024-11-02T13:42:02.009Z"
28
+ },
29
+ {
30
+ "id": "suongsaohate",
31
+ "item_name": "Sương sáo hạt é",
32
+ "image_url": "https://dacsannanggio.vn/image/catalog/Hat-e/cach-lam-suong-sao-hat-e.jpg",
33
+ "item_type": 3,
34
+ "description": "Món tráng miệng thanh mát với sương sáo và hạt é",
35
+ "price": 15000,
36
+ "create_at": "2024-11-02T13:42:02.009Z"
37
+ },
38
+ {
39
+ "id": "sinhtoxoai",
40
+ "item_name": "Sinh tố xoài",
41
+ "image_url": "https://beptruong.edu.vn/wp-content/uploads/2016/02/sinh-to-xoai-sua-tuoi.jpg",
42
+ "item_type": 2,
43
+ "description": "Sinh tố xoài chua ngọt tự nhiên",
44
+ "price": 30000,
45
+ "create_at": "2024-11-02T13:42:02.009Z"
46
+ },
47
+ {
48
+ "id": "sinhtobo",
49
+ "item_name": "Sinh tố bơ",
50
+ "image_url": "https://caygiongbo.com/datafiles/3/2019-02-24/99271623-sinh-to-bo-de-quoc-bao-lau-trong-tu-lanh-1.jpg",
51
+ "item_type": 2,
52
+ "description": "Sinh tố bơ béo ngậy và thơm ngon",
53
+ "price": 30000,
54
+ "create_at": "2024-11-02T13:42:02.009Z"
55
+ },
56
+ {
57
+ "id": "pho",
58
+ "item_name": "Phở bò",
59
+ "image_url": "https://cdn.tgdd.vn/Files/2017/03/18/962092/an-lien-3-bat-pho-voi-cong-thuc-nau-pho-nay-202201261419401397.jpg",
60
+ "item_type": 1,
61
+ "description": "Món phở truyền thống với nước dùng thanh ngọt",
62
+ "price": 35000,
63
+ "create_at": "2024-11-02T13:42:02.009Z"
64
+ },
65
+ {
66
+ "id": "pepsi",
67
+ "item_name": "Pepsi",
68
+ "image_url": "https://t4.ftcdn.net/jpg/02/84/65/61/360_F_284656175_G6SlGTBVl4pg8oXh6jr86cOmKUZjfrym.jpg",
69
+ "item_type": 2,
70
+ "description": "Nước ngọt Pepsi",
71
+ "price": 20000,
72
+ "create_at": "2024-11-02T13:42:02.009Z"
73
+ },
74
+ {
75
+ "id": "nuocsam",
76
+ "item_name": "Nước sâm",
77
+ "image_url": "https://assets.gia-hanoi.com/nau-nuoc-sam-bi-dao.jpg",
78
+ "item_type": 2,
79
+ "description": "Nước sâm mát lạnh, tốt cho sức khỏe",
80
+ "price": 15000,
81
+ "create_at": "2024-11-02T13:42:02.009Z"
82
+ },
83
+ {
84
+ "id": "nuocepduoi",
85
+ "item_name": "Nước ép ổi",
86
+ "image_url": "https://douongnhapkhau.com/wp-content/uploads/2024/08/nuoc-ep-oi-co-tac-dung-gi-cach-lam-nuoc-ep-oi-2.jpg",
87
+ "item_type": 2,
88
+ "description": "Nước ép ổi thơm ngon, giàu vitamin C",
89
+ "price": 25000,
90
+ "create_at": "2024-11-02T13:42:02.009Z"
91
+ },
92
+ {
93
+ "id": "nuocdua",
94
+ "item_name": "Nước dừa",
95
+ "image_url": "https://storage-vnportal.vnpt.vn/ndh-ubnd/5893/1223/uong-nuoc-dua.jpg",
96
+ "item_type": 2,
97
+ "description": "Nước dừa tươi, mát và bổ dưỡng",
98
+ "price": 25000,
99
+ "create_at": "2024-11-02T13:42:02.009Z"
100
+ },
101
+ {
102
+ "id": "nuoccam",
103
+ "item_name": "Nước cam",
104
+ "image_url": "https://baodongnai.com.vn/file/e7837c02876411cd0187645a2551379f/022024/174_mh_20240228114325.jpg",
105
+ "item_type": 2,
106
+ "description": "Nước cam tươi, cung cấp vitamin C",
107
+ "price": 25000,
108
+ "create_at": "2024-11-02T13:42:02.009Z"
109
+ },
110
+ {
111
+ "id": "miquang",
112
+ "item_name": "Mì Quảng",
113
+ "image_url": "https://helenrecipes.com/wp-content/uploads/2021/05/Screenshot-2021-05-31-142423-1200x675.png",
114
+ "item_type": 1,
115
+ "description": "Mì Quảng đặc sản với nước dùng đậm đà",
116
+ "price": 28000,
117
+ "create_at": "2024-11-02T13:42:02.009Z"
118
+ },
119
+ {
120
+ "id": "kemdua",
121
+ "item_name": "Kem dừa",
122
+ "image_url": "https://cdn.tgdd.vn/Files/2020/03/25/1244397/cach-lam-kem-dua-thom-ngon-tai-nha-bang-may-xay-sinh-to-202003250909366047.jpg",
123
+ "item_type": 3,
124
+ "description": "Kem dừa mát lạnh với hương dừa tự nhiên",
125
+ "price": 30000,
126
+ "create_at": "2024-11-02T13:42:02.009Z"
127
+ },
128
+ {
129
+ "id": "goicuon",
130
+ "item_name": "Gỏi cuốn",
131
+ "image_url": "https://khaihoanphuquoc.com.vn/wp-content/uploads/2023/11/nu%CC%9Bo%CC%9B%CC%81c-ma%CC%86%CC%81m-cha%CC%82%CC%81m-go%CC%89i-cuo%CC%82%CC%81n-1200x923.png",
132
+ "item_type": 1,
133
+ "description": "Cuốn tươi ngon với rau, tôm, thịt và bún",
134
+ "price": 15000,
135
+ "create_at": "2024-11-02T13:42:02.009Z"
136
+ },
137
+ {
138
+ "id": "comtam",
139
+ "item_name": "Cơm tấm",
140
+ "image_url": "https://static.vinwonders.com/production/com-tam-da-nang-1.jpg",
141
+ "item_type": 1,
142
+ "description": "Cơm tấm sườn, bì, chả truyền thống",
143
+ "price": 35000,
144
+ "create_at": "2024-11-02T13:42:02.009Z"
145
+ },
146
+ {
147
+ "id": "comchien",
148
+ "item_name": "Cơm chiên dương châu",
149
+ "image_url": "https://nineshield.com.vn/wp-content/uploads/2024/03/com-chien-duong-chau-ngon.jpg",
150
+ "item_type": 1,
151
+ "description": "Cơm chiên với trứng, tôm, thịt nguội và rau củ",
152
+ "price": 35000,
153
+ "create_at": "2024-11-02T13:42:02.009Z"
154
+ },
155
+ {
156
+ "id": "cocacola",
157
+ "item_name": "Coca-Cola",
158
+ "image_url": "https://t4.ftcdn.net/jpg/02/84/65/61/360_F_284656117_sPF8gVWaX627bq5qKrlrvCz1eFfowdBf.jpg",
159
+ "item_type": 2,
160
+ "description": "Nước ngọt Coca-Cola",
161
+ "price": 20000,
162
+ "create_at": "2024-11-02T13:42:02.009Z"
163
+ },
164
+ {
165
+ "id": "chethotnot",
166
+ "item_name": "Chè thốt nốt",
167
+ "image_url": "https://i.vietgiaitri.com/2022/6/19/cach-nau-che-thot-not-thanh-mat-don-gian-tai-nha-5c0-6500541.jpg",
168
+ "item_type": 3,
169
+ "description": "Chè thốt nốt tươi mát và bổ dưỡng",
170
+ "price": 18000,
171
+ "create_at": "2024-11-02T13:42:02.009Z"
172
+ },
173
+ {
174
+ "id": "chekhucbach",
175
+ "item_name": "Chè khúc bạch",
176
+ "image_url": "https://cdn.tgdd.vn/2021/10/CookDishThumb/che-khuc-bach-la-gi-che-khuc-bach-lam-tu-gi-nguyen-lieu-lam-thumb-620x620.jpg",
177
+ "item_type": 3,
178
+ "description": "Chè khúc bạch thơm béo với hạnh nhân và trái cây",
179
+ "price": 30000,
180
+ "create_at": "2024-11-02T13:42:02.009Z"
181
+ }
182
+ ]