Spaces:
Sleeping
Sleeping
Merge branch 'main' into feature/payment
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- Dockerfile +1 -1
- README.md +1 -0
- backend/src/app.module.ts +5 -1
- backend/src/common/enums/MenuItemType.enum.ts +4 -4
- backend/src/common/enums/OrderStatus.enum.ts +7 -0
- backend/src/common/enums/OrderType.enum.ts +5 -0
- backend/src/common/enums/PaymentMethod.enum.ts +5 -0
- backend/src/common/enums/role.enum.ts +8 -8
- backend/src/entities/branch-menu.entity.ts +8 -2
- backend/src/entities/branch.entity.ts +44 -35
- backend/src/entities/feed.entity.ts +2 -2
- backend/src/entities/menu-item.entity.ts +4 -3
- backend/src/entities/order-item.entity.ts +37 -0
- backend/src/entities/order.entity.ts +70 -0
- backend/src/entities/payment.entity.ts +25 -0
- backend/src/entities/receipt.entity.ts +57 -0
- backend/src/entities/user.entity.ts +59 -54
- backend/src/migrations/1729963419864-enum-role.ts +0 -22
- backend/src/migrations/1730474673934-RefactorAll.ts +56 -0
- backend/src/migrations/1730547520878-AddReceipt.ts +30 -0
- backend/src/migrations/1730549959767-RemoveEnums.ts +36 -0
- backend/src/modules/branch-menus/branch-menus.controller.ts +55 -0
- backend/src/modules/branch-menus/branch-menus.module.ts +13 -0
- backend/src/modules/branch-menus/branch-menus.service.ts +72 -0
- backend/src/modules/branch-menus/dto/create-branch-menu.dto.ts +10 -0
- backend/src/modules/branch-menus/dto/update-branch-menu.dto.ts +4 -0
- backend/src/modules/branch/branch.controller.ts +4 -4
- backend/src/modules/branch/branch.module.ts +1 -0
- backend/src/modules/branch/branch.service.ts +11 -2
- backend/src/modules/branch/dto/create-branch.dto.ts +3 -0
- backend/src/modules/menu-item/dto/create-menu-item.dto.ts +2 -2
- backend/src/modules/menu-item/dto/update-menu-item.dto.ts +2 -2
- backend/src/modules/menu-item/menu-item.module.ts +1 -0
- backend/src/modules/order/dto/create-order.dto.ts +26 -0
- backend/src/modules/order/dto/order-items.dto.ts +9 -0
- backend/src/modules/order/order.controller.ts +66 -0
- backend/src/modules/order/order.module.ts +12 -0
- backend/src/modules/order/order.service.ts +137 -0
- frontend/.gitignore +4 -0
- frontend/package-lock.json +153 -0
- frontend/package.json +4 -0
- frontend/public/default_avatar.jpg +0 -0
- frontend/src/index.js +12 -0
- frontend/src/molecules/AdminNavBar.js +0 -0
- frontend/src/molecules/Navbar.js +14 -12
- frontend/src/organisms/DataStorage.js +91 -0
- frontend/src/organisms/MenuSection.js +4 -3
- frontend/src/organisms/jwtDecoder.js +22 -0
- frontend/src/pages/AdminHomePage.js +0 -0
- 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
|
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 |
-
|
|
|
|
|
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 =
|
3 |
-
TRANG_MIENG =
|
4 |
-
GIAI_KHAT =
|
5 |
-
KHAC =
|
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 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
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 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
@
|
19 |
-
|
20 |
-
|
21 |
-
@Column()
|
22 |
-
|
23 |
-
|
24 |
-
@Column()
|
25 |
-
|
26 |
-
|
27 |
-
@
|
28 |
-
|
29 |
-
|
30 |
-
@
|
31 |
-
|
32 |
-
|
33 |
-
@
|
34 |
-
|
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 |
-
@
|
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({
|
24 |
-
item_type:
|
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 |
-
@
|
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 |
-
|
11 |
-
|
12 |
-
import {
|
13 |
-
import {
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
@
|
49 |
-
create_at: Date;
|
50 |
-
|
51 |
-
@
|
52 |
-
|
53 |
-
|
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 |
-
@
|
|
|
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 {
|
|
|
|
|
|
|
|
|
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('
|
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 |
-
@
|
14 |
@IsOptional()
|
15 |
-
|
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 |
-
@
|
13 |
@IsOptional()
|
14 |
-
|
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
|
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 |
-
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
16 |
navigate('/');
|
17 |
}
|
18 |
|
19 |
-
let username =
|
20 |
-
let 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
|
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 |
+
}
|