Compare commits

..

7 Commits

111 changed files with 4257 additions and 1313 deletions

View File

@@ -1,9 +0,0 @@
---
tags: frontend, side-menu
---
# REQ0181 frontend side menu configuration
## sources
`src/layouts/nav-config-dashboard.tsx`

View File

@@ -1,11 +0,0 @@
---
tags: frontend, product, details
---
# REQ0182 frontend product details
frontend page to display product details
## sources
T.B.A.

View File

@@ -1,15 +0,0 @@
---
tags: frontend, product, new
---
# REQ0183 frontend product new
frontend page to create new product
## sources
T.B.A.
## branch
develop/requirements/REQ0183

View File

@@ -1,17 +0,0 @@
---
tags: frontend, product, delete
---
# REQ0184 frontend product delete
frontend page to delete product
list page
## sources
T.B.A.
## branch
develop/requirements/REQ0184

View File

@@ -1,17 +0,0 @@
---
tags: frontend, product, update
---
# REQ0185 frontend product update
frontend page to update product
edit page T.B.A.
## sources
T.B.A.
## branch
develop/requirements/REQ0185

View File

@@ -158,8 +158,3 @@
- [REQ0169: DemoStorageExample](./REQ0169/index.md) - [REQ0169: DemoStorageExample](./REQ0169/index.md)
- [REQ0170: DemoWeatherAppUi](./REQ0170/index.md) - [REQ0170: DemoWeatherAppUi](./REQ0170/index.md)
- [REQ0180: REQ0180 service port schedule](./REQ0180/index.md) - [REQ0180: REQ0180 service port schedule](./REQ0180/index.md)
- [REQ0181: REQ0181 frontend side menu configuration](./REQ0181/index.md)
- [REQ0182: REQ0182 frontend product details](./REQ0182/index.md)
- [REQ0183: REQ0183 frontend product new](./REQ0183/index.md)
- [REQ0184: REQ0184 frontend product delete](./REQ0184/index.md)
- [REQ0185: REQ0185 frontend product update](./REQ0185/index.md)

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
yarn build
while true; do while true; do
yarn start yarn --dev
yarn dev
echo "restarting..." echo "restarting..."
sleep 1 sleep 1

View File

@@ -43,7 +43,6 @@
"@next-auth/prisma-adapter": "^1.0.7", "@next-auth/prisma-adapter": "^1.0.7",
"@prisma/adapter-pg": "^6.8.2", "@prisma/adapter-pg": "^6.8.2",
"@prisma/client": "^5.6.0", "@prisma/client": "^5.6.0",
"@types/bcrypt": "^5.0.2",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

View File

@@ -195,32 +195,32 @@ model ProductItem {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// //
available Int
category String
code String
colors String[]
coverUrl String
description String
gender String[]
images String[]
inventoryType String
name String
newLabel Json
price Float
priceSale Float?
publish String
quantity Int
ratings Json[]
reviews ProductReview[]
saleLabel Json
sizes String[]
sku String sku String
subDescription String name String
tags String[] code String
price Float
taxes Float taxes Float
tags String[]
sizes String[]
publish String
gender String[]
coverUrl String
images String[]
colors String[]
quantity Int
category String
available Int
totalSold Int
description String
totalRatings Float totalRatings Float
totalReviews Int totalReviews Int
totalSold Int inventoryType String
subDescription String
priceSale Float?
newLabel Json
saleLabel Json
ratings Json[]
reviews ProductReview[]
} }
model MailSender { model MailSender {
@@ -1144,7 +1144,6 @@ model EventReview {
eventItemId String? eventItemId String?
} }
// NOTE: need to consider with Event
// mapped to IEventItem // mapped to IEventItem
model EventItem { model EventItem {
id String @id @default(uuid()) id String @id @default(uuid())

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
yarn --dev
clear
while true; do
yarn db:push
yarn seed
yarn db:studio &
yarn dev
echo "restarting..."
sleep 1
done

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -x
yarn db:push
npx nodemon --ext ts,tsx --exec "yarn tsc"
# yarn build

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -x
npm run start

View File

@@ -10,8 +10,7 @@ import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response'; import { STATUS, response, handleError } from 'src/utils/response';
// import { AccessLogService } from '../../../../modules/AccessLog/AccessLog.service'; import { AccessLogService } from '../../../../modules/AccessLog/AccessLog.service';
import { getAccessLogById } from 'src/app/services/AccessLog.service';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -28,7 +27,7 @@ export async function GET(req: NextRequest) {
const accessLogId = searchParams.get('accessLogId'); const accessLogId = searchParams.get('accessLogId');
if (!accessLogId) return response({ message: 'accessLogId is required!' }, STATUS.BAD_REQUEST); if (!accessLogId) return response({ message: 'accessLogId is required!' }, STATUS.BAD_REQUEST);
const accessLog = await getAccessLogById(accessLogId); const accessLog = await AccessLogService.findById(accessLogId);
if (!accessLog) return response({ message: 'AccessLog not found!' }, STATUS.NOT_FOUND); if (!accessLog) return response({ message: 'AccessLog not found!' }, STATUS.NOT_FOUND);

View File

@@ -2,7 +2,7 @@ import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response'; import { STATUS, response, handleError } from 'src/utils/response';
import { listAppLogs, deleteAppLog, createNewAppLog } from 'src/app/services/AppLog.service'; import { listAppLogs, deleteAppLog, updateAppLog, createNewAppLog } from 'src/app/services/AppLog.service';
// import prisma from '../../lib/prisma'; // import prisma from '../../lib/prisma';
@@ -34,29 +34,28 @@ export async function POST(req: NextRequest) {
} }
} }
// TODO: delete `update AppLog` /**
// /** ***************************************
// *************************************** * PUT - update AppLog
// * PUT - update AppLog ***************************************
// *************************************** */
// */ export async function PUT(req: NextRequest) {
// export async function PUT(req: NextRequest) { const { searchParams } = req.nextUrl;
// const { searchParams } = req.nextUrl; const appLogId = searchParams.get('appLogId');
// const appLogId = searchParams.get('appLogId');
// const { data } = await req.json(); const { data } = await req.json();
// try { try {
// if (!appLogId) throw new Error('appLogId cannot null'); if (!appLogId) throw new Error('appLogId cannot null');
// const id: number = parseInt(appLogId); const id: number = parseInt(appLogId);
// const updateResult = await updateAppLog(id, data); const updateResult = await updateAppLog(id, data);
// return response(updateResult, STATUS.OK); return response(updateResult, STATUS.OK);
// } catch (error) { } catch (error) {
// return handleError('AppLog - Update', error); return handleError('AppLog - Update', error);
// } }
// } }
/** /**
*************************************** ***************************************
@@ -73,7 +72,7 @@ export async function DELETE(req: NextRequest) {
if (!appLogId) throw new Error('appLogId cannot null'); if (!appLogId) throw new Error('appLogId cannot null');
const id: number = parseInt(appLogId); const id: number = parseInt(appLogId);
const deleteResult = await deleteAppLog(id.toString()); const deleteResult = await deleteAppLog(id);
return response(deleteResult, STATUS.OK); return response(deleteResult, STATUS.OK);
} catch (error) { } catch (error) {

View File

@@ -7,28 +7,29 @@ import { _events } from 'src/_mock/_event';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// export const runtime = 'edge'; export const runtime = 'edge';
/** ************************************** /** **************************************
* GET - Search events * GET - Search events
*************************************** */ *************************************** */
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
// try { try {
// const { searchParams } = req.nextUrl;
// TODO: implement search events const query = searchParams.get('query')?.trim().toLowerCase();
//
// const { searchParams } = req.nextUrl;
// const query = searchParams.get('query')?.trim().toLowerCase();
// if (!query) {
// return response({ results: [] }, STATUS.OK);
// }
// const events = _events();
// // Accept search by name or sku
// const results = events.filter(({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query));
// logger('[Event] search-results', results.length);
// return response({ results }, STATUS.OK);
// } catch (error) { if (!query) {
return handleError('Event - Get search not implemented', {}); return response({ results: [] }, STATUS.OK);
// } }
const events = _events();
// Accept search by name or sku
const results = events.filter(({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query));
logger('[Event] search-results', results.length);
return response({ results }, STATUS.OK);
} catch (error) {
return handleError('Event - Get search', error);
}
} }

View File

@@ -28,7 +28,7 @@ export async function POST(req: NextRequest) {
try { try {
const order = await createOrder(data); const order = await createOrder(data);
return response(order, STATUS.OK); return response(order, STATUS.CREATED);
} catch (error) { } catch (error) {
return handleError('Order - Create', error); return handleError('Order - Create', error);
} }
@@ -48,7 +48,7 @@ export async function PUT(req: NextRequest) {
if (!orderId) throw new Error('orderId cannot be null'); if (!orderId) throw new Error('orderId cannot be null');
const id: number = parseInt(orderId); const id: number = parseInt(orderId);
const updatedOrder = await updateOrder(id.toString(), data); const updatedOrder = await updateOrder(id, data);
return response(updatedOrder, STATUS.OK); return response(updatedOrder, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('Order - Update', error); return handleError('Order - Update', error);
@@ -68,7 +68,7 @@ export async function DELETE(req: NextRequest) {
if (!orderId) throw new Error('orderId cannot be null'); if (!orderId) throw new Error('orderId cannot be null');
const id: number = parseInt(orderId); const id: number = parseInt(orderId);
await deleteOrder(id.toString()); await deleteOrder(id);
return response({ success: true }, STATUS.OK); return response({ success: true }, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('Order - Delete', error); return handleError('Order - Delete', error);

View File

@@ -1,43 +0,0 @@
// src/app/api/product/createProduct/route.ts
// REQ0183 frontend product new
//
// PURPOSE:
// create product to db
//
// RULES:
// T.B.A.
//
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { isDev } from 'src/constants';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* POST - Products
*************************************** */
export async function POST(req: NextRequest) {
const { productData } = await req.json();
try {
if (isDev) {
console.log({ productData });
}
await prisma.productItem.create({ data: productData });
if (isDev) {
console.log('create done');
}
return response({ hello: 'world' }, STATUS.OK);
} catch (error) {
console.log({ hello: 'world', productData });
return handleError('Product - Create', error);
}
}

View File

@@ -1,51 +0,0 @@
###
POST http://localhost:7272/api/product/createProduct
Content-Type: application/json
{
"data": {
"available": 99,
"category": "T-shirts",
"code": "PD-12345",
"colors": [
"Red"
],
"coverUrl": "",
"description": "this is description, 会員管理機能は、単なるデータ管理ツール以上の価値を持ちます。 企業は顧客の情報や動向を深く理解し、長期的な顧客関係の構築やロイヤルティの確立、そして迅速な市場変動への対応に直結します。 会員管理機能は、現代のビジネスにおいて企業の競争力を高める基盤として欠かせないものとなっています。",
"gender": [
"Men"
],
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"inventoryType": "test",
"name": "hello product",
"newLabel": {
"enabled": false,
"content": ""
},
"price": 99.99,
"priceSale": null,
"publish": "yes",
"quantity": 99,
"saleLabel": {
"enabled": false,
"content": ""
},
"sizes": [
"7"
],
"sku": "SK-122345",
"subDescription": "this is test sub-description",
"tags": [
"Travel",
"Finance"
],
"taxes": 0,
"totalRatings": 1,
"totalReviews": 1,
"totalSold": 1
}
}

View File

@@ -1,5 +1,4 @@
// src/app/api/product/createProduct/route.ts // src/app/api/product/createProduct/route.ts
// REQ0183 frontend product new
// //
// PURPOSE: // PURPOSE:
// create product to db // create product to db
@@ -12,8 +11,6 @@ import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response'; import { STATUS, response, handleError } from 'src/utils/response';
import { isDev } from 'src/constants';
import prisma from '../../../lib/prisma'; import prisma from '../../../lib/prisma';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -22,22 +19,57 @@ import prisma from '../../../lib/prisma';
* POST - Products * POST - Products
*************************************** */ *************************************** */
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
// logger('[Product] list', products.length);
const { data } = await req.json(); const { data } = await req.json();
const createForm: CreateProductData = data as unknown as CreateProductData;
console.log({ createForm });
try { try {
if (isDev) { console.log({ data });
console.log({ data }); await prisma.productItem.create({ data: createForm });
}
await prisma.productItem.create({ data });
if (isDev) {
console.log('create done');
}
return response({ hello: 'world' }, STATUS.OK); return response({ hello: 'world' }, STATUS.OK);
} catch (error) { } catch (error) {
console.log({ hello: 'world', data }); console.log({ hello: 'world', data });
return handleError('Product - Create', error); return handleError('Product - Create', error);
} }
} }
type CreateProductData = {
// 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;
inventoryType: string;
subDescription: string;
priceSale: number;
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
// ratings: {
// name: string;
// starCount: number;
// reviewCount: number;
// }[];
};

View File

@@ -1,51 +0,0 @@
###
POST http://localhost:7272/api/product/createProduct
Content-Type: application/json
{
"data": {
"available": 99,
"category": "T-shirts",
"code": "PD-12345",
"colors": [
"Red"
],
"coverUrl": "",
"description": "this is description, 会員管理機能は、単なるデータ管理ツール以上の価値を持ちます。 企業は顧客の情報や動向を深く理解し、長期的な顧客関係の構築やロイヤルティの確立、そして迅速な市場変動への対応に直結します。 会員管理機能は、現代のビジネスにおいて企業の競争力を高める基盤として欠かせないものとなっています。",
"gender": [
"Men"
],
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"inventoryType": "test",
"name": "hello product",
"newLabel": {
"enabled": false,
"content": ""
},
"price": 99.99,
"priceSale": null,
"publish": "yes",
"quantity": 99,
"saleLabel": {
"enabled": false,
"content": ""
},
"sizes": [
"7"
],
"sku": "SK-122345",
"subDescription": "this is test sub-description",
"tags": [
"Travel",
"Finance"
],
"taxes": 0,
"totalRatings": 1,
"totalReviews": 1,
"totalSold": 1
}
}

View File

@@ -1,22 +0,0 @@
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { deleteProduct } from 'src/app/services/product.service';
/** **************************************
* PATCH - Delete product
*************************************** */
export async function PATCH(req: NextRequest) {
try {
const { productId } = await req.json();
if (!productId) throw new Error('productId cannot be null');
await deleteProduct(productId);
return response({ productId }, STATUS.OK);
} catch (error) {
return handleError('Product - Delete', error);
}
}

View File

@@ -1,7 +1,5 @@
// src/app/api/product/details/route.ts // src/app/api/product/details/route.ts
// //
// REQ0182 frontend product details
//
// PURPOSE: // PURPOSE:
// get product from db by id // get product from db by id
// //

View File

@@ -1,5 +1,3 @@
### ###
GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
###
GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01 GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01

View File

@@ -0,0 +1,23 @@
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 - Products
*************************************** */
export async function GET() {
try {
const products = _products();
logger('[Product] list', products.length);
return response({ products }, STATUS.OK);
} catch (error) {
return handleError('Product - Get list', error);
}
}

View File

@@ -24,14 +24,13 @@ export async function GET(req: NextRequest, res: NextResponse) {
*************************************** ***************************************
*/ */
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
const OPERATION = 'Product - Create';
const { data } = await req.json(); const { data } = await req.json();
try { try {
const product = await createProduct(data); const product = await createProduct(data);
return response(OPERATION, STATUS.OK); return response(product, STATUS.CREATED);
} catch (error) { } catch (error) {
return handleError(OPERATION, error); return handleError('Product - Create', error);
} }
} }

View File

@@ -17,7 +17,7 @@ import prisma from '../../../lib/prisma';
/** ************************************** /** **************************************
* GET - Products * GET - Products
*************************************** */ *************************************** */
export async function PUT(req: NextRequest) { export async function POST(req: NextRequest) {
// logger('[Product] list', products.length); // logger('[Product] list', products.length);
const { data } = await req.json(); const { data } = await req.json();

View File

@@ -1,51 +0,0 @@
###
PUT http://localhost:7272/api/product/createProduct
Content-Type: application/json
{
"data": {
"available": 99,
"category": "T-shirts",
"code": "PD-12345",
"colors": [
"Red"
],
"coverUrl": "",
"description": "this is description, 会員管理機能は、単なるデータ管理ツール以上の価値を持ちます。 企業は顧客の情報や動向を深く理解し、長期的な顧客関係の構築やロイヤルティの確立、そして迅速な市場変動への対応に直結します。 会員管理機能は、現代のビジネスにおいて企業の競争力を高める基盤として欠かせないものとなっています。",
"gender": [
"Men"
],
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"inventoryType": "test",
"name": "hello product",
"newLabel": {
"enabled": false,
"content": ""
},
"price": 99.99,
"priceSale": null,
"publish": "yes",
"quantity": 99,
"saleLabel": {
"enabled": false,
"content": ""
},
"sizes": [
"7"
],
"sku": "SK-122345",
"subDescription": "this is test sub-description",
"tags": [
"Travel",
"Finance"
],
"taxes": 0,
"totalRatings": 1,
"totalReviews": 1,
"totalSold": 1
}
}

View File

@@ -1,129 +0,0 @@
// 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 PUT(req: NextRequest) {
// logger('[Product] list', products.length);
const { productData } = await req.json();
try {
const products = await prisma.productItem.updateMany({
data: {
name: productData.name,
sku: productData.sku,
code: productData.code,
price: productData.price,
taxes: productData.taxes,
tags: productData.tags,
sizes: productData.sizes,
publish: productData.publish,
gender: productData.gender,
coverUrl: productData.coverUrl,
images: productData.images,
colors: productData.colors,
quantity: productData.quantity,
category: productData.category,
available: productData.available,
totalSold: productData.totalSold,
description: productData.description,
totalRatings: productData.totalRatings,
totalReviews: productData.totalReviews,
inventoryType: productData.inventoryType,
subDescription: productData.subDescription,
priceSale: productData.priceSale,
//
newLabel: {
content: productData.newLabel?.content || '',
enabled: productData.newLabel?.enabled ?? false,
},
saleLabel: {
content: productData.saleLabel?.content || '',
enabled: productData.saleLabel?.enabled ?? false,
},
ratings: {
set: productData.ratings.map((rating: { name: string; starCount: number; reviewCount: number }) => ({
name: rating.name,
starCount: rating.starCount,
reviewCount: rating.reviewCount,
})),
},
},
where: { id: productData.id },
});
return response({ data: productData }, STATUS.OK);
} catch (error) {
console.log({ data: productData });
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[];
};

View File

@@ -1,51 +0,0 @@
###
PUT http://localhost:7272/api/product/createProduct
Content-Type: application/json
{
"data": {
"available": 99,
"category": "T-shirts",
"code": "PD-12345",
"colors": [
"Red"
],
"coverUrl": "",
"description": "this is description, 会員管理機能は、単なるデータ管理ツール以上の価値を持ちます。 企業は顧客の情報や動向を深く理解し、長期的な顧客関係の構築やロイヤルティの確立、そして迅速な市場変動への対応に直結します。 会員管理機能は、現代のビジネスにおいて企業の競争力を高める基盤として欠かせないものとなっています。",
"gender": [
"Men"
],
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"inventoryType": "test",
"name": "hello product",
"newLabel": {
"enabled": false,
"content": ""
},
"price": 99.99,
"priceSale": null,
"publish": "yes",
"quantity": 99,
"saleLabel": {
"enabled": false,
"content": ""
},
"sizes": [
"7"
],
"sku": "SK-122345",
"subDescription": "this is test sub-description",
"tags": [
"Travel",
"Finance"
],
"taxes": 0,
"totalRatings": 1,
"totalReviews": 1,
"totalSold": 1
}
}

View File

@@ -1,7 +1,6 @@
import prisma from '@/lib/prisma';
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import prisma from 'src/app/lib/prisma';
// GET: 获取所有学生 // GET: 获取所有学生
export async function GET() { export async function GET() {
try { try {

View File

@@ -14,8 +14,8 @@ export async function GET(req: NextRequest, res: NextResponse) {
if (!userId) throw new Error('userId cannot be null'); if (!userId) throw new Error('userId cannot be null');
const result = await isAdmin(userId); const result = await isAdmin(userId);
return response(result ? 'true' : 'false', STATUS.OK); return response(result, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('GET - checkAdmin', error); return handleError('Post - Get latest', error);
} }
} }

View File

@@ -14,8 +14,8 @@ export async function GET(req: NextRequest, res: NextResponse) {
if (!userId) throw new Error('userId cannot be null'); if (!userId) throw new Error('userId cannot be null');
const result = await isAdmin(userId); const result = await isAdmin(userId);
return response('GET - helloworld', STATUS.OK); return response(result, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('GET - helloworld', error); return handleError('Post - Get latest', error);
} }
} }

View File

@@ -22,17 +22,14 @@ export async function GET(req: NextRequest, res: NextResponse) {
*************************************** ***************************************
*/ */
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
const OPERATION = 'User - Create';
const { data } = await req.json(); const { data } = await req.json();
try { try {
// TODO: temporary ignore output from function due to `createNewUser` is still a dummy const createResult = await createNewUser(data);
// const createResult = await createNewUser(data);
await createNewUser(data);
return response(OPERATION, STATUS.OK); return response(createResult, STATUS.OK);
} catch (error) { } catch (error) {
return handleError(OPERATION, error); return handleError('User - Create', error);
} }
} }
@@ -51,7 +48,7 @@ export async function PUT(req: NextRequest) {
if (!userId) throw new Error('userId cannot null'); if (!userId) throw new Error('userId cannot null');
const id: number = parseInt(userId); const id: number = parseInt(userId);
const updateResult = await updateUser(id.toString(), data); const updateResult = await updateUser(id, data);
return response(updateResult, STATUS.OK); return response(updateResult, STATUS.OK);
} catch (error) { } catch (error) {
@@ -74,10 +71,10 @@ export async function DELETE(req: NextRequest) {
if (!userId) throw new Error('userId cannot null'); if (!userId) throw new Error('userId cannot null');
const id: number = parseInt(userId); const id: number = parseInt(userId);
await deleteUser(id); const deleteResult = await deleteUser(id);
return response('User - Delete', STATUS.OK); return response(deleteResult, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('User - Delete', error); return handleError('User - Update', error);
} }
} }

View File

@@ -30,15 +30,10 @@ async function listAccessLogs(): Promise<AccessLog[]> {
}); });
} }
// TODO: obsoleted getAccessLog, use getAccessLogById instead
async function getAccessLog(id: string): Promise<AccessLog | null> { async function getAccessLog(id: string): Promise<AccessLog | null> {
return prisma.accessLog.findUnique({ where: { id } }); return prisma.accessLog.findUnique({ where: { id } });
} }
async function getAccessLogById(id: string): Promise<AccessLog | null> {
return prisma.accessLog.findUnique({ where: { id } });
}
async function createAccessLog(userId?: string, message?: string, metadata?: Record<string, any>): Promise<AccessLog> { async function createAccessLog(userId?: string, message?: string, metadata?: Record<string, any>): Promise<AccessLog> {
return prisma.accessLog.create({ return prisma.accessLog.create({
data: { data: {
@@ -49,10 +44,6 @@ async function createAccessLog(userId?: string, message?: string, metadata?: Rec
}); });
} }
function helloworld(): string {
return 'helloworld';
}
// async function update(id: string, data: UpdateAccessLog): Promise<AccessLog> { // async function update(id: string, data: UpdateAccessLog): Promise<AccessLog> {
// return prisma.accessLog.update({ // return prisma.accessLog.update({
// where: { id }, // where: { id },
@@ -72,7 +63,4 @@ export {
getAccessLog, getAccessLog,
listAccessLogs, listAccessLogs,
createAccessLog, createAccessLog,
getAccessLogById,
//
helloworld,
}; };

View File

@@ -6,14 +6,11 @@
// RULES: // RULES:
// - Follows same pattern as helloworld.service.ts // - Follows same pattern as helloworld.service.ts
// //
import type { Event } from '@prisma/client';
import prisma from '../lib/prisma';
type CreateEvent = { type CreateEvent = {
eventDate: Date; eventDate: DateTime;
title: string; title: string;
joinMembers?: JSON[]; joinMembers?: Json[];
price: number; price: number;
currency: string; currency: string;
duration_m: number; duration_m: number;
@@ -25,9 +22,9 @@ type CreateEvent = {
}; };
type UpdateEvent = { type UpdateEvent = {
eventDate?: Date; eventDate?: DateTime;
title?: string; title?: string;
joinMembers?: JSON[]; joinMembers?: Json[];
price?: number; price?: number;
currency?: string; currency?: string;
duration_m?: number; duration_m?: number;
@@ -38,9 +35,9 @@ type UpdateEvent = {
memberId?: number; memberId?: number;
}; };
async function listEvents(): Promise<Event[]> { // async function listEvents(): Promise<Event[]> {
return prisma.event.findMany(); // return prisma.event.findMany();
} // }
// async function getEvent(eventId: number) { // async function getEvent(eventId: number) {
// return prisma.event.findFirst({ where: { id: eventId } }); // return prisma.event.findFirst({ where: { id: eventId } });

View File

@@ -12,9 +12,9 @@ import type { EventItem } from '@prisma/client';
import prisma from '../lib/prisma'; import prisma from '../lib/prisma';
type CreateEvent = { type CreateEvent = {
eventDate: Date; eventDate: DateTime;
title: string; title: string;
joinMembers?: JSON[]; joinMembers?: Json[];
price: number; price: number;
currency: string; currency: string;
duration_m: number; duration_m: number;
@@ -26,9 +26,9 @@ type CreateEvent = {
}; };
type UpdateEvent = { type UpdateEvent = {
eventDate?: Date; eventDate?: DateTime;
title?: string; title?: string;
joinMembers?: JSON[]; joinMembers?: Json[];
price?: number; price?: number;
currency?: string; currency?: string;
duration_m?: number; duration_m?: number;

View File

@@ -11,24 +11,9 @@ import type { OrderItem } from '@prisma/client';
import prisma from '../lib/prisma'; import prisma from '../lib/prisma';
type CreateOrderItem = { type CreateOrderItem = {
// orderNumber?: string; orderNumber?: string;
// status?: string; // status?: string;
// eventIds?: number[]; // eventIds?: number[];
taxes: number;
status: string;
shipping: number;
discount: number;
subtotal: number;
orderNumber: string;
totalAmount: number;
totalQuantity: number;
history: Record<string, any>;
payment: Record<string, any>;
customer: Record<string, any>;
delivery: Record<string, any>;
items: Record<string, any>[];
shippingAddress: Record<string, any>;
}; };
type UpdateOrderItem = { type UpdateOrderItem = {

View File

@@ -1,7 +1,5 @@
// src/app/services/product.service.ts // src/app/services/product.service.ts
// //
// REQ0182 frontend product details
//
// PURPOSE: // PURPOSE:
// - Service for handling ProductItem Record // - Service for handling ProductItem Record
// //
@@ -67,19 +65,11 @@ type UpdateProduct = {
}; };
async function listProducts(): Promise<ProductItem[]> { async function listProducts(): Promise<ProductItem[]> {
return prisma.productItem.findMany({ return prisma.productItem.findMany();
include: {
reviews: true,
},
});
} }
async function getProduct(productId: string): Promise<ProductItem | null> { async function getProduct(productId: string): Promise<ProductItem | null> {
return prisma.productItem.findUnique({ return prisma.productItem.findUnique({ where: { id: productId } });
where: { id: productId },
include: { reviews: true },
//
});
} }
async function createProduct(createForm: CreateProduct) { async function createProduct(createForm: CreateProduct) {

View File

@@ -38,7 +38,7 @@ async function getUserItem(userId: string): Promise<UserItem | null> {
return prisma.userItem.findFirst({ where: { id: userId } }); return prisma.userItem.findFirst({ where: { id: userId } });
} }
async function updateUser(userId: string, updateForm: UpdateUser): Promise<UserItem> { async function updateUser(userId: string, updateForm: UpdateUser): Promise<User> {
return prisma.userItem.update({ return prisma.userItem.update({
where: { id: userId }, where: { id: userId },
data: updateForm, data: updateForm,
@@ -82,7 +82,7 @@ async function changeToUser(userIdToPromote: string, userIdOfApplicant: string)
return promoteResult; return promoteResult;
} }
async function getUserById(id: string): Promise<UserItem | null> { async function getUserById1(id: string): Promise<UserItem | null> {
return prisma.userItem.findFirst({ where: { id } }); return prisma.userItem.findFirst({ where: { id } });
} }

View File

@@ -1,9 +1,7 @@
const isDev = process.env.NODE_ENV === 'development';
const L_ERROR = 0; const L_ERROR = 0;
const L_WARN = 1; const L_WARN = 1;
const L_INFO = 2; const L_INFO = 2;
const L_DEBUG = 3; const L_DEBUG = 3;
const L_TRACE = 4; const L_TRACE = 4;
export { L_WARN, L_INFO, L_ERROR, L_DEBUG, L_TRACE, isDev }; export { L_WARN, L_INFO, L_ERROR, L_DEBUG, L_TRACE };

View File

@@ -43,8 +43,7 @@
"**/*.draft", "**/*.draft",
"**/*.log", "**/*.log",
"**/*.tmp", "**/*.tmp",
"**/*del", "**/*del"
"prisma/*"
], ],
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",

View File

@@ -770,13 +770,6 @@
dependencies: dependencies:
tslib "^2.4.0" tslib "^2.4.0"
"@types/bcrypt@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.2.tgz#22fddc11945ea4fbc3655b3e8b8847cc9f811477"
integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==
dependencies:
"@types/node" "*"
"@types/estree@^1.0.6": "@types/estree@^1.0.6":
version "1.0.7" version "1.0.7"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz"
@@ -797,13 +790,6 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.17.tgz#fb85a04f47e9e4da888384feead0de05f7070355" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.17.tgz#fb85a04f47e9e4da888384feead0de05f7070355"
integrity sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ== integrity sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==
"@types/node@*":
version "24.0.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.1.tgz#e9bfcb1c35547437c294403b7bec497772a88b0a"
integrity sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==
dependencies:
undici-types "~7.8.0"
"@types/node@^22.13.13": "@types/node@^22.13.13":
version "22.13.13" version "22.13.13"
resolved "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz"
@@ -3500,11 +3486,6 @@ undici-types@~6.20.0:
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici-types@~7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294"
integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==
unique-names-generator@^4.7.1: unique-names-generator@^4.7.1:
version "4.7.1" version "4.7.1"
resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.7.1.tgz#966407b12ba97f618928f77322cfac8c80df5597" resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.7.1.tgz#966407b12ba97f618928f77322cfac8c80df5597"

View File

@@ -1,23 +1,14 @@
#
# REQ0180 service port schedule
#
services: services:
frontend: frontend:
command: "sleep infinity" command: 'sleep infinity'
ports:
- 8080:8080
mobile: mobile:
command: "sleep infinity" command: 'sleep infinity'
ports:
- 8081:3000
cms_backend: cms_backend:
command: "sleep infinity" command: 'sleep infinity'
ports:
- 7272:7272
- 5555:5555
postgres: postgres:
# container_name: postgres
ports: ports:
- 5432:5432 - '5432:5432'

View File

@@ -1,6 +1,3 @@
#
# REQ0180 service port schedule
#
volumes: volumes:
db: db:
driver: local driver: local
@@ -14,8 +11,8 @@ services:
- 10001:8080 - 10001:8080
volumes: volumes:
- ../frontend:/app - ../frontend:/app
working_dir: "/app" working_dir: '/app'
command: "./scripts/20_prod.sh" command: './dev.sh'
mobile: mobile:
image: 192.168.10.61:5000/hksingleparty_mobile image: 192.168.10.61:5000/hksingleparty_mobile
@@ -25,8 +22,8 @@ services:
- 10004:3000 - 10004:3000
volumes: volumes:
- ../mobile:/app - ../mobile:/app
working_dir: "/app" working_dir: '/app'
command: "./scripts/20_prod.sh" command: './dev.sh'
cms_backend: cms_backend:
image: 192.168.10.61:5000/demo_minimal_kit_backend image: 192.168.10.61:5000/demo_minimal_kit_backend
@@ -39,8 +36,8 @@ services:
- 10003:5555 - 10003:5555
volumes: volumes:
- ../cms_backend:/app - ../cms_backend:/app
working_dir: "/app" working_dir: '/app'
command: "./scripts/20_prod.sh" command: './dev.sh'
postgres: postgres:
image: postgres:14.1-alpine image: postgres:14.1-alpine
@@ -48,6 +45,6 @@ services:
env_file: env_file:
- .env - .env
expose: expose:
- "5432" - '5432'
volumes: volumes:
- db:/var/lib/postgresql/data - db:/var/lib/postgresql/data

View File

@@ -1,8 +1,3 @@
**/*del
**/*bak
**/*copy*
# Logs # Logs
logs logs
*.log *.log

View File

@@ -2,9 +2,8 @@
yarn --dev yarn --dev
clear
while true; do while true; do
# yarn tsc:print # yarn tsc:print
yarn lint:print yarn lint:print

View File

@@ -92,8 +92,6 @@ const sortImportsRules = () => {
}; };
return { return {
'perfectionist/sort-named-imports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-exports': [ 'perfectionist/sort-exports': [
1, 1,
{ {
@@ -102,6 +100,8 @@ const sortImportsRules = () => {
groupKind: 'values-first', groupKind: 'values-first',
}, },
], ],
'perfectionist/sort-named-imports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-imports': [ 'perfectionist/sort-imports': [
2, 2,
{ {

View File

@@ -21,7 +21,7 @@
"re:build": "yarn clean && yarn install && yarn build", "re:build": "yarn clean && yarn install && yarn build",
"re:build-npm": "npm run clean && npm install && npm run build", "re:build-npm": "npm run clean && npm install && npm run build",
"tsc:dev": "yarn dev & yarn tsc:watch", "tsc:dev": "yarn dev & yarn tsc:watch",
"tsc:w": "tsc --noEmit --watch", "tsc:watch": "tsc --noEmit --watch",
"tsc:print": "npx tsc --showConfig" "tsc:print": "npx tsc --showConfig"
}, },
"engines": { "engines": {
@@ -43,7 +43,6 @@
"@fontsource-variable/noto-sans-sc": "^5.2.5", "@fontsource-variable/noto-sans-sc": "^5.2.5",
"@fontsource-variable/noto-sans-tc": "^5.2.5", "@fontsource-variable/noto-sans-tc": "^5.2.5",
"@fontsource-variable/nunito-sans": "^5.2.5", "@fontsource-variable/nunito-sans": "^5.2.5",
"@fontsource-variable/public-sans": "^5.2.5",
"@fontsource/barlow": "^5.2.5", "@fontsource/barlow": "^5.2.5",
"@fullcalendar/core": "^6.1.15", "@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15",
@@ -53,7 +52,7 @@
"@fullcalendar/timegrid": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/timeline": "^6.1.15", "@fullcalendar/timeline": "^6.1.15",
"@hookform/resolvers": "^4.1.3", "@hookform/resolvers": "^4.1.3",
"@ianvs/prettier-plugin-sort-imports": "^4.4.2", "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@iconify/react": "^5.2.0", "@iconify/react": "^5.2.0",
"@mui/lab": "^7.0.0-beta.10", "@mui/lab": "^7.0.0-beta.10",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",

View File

@@ -12,7 +12,7 @@ const config = {
trailingComma: 'es5', trailingComma: 'es5',
plugins: [ plugins: [
// //
'@ianvs/prettier-plugin-sort-imports', // '@ianvs/prettier-plugin-sort-imports',
], ],
}; };

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env bash
yarn build

View File

@@ -1,8 +1,10 @@
import { useMemo } from 'react';
import { endpoints, fetcher } from 'src/lib/axios';
import type { IPostItem } from 'src/types/blog';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import type { IPostItem } from 'src/types/blog';
import useSWR from 'swr'; import useSWR from 'swr';
import { useMemo } from 'react';
import { fetcher, endpoints } from 'src/lib/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,9 +1,11 @@
import { useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import type { ICalendarEvent } from 'src/types/calendar';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import type { ICalendarEvent } from 'src/types/calendar';
import { useMemo } from 'react';
import useSWR, { mutate } from 'swr'; import useSWR, { mutate } from 'swr';
import axios, { fetcher, endpoints } from 'src/lib/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
const enableServer = false; const enableServer = false;

View File

@@ -1,10 +1,12 @@
import { keyBy } from 'es-toolkit';
import { useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import type { IChatConversation, IChatMessage, IChatParticipant } from 'src/types/chat';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import type { IChatMessage, IChatParticipant, IChatConversation } from 'src/types/chat';
import { useMemo } from 'react';
import { keyBy } from 'es-toolkit';
import useSWR, { mutate } from 'swr'; import useSWR, { mutate } from 'swr';
import axios, { fetcher, endpoints } from 'src/lib/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
const enableServer = false; const enableServer = false;

View File

@@ -1,9 +1,11 @@
import type { UniqueIdentifier } from '@dnd-kit/core';
import { startTransition, useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import type { IKanban, IKanbanColumn, IKanbanTask } from 'src/types/kanban';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import type { UniqueIdentifier } from '@dnd-kit/core';
import type { IKanban, IKanbanTask, IKanbanColumn } from 'src/types/kanban';
import useSWR, { mutate } from 'swr'; import useSWR, { mutate } from 'swr';
import { useMemo, startTransition } from 'react';
import axios, { fetcher, endpoints } from 'src/lib/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,9 +1,11 @@
import { keyBy } from 'es-toolkit';
import { useMemo } from 'react';
import { endpoints, fetcher } from 'src/lib/axios';
import type { IMail, IMailLabel } from 'src/types/mail';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import type { IMail, IMailLabel } from 'src/types/mail';
import useSWR from 'swr'; import useSWR from 'swr';
import { useMemo } from 'react';
import { keyBy } from 'es-toolkit';
import { fetcher, endpoints } from 'src/lib/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,8 +1,8 @@
// src/actions/order.ts // src/actions/order.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 { IOrderItem } from 'src/types/order';
import type { IProductItem } from 'src/types/product'; import type { IProductItem } from 'src/types/product';
import type { IOrderItem } from 'src/types/order';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import useSWR from 'swr'; import useSWR from 'swr';

View File

@@ -1,11 +1,9 @@
//
// src/actions/product.ts // 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';
import type { SWRConfiguration } from 'swr'; import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr'; import useSWR from 'swr';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -24,7 +22,11 @@ type ProductsData = {
export function useGetProducts() { export function useGetProducts() {
const url = endpoints.product.list; const url = endpoints.product.list;
const { data, isLoading, error, isValidating } = useSWR<ProductsData>(url, fetcher, swrOptions); const { data, isLoading, error, isValidating, mutate } = useSWR<ProductsData>(
url,
fetcher,
swrOptions
);
const memoizedValue = useMemo( const memoizedValue = useMemo(
() => ({ () => ({
@@ -33,8 +35,9 @@ export function useGetProducts() {
productsError: error, productsError: error,
productsValidating: isValidating, productsValidating: isValidating,
productsEmpty: !isLoading && !isValidating && !data?.products.length, productsEmpty: !isLoading && !isValidating && !data?.products.length,
mutate,
}), }),
[data?.products, error, isLoading, isValidating] [data?.products, error, isLoading, isValidating, mutate]
); );
return memoizedValue; return memoizedValue;
@@ -53,11 +56,10 @@ export function useGetProduct(productId: string) {
const memoizedValue = useMemo( const memoizedValue = useMemo(
() => ({ () => ({
product: data?.product, currentProduct: data?.product,
productLoading: isLoading, productLoading: isLoading,
productError: error, productError: error,
productValidating: isValidating, productValidating: isValidating,
mutate,
}), }),
[data?.product, error, isLoading, isValidating] [data?.product, error, isLoading, isValidating]
); );
@@ -95,80 +97,140 @@ export function useSearchProducts(query: string) {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export async function createProduct(productData: IProductItem) { type SaveProductData = {
/** // id: string;
* Work on server
*/
const data = { productData };
await axiosInstance.post(endpoints.product.create, data);
/** sku: string;
* Work in local name: string;
*/ code: string;
mutate( price: number | null;
endpoints.product.list, taxes: number | null;
(currentData: any) => { tags: string[];
const currentProducts: IProductItem[] = currentData?.products; sizes: string[];
// publish: string;
gender: string[];
// coverUrl: string;
images: (string | File)[];
colors: string[];
quantity: number | null;
category: string;
// available: number;
// totalSold: number;
description: string;
// totalRatings: number;
// totalReviews: number;
// inventoryType: string;
subDescription: string;
priceSale: number | null;
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
// ratings: {
// name: string;
// starCount: number;
// reviewCount: number;
// }[];
};
const products = [...currentProducts, productData]; export async function saveProduct(productId: string, saveProductData: SaveProductData) {
console.log('save product ?');
// const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
return { ...currentData, products }; const res = await axiosInstance.post('http://localhost:7272/api/product/saveProduct', {
}, data: saveProductData,
false });
);
return res;
}
export async function uploadProductImage(saveProductData: SaveProductData) {
console.log('save product ?');
// const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
const res = await axiosInstance.get('http://localhost:7272/api/product/helloworld');
return res;
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export async function updateProduct(productData: Partial<IProductItem>) { type CreateProductData = {
/** // id: string;
* Work on server sku: string;
*/ name: string;
const data = { productData }; code: string;
await axiosInstance.put(endpoints.product.update, data); price: number | null;
taxes: number | null;
tags: string[];
sizes: string[];
publish: string;
gender: string[];
coverUrl: string;
images: (string | File)[];
colors: string[];
quantity: number | null;
category: string;
available: number;
totalSold: number;
description: string;
totalRatings: number;
totalReviews: number;
inventoryType: string;
subDescription: string;
priceSale: number | null;
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
// ratings: {
// name: string;
// starCount: number;
// reviewCount: number;
// }[];
};
/** export async function createProduct(createProductData: CreateProductData) {
* Work in local console.log('create product ?');
*/ // const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
mutate( const res = await axiosInstance.post('http://localhost:7272/api/product/createProduct', {
endpoints.product.list, data: createProductData,
(currentData: any) => { });
const currentProducts: IProductItem[] = currentData?.products;
const products = currentProducts.map((product) => return res;
product.id === productData.id ? { ...product, ...productData } : product
);
return { ...currentData, products };
},
false
);
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export async function deleteProduct(productId: string) { type DeleteProductResponse = {
/** success: boolean;
* Work on server message?: string;
*/ };
const data = { productId };
await axiosInstance.patch(endpoints.product.delete, data);
/** export async function deleteProduct(productId: string): Promise<DeleteProductResponse> {
* Work in local const url = `http://localhost:7272/api/product/deleteProduct?productId=${productId}`;
*/
mutate( try {
endpoints.product.list, const res = await axiosInstance.delete(url);
(currentData: any) => { console.log({ res });
console.log({ currentData });
const currentProducts: IProductItem[] = currentData?.products;
const products = currentProducts.filter((product) => product.id !== productId); return {
success: true,
return { ...currentData, products }; message: 'Product deleted successfully',
}, };
false } catch (error) {
); return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete product',
};
}
} }

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function EmailInboxIcon({ sx, ...other }: SvgIconProps) { function EmailInboxIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function NewPasswordIcon({ sx, ...other }: SvgIconProps) { function NewPasswordIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function PasswordIcon({ sx, ...other }: SvgIconProps) { function PasswordIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function PlanFreeIcon({ sx, ...other }: SvgIconProps) { function PlanFreeIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function PlanPremiumIcon({ sx, ...other }: SvgIconProps) { function PlanPremiumIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function SentIcon({ sx, ...other }: SvgIconProps) { function SentIcon({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function AvatarShape({ sx, ...other }: SvgIconProps) { function AvatarShape({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function BookingIllustration({ sx, ...other }: SvgIconProps) { function BookingIllustration({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function CheckInIllustration({ sx, ...other }: SvgIconProps) { function CheckInIllustration({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function CheckoutIllustration({ sx, ...other }: SvgIconProps) { function CheckoutIllustration({ sx, ...other }: SvgIconProps) {

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,6 +1,9 @@
import type { SvgIconProps } from '@mui/material/SvgIcon'; import type { SvgIconProps } from '@mui/material/SvgIcon';
import SvgIcon from '@mui/material/SvgIcon';
import { memo } from 'react'; import { memo } from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import { BackgroundShape } from './background-shape'; import { BackgroundShape } from './background-shape';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,21 +1,15 @@
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import { useTranslation } from 'react-i18next';
import type { ConfirmDialogProps } from './types'; import type { ConfirmDialogProps } from './types';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export function ConfirmDialog({ export function ConfirmDialog({ open, title, action, content, onClose, ...other }: ConfirmDialogProps) {
open,
title,
action,
content,
onClose,
...other
}: ConfirmDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@@ -1,22 +1,22 @@
// src/components/hook-form/fields.tsx // src/components/hook-form/fields.tsx
// //
import { RHFAutocomplete } from './rhf-autocomplete';
import { RHFCheckbox, RHFMultiCheckbox } from './rhf-checkbox';
import { RHFCode } from './rhf-code'; import { RHFCode } from './rhf-code';
import { RHFCountrySelect } from './rhf-country-select';
import { RHFDatePicker, RHFMobileDateTimePicker } from './rhf-date-picker';
import { RHFEditor } from './rhf-editor';
import { RHFNumberInput } from './rhf-number-input';
import { RHFPhoneInput } from './rhf-phone-input';
import { RHFRadioGroup } from './rhf-radio-group';
import { RHFRating } from './rhf-rating'; import { RHFRating } from './rhf-rating';
import { RHFMultiSelect, RHFSelect } from './rhf-select'; import { RHFEditor } from './rhf-editor';
import { RHFSlider } from './rhf-slider'; import { RHFSlider } from './rhf-slider';
import { RHFMultiSwitch, RHFSwitch } from './rhf-switch';
import { RHFTextField } from './rhf-text-field';
import { RHFUpload } from './rhf-upload'; import { RHFUpload } from './rhf-upload';
import { RHFUploadAvatar } from './rhf-upload-avatar'; import { RHFTextField } from './rhf-text-field';
import { RHFUploadBox } from './rhf-upload-box'; import { RHFUploadBox } from './rhf-upload-box';
import { RHFRadioGroup } from './rhf-radio-group';
import { RHFPhoneInput } from './rhf-phone-input';
import { RHFNumberInput } from './rhf-number-input';
import { RHFAutocomplete } from './rhf-autocomplete';
import { RHFUploadAvatar } from './rhf-upload-avatar';
import { RHFCountrySelect } from './rhf-country-select';
import { RHFSwitch, RHFMultiSwitch } from './rhf-switch';
import { RHFSelect, RHFMultiSelect } from './rhf-select';
import { RHFCheckbox, RHFMultiCheckbox } from './rhf-checkbox';
import { RHFDatePicker, RHFMobileDateTimePicker } from './rhf-date-picker';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,9 +1,7 @@
import type { BoxProps } from '@mui/material/Box'; import type { BoxProps } from '@mui/material/Box';
import Box from '@mui/material/Box';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import { Upload, UploadAvatar, UploadBox } from '../upload'; import { Upload } from '../upload';
import type { UploadProps } from '../upload'; import type { UploadProps } from '../upload';
import { HelperText } from './help-text';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -14,48 +12,6 @@ export type RHFUploadProps = UploadProps & {
}; };
}; };
export function RHFUploadAvatar({ name, slotProps, ...other }: RHFUploadProps) {
const { control, setValue } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => {
const onDrop = (acceptedFiles: File[]) => {
const value = acceptedFiles[0];
setValue(name, value, { shouldValidate: true });
};
return (
<Box {...slotProps?.wrapper}>
<UploadAvatar value={field.value} error={!!error} onDrop={onDrop} {...other} />
<HelperText errorMessage={error?.message} sx={{ textAlign: 'center' }} />
</Box>
);
}}
/>
);
}
// ----------------------------------------------------------------------
export function RHFUploadBox({ name, ...other }: RHFUploadProps) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<UploadBox value={field.value} error={!!error} {...other} />
)}
/>
);
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export function RHFUpload({ name, multiple, helperText, ...other }: RHFUploadProps) { export function RHFUpload({ name, multiple, helperText, ...other }: RHFUploadProps) {
@@ -79,6 +35,8 @@ export function RHFUpload({ name, multiple, helperText, ...other }: RHFUploadPro
setValue(name, value, { shouldValidate: true }); setValue(name, value, { shouldValidate: true });
}; };
// return <>{JSON.stringify({ t: field.value })}</>;
return <Upload {...uploadProps} value={field.value} onDrop={onDrop} {...other} />; return <Upload {...uploadProps} value={field.value} onDrop={onDrop} {...other} />;
}} }}
/> />

View File

@@ -1,13 +1,17 @@
import { isEqualPath } from 'minimal-shared/utils';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { isEqualPath } from 'minimal-shared/utils';
import { useTranslation } from 'react-i18next';
import { RouterLink } from 'src/routes/components';
import { usePathname } from 'src/routes/hooks'; import { usePathname } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { megaMenuClasses } from '../styles'; import { megaMenuClasses } from '../styles';
import { NavUl, NavLi } from './nav-elements';
import type { NavSubItemProps, NavSubListProps } from '../types'; import type { NavSubItemProps, NavSubListProps } from '../types';
import { NavLi, NavUl } from './nav-elements'; import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -20,13 +24,7 @@ export function NavSubList({ data, slotProps, ...other }: NavSubListProps) {
{data?.map((list) => ( {data?.map((list) => (
<NavLi key={list?.subheader ?? list.items[0].title} {...other}> <NavLi key={list?.subheader ?? list.items[0].title} {...other}>
{list?.subheader && ( {list?.subheader && (
<Typography <Typography noWrap component="div" variant="subtitle2" className={megaMenuClasses.subheader} sx={{ mb: 1, ...slotProps?.subheader }}>
noWrap
component="div"
variant="subtitle2"
className={megaMenuClasses.subheader}
sx={{ mb: 1, ...slotProps?.subheader }}
>
{list.subheader} {list.subheader}
</Typography> </Typography>
)} )}

View File

@@ -1,25 +1,20 @@
import type { ListSubheaderProps } from '@mui/material/ListSubheader'; import type { ListSubheaderProps } from '@mui/material/ListSubheader';
import ListSubheader from '@mui/material/ListSubheader';
import { styled } from '@mui/material/styles';
import { mergeClasses } from 'minimal-shared/utils'; import { mergeClasses } from 'minimal-shared/utils';
import { Iconify, iconifyClasses } from '../../iconify';
import { styled } from '@mui/material/styles';
import ListSubheader from '@mui/material/ListSubheader';
import { navSectionClasses } from '../styles'; import { navSectionClasses } from '../styles';
import { Iconify, iconifyClasses } from '../../iconify';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export type NavSubheaderProps = ListSubheaderProps & { open?: boolean }; export type NavSubheaderProps = ListSubheaderProps & { open?: boolean };
export const NavSubheader = styled(({ open, children, className, ...other }: NavSubheaderProps) => ( export const NavSubheader = styled(({ open, children, className, ...other }: NavSubheaderProps) => (
<ListSubheader <ListSubheader disableSticky component="div" {...other} className={mergeClasses([navSectionClasses.subheader, className])}>
disableSticky <Iconify width={16} icon={open ? 'eva:arrow-ios-downward-fill' : 'eva:arrow-ios-forward-fill'} />
component="div"
{...other}
className={mergeClasses([navSectionClasses.subheader, className])}
>
<Iconify
width={16}
icon={open ? 'eva:arrow-ios-downward-fill' : 'eva:arrow-ios-forward-fill'}
/>
{children} {children}
</ListSubheader> </ListSubheader>
))(({ theme }) => ({ ))(({ theme }) => ({

View File

@@ -2,11 +2,11 @@ import Collapse from '@mui/material/Collapse';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { useBoolean } from 'minimal-shared/hooks'; import { useBoolean } from 'minimal-shared/hooks';
import { mergeClasses } from 'minimal-shared/utils'; import { mergeClasses } from 'minimal-shared/utils';
import { useTranslation } from 'react-i18next';
import { Nav, NavLi, NavSubheader, NavUl } from '../components'; import { Nav, NavLi, NavSubheader, NavUl } from '../components';
import { navSectionClasses, navSectionCssVars } from '../styles'; import { navSectionClasses, navSectionCssVars } from '../styles';
import type { NavGroupProps, NavSectionProps } from '../types'; import type { NavGroupProps, NavSectionProps } from '../types';
import { NavList } from './nav-list'; import { NavList } from './nav-list';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,11 +26,7 @@ export function NavSectionVertical({
const cssVars = { ...navSectionCssVars.vertical(theme), ...overridesVars }; const cssVars = { ...navSectionCssVars.vertical(theme), ...overridesVars };
return ( return (
<Nav <Nav className={mergeClasses([navSectionClasses.vertical, className])} sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]} {...other}>
className={mergeClasses([navSectionClasses.vertical, className])}
sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]}
{...other}
>
<NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}>
{data.map((group) => ( {data.map((group) => (
<Group <Group
@@ -50,14 +46,7 @@ export function NavSectionVertical({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function Group({ function Group({ items, render, subheader, slotProps, checkPermissions, enabledRootRedirect }: NavGroupProps) {
items,
render,
subheader,
slotProps,
checkPermissions,
enabledRootRedirect,
}: NavGroupProps) {
const groupOpen = useBoolean(true); const groupOpen = useBoolean(true);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -81,15 +70,9 @@ function Group({
<NavLi> <NavLi>
{subheader ? ( {subheader ? (
<> <>
<NavSubheader <NavSubheader data-title={subheader} open={groupOpen.value} onClick={groupOpen.onToggle} sx={slotProps?.subheader}>
data-title={subheader} {t(subheader)}
open={groupOpen.value}
onClick={groupOpen.onToggle}
sx={slotProps?.subheader}
>
{subheader}
</NavSubheader> </NavSubheader>
<Collapse in={groupOpen.value}>{renderContent()}</Collapse> <Collapse in={groupOpen.value}>{renderContent()}</Collapse>
</> </>
) : ( ) : (

View File

@@ -1,87 +0,0 @@
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const axiosInstance = axios.create({ baseURL: CONFIG.serverUrl });
axiosInstance.interceptors.response.use(
(response) => response,
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong!')
);
export default axiosInstance;
// ----------------------------------------------------------------------
export const fetcher = async (args: string | [string, AxiosRequestConfig]) => {
try {
const [url, config] = Array.isArray(args) ? args : [args];
const res = await axiosInstance.get(url, { ...config });
return res.data;
} catch (error) {
console.error('Failed to fetch:', error);
throw error;
}
};
// ----------------------------------------------------------------------
export const endpoints = {
chat: '/api/chat',
kanban: '/api/kanban',
calendar: '/api/calendar',
auth: {
me: '/api/auth/me',
signIn: '/api/auth/sign-in',
signUp: '/api/auth/sign-up',
},
mail: {
list: '/api/mail/list',
details: '/api/mail/details',
labels: '/api/mail/labels',
},
post: {
list: '/api/post/list',
details: '/api/post/details',
latest: '/api/post/latest',
search: '/api/post/search',
},
product: {
list: '/api/product/list',
details: '/api/product/details',
search: '/api/product/search',
save: '/api/product/saveProduct',
create: '/api/product/create',
update: '/api/product/update',
delete: '/api/product/delete',
},
user: {
list: '/api/user/list',
profile: '/api/user/profile',
update: '/api/user/update',
settings: '/api/user/settings',
details: '/api/user/details',
},
order: {
list: '/api/order/list',
profile: '/api/order/profile',
update: '/api/order/update',
settings: '/api/order/settings',
details: '/api/order/details',
changeStatus: (orderId: string) => `/api/order/changeStatus?orderId=${orderId}`,
},
invoice: {
list: '/api/invoice/list',
profile: '/api/invoice/profile',
update: '/api/invoice/update',
saveInvoice: (invoiceId: string) => `/api/invoice/saveInvoice?invoiceId=${invoiceId}`,
settings: '/api/invoice/settings',
details: '/api/invoice/details',
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
search: '/api/invoice/search',
},
};

View File

@@ -1,19 +0,0 @@
import type { FirebaseApp } from 'firebase/app';
import type { Auth as AuthType } from 'firebase/auth';
import type { Firestore as FirestoreType } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const isFirebase = CONFIG.auth.method === 'firebase';
export const firebaseApp = isFirebase ? initializeApp(CONFIG.firebase) : ({} as FirebaseApp);
export const AUTH = isFirebase ? getAuth(firebaseApp) : ({} as AuthType);
export const FIRESTORE = isFirebase ? getFirestore(firebaseApp) : ({} as FirestoreType);

View File

@@ -1,16 +0,0 @@
import type { SupabaseClient } from '@supabase/supabase-js';
import { createClient } from '@supabase/supabase-js';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const isSupabase = CONFIG.auth.method === 'supabase';
const supabaseUrl = CONFIG.supabase.url;
const supabaseKey = CONFIG.supabase.key;
export const supabase = isSupabase
? createClient(supabaseUrl, supabaseKey)
: ({} as SupabaseClient<any, 'public', any>);

View File

@@ -1,8 +1,10 @@
// src/pages/dashboard/product/details.tsx // src/pages/dashboard/product/details.tsx
import { useGetProduct } from 'src/actions/product';
import { CONFIG } from 'src/global-config';
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config';
import { useGetProduct } from 'src/actions/product';
import { ProductDetailsView } from 'src/sections/product/view'; import { ProductDetailsView } from 'src/sections/product/view';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -12,7 +14,7 @@ const metadata = { title: `Product details | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product, productLoading, productError } = useGetProduct(id); const { currentProduct: product, productLoading, productError } = useGetProduct(id);
return ( return (
<> <>

View File

@@ -1,6 +1,8 @@
import { useGetProduct } from 'src/actions/product';
import { CONFIG } from 'src/global-config';
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config';
import { useGetProduct } from 'src/actions/product';
import { ProductEditView } from 'src/sections/product/view'; import { ProductEditView } from 'src/sections/product/view';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -10,13 +12,13 @@ const metadata = { title: `Product edit | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product } = useGetProduct(id); const { currentProduct } = useGetProduct(id);
return ( return (
<> <>
<title>{metadata.title}</title> <title>{metadata.title}</title>
<ProductEditView product={product} /> <ProductEditView product={currentProduct} />
</> </>
); );
} }

View File

@@ -1,5 +1,5 @@
// src/pages/dashboard/product/list.tsx // src/pages/dashboard/product/list.tsx
//
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { ProductListView } from 'src/sections/product/view'; import { ProductListView } from 'src/sections/product/view';

View File

@@ -12,7 +12,7 @@ const metadata = { title: `Product details - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product, productLoading, productError } = useGetProduct(id); const { currentProduct: product, productLoading, productError } = useGetProduct(id);
return ( return (
<> <>

View File

@@ -1,5 +0,0 @@
function Helloworld() {
return <>helloworld</>;
}
export default Helloworld;

View File

@@ -1,5 +1,6 @@
import { useGetProducts } from 'src/actions/product';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { useGetProducts } from 'src/actions/product';
import { ProductShopView } from 'src/sections/product/view'; import { ProductShopView } from 'src/sections/product/view';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,16 +1,21 @@
import type { IProductItem } from 'src/types/product';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Fab, { fabClasses } from '@mui/material/Fab';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import Card from '@mui/material/Card';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import { ColorPreview } from 'src/components/color-utils'; import Fab, { fabClasses } from '@mui/material/Fab';
import { Iconify } from 'src/components/iconify';
import { Image } from 'src/components/image';
import { Label } from 'src/components/label';
import { RouterLink } from 'src/routes/components'; import { RouterLink } from 'src/routes/components';
import type { IProductItem } from 'src/types/product';
import { fCurrency } from 'src/utils/format-number'; import { fCurrency } from 'src/utils/format-number';
import { Label } from 'src/components/label';
import { Image } from 'src/components/image';
import { Iconify } from 'src/components/iconify';
import { ColorPreview } from 'src/components/color-utils';
import { useCheckoutContext } from '../checkout/context'; import { useCheckoutContext } from '../checkout/context';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,8 +1,11 @@
import type { BoxProps } from '@mui/material/Box'; import type { BoxProps } from '@mui/material/Box';
import type { IProductItem } from 'src/types/product';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Pagination, { paginationClasses } from '@mui/material/Pagination'; import Pagination, { paginationClasses } from '@mui/material/Pagination';
import { paths } from 'src/routes/paths'; import { paths } from 'src/routes/paths';
import type { IProductItem } from 'src/types/product';
import { ProductItem } from './product-item'; import { ProductItem } from './product-item';
import { ProductItemSkeleton } from './product-skeleton'; import { ProductItemSkeleton } from './product-skeleton';

View File

@@ -21,7 +21,7 @@ import {
PRODUCT_COLOR_NAME_OPTIONS, PRODUCT_COLOR_NAME_OPTIONS,
PRODUCT_SIZE_OPTIONS, PRODUCT_SIZE_OPTIONS,
} from 'src/_mock'; } from 'src/_mock';
import { createProduct, updateProduct } from 'src/actions/product'; import { createProduct, saveProduct } from 'src/actions/product';
import { Field, Form, schemaHelper } from 'src/components/hook-form'; import { Field, Form, schemaHelper } from 'src/components/hook-form';
import { Iconify } from 'src/components/iconify'; import { Iconify } from 'src/components/iconify';
import { toast } from 'src/components/snackbar'; import { toast } from 'src/components/snackbar';
@@ -194,6 +194,9 @@ export function ProductNewEditForm({ currentProduct }: Props) {
}; };
try { try {
// await new Promise((resolve) => setTimeout(resolve, 500));
// reset();
// sanitize file field // sanitize file field
for (let i = 0; i < values.images.length; i++) { for (let i = 0; i < values.images.length; i++) {
const temp: any = values.images[i]; const temp: any = values.images[i];
@@ -202,14 +205,12 @@ export function ProductNewEditForm({ currentProduct }: Props) {
} }
} }
const sanitizedValues: IProductItem = values as unknown as IProductItem;
if (currentProduct) { if (currentProduct) {
// perform save // perform save
updateProduct(sanitizedValues); await saveProduct(currentProduct.id, values);
} else { } else {
// perform create // perform create
createProduct(sanitizedValues); await createProduct(values);
} }
toast.success(currentProduct ? 'Update success!' : 'Create success!'); toast.success(currentProduct ? 'Update success!' : 'Create success!');

View File

@@ -1,14 +1,18 @@
// src/sections/product/product-table-row.tsx // src/sections/product/product-table-row.tsx
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import LinearProgress from '@mui/material/LinearProgress';
import Link from '@mui/material/Link';
import ListItemText from '@mui/material/ListItemText';
import type { GridCellParams } from '@mui/x-data-grid'; import type { GridCellParams } from '@mui/x-data-grid';
import { Label } from 'src/components/label';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Avatar from '@mui/material/Avatar';
import ListItemText from '@mui/material/ListItemText';
import LinearProgress from '@mui/material/LinearProgress';
import { RouterLink } from 'src/routes/components'; import { RouterLink } from 'src/routes/components';
import { fCurrency } from 'src/utils/format-number'; import { fCurrency } from 'src/utils/format-number';
import { fDate, fTime } from 'src/utils/format-time'; import { fTime, fDate } from 'src/utils/format-time';
import { Label } from 'src/components/label';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,6 +1,9 @@
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { DashboardContent } from 'src/layouts/dashboard';
import { paths } from 'src/routes/paths'; import { paths } from 'src/routes/paths';
import { DashboardContent } from 'src/layouts/dashboard';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { ProductNewEditForm } from '../product-new-edit-form'; import { ProductNewEditForm } from '../product-new-edit-form';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,7 +1,11 @@
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { DashboardContent } from 'src/layouts/dashboard';
import { paths } from 'src/routes/paths';
import type { IProductItem } from 'src/types/product'; import type { IProductItem } from 'src/types/product';
import { paths } from 'src/routes/paths';
import { DashboardContent } from 'src/layouts/dashboard';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { ProductNewEditForm } from '../product-new-edit-form'; import { ProductNewEditForm } from '../product-new-edit-form';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -35,11 +35,9 @@ import { EmptyContent } from 'src/components/empty-content';
import { Iconify } from 'src/components/iconify'; import { Iconify } from 'src/components/iconify';
import { toast } from 'src/components/snackbar'; import { toast } from 'src/components/snackbar';
import { DashboardContent } from 'src/layouts/dashboard'; import { DashboardContent } from 'src/layouts/dashboard';
import { endpoints } from 'src/lib/axios';
import { RouterLink } from 'src/routes/components'; import { RouterLink } from 'src/routes/components';
import { paths } from 'src/routes/paths'; import { paths } from 'src/routes/paths';
import type { IProductItem, IProductTableFilters } from 'src/types/product'; import type { IProductItem, IProductTableFilters } from 'src/types/product';
import { mutate } from 'swr';
import { ProductTableFiltersResult } from '../product-table-filters-result'; import { ProductTableFiltersResult } from '../product-table-filters-result';
import { import {
RenderCellCreatedAt, RenderCellCreatedAt,
@@ -61,8 +59,6 @@ const HIDE_COLUMNS_TOGGLABLE = ['category', 'actions'];
export function ProductListView() { export function ProductListView() {
const { t } = useTranslation(); const { t } = useTranslation();
const confirmDialog = useBoolean();
const PRODUCT_STOCK_OPTIONS = [ const PRODUCT_STOCK_OPTIONS = [
{ value: 'in stock', label: t('In stock') }, { value: 'in stock', label: t('In stock') },
{ value: 'low stock', label: t('Low stock') }, { value: 'low stock', label: t('Low stock') },
@@ -79,7 +75,7 @@ export function ProductListView() {
const confirmDeleteSingleItemDialog = useBoolean(); const confirmDeleteSingleItemDialog = useBoolean();
const [idToDelete, setIdToDelete] = useState<string | null>(null); const [idToDelete, setIdToDelete] = useState<string | null>(null);
const { products, productsLoading } = useGetProducts(); const { products, productsLoading, mutate } = useGetProducts();
const [tableData, setTableData] = useState<IProductItem[]>(products); const [tableData, setTableData] = useState<IProductItem[]>(products);
const [selectedRowIds, setSelectedRowIds] = useState<GridRowSelectionModel>([]); const [selectedRowIds, setSelectedRowIds] = useState<GridRowSelectionModel>([]);
@@ -117,29 +113,13 @@ export function ProductListView() {
toast.error('Delete failed!'); toast.error('Delete failed!');
} }
// TODO: reload table here
mutate();
// setTableData(deleteRow); // setTableData(deleteRow);
setDeleteInProgress(false); setDeleteInProgress(false);
}, [idToDelete, mutate]); }, [idToDelete, mutate]);
// NOTE: this is working using example from calendar
const handleDeleteRow = useCallback(
async (id: string) => {
try {
await deleteProduct(id);
// invalidate cache to reload list
await mutate(endpoints.product.list);
toast.success('Delete success!');
} catch (error) {
console.error(error);
toast.error('Delete error!');
}
},
[tableData]
);
const handleDeleteRows = useCallback(() => { const handleDeleteRows = useCallback(() => {
const deleteRows = tableData.filter((row) => !selectedRowIds.includes(row.id)); const deleteRows = tableData.filter((row) => !selectedRowIds.includes(row.id));

View File

@@ -1,28 +1,33 @@
import Box from '@mui/material/Box'; import type { IProductItem } from 'src/types/product';
import Button from '@mui/material/Button'; import type { Theme, SxProps } from '@mui/material/styles';
import Card from '@mui/material/Card';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import type { SxProps, Theme } from '@mui/material/styles';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import { useTabs } from 'minimal-shared/hooks'; import { useTabs } from 'minimal-shared/hooks';
import { varAlpha } from 'minimal-shared/utils'; import { varAlpha } from 'minimal-shared/utils';
import { useTranslation } from 'react-i18next';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs'; import Tab from '@mui/material/Tab';
import { EmptyContent } from 'src/components/empty-content'; import Box from '@mui/material/Box';
import { Iconify } from 'src/components/iconify'; import Tabs from '@mui/material/Tabs';
import { RouterLink } from 'src/routes/components'; import Card from '@mui/material/Card';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import { paths } from 'src/routes/paths'; import { paths } from 'src/routes/paths';
import type { IProductItem } from 'src/types/product'; import { RouterLink } from 'src/routes/components';
import { useCheckoutContext } from '../../checkout/context';
import { Iconify } from 'src/components/iconify';
import { EmptyContent } from 'src/components/empty-content';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { CartIcon } from '../cart-icon'; import { CartIcon } from '../cart-icon';
import { ProductDetailsCarousel } from '../product-details-carousel'; import { useCheckoutContext } from '../../checkout/context';
import { ProductDetailsDescription } from '../product-details-description'; import { ProductDetailsSkeleton } from '../product-skeleton';
import { ProductDetailsReview } from '../product-details-review'; import { ProductDetailsReview } from '../product-details-review';
import { ProductDetailsSummary } from '../product-details-summary'; import { ProductDetailsSummary } from '../product-details-summary';
import { ProductDetailsSkeleton } from '../product-skeleton'; import { ProductDetailsCarousel } from '../product-details-carousel';
import { ProductDetailsDescription } from '../product-details-description';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -11,7 +11,6 @@ import TableCell from '@mui/material/TableCell';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import { useBoolean, usePopover } from 'minimal-shared/hooks'; import { useBoolean, usePopover } from 'minimal-shared/hooks';
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ConfirmDialog } from 'src/components/custom-dialog'; import { ConfirmDialog } from 'src/components/custom-dialog';
import { CustomPopover } from 'src/components/custom-popover'; import { CustomPopover } from 'src/components/custom-popover';
@@ -20,6 +19,7 @@ import { Label } from 'src/components/label';
import { RouterLink } from 'src/routes/components'; import { RouterLink } from 'src/routes/components';
import type { IUserItem } from 'src/types/user'; import type { IUserItem } from 'src/types/user';
import { UserQuickEditForm } from './user-quick-edit-form'; import { UserQuickEditForm } from './user-quick-edit-form';
import { useState } from 'react';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -29,32 +29,41 @@ export type IProductReview = {
export type IProductItem = { export type IProductItem = {
id: string; id: string;
createdAt: IDateValue;
//
available: number;
category: string;
code: string;
colors: string[];
coverUrl: string;
description: string;
gender: string[];
images: string[];
inventoryType: string;
name: string;
newLabel: { content: string; enabled: boolean };
price: number;
priceSale: number | null;
publish: string;
quantity: number;
ratings: { name: string; starCount: number; reviewCount: number }[];
reviews: IProductReview[];
saleLabel: { content: string; enabled: boolean };
sizes: string[];
sku: string; sku: string;
subDescription: string; name: string;
tags: string[]; code: string;
price: number;
taxes: 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; totalRatings: number;
totalReviews: number; totalReviews: number;
totalSold: 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;
}[];
}; };

Some files were not shown because too many files have changed in this diff Show More