Trần Viết Sơn commited on
Commit
59748a1
·
unverified ·
2 Parent(s): 45dbd8e 3059320

Merge branch 'main' into feature/payment

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +1 -1
  2. README.md +1 -0
  3. backend/src/app.module.ts +5 -1
  4. backend/src/common/enums/MenuItemType.enum.ts +4 -4
  5. backend/src/common/enums/OrderStatus.enum.ts +7 -0
  6. backend/src/common/enums/OrderType.enum.ts +5 -0
  7. backend/src/common/enums/PaymentMethod.enum.ts +5 -0
  8. backend/src/common/enums/role.enum.ts +8 -8
  9. backend/src/entities/branch-menu.entity.ts +8 -2
  10. backend/src/entities/branch.entity.ts +44 -35
  11. backend/src/entities/feed.entity.ts +2 -2
  12. backend/src/entities/menu-item.entity.ts +4 -3
  13. backend/src/entities/order-item.entity.ts +37 -0
  14. backend/src/entities/order.entity.ts +70 -0
  15. backend/src/entities/payment.entity.ts +25 -0
  16. backend/src/entities/receipt.entity.ts +57 -0
  17. backend/src/entities/user.entity.ts +59 -54
  18. backend/src/migrations/1729963419864-enum-role.ts +0 -22
  19. backend/src/migrations/1730474673934-RefactorAll.ts +56 -0
  20. backend/src/migrations/1730547520878-AddReceipt.ts +30 -0
  21. backend/src/migrations/1730549959767-RemoveEnums.ts +36 -0
  22. backend/src/modules/branch-menus/branch-menus.controller.ts +55 -0
  23. backend/src/modules/branch-menus/branch-menus.module.ts +13 -0
  24. backend/src/modules/branch-menus/branch-menus.service.ts +72 -0
  25. backend/src/modules/branch-menus/dto/create-branch-menu.dto.ts +10 -0
  26. backend/src/modules/branch-menus/dto/update-branch-menu.dto.ts +4 -0
  27. backend/src/modules/branch/branch.controller.ts +4 -4
  28. backend/src/modules/branch/branch.module.ts +1 -0
  29. backend/src/modules/branch/branch.service.ts +11 -2
  30. backend/src/modules/branch/dto/create-branch.dto.ts +3 -0
  31. backend/src/modules/menu-item/dto/create-menu-item.dto.ts +2 -2
  32. backend/src/modules/menu-item/dto/update-menu-item.dto.ts +2 -2
  33. backend/src/modules/menu-item/menu-item.module.ts +1 -0
  34. backend/src/modules/order/dto/create-order.dto.ts +26 -0
  35. backend/src/modules/order/dto/order-items.dto.ts +9 -0
  36. backend/src/modules/order/order.controller.ts +66 -0
  37. backend/src/modules/order/order.module.ts +12 -0
  38. backend/src/modules/order/order.service.ts +137 -0
  39. frontend/.gitignore +4 -0
  40. frontend/package-lock.json +153 -0
  41. frontend/package.json +4 -0
  42. frontend/public/default_avatar.jpg +0 -0
  43. frontend/src/index.js +12 -0
  44. frontend/src/molecules/AdminNavBar.js +0 -0
  45. frontend/src/molecules/Navbar.js +14 -12
  46. frontend/src/organisms/DataStorage.js +91 -0
  47. frontend/src/organisms/MenuSection.js +4 -3
  48. frontend/src/organisms/jwtDecoder.js +22 -0
  49. frontend/src/pages/AdminHomePage.js +0 -0
  50. frontend/src/pages/CartPage.js +72 -0
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
  # Use an official Node.js runtime as a parent image
2
- FROM node:22-alpine
3
 
4
  # Set the working directory
5
  WORKDIR /app
 
1
  # Use an official Node.js runtime as a parent image
2
+ FROM node:22.4.0
3
 
4
  # Set the working directory
5
  WORKDIR /app
README.md CHANGED
@@ -14,3 +14,4 @@ app_port: 3000
14
 
15
  - [Trello](https://trello.com/w/pbl6hthngthongtin1)
16
  - [Google drive](https://drive.google.com/drive/folders/19qQ5Wn4uat0hSeDX3N5c0X19abYkQLfc?usp=drive_link)
 
 
14
 
15
  - [Trello](https://trello.com/w/pbl6hthngthongtin1)
16
  - [Google drive](https://drive.google.com/drive/folders/19qQ5Wn4uat0hSeDX3N5c0X19abYkQLfc?usp=drive_link)
17
+ - [API docs](https://hackmd.io/NCILuSy3Rxif-KRVQZjNIg#Menu-item)
backend/src/app.module.ts CHANGED
@@ -12,6 +12,8 @@ import { BranchModule } from './modules/branch/branch.module.js';
12
  import { AuthenticationModule } from './modules/authentication/authentication.module.js';
13
  import { MenuItemModule } from './modules/menu-item/menu-item.module.js';
14
  import { FeedsModule } from './modules/feeds/feeds.module.js';
 
 
15
  import { PaymentModule } from './payment/payment.module.js';
16
  @Module({
17
  imports: [
@@ -28,7 +30,9 @@ import { PaymentModule } from './payment/payment.module.js';
28
  AuthenticationModule,
29
  MenuItemModule,
30
  FeedsModule,
31
- PaymentModule,
 
 
32
  ],
33
  controllers: [AppController],
34
  providers: [AppService],
 
12
  import { AuthenticationModule } from './modules/authentication/authentication.module.js';
13
  import { MenuItemModule } from './modules/menu-item/menu-item.module.js';
14
  import { FeedsModule } from './modules/feeds/feeds.module.js';
15
+ import { OrderModule } from './modules/order/order.module.js';
16
+ import { BranchMenusModule } from './modules/branch-menus/branch-menus.module.js';
17
  import { PaymentModule } from './payment/payment.module.js';
18
  @Module({
19
  imports: [
 
30
  AuthenticationModule,
31
  MenuItemModule,
32
  FeedsModule,
33
+ OrderModule,
34
+ BranchMenusModule,
35
+ PaymentModule,
36
  ],
37
  controllers: [AppController],
38
  providers: [AppService],
backend/src/common/enums/MenuItemType.enum.ts CHANGED
@@ -1,6 +1,6 @@
1
  export enum MenuItemType {
2
- MON_CHINH = 'monchinh', // món chính
3
- TRANG_MIENG = 'trangmieng', // tráng miệng
4
- GIAI_KHAT = 'giaikhat', // giải khát
5
- KHAC = 'khac', // khác
6
  }
 
1
  export enum MenuItemType {
2
+ MON_CHINH = 1, // món chính
3
+ TRANG_MIENG = 2, // tráng miệng
4
+ GIAI_KHAT = 3, // giải khát
5
+ KHAC = 0, // khác
6
  }
backend/src/common/enums/OrderStatus.enum.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ export enum OrderStatus {
2
+ PENDING = 0, // KH đặt hàng đã thanh toán online, nhân viên chưa xác nhận <online>
3
+ CONFIRMED = 1, // nhân viên xác nhận và chuyển sang trạng thái <online>
4
+ PREPARING = 2, // nhân viên xác nhận và sang trạng thái preparing này ngay lập tức <online/offline>
5
+ DELIVERING = 3, // dang giao hàng <online>
6
+ DONE = 4, // <online, offline>
7
+ }
backend/src/common/enums/OrderType.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum OrderType {
2
+ TAKE_AWAY = 0,
3
+ OFFLINE = 1,
4
+ ONLINE = 2,
5
+ }
backend/src/common/enums/PaymentMethod.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum PaymentMethod {
2
+ CASH = 0,
3
+ CARD = 1,
4
+ ONLINE_PAYMENT = 2,
5
+ }
backend/src/common/enums/role.enum.ts CHANGED
@@ -1,8 +1,8 @@
1
- export enum Role {
2
- CUSTOMER = 'CUSTOMER',
3
- ADMIN = 'ADMIN',
4
- BRANCH_MANAGER = 'BRANCH_MANAGER',
5
- AREA_MANAGER = 'AREA_MANAGER',
6
- STAFF = 'STAFF',
7
- SHIPPER = 'SHIPPER'
8
- }
 
1
+ export enum Role {
2
+ CUSTOMER = 'CUSTOMER',
3
+ ADMIN = 'ADMIN',
4
+ BRANCH_MANAGER = 'BRANCH_MANAGER',
5
+ AREA_MANAGER = 'AREA_MANAGER',
6
+ STAFF = 'STAFF',
7
+ SHIPPER = 'SHIPPER',
8
+ }
backend/src/entities/branch-menu.entity.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
  BaseEntity,
3
  Column,
4
  Entity,
 
5
  ManyToOne,
6
  OneToMany,
7
  PrimaryGeneratedColumn,
@@ -21,15 +22,20 @@ export class BranchMenuEntity extends BaseEntity {
21
  @Column()
22
  menu_id: string;
23
 
24
- @Column()
25
  description: string;
26
 
27
- @Column()
28
  is_open: boolean;
29
 
 
 
 
30
  @ManyToOne(() => BranchEntity, (a) => a.menu_items)
 
31
  branch: Relation<BranchEntity>;
32
 
33
  @ManyToOne(() => MenuItemEntity, (a) => a.branch_menus)
 
34
  menu_item: Relation<MenuItemEntity>;
35
  }
 
2
  BaseEntity,
3
  Column,
4
  Entity,
5
+ JoinColumn,
6
  ManyToOne,
7
  OneToMany,
8
  PrimaryGeneratedColumn,
 
22
  @Column()
23
  menu_id: string;
24
 
25
+ @Column({ nullable: true })
26
  description: string;
27
 
28
+ @Column({ default: true })
29
  is_open: boolean;
30
 
31
+ @Column({ default: 0 })
32
+ sold_count: number;
33
+
34
  @ManyToOne(() => BranchEntity, (a) => a.menu_items)
35
+ @JoinColumn({ name: 'branch_id' })
36
  branch: Relation<BranchEntity>;
37
 
38
  @ManyToOne(() => MenuItemEntity, (a) => a.branch_menus)
39
+ @JoinColumn({ name: 'menu_id' })
40
  menu_item: Relation<MenuItemEntity>;
41
  }
backend/src/entities/branch.entity.ts CHANGED
@@ -1,35 +1,44 @@
1
- import {
2
- BaseEntity,
3
- Column,
4
- Entity,
5
- ManyToOne,
6
- OneToMany,
7
- PrimaryGeneratedColumn,
8
- Relation,
9
- } from 'typeorm';
10
- import { UserEntity } from './user.entity.js';
11
- import { BranchMenuEntity } from './branch-menu.entity.js';
12
-
13
- @Entity('branches')
14
- export class BranchEntity extends BaseEntity {
15
- @PrimaryGeneratedColumn('uuid')
16
- id: string;
17
-
18
- @Column()
19
- name: string;
20
-
21
- @Column()
22
- location: string;
23
-
24
- @Column()
25
- phone_number: string;
26
-
27
- @ManyToOne(() => UserEntity, (user) => user.branches)
28
- owner: Relation<UserEntity>;
29
-
30
- @OneToMany(() => BranchMenuEntity, (a) => a.branch)
31
- menu_items: Relation<BranchMenuEntity>[];
32
-
33
- @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
34
- create_at: Date;
35
- }
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ CreateDateColumn,
5
+ Entity,
6
+ ManyToOne,
7
+ OneToMany,
8
+ PrimaryColumn,
9
+ PrimaryGeneratedColumn,
10
+ Relation,
11
+ } from 'typeorm';
12
+ import { UserEntity } from './user.entity.js';
13
+ import { BranchMenuEntity } from './branch-menu.entity.js';
14
+ import { ReceiptEntity } from './receipt.entity.js';
15
+
16
+ @Entity('branches')
17
+ export class BranchEntity extends BaseEntity {
18
+ @PrimaryColumn()
19
+ id: string;
20
+
21
+ @Column({ nullable: true })
22
+ name: string;
23
+
24
+ @Column({ nullable: true })
25
+ image_url: string;
26
+
27
+ @Column({ nullable: true })
28
+ location: string;
29
+
30
+ @Column({ nullable: true })
31
+ phone_number: string;
32
+
33
+ @ManyToOne(() => UserEntity, (user) => user.branches)
34
+ owner: Relation<UserEntity>;
35
+
36
+ @OneToMany(() => BranchMenuEntity, (a) => a.branch)
37
+ menu_items: Relation<BranchMenuEntity>[];
38
+
39
+ @OneToMany(() => ReceiptEntity, (a) => a.branch)
40
+ receipts: Relation<ReceiptEntity>[];
41
+
42
+ @CreateDateColumn()
43
+ create_at: Date;
44
+ }
backend/src/entities/feed.entity.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Entity, Column, BaseEntity, PrimaryGeneratedColumn } from 'typeorm';
2
 
3
  @Entity('feeds')
4
  export class FeedEntity extends BaseEntity {
@@ -17,6 +17,6 @@ export class FeedEntity extends BaseEntity {
17
  @Column({ nullable: true })
18
  description: string;
19
 
20
- @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
21
  create_at: Date;
22
  }
 
1
+ import { Entity, Column, BaseEntity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
2
 
3
  @Entity('feeds')
4
  export class FeedEntity extends BaseEntity {
 
17
  @Column({ nullable: true })
18
  description: string;
19
 
20
+ @CreateDateColumn()
21
  create_at: Date;
22
  }
backend/src/entities/menu-item.entity.ts CHANGED
@@ -1,6 +1,7 @@
1
  import {
2
  BaseEntity,
3
  Column,
 
4
  Entity,
5
  OneToMany,
6
  PrimaryColumn,
@@ -20,8 +21,8 @@ export class MenuItemEntity extends BaseEntity {
20
  @Column({ nullable: true })
21
  image_url: string;
22
 
23
- @Column({ type: 'enum', enum: MenuItemType, default: 'khac' })
24
- item_type: MenuItemType;
25
 
26
  @Column()
27
  description: string;
@@ -32,6 +33,6 @@ export class MenuItemEntity extends BaseEntity {
32
  @OneToMany(() => BranchMenuEntity, (a) => a.menu_item)
33
  branch_menus: Relation<BranchMenuEntity>[];
34
 
35
- @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
36
  create_at: Date;
37
  }
 
1
  import {
2
  BaseEntity,
3
  Column,
4
+ CreateDateColumn,
5
  Entity,
6
  OneToMany,
7
  PrimaryColumn,
 
21
  @Column({ nullable: true })
22
  image_url: string;
23
 
24
+ @Column({ default: 0 })
25
+ item_type: number;
26
 
27
  @Column()
28
  description: string;
 
33
  @OneToMany(() => BranchMenuEntity, (a) => a.menu_item)
34
  branch_menus: Relation<BranchMenuEntity>[];
35
 
36
+ @CreateDateColumn()
37
  create_at: Date;
38
  }
backend/src/entities/order-item.entity.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ Entity,
5
+ JoinColumn,
6
+ ManyToOne,
7
+ PrimaryGeneratedColumn,
8
+ Relation,
9
+ } from 'typeorm';
10
+ import { BranchMenuEntity } from './branch-menu.entity.js';
11
+ import { OrderEntity } from './order.entity.js';
12
+
13
+ @Entity('order_items')
14
+ export class OrderItemEntity extends BaseEntity {
15
+ @PrimaryGeneratedColumn()
16
+ id: number;
17
+
18
+ @Column()
19
+ order_id: number;
20
+
21
+ @ManyToOne(() => OrderEntity, (a) => a.order_items)
22
+ @JoinColumn({ name: 'order_id' })
23
+ order: Relation<OrderEntity>;
24
+
25
+ @Column()
26
+ branch_menu_id: string;
27
+
28
+ @ManyToOne(() => BranchMenuEntity)
29
+ @JoinColumn({ name: 'branch_menu_id' })
30
+ branch_menu: Relation<BranchMenuEntity>;
31
+
32
+ @Column()
33
+ quantity: number;
34
+
35
+ @Column()
36
+ price: number;
37
+ }
backend/src/entities/order.entity.ts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ CreateDateColumn,
5
+ Entity,
6
+ JoinColumn,
7
+ ManyToOne,
8
+ OneToMany,
9
+ OneToOne,
10
+ PrimaryGeneratedColumn,
11
+ Relation,
12
+ } from 'typeorm';
13
+ import { BranchEntity } from './branch.entity.js';
14
+ import { UserEntity } from './user.entity.js';
15
+ import { OrderType } from '../common/enums/OrderType.enum.js';
16
+ import { OrderStatus } from '../common/enums/OrderStatus.enum.js';
17
+ import { OrderItemEntity } from './order-item.entity.js';
18
+ import { PaymentEntity } from './payment.entity.js';
19
+
20
+ @Entity('orders')
21
+ export class OrderEntity extends BaseEntity {
22
+ @PrimaryGeneratedColumn()
23
+ id: number;
24
+
25
+ @Column({ nullable: true })
26
+ customer_id: string;
27
+
28
+ @ManyToOne(() => UserEntity, { nullable: true })
29
+ @JoinColumn({ name: 'customer_id' })
30
+ customer: Relation<UserEntity>;
31
+
32
+ @Column()
33
+ branch_id: string;
34
+
35
+ @ManyToOne(() => BranchEntity)
36
+ @JoinColumn({ name: 'branch_id' })
37
+ branch: Relation<BranchEntity>;
38
+
39
+ @Column({ nullable: true })
40
+ staff_id: string;
41
+
42
+ @ManyToOne(() => UserEntity, { nullable: true })
43
+ @JoinColumn({ name: 'staff_id' })
44
+ staff: Relation<UserEntity>;
45
+
46
+ @Column({ nullable: true })
47
+ table_number: number;
48
+
49
+ @Column()
50
+ total_value: number;
51
+
52
+ @CreateDateColumn()
53
+ create_at: Date;
54
+
55
+ @Column({ default: 0 })
56
+ order_type: number;
57
+
58
+ @Column({ default: 0 })
59
+ order_status: number;
60
+
61
+ @OneToMany(() => OrderItemEntity, (a) => a.order)
62
+ order_items: Relation<OrderItemEntity>[];
63
+
64
+ @Column({ nullable: true })
65
+ payment_id: number;
66
+
67
+ @OneToOne(() => PaymentEntity, (a) => a.order)
68
+ @JoinColumn({ name: 'payment_id' })
69
+ payment: Relation<PaymentEntity>;
70
+ }
backend/src/entities/payment.entity.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ Entity,
5
+ OneToOne,
6
+ PrimaryGeneratedColumn,
7
+ Relation,
8
+ } from 'typeorm';
9
+ import { OrderEntity } from './order.entity.js';
10
+ import { PaymentMethod } from '../common/enums/PaymentMethod.enum.js';
11
+
12
+ @Entity('payments')
13
+ export class PaymentEntity extends BaseEntity {
14
+ @PrimaryGeneratedColumn()
15
+ id: number;
16
+
17
+ @OneToOne(() => OrderEntity, (a) => a.payment)
18
+ order: Relation<OrderEntity>;
19
+
20
+ @Column({ default: 0 })
21
+ payment_method: number; // E.g., 'Cash', 'Credit Card', 'Online Payment'
22
+
23
+ @Column()
24
+ value: number;
25
+ }
backend/src/entities/receipt.entity.ts ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ CreateDateColumn,
5
+ Entity,
6
+ JoinColumn,
7
+ ManyToOne,
8
+ PrimaryGeneratedColumn,
9
+ Relation,
10
+ } from 'typeorm';
11
+ import { BranchEntity } from './branch.entity.js';
12
+ import { UserEntity } from './user.entity.js';
13
+
14
+ @Entity('receipts')
15
+ export class ReceiptEntity extends BaseEntity {
16
+ @PrimaryGeneratedColumn()
17
+ id: number;
18
+
19
+ @Column()
20
+ branch_id: string;
21
+
22
+ @ManyToOne(() => BranchEntity, (a) => a.receipts)
23
+ @JoinColumn({ name: 'branch_id' })
24
+ branch: Relation<BranchEntity>;
25
+
26
+ @Column({ default: 0 })
27
+ income: number;
28
+
29
+ @Column({ default: 0 })
30
+ spend: number;
31
+
32
+ @Column({ nullable: true })
33
+ description: string;
34
+
35
+ @Column({ default: 0 })
36
+ type: number;
37
+
38
+ @Column({ default: 0 })
39
+ sub_type: number;
40
+
41
+ @Column({ nullable: true })
42
+ sender_id: string;
43
+
44
+ @Column({ nullable: true })
45
+ receiver_id: string;
46
+
47
+ @ManyToOne(() => UserEntity, (a) => a.out_receipts)
48
+ @JoinColumn({ name: 'sender_id' })
49
+ sender: Relation<UserEntity>;
50
+
51
+ @ManyToOne(() => UserEntity, (a) => a.in_receipts)
52
+ @JoinColumn({ name: 'receiver_id' })
53
+ receiver: Relation<UserEntity>;
54
+
55
+ @CreateDateColumn()
56
+ created_at: Date;
57
+ }
backend/src/entities/user.entity.ts CHANGED
@@ -1,54 +1,59 @@
1
- import {
2
- Entity,
3
- Column,
4
- BaseEntity,
5
- PrimaryGeneratedColumn,
6
- OneToMany,
7
- ManyToOne,
8
- Relation,
9
- JoinColumn,
10
- } from 'typeorm';
11
- import { BranchEntity } from './branch.entity.js';
12
- import { IsOptional } from 'class-validator';
13
- import { Role } from '../common/enums/role.enum.js';
14
-
15
- @Entity('users')
16
- export class UserEntity extends BaseEntity {
17
- @PrimaryGeneratedColumn('uuid')
18
- id: string;
19
-
20
- @IsOptional()
21
- @Column({ nullable: true })
22
- avatar: string;
23
-
24
- @Column()
25
- full_name: string;
26
-
27
- @Column({ unique: true })
28
- phone_number: string;
29
-
30
- @IsOptional()
31
- @Column({ nullable: true })
32
- address: string;
33
-
34
- @Column({ nullable: true, unique: true })
35
- email: string;
36
-
37
- @Column({ type: 'enum', enum: Role, default: 'CUSTOMER' })
38
- role: Role;
39
-
40
- @Column()
41
- hash_password: string;
42
-
43
- @IsOptional()
44
- @Column({ default: true })
45
- is_valid: boolean;
46
-
47
- @IsOptional()
48
- @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
49
- create_at: Date;
50
-
51
- @IsOptional()
52
- @OneToMany(() => BranchEntity, (branch) => branch.owner)
53
- branches: Relation<BranchEntity>[];
54
- }
 
 
 
 
 
 
1
+ import {
2
+ Entity,
3
+ Column,
4
+ BaseEntity,
5
+ PrimaryGeneratedColumn,
6
+ OneToMany,
7
+ ManyToOne,
8
+ Relation,
9
+ JoinColumn,
10
+ CreateDateColumn,
11
+ } from 'typeorm';
12
+ import { BranchEntity } from './branch.entity.js';
13
+ import { IsOptional } from 'class-validator';
14
+ import { Role } from '../common/enums/role.enum.js';
15
+ import { ReceiptEntity } from './receipt.entity.js';
16
+
17
+ @Entity('users')
18
+ export class UserEntity extends BaseEntity {
19
+ @PrimaryGeneratedColumn('uuid')
20
+ id: string;
21
+
22
+ @IsOptional()
23
+ @Column({ nullable: true })
24
+ avatar: string;
25
+
26
+ @Column()
27
+ full_name: string;
28
+
29
+ @Column({ unique: true })
30
+ phone_number: string;
31
+
32
+ @IsOptional()
33
+ @Column({ nullable: true })
34
+ address: string;
35
+
36
+ @Column({ nullable: true, unique: true })
37
+ email: string;
38
+
39
+ @Column({ type: 'enum', enum: Role, default: 'CUSTOMER' })
40
+ role: Role;
41
+
42
+ @Column()
43
+ hash_password: string;
44
+
45
+ @Column({ default: true })
46
+ is_valid: boolean;
47
+
48
+ @CreateDateColumn()
49
+ create_at: Date;
50
+
51
+ @OneToMany(() => BranchEntity, (branch) => branch.owner)
52
+ branches: Relation<BranchEntity>[];
53
+
54
+ @OneToMany(() => ReceiptEntity, (receipt) => receipt.sender)
55
+ in_receipts: Relation<ReceiptEntity>[];
56
+
57
+ @OneToMany(() => ReceiptEntity, (receipt) => receipt.receiver)
58
+ out_receipts: Relation<ReceiptEntity>[];
59
+ }
backend/src/migrations/1729963419864-enum-role.ts DELETED
@@ -1,22 +0,0 @@
1
- import { MigrationInterface, QueryRunner } from "typeorm";
2
-
3
- export class EnumRole1729963419864 implements MigrationInterface {
4
- name = 'EnumRole1729963419864'
5
-
6
- public async up(queryRunner: QueryRunner): Promise<void> {
7
- await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT "FK_a2cecd1a3531c0b041e29ba46e1"`);
8
- await queryRunner.query(`ALTER TABLE "users" RENAME COLUMN "role_id" TO "role"`);
9
- await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "role"`);
10
- await queryRunner.query(`CREATE TYPE "public"."users_role_enum" AS ENUM('CUSTOMER', 'ADMIN', 'BRANCH_MANAGER', 'AREA_MANAGER', 'STAFF', 'SHIPPER')`);
11
- await queryRunner.query(`ALTER TABLE "users" ADD "role" "public"."users_role_enum" NOT NULL DEFAULT 'CUSTOMER'`);
12
- }
13
-
14
- public async down(queryRunner: QueryRunner): Promise<void> {
15
- await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "role"`);
16
- await queryRunner.query(`DROP TYPE "public"."users_role_enum"`);
17
- await queryRunner.query(`ALTER TABLE "users" ADD "role" uuid NOT NULL DEFAULT 'f3750930-48ab-4c30-8681-d50e68e2bda7'`);
18
- await queryRunner.query(`ALTER TABLE "users" RENAME COLUMN "role" TO "role_id"`);
19
- await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "FK_a2cecd1a3531c0b041e29ba46e1" FOREIGN KEY ("role_id") REFERENCES "role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
20
- }
21
-
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/src/migrations/1730474673934-RefactorAll.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class RefactorAll1730474673934 implements MigrationInterface {
4
+ name = 'RefactorAll1730474673934'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`CREATE TABLE "feeds" ("id" SERIAL NOT NULL, "author_id" character varying, "image_url" character varying, "title" character varying NOT NULL, "description" character varying, "create_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_3dafbf766ecbb1eb2017732153f" PRIMARY KEY ("id"))`);
8
+ await queryRunner.query(`CREATE TYPE "public"."users_role_enum" AS ENUM('CUSTOMER', 'ADMIN', 'BRANCH_MANAGER', 'AREA_MANAGER', 'STAFF', 'SHIPPER')`);
9
+ await queryRunner.query(`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "avatar" character varying, "full_name" character varying NOT NULL, "phone_number" character varying NOT NULL, "address" character varying, "email" character varying, "role" "public"."users_role_enum" NOT NULL DEFAULT 'CUSTOMER', "hash_password" character varying NOT NULL, "is_valid" boolean NOT NULL DEFAULT true, "create_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_17d1817f241f10a3dbafb169fd2" UNIQUE ("phone_number"), CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`);
10
+ await queryRunner.query(`CREATE TABLE "branches" ("id" character varying NOT NULL, "name" character varying NOT NULL, "location" character varying NOT NULL, "phone_number" character varying NOT NULL, "create_at" TIMESTAMP NOT NULL DEFAULT now(), "ownerId" uuid, CONSTRAINT "PK_7f37d3b42defea97f1df0d19535" PRIMARY KEY ("id"))`);
11
+ await queryRunner.query(`CREATE TYPE "public"."menu_items_item_type_enum" AS ENUM('monchinh', 'trangmieng', 'giaikhat', 'khac')`);
12
+ await queryRunner.query(`CREATE TABLE "menu_items" ("id" character varying NOT NULL, "item_name" character varying NOT NULL, "image_url" character varying, "item_type" "public"."menu_items_item_type_enum" NOT NULL DEFAULT 'khac', "description" character varying NOT NULL, "price" integer NOT NULL, "create_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_57e6188f929e5dc6919168620c8" PRIMARY KEY ("id"))`);
13
+ await queryRunner.query(`CREATE TABLE "branch_menu" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "branch_id" character varying NOT NULL, "menu_id" character varying NOT NULL, "description" character varying, "is_open" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_977becffe98bbc626a56031b9e7" PRIMARY KEY ("id"))`);
14
+ await queryRunner.query(`CREATE TYPE "public"."payments_payment_method_enum" AS ENUM('cash', 'card', 'online_payment')`);
15
+ await queryRunner.query(`CREATE TABLE "payments" ("id" SERIAL NOT NULL, "payment_method" "public"."payments_payment_method_enum" NOT NULL DEFAULT 'cash', "value" integer NOT NULL, CONSTRAINT "PK_197ab7af18c93fbb0c9b28b4a59" PRIMARY KEY ("id"))`);
16
+ await queryRunner.query(`CREATE TYPE "public"."orders_order_type_enum" AS ENUM('take_away', 'offline', 'online')`);
17
+ await queryRunner.query(`CREATE TYPE "public"."orders_order_status_enum" AS ENUM('pending', 'confirmed', 'preparing', 'delivering', 'done')`);
18
+ await queryRunner.query(`CREATE TABLE "orders" ("id" SERIAL NOT NULL, "customer_id" uuid, "branch_id" character varying NOT NULL, "staff_id" uuid, "table_number" integer, "total_value" integer NOT NULL, "create_at" TIMESTAMP NOT NULL DEFAULT now(), "order_type" "public"."orders_order_type_enum" NOT NULL DEFAULT 'online', "order_status" "public"."orders_order_status_enum" NOT NULL DEFAULT 'pending', "payment_id" integer, CONSTRAINT "REL_5b3e94bd2aedc184f9ad8c1043" UNIQUE ("payment_id"), CONSTRAINT "PK_710e2d4957aa5878dfe94e4ac2f" PRIMARY KEY ("id"))`);
19
+ await queryRunner.query(`CREATE TABLE "order_items" ("id" SERIAL NOT NULL, "order_id" integer NOT NULL, "branch_menu_id" uuid NOT NULL, "quantity" integer NOT NULL, "price" integer NOT NULL, CONSTRAINT "PK_005269d8574e6fac0493715c308" PRIMARY KEY ("id"))`);
20
+ await queryRunner.query(`ALTER TABLE "branches" ADD CONSTRAINT "FK_8c6ae9f9c654c4fac71bccbb7ed" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
21
+ await queryRunner.query(`ALTER TABLE "branch_menu" ADD CONSTRAINT "FK_96fd74bed807987cf2ee5d8f168" FOREIGN KEY ("branch_id") REFERENCES "branches"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
22
+ await queryRunner.query(`ALTER TABLE "branch_menu" ADD CONSTRAINT "FK_703aa953158d2e80f3fbb0eb9ea" FOREIGN KEY ("menu_id") REFERENCES "menu_items"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
23
+ await queryRunner.query(`ALTER TABLE "orders" ADD CONSTRAINT "FK_772d0ce0473ac2ccfa26060dbe9" FOREIGN KEY ("customer_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
24
+ await queryRunner.query(`ALTER TABLE "orders" ADD CONSTRAINT "FK_17b723da2c12837f4bc21e33398" FOREIGN KEY ("branch_id") REFERENCES "branches"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
25
+ await queryRunner.query(`ALTER TABLE "orders" ADD CONSTRAINT "FK_40337bbb0e0cc7113dc3037fc60" FOREIGN KEY ("staff_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
26
+ await queryRunner.query(`ALTER TABLE "orders" ADD CONSTRAINT "FK_5b3e94bd2aedc184f9ad8c10439" FOREIGN KEY ("payment_id") REFERENCES "payments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
27
+ await queryRunner.query(`ALTER TABLE "order_items" ADD CONSTRAINT "FK_145532db85752b29c57d2b7b1f1" FOREIGN KEY ("order_id") REFERENCES "orders"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
28
+ await queryRunner.query(`ALTER TABLE "order_items" ADD CONSTRAINT "FK_927879f38b3098216737427d2f0" FOREIGN KEY ("branch_menu_id") REFERENCES "branch_menu"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
29
+ }
30
+
31
+ public async down(queryRunner: QueryRunner): Promise<void> {
32
+ await queryRunner.query(`ALTER TABLE "order_items" DROP CONSTRAINT "FK_927879f38b3098216737427d2f0"`);
33
+ await queryRunner.query(`ALTER TABLE "order_items" DROP CONSTRAINT "FK_145532db85752b29c57d2b7b1f1"`);
34
+ await queryRunner.query(`ALTER TABLE "orders" DROP CONSTRAINT "FK_5b3e94bd2aedc184f9ad8c10439"`);
35
+ await queryRunner.query(`ALTER TABLE "orders" DROP CONSTRAINT "FK_40337bbb0e0cc7113dc3037fc60"`);
36
+ await queryRunner.query(`ALTER TABLE "orders" DROP CONSTRAINT "FK_17b723da2c12837f4bc21e33398"`);
37
+ await queryRunner.query(`ALTER TABLE "orders" DROP CONSTRAINT "FK_772d0ce0473ac2ccfa26060dbe9"`);
38
+ await queryRunner.query(`ALTER TABLE "branch_menu" DROP CONSTRAINT "FK_703aa953158d2e80f3fbb0eb9ea"`);
39
+ await queryRunner.query(`ALTER TABLE "branch_menu" DROP CONSTRAINT "FK_96fd74bed807987cf2ee5d8f168"`);
40
+ await queryRunner.query(`ALTER TABLE "branches" DROP CONSTRAINT "FK_8c6ae9f9c654c4fac71bccbb7ed"`);
41
+ await queryRunner.query(`DROP TABLE "order_items"`);
42
+ await queryRunner.query(`DROP TABLE "orders"`);
43
+ await queryRunner.query(`DROP TYPE "public"."orders_order_status_enum"`);
44
+ await queryRunner.query(`DROP TYPE "public"."orders_order_type_enum"`);
45
+ await queryRunner.query(`DROP TABLE "payments"`);
46
+ await queryRunner.query(`DROP TYPE "public"."payments_payment_method_enum"`);
47
+ await queryRunner.query(`DROP TABLE "branch_menu"`);
48
+ await queryRunner.query(`DROP TABLE "menu_items"`);
49
+ await queryRunner.query(`DROP TYPE "public"."menu_items_item_type_enum"`);
50
+ await queryRunner.query(`DROP TABLE "branches"`);
51
+ await queryRunner.query(`DROP TABLE "users"`);
52
+ await queryRunner.query(`DROP TYPE "public"."users_role_enum"`);
53
+ await queryRunner.query(`DROP TABLE "feeds"`);
54
+ }
55
+
56
+ }
backend/src/migrations/1730547520878-AddReceipt.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddReceipt1730547520878 implements MigrationInterface {
4
+ name = 'AddReceipt1730547520878'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`CREATE TABLE "receipts" ("id" SERIAL NOT NULL, "branch_id" character varying NOT NULL, "income" integer NOT NULL DEFAULT '0', "spend" integer NOT NULL DEFAULT '0', "description" character varying, "type" integer NOT NULL DEFAULT '0', "sub_type" integer NOT NULL DEFAULT '0', "sender_id" uuid, "receiver_id" uuid, "created_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_5e8182d7c29e023da6e1ff33bfe" PRIMARY KEY ("id"))`);
8
+ await queryRunner.query(`ALTER TABLE "branches" ADD "image_url" character varying`);
9
+ await queryRunner.query(`ALTER TABLE "branch_menu" ADD "sold_count" integer NOT NULL DEFAULT '0'`);
10
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "name" DROP NOT NULL`);
11
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "location" DROP NOT NULL`);
12
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "phone_number" DROP NOT NULL`);
13
+ await queryRunner.query(`ALTER TABLE "receipts" ADD CONSTRAINT "FK_82e9dee911c0e7393154d1d98ad" FOREIGN KEY ("branch_id") REFERENCES "branches"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
14
+ await queryRunner.query(`ALTER TABLE "receipts" ADD CONSTRAINT "FK_eda4c4e486a25beef4dc82a41d5" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
15
+ await queryRunner.query(`ALTER TABLE "receipts" ADD CONSTRAINT "FK_366c3d3cf125da97552f40001a1" FOREIGN KEY ("receiver_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
16
+ }
17
+
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(`ALTER TABLE "receipts" DROP CONSTRAINT "FK_366c3d3cf125da97552f40001a1"`);
20
+ await queryRunner.query(`ALTER TABLE "receipts" DROP CONSTRAINT "FK_eda4c4e486a25beef4dc82a41d5"`);
21
+ await queryRunner.query(`ALTER TABLE "receipts" DROP CONSTRAINT "FK_82e9dee911c0e7393154d1d98ad"`);
22
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "phone_number" SET NOT NULL`);
23
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "location" SET NOT NULL`);
24
+ await queryRunner.query(`ALTER TABLE "branches" ALTER COLUMN "name" SET NOT NULL`);
25
+ await queryRunner.query(`ALTER TABLE "branch_menu" DROP COLUMN "sold_count"`);
26
+ await queryRunner.query(`ALTER TABLE "branches" DROP COLUMN "image_url"`);
27
+ await queryRunner.query(`DROP TABLE "receipts"`);
28
+ }
29
+
30
+ }
backend/src/migrations/1730549959767-RemoveEnums.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class RemoveEnums1730549959767 implements MigrationInterface {
4
+ name = 'RemoveEnums1730549959767'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE "menu_items" DROP COLUMN "item_type"`);
8
+ await queryRunner.query(`DROP TYPE "public"."menu_items_item_type_enum"`);
9
+ await queryRunner.query(`ALTER TABLE "menu_items" ADD "item_type" integer NOT NULL DEFAULT '0'`);
10
+ await queryRunner.query(`ALTER TABLE "payments" DROP COLUMN "payment_method"`);
11
+ await queryRunner.query(`DROP TYPE "public"."payments_payment_method_enum"`);
12
+ await queryRunner.query(`ALTER TABLE "payments" ADD "payment_method" integer NOT NULL DEFAULT '0'`);
13
+ await queryRunner.query(`ALTER TABLE "orders" DROP COLUMN "order_type"`);
14
+ await queryRunner.query(`DROP TYPE "public"."orders_order_type_enum"`);
15
+ await queryRunner.query(`ALTER TABLE "orders" ADD "order_type" integer NOT NULL DEFAULT '0'`);
16
+ await queryRunner.query(`ALTER TABLE "orders" DROP COLUMN "order_status"`);
17
+ await queryRunner.query(`DROP TYPE "public"."orders_order_status_enum"`);
18
+ await queryRunner.query(`ALTER TABLE "orders" ADD "order_status" integer NOT NULL DEFAULT '0'`);
19
+ }
20
+
21
+ public async down(queryRunner: QueryRunner): Promise<void> {
22
+ await queryRunner.query(`ALTER TABLE "orders" DROP COLUMN "order_status"`);
23
+ await queryRunner.query(`CREATE TYPE "public"."orders_order_status_enum" AS ENUM('pending', 'confirmed', 'preparing', 'delivering', 'done')`);
24
+ await queryRunner.query(`ALTER TABLE "orders" ADD "order_status" "public"."orders_order_status_enum" NOT NULL DEFAULT 'pending'`);
25
+ await queryRunner.query(`ALTER TABLE "orders" DROP COLUMN "order_type"`);
26
+ await queryRunner.query(`CREATE TYPE "public"."orders_order_type_enum" AS ENUM('take_away', 'offline', 'online')`);
27
+ await queryRunner.query(`ALTER TABLE "orders" ADD "order_type" "public"."orders_order_type_enum" NOT NULL DEFAULT 'online'`);
28
+ await queryRunner.query(`ALTER TABLE "payments" DROP COLUMN "payment_method"`);
29
+ await queryRunner.query(`CREATE TYPE "public"."payments_payment_method_enum" AS ENUM('cash', 'card', 'online_payment')`);
30
+ await queryRunner.query(`ALTER TABLE "payments" ADD "payment_method" "public"."payments_payment_method_enum" NOT NULL DEFAULT 'cash'`);
31
+ await queryRunner.query(`ALTER TABLE "menu_items" DROP COLUMN "item_type"`);
32
+ await queryRunner.query(`CREATE TYPE "public"."menu_items_item_type_enum" AS ENUM('monchinh', 'trangmieng', 'giaikhat', 'khac')`);
33
+ await queryRunner.query(`ALTER TABLE "menu_items" ADD "item_type" "public"."menu_items_item_type_enum" NOT NULL DEFAULT 'khac'`);
34
+ }
35
+
36
+ }
backend/src/modules/branch-menus/branch-menus.controller.ts ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Controller,
3
+ Get,
4
+ Post,
5
+ Body,
6
+ Patch,
7
+ Param,
8
+ Delete,
9
+ } from '@nestjs/common';
10
+ import { BranchMenusService } from './branch-menus.service.js';
11
+ import { CreateBranchMenuDto } from './dto/create-branch-menu.dto.js';
12
+ import { UpdateBranchMenuDto } from './dto/update-branch-menu.dto.js';
13
+ import { Public } from '../authentication/authentication.decorator.js';
14
+ import { Paginate, PaginateQuery } from 'nestjs-paginate';
15
+
16
+ @Public()
17
+ @Controller('branchs/:branchId/menus')
18
+ export class BranchMenusController {
19
+ constructor(private readonly branchMenusService: BranchMenusService) {}
20
+
21
+ @Post() // thêm menu vào branch
22
+ create(
23
+ @Param('branchId') branchId: string,
24
+ @Body() createBranchMenuDto: CreateBranchMenuDto,
25
+ ) {
26
+ return this.branchMenusService.create(branchId, createBranchMenuDto);
27
+ }
28
+
29
+ @Get() // lấy danh sách menu trong branch
30
+ findAll(
31
+ @Param('branchId') branchId: string,
32
+ @Paginate() query: PaginateQuery,
33
+ ) {
34
+ // console.log('branchId', branchId);
35
+ return this.branchMenusService.findAll(branchId, query);
36
+ }
37
+
38
+ @Get(':id') // lấy một menu trong branch
39
+ findOne(@Param('branchId') branchId: string, @Param('id') id: string) {
40
+ return this.branchMenusService.findOne(branchId, id);
41
+ }
42
+
43
+ @Patch(':id')
44
+ update(
45
+ @Param('id') id: string,
46
+ @Body() updateBranchMenuDto: UpdateBranchMenuDto,
47
+ ) {
48
+ return this.branchMenusService.update(+id, updateBranchMenuDto);
49
+ }
50
+
51
+ @Delete(':id')
52
+ remove(@Param('id') id: string) {
53
+ return this.branchMenusService.remove(+id);
54
+ }
55
+ }
backend/src/modules/branch-menus/branch-menus.module.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { BranchMenusService } from './branch-menus.service.js';
3
+ import { BranchMenusController } from './branch-menus.controller.js';
4
+ import { BranchModule } from '../branch/branch.module.js';
5
+ import { MenuItemModule } from '../menu-item/menu-item.module.js';
6
+
7
+ @Module({
8
+ imports: [BranchModule, MenuItemModule],
9
+ controllers: [BranchMenusController],
10
+ providers: [BranchMenusService],
11
+ exports: [BranchMenusService],
12
+ })
13
+ export class BranchMenusModule {}
backend/src/modules/branch-menus/branch-menus.service.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable } from '@nestjs/common';
2
+ import { CreateBranchMenuDto } from './dto/create-branch-menu.dto.js';
3
+ import { UpdateBranchMenuDto } from './dto/update-branch-menu.dto.js';
4
+ import { BranchService } from '../branch/branch.service.js';
5
+ import { MenuItemService } from '../menu-item/menu-item.service.js';
6
+ import { BranchMenuEntity } from '../../entities/branch-menu.entity.js';
7
+ import { paginate, PaginateConfig, PaginateQuery } from 'nestjs-paginate';
8
+ import { isUUID } from 'class-validator';
9
+
10
+ @Injectable()
11
+ export class BranchMenusService {
12
+ constructor(
13
+ private readonly branchService: BranchService,
14
+ private readonly menuItemService: MenuItemService,
15
+ ) {}
16
+ async create(branchId: string, createBranchMenuDto: CreateBranchMenuDto) {
17
+ const branch = await this.branchService.getBranchOrError(branchId);
18
+ const menuItem = await this.menuItemService.getMenuItemOrError(
19
+ createBranchMenuDto.menu_id,
20
+ );
21
+ if (createBranchMenuDto.description) {
22
+ return await BranchMenuEntity.create({
23
+ ...createBranchMenuDto,
24
+ branch_id: branchId,
25
+ }).save();
26
+ } else {
27
+ return await BranchMenuEntity.create({
28
+ ...createBranchMenuDto,
29
+ branch_id: branchId,
30
+ description: menuItem.description,
31
+ }).save();
32
+ }
33
+ }
34
+
35
+ async findAll(branchId: string, query: PaginateQuery) {
36
+ const paginateConfig: PaginateConfig<BranchMenuEntity> = {
37
+ sortableColumns: ['id', 'branch_id', 'menu_id', 'description'],
38
+ nullSort: 'last',
39
+ defaultSortBy: [['id', 'DESC']],
40
+ searchableColumns: ['description'],
41
+ filterableColumns: {
42
+ // price: [],
43
+ // item_type: [FilterOperator.EQ],
44
+ },
45
+ };
46
+ return paginate(
47
+ query,
48
+ BranchMenuEntity.createQueryBuilder('bm')
49
+ .leftJoinAndSelect('bm.menu_item', 'menu_item')
50
+ .where('bm.branch_id = :branchId', { branchId: branchId }),
51
+ paginateConfig,
52
+ );
53
+ }
54
+
55
+ async findOne(branchId: string, id: string) {
56
+ if (isUUID(id)) return await BranchMenuEntity.findOneBy({ id });
57
+ else {
58
+ return await BranchMenuEntity.findOne({
59
+ where: { branch_id: branchId, menu_id: id },
60
+ relations: ['menu_item'],
61
+ });
62
+ }
63
+ }
64
+
65
+ update(id: number, updateBranchMenuDto: UpdateBranchMenuDto) {
66
+ return `This action updates a #${id} branchMenu`;
67
+ }
68
+
69
+ remove(id: number) {
70
+ return `This action removes a #${id} branchMenu`;
71
+ }
72
+ }
backend/src/modules/branch-menus/dto/create-branch-menu.dto.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IsOptional, IsString } from 'class-validator';
2
+
3
+ export class CreateBranchMenuDto {
4
+ @IsString()
5
+ menu_id: string;
6
+
7
+ @IsString()
8
+ @IsOptional()
9
+ description?: string;
10
+ }
backend/src/modules/branch-menus/dto/update-branch-menu.dto.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { PartialType } from '@nestjs/mapped-types';
2
+ import { CreateBranchMenuDto } from './create-branch-menu.dto.js';
3
+
4
+ export class UpdateBranchMenuDto extends PartialType(CreateBranchMenuDto) {}
backend/src/modules/branch/branch.controller.ts CHANGED
@@ -10,8 +10,10 @@ import {
10
  import { BranchService } from './branch.service.js';
11
  import { CreateBranchDto } from './dto/create-branch.dto.js';
12
  import { UpdateBranchDto } from './dto/update-branch.dto.js';
 
13
 
14
- @Controller('branch')
 
15
  export class BranchController {
16
  constructor(private readonly branchService: BranchService) {}
17
 
@@ -47,7 +49,5 @@ export class BranchController {
47
  async addMenuItemToBranch(@Param('id') id: string) {}
48
 
49
  @Get(':id/menu-items')
50
- async getMenuItemWithBranchId(@Param('id') id: string) {
51
-
52
- }
53
  }
 
10
  import { BranchService } from './branch.service.js';
11
  import { CreateBranchDto } from './dto/create-branch.dto.js';
12
  import { UpdateBranchDto } from './dto/update-branch.dto.js';
13
+ import { Public } from '../authentication/authentication.decorator.js';
14
 
15
+ @Public()
16
+ @Controller('branchs')
17
  export class BranchController {
18
  constructor(private readonly branchService: BranchService) {}
19
 
 
49
  async addMenuItemToBranch(@Param('id') id: string) {}
50
 
51
  @Get(':id/menu-items')
52
+ async getMenuItemWithBranchId(@Param('id') id: string) {}
 
 
53
  }
backend/src/modules/branch/branch.module.ts CHANGED
@@ -5,5 +5,6 @@ import { BranchController } from './branch.controller.js';
5
  @Module({
6
  controllers: [BranchController],
7
  providers: [BranchService],
 
8
  })
9
  export class BranchModule {}
 
5
  @Module({
6
  controllers: [BranchController],
7
  providers: [BranchService],
8
+ exports: [BranchService],
9
  })
10
  export class BranchModule {}
backend/src/modules/branch/branch.service.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { Injectable, NotFoundException } from '@nestjs/common';
 
 
 
 
2
  import { CreateBranchDto } from './dto/create-branch.dto.js';
3
  import { BranchEntity } from '../../entities/branch.entity.js';
4
  import { Public } from '../authentication/authentication.decorator.js';
@@ -9,6 +13,10 @@ import { plainToClass } from 'class-transformer';
9
  @Injectable()
10
  export class BranchService {
11
  async create(createBranchDto: CreateBranchDto) {
 
 
 
 
12
  return await BranchEntity.create({ ...createBranchDto }).save();
13
  }
14
 
@@ -21,9 +29,10 @@ export class BranchService {
21
  }
22
 
23
  async getBranchOrError(id: string) {
 
24
  const branch = await BranchEntity.findOneBy({ id });
25
  if (!branch) {
26
- throw new NotFoundException('Menu item not found');
27
  }
28
  return branch;
29
  }
 
1
+ import {
2
+ BadRequestException,
3
+ Injectable,
4
+ NotFoundException,
5
+ } from '@nestjs/common';
6
  import { CreateBranchDto } from './dto/create-branch.dto.js';
7
  import { BranchEntity } from '../../entities/branch.entity.js';
8
  import { Public } from '../authentication/authentication.decorator.js';
 
13
  @Injectable()
14
  export class BranchService {
15
  async create(createBranchDto: CreateBranchDto) {
16
+ const branch = await BranchEntity.findOneBy({ id: createBranchDto.id });
17
+ if (branch) {
18
+ throw new BadRequestException('Branch already exists');
19
+ }
20
  return await BranchEntity.create({ ...createBranchDto }).save();
21
  }
22
 
 
29
  }
30
 
31
  async getBranchOrError(id: string) {
32
+ console.log(id);
33
  const branch = await BranchEntity.findOneBy({ id });
34
  if (!branch) {
35
+ throw new NotFoundException('Branch not found');
36
  }
37
  return branch;
38
  }
backend/src/modules/branch/dto/create-branch.dto.ts CHANGED
@@ -1,6 +1,9 @@
1
  import { IsString } from 'class-validator';
2
 
3
  export class CreateBranchDto {
 
 
 
4
  @IsString()
5
  name: string;
6
 
 
1
  import { IsString } from 'class-validator';
2
 
3
  export class CreateBranchDto {
4
+ @IsString()
5
+ id: string;
6
+
7
  @IsString()
8
  name: string;
9
 
backend/src/modules/menu-item/dto/create-menu-item.dto.ts CHANGED
@@ -10,9 +10,9 @@ export class CreateMenuItemDto {
10
  @IsUrl()
11
  image_url: string;
12
 
13
- @IsString()
14
  @IsOptional()
15
- item_group_id?: string;
16
 
17
  @IsString()
18
  @IsOptional()
 
10
  @IsUrl()
11
  image_url: string;
12
 
13
+ @IsNumber()
14
  @IsOptional()
15
+ item_type?: number;
16
 
17
  @IsString()
18
  @IsOptional()
backend/src/modules/menu-item/dto/update-menu-item.dto.ts CHANGED
@@ -9,9 +9,9 @@ export class UpdateMenuItemDto {
9
  @IsOptional()
10
  image_url: string;
11
 
12
- @IsString()
13
  @IsOptional()
14
- item_group_id?: string;
15
 
16
  @IsString()
17
  @IsOptional()
 
9
  @IsOptional()
10
  image_url: string;
11
 
12
+ @IsNumber()
13
  @IsOptional()
14
+ item_type?: number;
15
 
16
  @IsString()
17
  @IsOptional()
backend/src/modules/menu-item/menu-item.module.ts CHANGED
@@ -5,5 +5,6 @@ import { MenuItemController } from './menu-item.controller.js';
5
  @Module({
6
  controllers: [MenuItemController],
7
  providers: [MenuItemService],
 
8
  })
9
  export class MenuItemModule {}
 
5
  @Module({
6
  controllers: [MenuItemController],
7
  providers: [MenuItemService],
8
+ exports: [MenuItemService],
9
  })
10
  export class MenuItemModule {}
backend/src/modules/order/dto/create-order.dto.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ IsArray,
3
+ IsEnum,
4
+ IsNumber,
5
+ IsObject,
6
+ IsOptional,
7
+ IsString,
8
+ ValidateNested,
9
+ } from 'class-validator';
10
+ import { OrderType } from '../../../common/enums/OrderType.enum.js';
11
+ import { OrderItemsDto } from './order-items.dto.js';
12
+ import { Type } from 'class-transformer';
13
+
14
+ export class CreateOrderDto {
15
+ @IsOptional()
16
+ @IsNumber()
17
+ table_number?: number;
18
+
19
+ @IsNumber()
20
+ order_type: number;
21
+
22
+ @IsArray()
23
+ @ValidateNested()
24
+ @Type(() => OrderItemsDto)
25
+ order_items: OrderItemsDto[];
26
+ }
backend/src/modules/order/dto/order-items.dto.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { IsNumber, IsString } from 'class-validator';
2
+
3
+ export class OrderItemsDto {
4
+ @IsString()
5
+ menu_id: string;
6
+
7
+ @IsNumber()
8
+ quantity: number;
9
+ }
backend/src/modules/order/order.controller.ts ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Controller,
3
+ Get,
4
+ Post,
5
+ Body,
6
+ Patch,
7
+ Param,
8
+ Delete,
9
+ Req,
10
+ } from '@nestjs/common';
11
+ import { OrderService } from './order.service.js';
12
+ import { CreateOrderDto } from './dto/create-order.dto.js';
13
+ import { Role } from '../../common/enums/role.enum.js';
14
+
15
+ @Controller('branchs/:branchId/orders')
16
+ export class OrderController {
17
+ constructor(private readonly orderService: OrderService) {}
18
+
19
+ @Post()
20
+ async create(
21
+ @Param('branchId') branchId: string,
22
+ @Req() req: Request,
23
+ @Body() createOrderDto: CreateOrderDto,
24
+ ) {
25
+ const userId = req['user'].sub;
26
+ const role = req['user'].roles;
27
+ console.log(req['user']);
28
+ if (role == Role.CUSTOMER)
29
+ return this.orderService.createFromCustomer(
30
+ branchId,
31
+ userId,
32
+ createOrderDto,
33
+ );
34
+ else
35
+ return this.orderService.createFromStaff(
36
+ branchId,
37
+ userId,
38
+ createOrderDto,
39
+ );
40
+ }
41
+
42
+ @Get()
43
+ async findAll(@Req() req: Request) {
44
+ const userId = req['user'].sub;
45
+ console.log(req['user']);
46
+ return this.orderService.findAll();
47
+ }
48
+
49
+ @Get(':id')
50
+ async findOne(@Param('id') id: string) {
51
+ return this.orderService.findOne(+id);
52
+ }
53
+
54
+ // @Patch(':id')
55
+ // async update(
56
+ // @Param('id') id: string,
57
+ // @Body() updateOrderDto: UpdateOrderDto,
58
+ // ) {
59
+ // return this.orderService.update(+id, updateOrderDto);
60
+ // }
61
+
62
+ @Delete(':id')
63
+ remove(@Param('id') id: string) {
64
+ return this.orderService.remove(+id);
65
+ }
66
+ }
backend/src/modules/order/order.module.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { OrderService } from './order.service.js';
3
+ import { OrderController } from './order.controller.js';
4
+ import { BranchModule } from '../branch/branch.module.js';
5
+ import { BranchMenusModule } from '../branch-menus/branch-menus.module.js';
6
+
7
+ @Module({
8
+ imports: [BranchModule, BranchMenusModule],
9
+ controllers: [OrderController],
10
+ providers: [OrderService],
11
+ })
12
+ export class OrderModule {}
backend/src/modules/order/order.service.ts ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { BadRequestException, Injectable } from '@nestjs/common';
2
+ import { CreateOrderDto } from './dto/create-order.dto.js';
3
+ import { UserEntity } from '../../entities/user.entity.js';
4
+ import { OrderEntity } from '../../entities/order.entity.js';
5
+ import { BranchService } from '../branch/branch.service.js';
6
+ import { OrderType } from '../../common/enums/OrderType.enum.js';
7
+ import { isUUID } from 'class-validator';
8
+ import { OrderItemEntity } from '../../entities/order-item.entity.js';
9
+ import { BranchMenuEntity } from '../../entities/branch-menu.entity.js';
10
+ import { OrderStatus } from '../../common/enums/OrderStatus.enum.js';
11
+
12
+ @Injectable()
13
+ export class OrderService {
14
+ constructor(private readonly branchService: BranchService) {}
15
+ async createFromCustomer(
16
+ branchId: string,
17
+ userId: string,
18
+ createOrderDto: CreateOrderDto,
19
+ ) {
20
+ console.log('??');
21
+ if (createOrderDto.order_type != OrderType.ONLINE) {
22
+ throw new BadRequestException('customer cannot create offline order');
23
+ }
24
+
25
+ const user = await UserEntity.findOneBy({ id: userId });
26
+ if (!user) {
27
+ throw new BadRequestException('User not found');
28
+ }
29
+ const branch = await this.branchService.getBranchOrError(branchId);
30
+ if (!branch) {
31
+ throw new BadRequestException('Branch not found');
32
+ }
33
+ const order = OrderEntity.create();
34
+ order.branch = branch;
35
+ order.customer = user;
36
+ order.order_type = createOrderDto.order_type;
37
+ order.order_status = OrderStatus.PENDING;
38
+ order.total_value = 0;
39
+ await order.save();
40
+
41
+ let orderItems: OrderItemEntity[] = [];
42
+ let totalValue = 0;
43
+ for (const item of createOrderDto.order_items) {
44
+ let branchMenu: BranchMenuEntity;
45
+ if (!isUUID(item.menu_id)) {
46
+ branchMenu = await BranchMenuEntity.findOne({
47
+ where: { branch_id: branchId, menu_id: item.menu_id },
48
+ relations: ['menu_item'],
49
+ });
50
+ } else {
51
+ branchMenu = await BranchMenuEntity.findOne({
52
+ where: { branch_id: branchId, id: item.menu_id },
53
+ relations: ['menu_item'],
54
+ });
55
+ }
56
+ if (!branchMenu) {
57
+ throw new BadRequestException('Item not found in branch menu');
58
+ }
59
+ const orderItem = OrderItemEntity.create();
60
+ orderItem.branch_menu = branchMenu;
61
+ orderItem.price = branchMenu.menu_item.price;
62
+ orderItem.quantity = item.quantity;
63
+ orderItem.order_id = order.id;
64
+ totalValue += orderItem.price * orderItem.quantity;
65
+ orderItems.push(orderItem);
66
+ }
67
+ await order.save();
68
+ order.total_value = totalValue;
69
+ await OrderItemEntity.save(orderItems);
70
+ return { ...order, order_items: orderItems };
71
+ }
72
+
73
+ async createFromStaff(
74
+ branchId: string,
75
+ userId: string,
76
+ createOrderDto: CreateOrderDto,
77
+ ) {
78
+ if (createOrderDto.order_type == OrderType.ONLINE) {
79
+ throw new BadRequestException('staff cannot create online order');
80
+ }
81
+ // staff
82
+ const staff = await UserEntity.findOneBy({ id: userId });
83
+ const branch = await this.branchService.getBranchOrError(branchId);
84
+ const order = OrderEntity.create();
85
+
86
+ order.branch = branch;
87
+ order.staff = staff;
88
+ order.order_type = createOrderDto.order_type;
89
+ order.table_number = createOrderDto.table_number;
90
+ order.order_status = OrderStatus.PREPARING;
91
+ order.total_value = 0;
92
+ await order.save();
93
+
94
+ let orderItems: OrderItemEntity[] = [];
95
+ let totalValue = 0;
96
+ for (const item of createOrderDto.order_items) {
97
+ let branchMenu: BranchMenuEntity;
98
+ if (!isUUID(item.menu_id)) {
99
+ branchMenu = await BranchMenuEntity.findOne({
100
+ where: { branch_id: branchId, menu_id: item.menu_id },
101
+ relations: ['menu_item'],
102
+ });
103
+ } else {
104
+ branchMenu = await BranchMenuEntity.findOne({
105
+ where: { branch_id: branchId, id: item.menu_id },
106
+ relations: ['menu_item'],
107
+ });
108
+ }
109
+ if (!branchMenu) {
110
+ throw new BadRequestException('Item not found in branch menu');
111
+ }
112
+ const orderItem = OrderItemEntity.create();
113
+ orderItem.branch_menu = branchMenu;
114
+ orderItem.price = branchMenu.menu_item.price;
115
+ orderItem.quantity = item.quantity;
116
+ orderItem.order_id = order.id;
117
+ totalValue += orderItem.price * orderItem.quantity;
118
+ orderItems.push(orderItem);
119
+ }
120
+ await order.save();
121
+ order.total_value = totalValue;
122
+ await OrderItemEntity.save(orderItems);
123
+ return { ...order, order_items: orderItems };
124
+ }
125
+
126
+ findAll() {
127
+ return `This action returns all order`;
128
+ }
129
+
130
+ findOne(id: number) {
131
+ return `This action returns a #${id} order`;
132
+ }
133
+
134
+ remove(id: number) {
135
+ return `This action removes a #${id} order`;
136
+ }
137
+ }
frontend/.gitignore CHANGED
@@ -21,3 +21,7 @@
21
  npm-debug.log*
22
  yarn-debug.log*
23
  yarn-error.log*
 
 
 
 
 
21
  npm-debug.log*
22
  yarn-debug.log*
23
  yarn-error.log*
24
+
25
+ .env
26
+ note_dev.txt
27
+ object.json
frontend/package-lock.json CHANGED
@@ -11,10 +11,14 @@
11
  "@testing-library/jest-dom": "^5.17.0",
12
  "@testing-library/react": "^13.4.0",
13
  "@testing-library/user-event": "^13.5.0",
 
14
  "bootstrap": "^5.3.3",
 
 
15
  "react": "^18.3.1",
16
  "react-bootstrap": "^2.10.5",
17
  "react-dom": "^18.3.1",
 
18
  "react-router-dom": "^6.27.0",
19
  "react-scripts": "5.0.1",
20
  "validator": "^13.12.0",
@@ -5870,6 +5874,31 @@
5870
  "node": ">=4"
5871
  }
5872
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5873
  "node_modules/axobject-query": {
5874
  "version": "4.1.0",
5875
  "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -6402,6 +6431,12 @@
6402
  "node-int64": "^0.4.0"
6403
  }
6404
  },
 
 
 
 
 
 
6405
  "node_modules/buffer-from": {
6406
  "version": "1.1.2",
6407
  "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -7812,6 +7847,15 @@
7812
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
7813
  "license": "MIT"
7814
  },
 
 
 
 
 
 
 
 
 
7815
  "node_modules/ee-first": {
7816
  "version": "1.1.1",
7817
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -13187,6 +13231,15 @@
13187
  "jiti": "bin/jiti.js"
13188
  }
13189
  },
 
 
 
 
 
 
 
 
 
13190
  "node_modules/js-tokens": {
13191
  "version": "4.0.0",
13192
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -13350,6 +13403,28 @@
13350
  "node": ">=0.10.0"
13351
  }
13352
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13353
  "node_modules/jsx-ast-utils": {
13354
  "version": "3.3.5",
13355
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -13365,6 +13440,27 @@
13365
  "node": ">=4.0"
13366
  }
13367
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13368
  "node_modules/keyv": {
13369
  "version": "4.5.4",
13370
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -13513,6 +13609,42 @@
13513
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
13514
  "license": "MIT"
13515
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13516
  "node_modules/lodash.memoize": {
13517
  "version": "4.1.2",
13518
  "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -13525,6 +13657,12 @@
13525
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
13526
  "license": "MIT"
13527
  },
 
 
 
 
 
 
13528
  "node_modules/lodash.sortby": {
13529
  "version": "4.7.0",
13530
  "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -15955,6 +16093,12 @@
15955
  "node": ">= 0.10"
15956
  }
15957
  },
 
 
 
 
 
 
15958
  "node_modules/psl": {
15959
  "version": "1.9.0",
15960
  "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -16356,6 +16500,15 @@
16356
  "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
16357
  "license": "MIT"
16358
  },
 
 
 
 
 
 
 
 
 
16359
  "node_modules/react-is": {
16360
  "version": "17.0.2",
16361
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 
11
  "@testing-library/jest-dom": "^5.17.0",
12
  "@testing-library/react": "^13.4.0",
13
  "@testing-library/user-event": "^13.5.0",
14
+ "axios": "^1.7.7",
15
  "bootstrap": "^5.3.3",
16
+ "js-cookie": "^3.0.5",
17
+ "jsonwebtoken": "^9.0.2",
18
  "react": "^18.3.1",
19
  "react-bootstrap": "^2.10.5",
20
  "react-dom": "^18.3.1",
21
+ "react-image-crop": "^11.0.7",
22
  "react-router-dom": "^6.27.0",
23
  "react-scripts": "5.0.1",
24
  "validator": "^13.12.0",
 
5874
  "node": ">=4"
5875
  }
5876
  },
5877
+ "node_modules/axios": {
5878
+ "version": "1.7.7",
5879
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
5880
+ "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
5881
+ "license": "MIT",
5882
+ "dependencies": {
5883
+ "follow-redirects": "^1.15.6",
5884
+ "form-data": "^4.0.0",
5885
+ "proxy-from-env": "^1.1.0"
5886
+ }
5887
+ },
5888
+ "node_modules/axios/node_modules/form-data": {
5889
+ "version": "4.0.1",
5890
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
5891
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
5892
+ "license": "MIT",
5893
+ "dependencies": {
5894
+ "asynckit": "^0.4.0",
5895
+ "combined-stream": "^1.0.8",
5896
+ "mime-types": "^2.1.12"
5897
+ },
5898
+ "engines": {
5899
+ "node": ">= 6"
5900
+ }
5901
+ },
5902
  "node_modules/axobject-query": {
5903
  "version": "4.1.0",
5904
  "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
 
6431
  "node-int64": "^0.4.0"
6432
  }
6433
  },
6434
+ "node_modules/buffer-equal-constant-time": {
6435
+ "version": "1.0.1",
6436
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
6437
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
6438
+ "license": "BSD-3-Clause"
6439
+ },
6440
  "node_modules/buffer-from": {
6441
  "version": "1.1.2",
6442
  "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 
7847
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
7848
  "license": "MIT"
7849
  },
7850
+ "node_modules/ecdsa-sig-formatter": {
7851
+ "version": "1.0.11",
7852
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
7853
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
7854
+ "license": "Apache-2.0",
7855
+ "dependencies": {
7856
+ "safe-buffer": "^5.0.1"
7857
+ }
7858
+ },
7859
  "node_modules/ee-first": {
7860
  "version": "1.1.1",
7861
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
 
13231
  "jiti": "bin/jiti.js"
13232
  }
13233
  },
13234
+ "node_modules/js-cookie": {
13235
+ "version": "3.0.5",
13236
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
13237
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
13238
+ "license": "MIT",
13239
+ "engines": {
13240
+ "node": ">=14"
13241
+ }
13242
+ },
13243
  "node_modules/js-tokens": {
13244
  "version": "4.0.0",
13245
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 
13403
  "node": ">=0.10.0"
13404
  }
13405
  },
13406
+ "node_modules/jsonwebtoken": {
13407
+ "version": "9.0.2",
13408
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
13409
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
13410
+ "license": "MIT",
13411
+ "dependencies": {
13412
+ "jws": "^3.2.2",
13413
+ "lodash.includes": "^4.3.0",
13414
+ "lodash.isboolean": "^3.0.3",
13415
+ "lodash.isinteger": "^4.0.4",
13416
+ "lodash.isnumber": "^3.0.3",
13417
+ "lodash.isplainobject": "^4.0.6",
13418
+ "lodash.isstring": "^4.0.1",
13419
+ "lodash.once": "^4.0.0",
13420
+ "ms": "^2.1.1",
13421
+ "semver": "^7.5.4"
13422
+ },
13423
+ "engines": {
13424
+ "node": ">=12",
13425
+ "npm": ">=6"
13426
+ }
13427
+ },
13428
  "node_modules/jsx-ast-utils": {
13429
  "version": "3.3.5",
13430
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
 
13440
  "node": ">=4.0"
13441
  }
13442
  },
13443
+ "node_modules/jwa": {
13444
+ "version": "1.4.1",
13445
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
13446
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
13447
+ "license": "MIT",
13448
+ "dependencies": {
13449
+ "buffer-equal-constant-time": "1.0.1",
13450
+ "ecdsa-sig-formatter": "1.0.11",
13451
+ "safe-buffer": "^5.0.1"
13452
+ }
13453
+ },
13454
+ "node_modules/jws": {
13455
+ "version": "3.2.2",
13456
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
13457
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
13458
+ "license": "MIT",
13459
+ "dependencies": {
13460
+ "jwa": "^1.4.1",
13461
+ "safe-buffer": "^5.0.1"
13462
+ }
13463
+ },
13464
  "node_modules/keyv": {
13465
  "version": "4.5.4",
13466
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 
13609
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
13610
  "license": "MIT"
13611
  },
13612
+ "node_modules/lodash.includes": {
13613
+ "version": "4.3.0",
13614
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
13615
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
13616
+ "license": "MIT"
13617
+ },
13618
+ "node_modules/lodash.isboolean": {
13619
+ "version": "3.0.3",
13620
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
13621
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
13622
+ "license": "MIT"
13623
+ },
13624
+ "node_modules/lodash.isinteger": {
13625
+ "version": "4.0.4",
13626
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
13627
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
13628
+ "license": "MIT"
13629
+ },
13630
+ "node_modules/lodash.isnumber": {
13631
+ "version": "3.0.3",
13632
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
13633
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
13634
+ "license": "MIT"
13635
+ },
13636
+ "node_modules/lodash.isplainobject": {
13637
+ "version": "4.0.6",
13638
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
13639
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
13640
+ "license": "MIT"
13641
+ },
13642
+ "node_modules/lodash.isstring": {
13643
+ "version": "4.0.1",
13644
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
13645
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
13646
+ "license": "MIT"
13647
+ },
13648
  "node_modules/lodash.memoize": {
13649
  "version": "4.1.2",
13650
  "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
 
13657
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
13658
  "license": "MIT"
13659
  },
13660
+ "node_modules/lodash.once": {
13661
+ "version": "4.1.1",
13662
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
13663
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
13664
+ "license": "MIT"
13665
+ },
13666
  "node_modules/lodash.sortby": {
13667
  "version": "4.7.0",
13668
  "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
 
16093
  "node": ">= 0.10"
16094
  }
16095
  },
16096
+ "node_modules/proxy-from-env": {
16097
+ "version": "1.1.0",
16098
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
16099
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
16100
+ "license": "MIT"
16101
+ },
16102
  "node_modules/psl": {
16103
  "version": "1.9.0",
16104
  "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
 
16500
  "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
16501
  "license": "MIT"
16502
  },
16503
+ "node_modules/react-image-crop": {
16504
+ "version": "11.0.7",
16505
+ "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz",
16506
+ "integrity": "sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==",
16507
+ "license": "ISC",
16508
+ "peerDependencies": {
16509
+ "react": ">=16.13.1"
16510
+ }
16511
+ },
16512
  "node_modules/react-is": {
16513
  "version": "17.0.2",
16514
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
frontend/package.json CHANGED
@@ -6,10 +6,14 @@
6
  "@testing-library/jest-dom": "^5.17.0",
7
  "@testing-library/react": "^13.4.0",
8
  "@testing-library/user-event": "^13.5.0",
 
9
  "bootstrap": "^5.3.3",
 
 
10
  "react": "^18.3.1",
11
  "react-bootstrap": "^2.10.5",
12
  "react-dom": "^18.3.1",
 
13
  "react-router-dom": "^6.27.0",
14
  "react-scripts": "5.0.1",
15
  "validator": "^13.12.0",
 
6
  "@testing-library/jest-dom": "^5.17.0",
7
  "@testing-library/react": "^13.4.0",
8
  "@testing-library/user-event": "^13.5.0",
9
+ "axios": "^1.7.7",
10
  "bootstrap": "^5.3.3",
11
+ "js-cookie": "^3.0.5",
12
+ "jsonwebtoken": "^9.0.2",
13
  "react": "^18.3.1",
14
  "react-bootstrap": "^2.10.5",
15
  "react-dom": "^18.3.1",
16
+ "react-image-crop": "^11.0.7",
17
  "react-router-dom": "^6.27.0",
18
  "react-scripts": "5.0.1",
19
  "validator": "^13.12.0",
frontend/public/default_avatar.jpg ADDED
frontend/src/index.js CHANGED
@@ -10,6 +10,8 @@ import LoginPage from './pages/LoginPage';
10
  import RegisterPage from './pages/RegisterPage';
11
  import NewsPage from './pages/NewsPage';
12
  import MenuPage from './pages/MenuPage';
 
 
13
 
14
  const router = createBrowserRouter([
15
  {
@@ -35,6 +37,16 @@ const router = createBrowserRouter([
35
  {
36
  path: "/menu",
37
  element: <MenuPage/>
 
 
 
 
 
 
 
 
 
 
38
  }
39
  ]);
40
 
 
10
  import RegisterPage from './pages/RegisterPage';
11
  import NewsPage from './pages/NewsPage';
12
  import MenuPage from './pages/MenuPage';
13
+ import CartPage from './pages/CartPage';
14
+ import UserInfoPage from './pages/UserInfoPage';
15
 
16
  const router = createBrowserRouter([
17
  {
 
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
 
frontend/src/molecules/AdminNavBar.js ADDED
File without changes
frontend/src/molecules/Navbar.js CHANGED
@@ -1,23 +1,23 @@
1
- import Container from 'react-bootstrap/Container';
2
- import Nav from 'react-bootstrap/Nav';
3
- import Navbar from 'react-bootstrap/Navbar';
4
- import Button from 'react-bootstrap/Button';
5
- import { Stack } from 'react-bootstrap';
6
  import { useNavigate } from 'react-router-dom';
 
7
 
8
  export default function ANavbar() {
9
 
10
  const navigate = useNavigate();
11
-
12
  function handleLogout() {
13
- sessionStorage.setItem('isLoggedIn','false');
14
- sessionStorage.removeItem('username');
15
- sessionStorage.removeItem('cart');
 
 
 
16
  navigate('/');
17
  }
18
 
19
- let username = sessionStorage.getItem('username');
20
- let isLoggedIn = sessionStorage.getItem('isLoggedIn');
21
 
22
  let userContent;
23
  if (isLoggedIn === 'true') {
@@ -26,7 +26,9 @@ export default function ANavbar() {
26
  <Button href="/userinfo" variant='primary'>
27
  Xin chào, {username}
28
  </Button>
29
- {' '}
 
 
30
  <Button onClick={handleLogout} variant='outline-primary'>
31
  Đăng xuất
32
  </Button>
 
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 ANavbar() {
6
 
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');
14
+ DataStorage.remove('cart');
15
+ DataStorage.remove('expiryDate');
16
  navigate('/');
17
  }
18
 
19
+ let username = DataStorage.get('username');
20
+ let isLoggedIn = DataStorage.get('isLoggedIn');
21
 
22
  let userContent;
23
  if (isLoggedIn === 'true') {
 
26
  <Button href="/userinfo" variant='primary'>
27
  Xin chào, {username}
28
  </Button>
29
+ <Button href="/cart" variant='outline-primary'>
30
+ Giỏ hàng
31
+ </Button>
32
  <Button onClick={handleLogout} variant='outline-primary'>
33
  Đăng xuất
34
  </Button>
frontend/src/organisms/DataStorage.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Cookies from 'js-cookie';
2
+
3
+ export default class DataStorage {
4
+ static storageMethod = process.env.REACT_APP_STORAGE_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,5 +1,5 @@
1
  import { useState } from 'react';
2
- import { Container, Carousel, Row, Col } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
 
5
  function MenuSection() {
@@ -20,7 +20,7 @@ function MenuSection() {
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 as='a' href='/menu' 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%' }}>
@@ -33,7 +33,7 @@ function MenuSection() {
33
  </div>
34
 
35
  <Carousel.Caption>
36
- <div >
37
  <Row md={3} className="g-4">
38
  {menuItems.map((item, idx) => (
39
  <Col key={idx}>
@@ -45,6 +45,7 @@ function MenuSection() {
45
  </Col>
46
  ))}
47
  </Row>
 
48
  </div>
49
  </Carousel.Caption>
50
  </Carousel.Item>
 
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() {
 
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%' }}>
 
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}>
 
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>
frontend/src/organisms/jwtDecoder.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function decodeBase64Url(base64Url) {
2
+ // Thay các ký tự theo chuẩn Base64 URL thành chuẩn Base64 thông thường
3
+ let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
4
+ // Thêm padding nếu thiếu
5
+ base64 += '='.repeat((4 - base64.length % 4) % 4);
6
+ // Giải mã từ Base64 sang chuỗi JSON
7
+ return JSON.parse(atob(base64));
8
+ }
9
+
10
+ export default function jwtDecoder(jwtToken) {
11
+ const [header, payload, _] = jwtToken.split('.');
12
+
13
+ // Giải mã Header và Payload
14
+ const decodedHeader = decodeBase64Url(header);
15
+ const decodedPayload = decodeBase64Url(payload);
16
+ console.log("Signature:", _);
17
+
18
+ console.log("Header:", decodedHeader);
19
+ console.log("Payload:", decodedPayload);
20
+ return {"header": decodedHeader, "payload": decodedPayload}
21
+ }
22
+
frontend/src/pages/AdminHomePage.js ADDED
File without changes
frontend/src/pages/CartPage.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BasicTemplate from "../templates/BasicTemplate";
2
+ import { Container, Row, Col, Card, Button } from "react-bootstrap";
3
+ import { useState, useEffect } from "react";
4
+ import DataStorage from "../organisms/DataStorage";
5
+
6
+ export default function CartPage() {
7
+
8
+ const [cartItems, setCartItems] = useState([]);
9
+
10
+ useEffect(() => {
11
+ // Lấy giỏ hàng từ sessionStorage
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"
41
+ src={item.imageSrc}
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">
64
+ <p className="text-center my-3">Giỏ hàng của bạn hiện đang trống.</p>
65
+ <Button as='a' href='/menu'>Xem menu</Button>
66
+ </div>
67
+ )}
68
+ </Container>
69
+ )
70
+ } />
71
+ )
72
+ }