"feat: enhance order management with new APIs and schema changes"
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
"unseed": "tsx ./prisma/unseed.ts",
|
"unseed": "tsx ./prisma/unseed.ts",
|
||||||
"db:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:push": "prisma db push --force-reset",
|
"db:push": "prisma db push --force-reset",
|
||||||
"db:push:w": "npx nodemon --delay 5 --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed && yarn db:studio\"",
|
"db:push:w": "npx nodemon --delay 5 --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed\"",
|
||||||
"db:studio": "prisma studio"
|
"db:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@@ -266,85 +266,85 @@ model Mail {
|
|||||||
// attachments MailAttachment[]
|
// attachments MailAttachment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model OrderHistory {
|
// model OrderHistory {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
orderTime DateTime
|
// orderTime DateTime @default(now())
|
||||||
paymentTime DateTime
|
// paymentTime DateTime
|
||||||
deliveryTime DateTime
|
// deliveryTime DateTime
|
||||||
completionTime DateTime
|
// completionTime DateTime
|
||||||
timeline Json[]
|
// timeline Json[]
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderShippingAddress {
|
// model OrderShippingAddress {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
fullAddress String
|
// fullAddress String
|
||||||
phoneNumber String
|
// phoneNumber String
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderPayment {
|
// model OrderPayment {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
cardType String
|
// cardType String
|
||||||
cardNumber String
|
// cardNumber String
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderDelivery {
|
// model OrderDelivery {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
shipBy String
|
// shipBy String
|
||||||
speedy String
|
// speedy String
|
||||||
trackingNumber String
|
// trackingNumber String
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderCustomer {
|
// model OrderCustomer {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
name String
|
// name String
|
||||||
email String
|
// email String
|
||||||
avatarUrl String
|
// avatarUrl String
|
||||||
ipAddress String
|
// ipAddress String
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderProductItem {
|
// model OrderProductItem {
|
||||||
id Int @id @default(autoincrement())
|
// id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
// createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
// updatedAt DateTime @updatedAt
|
||||||
//
|
// //
|
||||||
sku String
|
// sku String
|
||||||
name String
|
// name String
|
||||||
price Float
|
// price Float
|
||||||
coverUrl String
|
// coverUrl String
|
||||||
quantity Float
|
// quantity Float
|
||||||
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
// OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
|
||||||
orderItemId Int?
|
// orderItemId Int?
|
||||||
}
|
// }
|
||||||
|
|
||||||
model OrderItem {
|
model OrderItem {
|
||||||
id Int @id @default(autoincrement())
|
id String @id @default(uuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
//
|
//
|
||||||
taxes Float
|
taxes Float
|
||||||
status String
|
status String
|
||||||
@@ -354,12 +354,18 @@ model OrderItem {
|
|||||||
orderNumber String
|
orderNumber String
|
||||||
totalAmount Float
|
totalAmount Float
|
||||||
totalQuantity Float
|
totalQuantity Float
|
||||||
history OrderHistory[]
|
history Json
|
||||||
payment OrderPayment[]
|
payment Json
|
||||||
customer OrderCustomer[]
|
customer Json
|
||||||
delivery OrderDelivery[]
|
delivery Json
|
||||||
items OrderProductItem[]
|
items Json[]
|
||||||
shippingAddress OrderShippingAddress[]
|
shippingAddress Json
|
||||||
|
// OrderProductItem OrderProductItem[]
|
||||||
|
// OrderHistory OrderHistory[]
|
||||||
|
// OrderDelivery OrderDelivery[]
|
||||||
|
// OrderCustomer OrderCustomer[]
|
||||||
|
// OrderPayment OrderPayment[]
|
||||||
|
// OrderShippingAddress OrderShippingAddress[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/types/tour.ts
|
// src/types/tour.ts
|
||||||
|
@@ -23,6 +23,8 @@ import { ProductReview } from './seeds/productReview';
|
|||||||
import { ProductItem } from './seeds/productItem';
|
import { ProductItem } from './seeds/productItem';
|
||||||
import { FileStore } from './seeds/fileStore';
|
import { FileStore } from './seeds/fileStore';
|
||||||
import { userItemSeed } from './seeds/userItem';
|
import { userItemSeed } from './seeds/userItem';
|
||||||
|
import { orderItemSeed } from './seeds/orderItem';
|
||||||
|
|
||||||
//
|
//
|
||||||
// import { Blog } from './seeds/blog';
|
// import { Blog } from './seeds/blog';
|
||||||
// import { Mail } from './seeds/mail';
|
// import { Mail } from './seeds/mail';
|
||||||
@@ -39,6 +41,7 @@ import { userItemSeed } from './seeds/userItem';
|
|||||||
await FileStore;
|
await FileStore;
|
||||||
await ProductItem;
|
await ProductItem;
|
||||||
await userItemSeed;
|
await userItemSeed;
|
||||||
|
await orderItemSeed;
|
||||||
// await Blog;
|
// await Blog;
|
||||||
// await Mail;
|
// await Mail;
|
||||||
// await File;
|
// await File;
|
||||||
|
@@ -10,8 +10,8 @@ async function order() {
|
|||||||
title: 'Single Party with Dating',
|
title: 'Single Party with Dating',
|
||||||
order_time: new Date(),
|
order_time: new Date(),
|
||||||
last_payment_date: new Date(),
|
last_payment_date: new Date(),
|
||||||
status: 'Pending'
|
status: 'Pending',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
03_source/cms_backend/prisma/seeds/orderItem.ts
Normal file
94
03_source/cms_backend/prisma/seeds/orderItem.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { _mock } from './_mock';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
const ITEMS = Array.from({ length: 3 }, (_, index) => ({
|
||||||
|
id: _mock.id(index),
|
||||||
|
sku: `16H9UR${index}`,
|
||||||
|
quantity: index + 1,
|
||||||
|
name: _mock.productName(index),
|
||||||
|
coverUrl: _mock.image.product(index),
|
||||||
|
price: _mock.number.price(index),
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function orderItem() {
|
||||||
|
await prisma.orderItem.deleteMany({});
|
||||||
|
|
||||||
|
for (let index = 1; index < 20 + 1; index++) {
|
||||||
|
const shipping = 10;
|
||||||
|
const discount = 10;
|
||||||
|
const taxes = 10;
|
||||||
|
const items = (index % 2 && ITEMS.slice(0, 1)) || (index % 3 && ITEMS.slice(1, 3)) || ITEMS;
|
||||||
|
const totalQuantity = items.reduce((accumulator, item) => accumulator + item.quantity, 0);
|
||||||
|
const subtotal = items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0);
|
||||||
|
const totalAmount = subtotal - shipping - discount + taxes;
|
||||||
|
|
||||||
|
const customer = {
|
||||||
|
id: _mock.id(index),
|
||||||
|
name: _mock.fullName(index),
|
||||||
|
email: _mock.email(index),
|
||||||
|
avatarUrl: _mock.image.avatar(index),
|
||||||
|
ipAddress: '192.158.1.38',
|
||||||
|
};
|
||||||
|
|
||||||
|
const delivery = { shipBy: 'DHL', speedy: 'Standard', trackingNumber: 'SPX037739199373' };
|
||||||
|
|
||||||
|
const history = {
|
||||||
|
orderTime: _mock.time(1),
|
||||||
|
paymentTime: _mock.time(2),
|
||||||
|
deliveryTime: _mock.time(3),
|
||||||
|
completionTime: _mock.time(4),
|
||||||
|
timeline: [
|
||||||
|
{ title: 'Delivery successful', time: _mock.time(1) },
|
||||||
|
{ title: 'Transporting to [2]', time: _mock.time(2) },
|
||||||
|
{ title: 'Transporting to [1]', time: _mock.time(3) },
|
||||||
|
{ title: 'The shipping unit has picked up the goods', time: _mock.time(4) },
|
||||||
|
{ title: 'Order has been created', time: _mock.time(5) },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const temp = await prisma.orderItem.upsert({
|
||||||
|
where: { id: index.toString() },
|
||||||
|
update: {},
|
||||||
|
create: {
|
||||||
|
id: index.toString(),
|
||||||
|
orderNumber: `#601${index}`,
|
||||||
|
taxes,
|
||||||
|
items,
|
||||||
|
history,
|
||||||
|
subtotal: items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0),
|
||||||
|
shipping,
|
||||||
|
discount,
|
||||||
|
customer,
|
||||||
|
delivery,
|
||||||
|
totalAmount,
|
||||||
|
totalQuantity,
|
||||||
|
shippingAddress: {
|
||||||
|
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
|
||||||
|
phoneNumber: '365-374-4961',
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
//
|
||||||
|
cardType: 'mastercard',
|
||||||
|
cardNumber: '4111 1111 1111 1111',
|
||||||
|
},
|
||||||
|
status: (index % 2 && 'completed') || (index % 3 && 'pending') || (index % 4 && 'cancelled') || 'refunded',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('seed orderItem done');
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderItemSeed = orderItem()
|
||||||
|
.then(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
console.error(e);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { orderItemSeed };
|
@@ -0,0 +1,98 @@
|
|||||||
|
// src/app/api/product/saveProduct/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// save product to db by id
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* PUT - change order status
|
||||||
|
*************************************** */
|
||||||
|
export async function PUT(req: NextRequest) {
|
||||||
|
// logger('[Order] list', products.length);
|
||||||
|
const { searchParams } = req.nextUrl;
|
||||||
|
const orderId = searchParams.get('orderId');
|
||||||
|
|
||||||
|
// RULES: orderId must exist
|
||||||
|
if (!orderId) {
|
||||||
|
return response({ message: 'Order ID is required!' }, STATUS.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await req.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const order = await prisma.orderItem.updateMany({
|
||||||
|
where: { id: orderId },
|
||||||
|
data: { status: data.status },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response({ order }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ data });
|
||||||
|
return handleError('Order - Get list', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IProductItem = {
|
||||||
|
id: string;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
price: number;
|
||||||
|
taxes: number;
|
||||||
|
tags: string[];
|
||||||
|
sizes: string[];
|
||||||
|
publish: string;
|
||||||
|
gender: string[];
|
||||||
|
coverUrl: string;
|
||||||
|
images: string[];
|
||||||
|
colors: string[];
|
||||||
|
quantity: number;
|
||||||
|
category: string;
|
||||||
|
available: number;
|
||||||
|
totalSold: number;
|
||||||
|
description: string;
|
||||||
|
totalRatings: number;
|
||||||
|
totalReviews: number;
|
||||||
|
// createdAt: IDateValue;
|
||||||
|
inventoryType: string;
|
||||||
|
subDescription: string;
|
||||||
|
priceSale: number | null;
|
||||||
|
// reviews: IProductReview[];
|
||||||
|
newLabel: {
|
||||||
|
content: string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
saleLabel: {
|
||||||
|
content: string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
ratings: {
|
||||||
|
name: string;
|
||||||
|
starCount: number;
|
||||||
|
reviewCount: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IDateValue = string | number | null;
|
||||||
|
|
||||||
|
export type IProductReview = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
rating: number;
|
||||||
|
comment: string;
|
||||||
|
helpful: number;
|
||||||
|
avatarUrl: string;
|
||||||
|
postedAt: IDateValue;
|
||||||
|
isPurchased: boolean;
|
||||||
|
attachments?: string[];
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
PUT http://localhost:7272/api/order/changeStatus?orderId=1
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"data":{"status": "helloworld"}
|
||||||
|
}
|
||||||
|
|
53
03_source/cms_backend/src/app/api/order/createUser/route.ts
Normal file
53
03_source/cms_backend/src/app/api/order/createUser/route.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// src/app/api/user/createUser/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// create user to db
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
//
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
***************************************
|
||||||
|
* POST - create User
|
||||||
|
***************************************
|
||||||
|
*/
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
// logger('[User] list', users.length);
|
||||||
|
const { data } = await req.json();
|
||||||
|
const createForm: CreateUserData = data as unknown as CreateUserData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await prisma.userItem.create({ data: createForm });
|
||||||
|
return response({ user }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
return handleError('User - Create', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateUserData = {
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
role: string;
|
||||||
|
email: string;
|
||||||
|
state: string;
|
||||||
|
status: string;
|
||||||
|
address: string;
|
||||||
|
country: string;
|
||||||
|
zipCode: string;
|
||||||
|
company: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
isVerified: boolean;
|
||||||
|
//
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
@@ -0,0 +1,4 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:7272/api/user/createUser
|
||||||
|
|
47
03_source/cms_backend/src/app/api/order/deleteUser/route.ts
Normal file
47
03_source/cms_backend/src/app/api/order/deleteUser/route.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// src/app/api/product/deleteUser/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// delete product from db by id
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { logger } from 'src/utils/logger';
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* handle Delete Users
|
||||||
|
*************************************** */
|
||||||
|
export async function DELETE(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = req.nextUrl;
|
||||||
|
|
||||||
|
// RULES: userId must exist
|
||||||
|
const userId = searchParams.get('userId');
|
||||||
|
if (!userId) {
|
||||||
|
return response({ message: 'User ID is required!' }, STATUS.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: userId confirmed exist, run below
|
||||||
|
const user = await prisma.userItem.delete({
|
||||||
|
//
|
||||||
|
where: { id: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger('[User] details', user.id);
|
||||||
|
|
||||||
|
return response({ user }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
return handleError('User - Get details', error);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
DELETE http://localhost:7272/api/user/deleteUser?userId=3f431e6f-ad05-4d60-9c25-6a7e92a954ad
|
47
03_source/cms_backend/src/app/api/order/details/route.ts
Normal file
47
03_source/cms_backend/src/app/api/order/details/route.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// src/app/api/order/details/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// read order from db by id
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { logger } from 'src/utils/logger';
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* GET Order detail
|
||||||
|
*************************************** */
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = req.nextUrl;
|
||||||
|
|
||||||
|
// RULES: orderId must exist
|
||||||
|
const orderId = searchParams.get('orderId');
|
||||||
|
if (!orderId) {
|
||||||
|
return response({ message: 'orderId is required!' }, STATUS.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: orderId confirmed exist, run below
|
||||||
|
const order = await prisma.orderItem.findFirst({
|
||||||
|
// include: { reviews: true },
|
||||||
|
where: { id: orderId.toString() },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
return response({ message: 'Order not found!' }, STATUS.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger('[Order] details', order.id);
|
||||||
|
|
||||||
|
return response({ order }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
return handleError('Product - Get details', error);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
GET http://localhost:7272/api/order/details?orderId=1
|
||||||
|
|
@@ -0,0 +1,30 @@
|
|||||||
|
// src/app/api/product/image/upload/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// handle upload product image
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
// import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* GET - Products
|
||||||
|
*************************************** */
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { data } = await req.json();
|
||||||
|
console.log('helloworld');
|
||||||
|
|
||||||
|
return response({ hello: 'world' }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ hello: 'world' });
|
||||||
|
return handleError('Product - store product image', error);
|
||||||
|
}
|
||||||
|
}
|
22
03_source/cms_backend/src/app/api/order/list/route.ts
Normal file
22
03_source/cms_backend/src/app/api/order/list/route.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// src/app/api/order/list/route.ts
|
||||||
|
import { logger } from 'src/utils/logger';
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* GET - OrderItem
|
||||||
|
*************************************** */
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const orders = await prisma.orderItem.findMany();
|
||||||
|
|
||||||
|
logger('[Order] list', orders.length);
|
||||||
|
|
||||||
|
return response({ orders }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
return handleError('OrderItem - Get list', error);
|
||||||
|
}
|
||||||
|
}
|
3
03_source/cms_backend/src/app/api/order/list/test.http
Normal file
3
03_source/cms_backend/src/app/api/order/list/test.http
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
GET http://localhost:7272/api/order/list
|
115
03_source/cms_backend/src/app/api/order/saveUser/route.ts
Normal file
115
03_source/cms_backend/src/app/api/order/saveUser/route.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// src/app/api/product/saveProduct/route.ts
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// save product to db by id
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// T.B.A.
|
||||||
|
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import prisma from '../../../lib/prisma';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* GET - Products
|
||||||
|
*************************************** */
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
// logger('[Product] list', products.length);
|
||||||
|
const { searchParams } = req.nextUrl;
|
||||||
|
const userId = searchParams.get('userId');
|
||||||
|
|
||||||
|
// RULES: userId must exist
|
||||||
|
if (!userId) {
|
||||||
|
return response({ message: 'Product ID is required!' }, STATUS.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await req.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await prisma.userItem.updateMany({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
status: data.status,
|
||||||
|
avatarUrl: data.avatarUrl,
|
||||||
|
isVerified: data.isVerified,
|
||||||
|
name: data.name,
|
||||||
|
email: data.email,
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
country: data.country,
|
||||||
|
state: data.state,
|
||||||
|
city: data.city,
|
||||||
|
address: data.address,
|
||||||
|
zipCode: data.zipCode,
|
||||||
|
company: data.company,
|
||||||
|
role: data.role,
|
||||||
|
//
|
||||||
|
username: data.username,
|
||||||
|
password: data.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response({ user }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ hello: 'world', data });
|
||||||
|
return handleError('Product - Get list', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IProductItem = {
|
||||||
|
id: string;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
price: number;
|
||||||
|
taxes: number;
|
||||||
|
tags: string[];
|
||||||
|
sizes: string[];
|
||||||
|
publish: string;
|
||||||
|
gender: string[];
|
||||||
|
coverUrl: string;
|
||||||
|
images: string[];
|
||||||
|
colors: string[];
|
||||||
|
quantity: number;
|
||||||
|
category: string;
|
||||||
|
available: number;
|
||||||
|
totalSold: number;
|
||||||
|
description: string;
|
||||||
|
totalRatings: number;
|
||||||
|
totalReviews: number;
|
||||||
|
// createdAt: IDateValue;
|
||||||
|
inventoryType: string;
|
||||||
|
subDescription: string;
|
||||||
|
priceSale: number | null;
|
||||||
|
// reviews: IProductReview[];
|
||||||
|
newLabel: {
|
||||||
|
content: string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
saleLabel: {
|
||||||
|
content: string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
ratings: {
|
||||||
|
name: string;
|
||||||
|
starCount: number;
|
||||||
|
reviewCount: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IDateValue = string | number | null;
|
||||||
|
|
||||||
|
export type IProductReview = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
rating: number;
|
||||||
|
comment: string;
|
||||||
|
helpful: number;
|
||||||
|
avatarUrl: string;
|
||||||
|
postedAt: IDateValue;
|
||||||
|
isPurchased: boolean;
|
||||||
|
attachments?: string[];
|
||||||
|
};
|
@@ -0,0 +1,3 @@
|
|||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:7272/api/user/list
|
37
03_source/cms_backend/src/app/api/order/search/route.ts
Normal file
37
03_source/cms_backend/src/app/api/order/search/route.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { logger } from 'src/utils/logger';
|
||||||
|
import { STATUS, response, handleError } from 'src/utils/response';
|
||||||
|
|
||||||
|
import { _products } from 'src/_mock/_product';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
/** **************************************
|
||||||
|
* GET - Search products
|
||||||
|
*************************************** */
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = req.nextUrl;
|
||||||
|
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
return response({ results: [] }, STATUS.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = _products();
|
||||||
|
|
||||||
|
// Accept search by name or sku
|
||||||
|
const results = products.filter(
|
||||||
|
({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
|
||||||
|
logger('[Product] search-results', results.length);
|
||||||
|
|
||||||
|
return response({ results }, STATUS.OK);
|
||||||
|
} catch (error) {
|
||||||
|
return handleError('Product - Get search', error);
|
||||||
|
}
|
||||||
|
}
|
@@ -75,7 +75,11 @@ export const _orders = Array.from({ length: 20 }, (_, index) => {
|
|||||||
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
|
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
|
||||||
phoneNumber: '365-374-4961',
|
phoneNumber: '365-374-4961',
|
||||||
},
|
},
|
||||||
payment: { cardType: 'mastercard', cardNumber: '**** **** **** 5678' },
|
payment: {
|
||||||
|
//
|
||||||
|
cardType: 'mastercard',
|
||||||
|
cardNumber: '**** **** **** 5678',
|
||||||
|
},
|
||||||
status:
|
status:
|
||||||
(index % 2 && 'completed') ||
|
(index % 2 && 'completed') ||
|
||||||
(index % 3 && 'pending') ||
|
(index % 3 && 'pending') ||
|
||||||
|
226
03_source/frontend/src/actions/order.ts
Normal file
226
03_source/frontend/src/actions/order.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
// src/actions/order.ts
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||||
|
import type { IProductItem } from 'src/types/product';
|
||||||
|
import type { IOrderItem } from 'src/types/order';
|
||||||
|
import type { SWRConfiguration } from 'swr';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
const swrOptions: SWRConfiguration = {
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type OrdersData = {
|
||||||
|
orders: IOrderItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useGetOrders() {
|
||||||
|
const url = endpoints.order.list;
|
||||||
|
|
||||||
|
const { data, isLoading, error, isValidating, mutate } = useSWR<OrdersData>(
|
||||||
|
url,
|
||||||
|
fetcher,
|
||||||
|
swrOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
orders: data?.orders || [],
|
||||||
|
ordersLoading: isLoading,
|
||||||
|
ordersError: error,
|
||||||
|
ordersValidating: isValidating,
|
||||||
|
ordersEmpty: !isLoading && !isValidating && !data?.orders.length,
|
||||||
|
mutate,
|
||||||
|
}),
|
||||||
|
[data?.orders, error, isLoading, isValidating, mutate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return memoizedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type OrderData = {
|
||||||
|
order: IOrderItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useGetOrder(orderId: string) {
|
||||||
|
const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
|
||||||
|
|
||||||
|
const { data, isLoading, error, isValidating } = useSWR<OrderData>(url, fetcher, swrOptions);
|
||||||
|
|
||||||
|
const memoizedValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
order: data?.order,
|
||||||
|
orderLoading: isLoading,
|
||||||
|
orderError: error,
|
||||||
|
orderValidating: isValidating,
|
||||||
|
}),
|
||||||
|
[data?.order, error, isLoading, isValidating]
|
||||||
|
);
|
||||||
|
|
||||||
|
return memoizedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type SearchResultsData = {
|
||||||
|
results: IProductItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useSearchProducts(query: string) {
|
||||||
|
const url = query ? [endpoints.product.search, { params: { query } }] : '';
|
||||||
|
|
||||||
|
const { data, isLoading, error, isValidating } = useSWR<SearchResultsData>(url, fetcher, {
|
||||||
|
...swrOptions,
|
||||||
|
keepPreviousData: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const memoizedValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
searchResults: data?.results || [],
|
||||||
|
searchLoading: isLoading,
|
||||||
|
searchError: error,
|
||||||
|
searchValidating: isValidating,
|
||||||
|
searchEmpty: !isLoading && !isValidating && !data?.results.length,
|
||||||
|
}),
|
||||||
|
[data?.results, error, isLoading, isValidating]
|
||||||
|
);
|
||||||
|
|
||||||
|
return memoizedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type SaveOrderData = {
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
role: string;
|
||||||
|
email: string;
|
||||||
|
state: string;
|
||||||
|
status: string;
|
||||||
|
address: string;
|
||||||
|
country: string;
|
||||||
|
zipCode: string;
|
||||||
|
company: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
isVerified: boolean;
|
||||||
|
//
|
||||||
|
ordername: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function saveOrder(orderId: string, saveOrderData: SaveOrderData) {
|
||||||
|
// const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
|
||||||
|
|
||||||
|
const res = await axiosInstance.post(
|
||||||
|
//
|
||||||
|
`http://localhost:7272/api/order/saveOrder?orderId=${orderId}`,
|
||||||
|
{
|
||||||
|
data: saveOrderData,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadOrderImage(saveOrderData: SaveOrderData) {
|
||||||
|
console.log('uploadOrderImage ?');
|
||||||
|
// const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
|
||||||
|
|
||||||
|
const res = await axiosInstance.get('http://localhost:7272/api/product/helloworld');
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type CreateOrderData = {
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
role: string;
|
||||||
|
email: string;
|
||||||
|
state: string;
|
||||||
|
status: string;
|
||||||
|
address: string;
|
||||||
|
country: string;
|
||||||
|
zipCode: string;
|
||||||
|
company: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
isVerified: boolean;
|
||||||
|
//
|
||||||
|
ordername: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createOrder(createOrderData: CreateOrderData) {
|
||||||
|
console.log('create product ?');
|
||||||
|
// const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
|
||||||
|
|
||||||
|
const res = await axiosInstance.post('http://localhost:7272/api/order/createOrder', {
|
||||||
|
data: createOrderData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type DeleteOrderResponse = {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function deleteOrder(orderId: string): Promise<DeleteOrderResponse> {
|
||||||
|
const url = `http://localhost:7272/api/order/deleteOrder?orderId=${orderId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axiosInstance.delete(url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Order deleted successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : 'Failed to delete product',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ChangeStatusResponse = {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function changeStatus(
|
||||||
|
orderId: string,
|
||||||
|
newOrderStatus: string
|
||||||
|
): Promise<ChangeStatusResponse> {
|
||||||
|
const url = endpoints.order.changeStatus(orderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axiosInstance.put(url, { data: { status: newOrderStatus } });
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'status updated successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : 'Failed to delete product',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
// src/actions/product.ts
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||||
import type { IProductItem } from 'src/types/product';
|
import type { IProductItem } from 'src/types/product';
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
|
// src/pages/dashboard/order/details.tsx
|
||||||
import { useParams } from 'src/routes/hooks';
|
import { useParams } from 'src/routes/hooks';
|
||||||
|
|
||||||
import { _orders } from 'src/_mock/_order';
|
import { _orders } from 'src/_mock/_order';
|
||||||
import { CONFIG } from 'src/global-config';
|
import { CONFIG } from 'src/global-config';
|
||||||
|
import { useGetOrder } from 'src/actions/order';
|
||||||
|
|
||||||
import { OrderDetailsView } from 'src/sections/order/view';
|
import { OrderDetailsView } from 'src/sections/order/view';
|
||||||
|
|
||||||
@@ -12,13 +14,18 @@ const metadata = { title: `Order details | Dashboard - ${CONFIG.appName}` };
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { id = '' } = useParams();
|
const { id = '' } = useParams();
|
||||||
|
|
||||||
const currentOrder = _orders.find((order) => order.id === id);
|
// const currentOrder = _orders.find((order) => order.id === id);
|
||||||
|
// TODO: error handling
|
||||||
|
const { order, orderLoading, orderError } = useGetOrder(id);
|
||||||
|
|
||||||
|
if (!order) return <>loading</>;
|
||||||
|
if (orderLoading) return <>loading</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<title>{metadata.title}</title>
|
<title>{metadata.title}</title>
|
||||||
|
|
||||||
<OrderDetailsView order={currentOrder} />
|
<OrderDetailsView order={order} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// src/pages/dashboard/product/details.tsx
|
||||||
|
|
||||||
import { useParams } from 'src/routes/hooks';
|
import { useParams } from 'src/routes/hooks';
|
||||||
|
|
||||||
import { CONFIG } from 'src/global-config';
|
import { CONFIG } from 'src/global-config';
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
// src/sections/order/order-details-history.tsx
|
||||||
import type { IOrderHistory } from 'src/types/order';
|
import type { IOrderHistory } from 'src/types/order';
|
||||||
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -13,6 +14,7 @@ import TimelineConnector from '@mui/lab/TimelineConnector';
|
|||||||
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
||||||
|
|
||||||
import { fDateTime } from 'src/utils/format-time';
|
import { fDateTime } from 'src/utils/format-time';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function OrderDetailsHistory({ history }: Props) {
|
export function OrderDetailsHistory({ history }: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderSummary = () => (
|
const renderSummary = () => (
|
||||||
<Paper
|
<Paper
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -37,22 +41,22 @@ export function OrderDetailsHistory({ history }: Props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>Order time</Box>
|
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>{t('Order time')}</Box>
|
||||||
{fDateTime(history?.orderTime)}
|
{fDateTime(history?.orderTime)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>Payment time</Box>
|
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>{t('Payment time')}</Box>
|
||||||
{fDateTime(history?.orderTime)}
|
{fDateTime(history?.orderTime)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>Delivery time for the carrier</Box>
|
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>{t('Delivery time for the carrier')}</Box>
|
||||||
{fDateTime(history?.orderTime)}
|
{fDateTime(history?.orderTime)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>Completion time</Box>
|
<Box sx={{ mb: 0.5, color: 'text.disabled' }}>{t('Completion time')}</Box>
|
||||||
{fDateTime(history?.orderTime)}
|
{fDateTime(history?.orderTime)}
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@@ -12,6 +12,7 @@ import { fCurrency } from 'src/utils/format-number';
|
|||||||
|
|
||||||
import { Iconify } from 'src/components/iconify';
|
import { Iconify } from 'src/components/iconify';
|
||||||
import { Scrollbar } from 'src/components/scrollbar';
|
import { Scrollbar } from 'src/components/scrollbar';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ export function OrderDetailsItems({
|
|||||||
totalAmount,
|
totalAmount,
|
||||||
...other
|
...other
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const renderTotal = () => (
|
const renderTotal = () => (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -47,32 +49,32 @@ export function OrderDetailsItems({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<Box sx={{ color: 'text.secondary' }}>Subtotal</Box>
|
<Box sx={{ color: 'text.secondary' }}>{t('Subtotal')}</Box>
|
||||||
<Box sx={{ width: 160, typography: 'subtitle2' }}>{fCurrency(subtotal) || '-'}</Box>
|
<Box sx={{ width: 160, typography: 'subtitle2' }}>{fCurrency(subtotal) || '-'}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<Box sx={{ color: 'text.secondary' }}>Shipping</Box>
|
<Box sx={{ color: 'text.secondary' }}>{t('Shipping')}</Box>
|
||||||
<Box sx={{ width: 160, ...(shipping && { color: 'error.main' }) }}>
|
<Box sx={{ width: 160, ...(shipping && { color: 'error.main' }) }}>
|
||||||
{shipping ? `- ${fCurrency(shipping)}` : '-'}
|
{shipping ? `- ${fCurrency(shipping)}` : '-'}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<Box sx={{ color: 'text.secondary' }}>Discount</Box>
|
<Box sx={{ color: 'text.secondary' }}>{t('Discount')}</Box>
|
||||||
<Box sx={{ width: 160, ...(discount && { color: 'error.main' }) }}>
|
<Box sx={{ width: 160, ...(discount && { color: 'error.main' }) }}>
|
||||||
{discount ? `- ${fCurrency(discount)}` : '-'}
|
{discount ? `- ${fCurrency(discount)}` : '-'}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<Box sx={{ color: 'text.secondary' }}>Taxes</Box>
|
<Box sx={{ color: 'text.secondary' }}>{t('Taxes')}</Box>
|
||||||
|
|
||||||
<Box sx={{ width: 160 }}>{taxes ? fCurrency(taxes) : '-'}</Box>
|
<Box sx={{ width: 160 }}>{taxes ? fCurrency(taxes) : '-'}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', typography: 'subtitle1' }}>
|
<Box sx={{ display: 'flex', typography: 'subtitle1' }}>
|
||||||
<div>Total</div>
|
<div>{t('Total')}</div>
|
||||||
<Box sx={{ width: 160 }}>{fCurrency(totalAmount) || '-'}</Box>
|
<Box sx={{ width: 160 }}>{fCurrency(totalAmount) || '-'}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// src/sections/order/order-details-toolbar.tsx
|
||||||
|
|
||||||
import type { IDateValue } from 'src/types/common';
|
import type { IDateValue } from 'src/types/common';
|
||||||
|
|
||||||
import { usePopover } from 'minimal-shared/hooks';
|
import { usePopover } from 'minimal-shared/hooks';
|
||||||
@@ -17,6 +19,7 @@ import { fDateTime } from 'src/utils/format-time';
|
|||||||
import { Label } from 'src/components/label';
|
import { Label } from 'src/components/label';
|
||||||
import { Iconify } from 'src/components/iconify';
|
import { Iconify } from 'src/components/iconify';
|
||||||
import { CustomPopover } from 'src/components/custom-popover';
|
import { CustomPopover } from 'src/components/custom-popover';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ export function OrderDetailsToolbar({
|
|||||||
statusOptions,
|
statusOptions,
|
||||||
onChangeStatus,
|
onChangeStatus,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const menuActions = usePopover();
|
const menuActions = usePopover();
|
||||||
|
|
||||||
const renderMenuActions = () => (
|
const renderMenuActions = () => (
|
||||||
@@ -56,7 +60,7 @@ export function OrderDetailsToolbar({
|
|||||||
onChangeStatus(option.value);
|
onChangeStatus(option.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option.label}
|
{t(option.label)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
@@ -124,11 +128,11 @@ export function OrderDetailsToolbar({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<Iconify icon="solar:printer-minimalistic-bold" />}
|
startIcon={<Iconify icon="solar:printer-minimalistic-bold" />}
|
||||||
>
|
>
|
||||||
Print
|
{t('Print (not implemented)')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="inherit" variant="contained" startIcon={<Iconify icon="solar:pen-bold" />}>
|
<Button color="inherit" variant="contained" startIcon={<Iconify icon="solar:pen-bold" />}>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
// src/sections/order/view/order-details-view.tsx
|
||||||
|
|
||||||
import type { IOrderItem } from 'src/types/order';
|
import type { IOrderItem } from 'src/types/order';
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
@@ -19,19 +21,39 @@ import { OrderDetailsPayment } from '../order-details-payment';
|
|||||||
import { OrderDetailsCustomer } from '../order-details-customer';
|
import { OrderDetailsCustomer } from '../order-details-customer';
|
||||||
import { OrderDetailsDelivery } from '../order-details-delivery';
|
import { OrderDetailsDelivery } from '../order-details-delivery';
|
||||||
import { OrderDetailsShipping } from '../order-details-shipping';
|
import { OrderDetailsShipping } from '../order-details-shipping';
|
||||||
|
import { useTranslate } from 'src/locales';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { changeStatus } from 'src/actions/order';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
order?: IOrderItem;
|
order: IOrderItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function OrderDetailsView({ order }: Props) {
|
export function OrderDetailsView({ order }: Props) {
|
||||||
const [status, setStatus] = useState(order?.status);
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangeStatus = useCallback((newValue: string) => {
|
const [status, setStatus] = useState(order.status);
|
||||||
setStatus(newValue);
|
|
||||||
}, []);
|
const handleChangeStatus = useCallback(
|
||||||
|
async (newValue: string) => {
|
||||||
|
setStatus(newValue);
|
||||||
|
// change order status
|
||||||
|
try {
|
||||||
|
if (order?.id) {
|
||||||
|
await changeStatus(order.id, newValue);
|
||||||
|
|
||||||
|
toast.success('order status updated');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.warning('error during update order status');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[order.id]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardContent>
|
<DashboardContent>
|
||||||
@@ -47,7 +69,12 @@ export function OrderDetailsView({ order }: Props) {
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid size={{ xs: 12, md: 8 }}>
|
<Grid size={{ xs: 12, md: 8 }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{ gap: 3, display: 'flex', flexDirection: { xs: 'column-reverse', md: 'column' } }}
|
sx={{
|
||||||
|
//
|
||||||
|
gap: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column-reverse', md: 'column' },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<OrderDetailsItems
|
<OrderDetailsItems
|
||||||
items={order?.items}
|
items={order?.items}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
import type { TableHeadCellProps } from 'src/components/table';
|
import type { TableHeadCellProps } from 'src/components/table';
|
||||||
import type { IOrderItem, IOrderTableFilters } from 'src/types/order';
|
import type { IOrderItem, IOrderTableFilters } from 'src/types/order';
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { varAlpha } from 'minimal-shared/utils';
|
import { varAlpha } from 'minimal-shared/utils';
|
||||||
import { useBoolean, useSetState } from 'minimal-shared/hooks';
|
import { useBoolean, useSetState } from 'minimal-shared/hooks';
|
||||||
|
|
||||||
@@ -46,6 +46,8 @@ import { OrderTableRow } from '../order-table-row';
|
|||||||
import { OrderTableToolbar } from '../order-table-toolbar';
|
import { OrderTableToolbar } from '../order-table-toolbar';
|
||||||
import { OrderTableFiltersResult } from '../order-table-filters-result';
|
import { OrderTableFiltersResult } from '../order-table-filters-result';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useRouter } from 'src/routes/hooks';
|
||||||
|
import { deleteOrder, useGetOrders } from 'src/actions/order';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -55,12 +57,7 @@ const STATUS_OPTIONS = [{ value: 'all', label: 'All' }, ...ORDER_STATUS_OPTIONS]
|
|||||||
|
|
||||||
export function OrderListView() {
|
export function OrderListView() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
const table = useTable({ defaultOrderBy: 'orderNumber' });
|
|
||||||
|
|
||||||
const confirmDialog = useBoolean();
|
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<IOrderItem[]>(_orders);
|
|
||||||
|
|
||||||
const TABLE_HEAD: TableHeadCellProps[] = [
|
const TABLE_HEAD: TableHeadCellProps[] = [
|
||||||
{ id: 'orderNumber', label: t('Order'), width: 88 },
|
{ id: 'orderNumber', label: t('Order'), width: 88 },
|
||||||
@@ -72,6 +69,18 @@ export function OrderListView() {
|
|||||||
{ id: '', width: 88 },
|
{ id: '', width: 88 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const { orders, mutate, ordersLoading } = useGetOrders();
|
||||||
|
|
||||||
|
const table = useTable({ defaultOrderBy: 'orderNumber' });
|
||||||
|
|
||||||
|
const confirmDialog = useBoolean();
|
||||||
|
|
||||||
|
const [tableData, setTableData] = useState<IOrderItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTableData(orders);
|
||||||
|
}, [orders]);
|
||||||
|
|
||||||
const filters = useSetState<IOrderTableFilters>({
|
const filters = useSetState<IOrderTableFilters>({
|
||||||
name: '',
|
name: '',
|
||||||
status: 'all',
|
status: 'all',
|
||||||
@@ -99,16 +108,23 @@ export function OrderListView() {
|
|||||||
const notFound = (!dataFiltered.length && canReset) || !dataFiltered.length;
|
const notFound = (!dataFiltered.length && canReset) || !dataFiltered.length;
|
||||||
|
|
||||||
const handleDeleteRow = useCallback(
|
const handleDeleteRow = useCallback(
|
||||||
(id: string) => {
|
async (id: string) => {
|
||||||
const deleteRow = tableData.filter((row) => row.id !== id);
|
// const deleteRow = tableData.filter((row) => row.id !== id);
|
||||||
|
|
||||||
toast.success('Delete success!');
|
try {
|
||||||
|
await deleteOrder(id);
|
||||||
|
toast.success('Delete success!');
|
||||||
|
mutate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('Delete failed!');
|
||||||
|
}
|
||||||
|
|
||||||
setTableData(deleteRow);
|
// toast.success('Delete success!');
|
||||||
|
// setTableData(deleteRow);
|
||||||
table.onUpdatePageDeleteRow(dataInPage.length);
|
// table.onUpdatePageDeleteRow(dataInPage.length);
|
||||||
},
|
},
|
||||||
[dataInPage.length, table, tableData]
|
[table, tableData, mutate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteRows = useCallback(() => {
|
const handleDeleteRows = useCallback(() => {
|
||||||
@@ -133,7 +149,7 @@ export function OrderListView() {
|
|||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={confirmDialog.value}
|
open={confirmDialog.value}
|
||||||
onClose={confirmDialog.onFalse}
|
onClose={confirmDialog.onFalse}
|
||||||
title="Delete"
|
title={t('Delete')}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
Are you sure want to delete <strong> {table.selected.length} </strong> items?
|
Are you sure want to delete <strong> {table.selected.length} </strong> items?
|
||||||
@@ -145,7 +161,7 @@ export function OrderListView() {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteRows();
|
handleDeleteRows();
|
||||||
confirmDialog.onFalse();
|
// confirmDialog.onFalse();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Delete')}
|
{t('Delete')}
|
||||||
@@ -154,12 +170,20 @@ export function OrderListView() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mutate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!orders) return <>loading</>;
|
||||||
|
if (ordersLoading) return <>loading</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DashboardContent>
|
<DashboardContent>
|
||||||
<CustomBreadcrumbs
|
<CustomBreadcrumbs
|
||||||
heading="List"
|
heading="List"
|
||||||
links={[
|
links={[
|
||||||
|
//
|
||||||
{ name: t('Dashboard'), href: paths.dashboard.root },
|
{ name: t('Dashboard'), href: paths.dashboard.root },
|
||||||
{ name: t('Order'), href: paths.dashboard.order.root },
|
{ name: t('Order'), href: paths.dashboard.order.root },
|
||||||
{ name: t('List') },
|
{ name: t('List') },
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// src/sections/product/view/product-details-view.tsx
|
||||||
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
|
@@ -252,7 +252,7 @@ export function UserListView() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Tooltip title="Delete">
|
<Tooltip title={t('Delete')}>
|
||||||
<IconButton color="primary" onClick={confirmDialog.onTrue}>
|
<IconButton color="primary" onClick={confirmDialog.onTrue}>
|
||||||
<Iconify icon="solar:trash-bin-trash-bold" />
|
<Iconify icon="solar:trash-bin-trash-bold" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -52,6 +52,8 @@ export type IOrderProductItem = {
|
|||||||
|
|
||||||
export type IOrderItem = {
|
export type IOrderItem = {
|
||||||
id: string;
|
id: string;
|
||||||
|
createdAt: IDateValue;
|
||||||
|
//
|
||||||
taxes: number;
|
taxes: number;
|
||||||
status: string;
|
status: string;
|
||||||
shipping: number;
|
shipping: number;
|
||||||
@@ -60,8 +62,7 @@ export type IOrderItem = {
|
|||||||
orderNumber: string;
|
orderNumber: string;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
totalQuantity: number;
|
totalQuantity: number;
|
||||||
createdAt: IDateValue;
|
history: IOrderHistory | undefined;
|
||||||
history: IOrderHistory;
|
|
||||||
payment: IOrderPayment;
|
payment: IOrderPayment;
|
||||||
customer: IOrderCustomer;
|
customer: IOrderCustomer;
|
||||||
delivery: IOrderDelivery;
|
delivery: IOrderDelivery;
|
||||||
|
Reference in New Issue
Block a user