From 9f5367e35ce2cc3821ad5de276e8980ee57f2191 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Wed, 28 May 2025 23:17:04 +0800 Subject: [PATCH] init user edit, --- 03_source/cms_backend/package.json | 3 +- 03_source/cms_backend/prisma/schema.prisma | 3 + 03_source/cms_backend/prisma/seed.ts | 2 + 03_source/cms_backend/prisma/seeds/user.ts | 8 +- .../cms_backend/prisma/seeds/userItem.ts | 97 +++++++ .../src/app/api/user/createProduct/route.ts | 75 ++++++ .../src/app/api/user/deleteProduct/route.ts | 44 ++++ .../src/app/api/user/deleteProduct/test.http | 3 + .../src/app/api/user/details/route.ts | 47 ++++ .../src/app/api/user/image/upload/route.ts | 30 +++ .../src/app/api/user/list/route.ts | 22 ++ .../src/app/api/user/list/test.http | 4 + .../src/app/api/user/saveProduct/route.ts | 129 ++++++++++ .../src/app/api/user/search/route.ts | 37 +++ 03_source/cms_backend/yarn.lock | 5 + 03_source/frontend/src/actions/user.ts | 236 ++++++++++++++++++ .../main/nav/desktop/nav-desktop-item.tsx | 10 +- .../main/nav/desktop/nav-desktop-list.tsx | 13 +- .../layouts/main/nav/desktop/nav-desktop.tsx | 3 +- .../main/nav/mobile/nav-mobile-item.tsx | 10 +- .../main/nav/mobile/nav-mobile-list.tsx | 20 +- .../layouts/main/nav/mobile/nav-mobile.tsx | 15 +- .../frontend/src/layouts/main/nav/types.ts | 2 +- .../src/layouts/nav-config-account.tsx | 1 - 03_source/frontend/src/locales/all-langs.ts | 13 + .../frontend/src/locales/langs/hk/common.json | 13 + .../frontend/src/locales/langs/jp/common.json | 74 ++++++ .../frontend/src/locales/langs/jp/navbar.json | 12 + .../frontend/src/locales/locales-config.ts | 2 +- .../src/pages/dashboard/user/edit.tsx | 6 +- .../src/pages/dashboard/user/list.tsx | 1 - .../src/pages/dashboard/user/profile.tsx | 1 - .../product/product-table-toolbar.tsx | 9 +- .../product/view/product-list-view.tsx | 1 + .../src/sections/user/user-new-edit-form.tsx | 30 ++- .../sections/user/user-quick-edit-form.tsx | 25 +- .../src/sections/user/user-table-row.tsx | 37 ++- .../src/sections/user/view/user-edit-view.tsx | 10 +- .../src/sections/user/view/user-list-view.tsx | 89 +++---- 39 files changed, 975 insertions(+), 167 deletions(-) create mode 100644 03_source/cms_backend/prisma/seeds/userItem.ts create mode 100644 03_source/cms_backend/src/app/api/user/createProduct/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/deleteProduct/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/deleteProduct/test.http create mode 100644 03_source/cms_backend/src/app/api/user/details/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/image/upload/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/list/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/list/test.http create mode 100644 03_source/cms_backend/src/app/api/user/saveProduct/route.ts create mode 100644 03_source/cms_backend/src/app/api/user/search/route.ts create mode 100644 03_source/frontend/src/actions/user.ts create mode 100755 03_source/frontend/src/locales/langs/jp/common.json create mode 100755 03_source/frontend/src/locales/langs/jp/navbar.json diff --git a/03_source/cms_backend/package.json b/03_source/cms_backend/package.json index 6cbba2e..0d488b3 100644 --- a/03_source/cms_backend/package.json +++ b/03_source/cms_backend/package.json @@ -37,6 +37,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@faker-js/faker": "^9.8.0", "@mui/material": "^6.4.8", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-pg": "^6.8.2", @@ -75,4 +76,4 @@ "typescript": "^5.8.2", "typescript-eslint": "^8.28.0" } -} \ No newline at end of file +} diff --git a/03_source/cms_backend/prisma/schema.prisma b/03_source/cms_backend/prisma/schema.prisma index f670f34..5e930e9 100644 --- a/03_source/cms_backend/prisma/schema.prisma +++ b/03_source/cms_backend/prisma/schema.prisma @@ -528,6 +528,9 @@ model UserItem { avatarUrl String phoneNumber String isVerified Boolean + // + username String + password String } model UserAccountBillingHistory { diff --git a/03_source/cms_backend/prisma/seed.ts b/03_source/cms_backend/prisma/seed.ts index 9690dae..8101a92 100644 --- a/03_source/cms_backend/prisma/seed.ts +++ b/03_source/cms_backend/prisma/seed.ts @@ -22,6 +22,7 @@ import { userSeed } from './seeds/user'; import { ProductReview } from './seeds/productReview'; import { ProductItem } from './seeds/productItem'; import { FileStore } from './seeds/fileStore'; +import { userItemSeed } from './seeds/userItem'; // // import { Blog } from './seeds/blog'; // import { Mail } from './seeds/mail'; @@ -37,6 +38,7 @@ import { FileStore } from './seeds/fileStore'; await ProductReview; await FileStore; await ProductItem; + await userItemSeed; // await Blog; // await Mail; // await File; diff --git a/03_source/cms_backend/prisma/seeds/user.ts b/03_source/cms_backend/prisma/seeds/user.ts index 473f27a..d3f36a3 100644 --- a/03_source/cms_backend/prisma/seeds/user.ts +++ b/03_source/cms_backend/prisma/seeds/user.ts @@ -8,8 +8,8 @@ async function user() { create: { email: 'alice@prisma.io', name: 'Alice', - password: 'Aa12345678' - } + password: 'Aa12345678', + }, }); const bob = await prisma.user.upsert({ @@ -18,8 +18,8 @@ async function user() { create: { email: 'bob@prisma.io', name: 'Bob', - password: 'Aa12345678' - } + password: 'Aa12345678', + }, }); console.log('seed user done'); } diff --git a/03_source/cms_backend/prisma/seeds/userItem.ts b/03_source/cms_backend/prisma/seeds/userItem.ts new file mode 100644 index 0000000..852ab14 --- /dev/null +++ b/03_source/cms_backend/prisma/seeds/userItem.ts @@ -0,0 +1,97 @@ +import { PrismaClient } from '@prisma/client'; +import { generateHash } from 'src/utils/hash'; +import { Config, names, uniqueNamesGenerator } from 'unique-names-generator'; +import { faker } from '@faker-js/faker'; + +import { faker as enFaker } from '@faker-js/faker/locale/en_US'; +import { faker as zhFaker } from '@faker-js/faker/locale/zh_CN'; +import { faker as jaFaker } from '@faker-js/faker/locale/ja'; +import { faker as koFaker } from '@faker-js/faker/locale/ko'; +import { faker as twFaker } from '@faker-js/faker/locale/zh_TW'; + +const SEED_EMAIL_DOMAIN = 'seed.com'; + +const prisma = new PrismaClient(); + +async function userItem() { + const config: Config = { dictionaries: [names] }; + const firstName = uniqueNamesGenerator(config); + const lastName = uniqueNamesGenerator(config); + const username = `${firstName.toLowerCase()}-${lastName.toLowerCase()}`; + + const alice = await prisma.userItem.upsert({ + where: { id: 0 }, + update: {}, + create: { + name: `${firstName} ${lastName}`, + city: '', + role: '', + email: `${username}@${SEED_EMAIL_DOMAIN}`, + state: '', + status: '', + address: '', + country: '', + zipCode: '', + company: '', + avatarUrl: '', + phoneNumber: '', + isVerified: true, + // + username, + password: await generateHash('Abc1234!'), + }, + }); + + for (let i = 1; i < 20; i++) { + const CJK_LOCALES = { + en: enFaker, + zh: zhFaker, + ja: jaFaker, + ko: koFaker, + tw: twFaker, + }; + function getRandomCJKFaker() { + const locales = Object.keys(CJK_LOCALES); + const randomKey = locales[Math.floor(Math.random() * locales.length)] as keyof typeof CJK_LOCALES; + return CJK_LOCALES[randomKey]; + } + const randomFaker = getRandomCJKFaker(); + + const alice = await prisma.userItem.upsert({ + where: { id: i }, + update: {}, + create: { + name: randomFaker.person.fullName(), + city: randomFaker.location.city(), + role: 'user', + email: randomFaker.internet.email(), + state: randomFaker.location.state(), + status: '', + address: randomFaker.location.streetAddress(), + country: randomFaker.location.country(), + zipCode: randomFaker.location.zipCode(), + company: randomFaker.company.name(), + avatarUrl: randomFaker.image.avatar(), + phoneNumber: randomFaker.phone.number(), + isVerified: true, + // + username: randomFaker.internet.username(), + password: await generateHash('Abc1234!'), + }, + }); + } + + console.log('seed user done'); +} + +const userItemSeed = userItem() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); + +export { userItemSeed }; diff --git a/03_source/cms_backend/src/app/api/user/createProduct/route.ts b/03_source/cms_backend/src/app/api/user/createProduct/route.ts new file mode 100644 index 0000000..84f3bc8 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/createProduct/route.ts @@ -0,0 +1,75 @@ +// src/app/api/product/createProduct/route.ts +// +// PURPOSE: +// create product to db +// +// RULES: +// T.B.A. +// + +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * POST - Products + *************************************** */ +export async function POST(req: NextRequest) { + // logger('[Product] list', products.length); + const { data } = await req.json(); + const createForm: CreateProductData = data as unknown as CreateProductData; + + console.log({ createForm }); + + try { + console.log({ data }); + await prisma.productItem.create({ data: createForm }); + return response({ hello: 'world' }, STATUS.OK); + } catch (error) { + console.log({ hello: 'world', data }); + 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; + // }[]; +}; diff --git a/03_source/cms_backend/src/app/api/user/deleteProduct/route.ts b/03_source/cms_backend/src/app/api/user/deleteProduct/route.ts new file mode 100644 index 0000000..f1dcf17 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/deleteProduct/route.ts @@ -0,0 +1,44 @@ +// src/app/api/product/deleteProduct/route.ts +// +// PURPOSE: +// delete product from db by id +// +// RULES: +// T.B.A. + +import type { NextRequest } from 'next/server'; + +import { logger } from 'src/utils/logger'; +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * handle Delete Products + *************************************** */ +export async function DELETE(req: NextRequest) { + try { + const { searchParams } = req.nextUrl; + + // RULES: productId must exist + const productId = searchParams.get('productId'); + if (!productId) { + return response({ message: 'Product ID is required!' }, STATUS.BAD_REQUEST); + } + + // NOTE: productId confirmed exist, run below + const product = await prisma.productItem.delete({ where: { id: productId } }); + + if (!product) { + return response({ message: 'Product not found!' }, STATUS.NOT_FOUND); + } + + logger('[Product] details', product.id); + + return response({ product }, STATUS.OK); + } catch (error) { + return handleError('Product - Get details', error); + } +} diff --git a/03_source/cms_backend/src/app/api/user/deleteProduct/test.http b/03_source/cms_backend/src/app/api/user/deleteProduct/test.http new file mode 100644 index 0000000..4d6bfb6 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/deleteProduct/test.http @@ -0,0 +1,3 @@ +### + +DELETE http://localhost:7272/api/product/deleteProduct?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b06 diff --git a/03_source/cms_backend/src/app/api/user/details/route.ts b/03_source/cms_backend/src/app/api/user/details/route.ts new file mode 100644 index 0000000..f75d47c --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/details/route.ts @@ -0,0 +1,47 @@ +// src/app/api/product/details/route.ts +// +// PURPOSE: +// save product to db by id +// +// RULES: +// T.B.A. + +import type { NextRequest } from 'next/server'; + +import { logger } from 'src/utils/logger'; +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * GET Product detail + *************************************** */ +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl; + + // RULES: productId must exist + const productId = searchParams.get('productId'); + if (!productId) { + return response({ message: 'Product ID is required!' }, STATUS.BAD_REQUEST); + } + + // NOTE: productId confirmed exist, run below + const product = await prisma.productItem.findFirst({ + include: { reviews: true }, + where: { id: productId }, + }); + + if (!product) { + return response({ message: 'Product not found!' }, STATUS.NOT_FOUND); + } + + logger('[Product] details', product.id); + + return response({ product }, STATUS.OK); + } catch (error) { + return handleError('Product - Get details', error); + } +} diff --git a/03_source/cms_backend/src/app/api/user/image/upload/route.ts b/03_source/cms_backend/src/app/api/user/image/upload/route.ts new file mode 100644 index 0000000..22a1330 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/image/upload/route.ts @@ -0,0 +1,30 @@ +// src/app/api/product/image/upload/route.ts +// +// PURPOSE: +// handle upload product image +// +// RULES: +// T.B.A. + +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +// import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * GET - Products + *************************************** */ +export async function POST(req: NextRequest) { + try { + const { data } = await req.json(); + console.log('helloworld'); + + return response({ hello: 'world' }, STATUS.OK); + } catch (error) { + console.log({ hello: 'world' }); + return handleError('Product - store product image', error); + } +} diff --git a/03_source/cms_backend/src/app/api/user/list/route.ts b/03_source/cms_backend/src/app/api/user/list/route.ts new file mode 100644 index 0000000..097f217 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/list/route.ts @@ -0,0 +1,22 @@ +// src/app/api/product/list/route.ts +import { logger } from 'src/utils/logger'; +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * GET - Products + *************************************** */ +export async function GET() { + try { + const users = await prisma.userItem.findMany(); + + logger('[User] list', users.length); + + return response({ users }, STATUS.OK); + } catch (error) { + return handleError('Product - Get list', error); + } +} diff --git a/03_source/cms_backend/src/app/api/user/list/test.http b/03_source/cms_backend/src/app/api/user/list/test.http new file mode 100644 index 0000000..c763574 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/list/test.http @@ -0,0 +1,4 @@ +### + +GET http://localhost:7272/api/user/list + diff --git a/03_source/cms_backend/src/app/api/user/saveProduct/route.ts b/03_source/cms_backend/src/app/api/user/saveProduct/route.ts new file mode 100644 index 0000000..fd462e5 --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/saveProduct/route.ts @@ -0,0 +1,129 @@ +// src/app/api/product/saveProduct/route.ts +// +// PURPOSE: +// save product to db by id +// +// RULES: +// T.B.A. + +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * GET - Products + *************************************** */ +export async function POST(req: NextRequest) { + // logger('[Product] list', products.length); + const { data } = await req.json(); + + try { + const products = await prisma.productItem.updateMany({ + data: { + name: data.name, + sku: data.sku, + code: data.code, + price: data.price, + taxes: data.taxes, + tags: data.tags, + sizes: data.sizes, + publish: data.publish, + gender: data.gender, + coverUrl: data.coverUrl, + images: data.images, + colors: data.colors, + quantity: data.quantity, + category: data.category, + available: data.available, + totalSold: data.totalSold, + description: data.description, + totalRatings: data.totalRatings, + totalReviews: data.totalReviews, + inventoryType: data.inventoryType, + subDescription: data.subDescription, + priceSale: data.priceSale, + // + newLabel: { + content: data.newLabel?.content || '', + enabled: data.newLabel?.enabled ?? false, + }, + saleLabel: { + content: data.saleLabel?.content || '', + enabled: data.saleLabel?.enabled ?? false, + }, + ratings: { + set: data.ratings.map((rating: { name: string; starCount: number; reviewCount: number }) => ({ + name: rating.name, + starCount: rating.starCount, + reviewCount: rating.reviewCount, + })), + }, + }, + where: { id: data.id }, + }); + + return response({ hello: 'world', data }, STATUS.OK); + } catch (error) { + console.log({ hello: 'world', data }); + return handleError('Product - Get list', error); + } +} + +export type IProductItem = { + id: string; + sku: string; + name: string; + code: string; + price: number; + taxes: number; + tags: string[]; + sizes: string[]; + publish: string; + gender: string[]; + coverUrl: string; + images: string[]; + colors: string[]; + quantity: number; + category: string; + available: number; + totalSold: number; + description: string; + totalRatings: number; + totalReviews: number; + // createdAt: IDateValue; + inventoryType: string; + subDescription: string; + priceSale: number | null; + // reviews: IProductReview[]; + newLabel: { + content: string; + enabled: boolean; + }; + saleLabel: { + content: string; + enabled: boolean; + }; + ratings: { + name: string; + starCount: number; + reviewCount: number; + }[]; +}; + +export type IDateValue = string | number | null; + +export type IProductReview = { + id: string; + name: string; + rating: number; + comment: string; + helpful: number; + avatarUrl: string; + postedAt: IDateValue; + isPurchased: boolean; + attachments?: string[]; +}; diff --git a/03_source/cms_backend/src/app/api/user/search/route.ts b/03_source/cms_backend/src/app/api/user/search/route.ts new file mode 100644 index 0000000..bf77d2d --- /dev/null +++ b/03_source/cms_backend/src/app/api/user/search/route.ts @@ -0,0 +1,37 @@ +import type { NextRequest } from 'next/server'; + +import { logger } from 'src/utils/logger'; +import { STATUS, response, handleError } from 'src/utils/response'; + +import { _products } from 'src/_mock/_product'; + +// ---------------------------------------------------------------------- + +export const runtime = 'edge'; + +/** ************************************** + * GET - Search products + *************************************** */ +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl; + const query = searchParams.get('query')?.trim().toLowerCase(); + + if (!query) { + return response({ results: [] }, STATUS.OK); + } + + const products = _products(); + + // Accept search by name or sku + const results = products.filter( + ({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query) + ); + + logger('[Product] search-results', results.length); + + return response({ results }, STATUS.OK); + } catch (error) { + return handleError('Product - Get search', error); + } +} diff --git a/03_source/cms_backend/yarn.lock b/03_source/cms_backend/yarn.lock index 4f4d74a..ac0c661 100644 --- a/03_source/cms_backend/yarn.lock +++ b/03_source/cms_backend/yarn.lock @@ -411,6 +411,11 @@ "@eslint/core" "^0.12.0" levn "^0.4.1" +"@faker-js/faker@^9.8.0": + version "9.8.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.8.0.tgz#3344284028d1c9dc98dee2479f82939310370d88" + integrity sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" diff --git a/03_source/frontend/src/actions/user.ts b/03_source/frontend/src/actions/user.ts new file mode 100644 index 0000000..00fda7d --- /dev/null +++ b/03_source/frontend/src/actions/user.ts @@ -0,0 +1,236 @@ +import { useMemo } from 'react'; +import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import type { IProductItem } from 'src/types/product'; +import { IUserItem } from 'src/types/user'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + +// ---------------------------------------------------------------------- + +const swrOptions: SWRConfiguration = { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, +}; + +// ---------------------------------------------------------------------- + +type UserData = { + users: IUserItem[]; +}; + +export function useGetUsers() { + // const url = endpoints.user.list; + const url = `http://localhost:7272/api/user/list`; + + const { data, isLoading, error, isValidating, mutate } = useSWR( + url, + fetcher, + swrOptions + ); + + const memoizedValue = useMemo( + () => ({ + users: data?.users || [], + usersLoading: isLoading, + usersError: error, + usersValidating: isValidating, + usersEmpty: !isLoading && !isValidating && !data?.users.length, + mutate, + }), + [data?.users, error, isLoading, isValidating, mutate] + ); + + return memoizedValue; +} + +// ---------------------------------------------------------------------- + +type ProductData = { + product: IProductItem; +}; + +export function useGetProduct(productId: string) { + const url = productId ? [endpoints.product.details, { params: { productId } }] : ''; + + const { data, isLoading, error, isValidating } = useSWR(url, fetcher, swrOptions); + + const memoizedValue = useMemo( + () => ({ + product: data?.product, + productLoading: isLoading, + productError: error, + productValidating: isValidating, + }), + [data?.product, error, isLoading, isValidating] + ); + + return memoizedValue; +} + +// ---------------------------------------------------------------------- + +type SearchResultsData = { + results: IProductItem[]; +}; + +export function useSearchProducts(query: string) { + const url = query ? [endpoints.product.search, { params: { query } }] : ''; + + const { data, isLoading, error, isValidating } = useSWR(url, fetcher, { + ...swrOptions, + keepPreviousData: true, + }); + + const memoizedValue = useMemo( + () => ({ + searchResults: data?.results || [], + searchLoading: isLoading, + searchError: error, + searchValidating: isValidating, + searchEmpty: !isLoading && !isValidating && !data?.results.length, + }), + [data?.results, error, isLoading, isValidating] + ); + + return memoizedValue; +} + +// ---------------------------------------------------------------------- + +type SaveProductData = { + // id: string; + sku: string; + name: string; + code: string; + 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 saveProduct(productId: string, saveProductData: SaveProductData) { + console.log('save product ?'); + // const url = productId ? [endpoints.product.details, { params: { productId } }] : ''; + + const res = await axiosInstance.post('http://localhost:7272/api/product/saveProduct', { + data: saveProductData, + }); + + 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; +} + +// ---------------------------------------------------------------------- + +type CreateProductData = { + // id: string; + sku: string; + name: string; + code: string; + 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) { + console.log('create product ?'); + // const url = productId ? [endpoints.product.details, { params: { productId } }] : ''; + + const res = await axiosInstance.post('http://localhost:7272/api/product/createProduct', { + data: createProductData, + }); + + return res; +} + +// ---------------------------------------------------------------------- + +type DeleteProductResponse = { + success: boolean; + message?: string; +}; + +export async function deleteUser(productId: string): Promise { + const url = `http://localhost:7272/api/product/deleteProduct?productId=${productId}`; + + try { + const res = await axiosInstance.delete(url); + console.log({ res }); + + return { + success: true, + message: 'Product deleted successfully', + }; + } catch (error) { + return { + success: false, + message: error instanceof Error ? error.message : 'Failed to delete product', + }; + } +} diff --git a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-item.tsx b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-item.tsx index 21f37b6..65c2b27 100644 --- a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-item.tsx +++ b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-item.tsx @@ -1,13 +1,9 @@ -import type { CSSObject } from '@mui/material/styles'; - -import { varAlpha, mergeClasses } from 'minimal-shared/utils'; - -import { styled } from '@mui/material/styles'; import ButtonBase from '@mui/material/ButtonBase'; - +import type { CSSObject } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; +import { mergeClasses, varAlpha } from 'minimal-shared/utils'; import { Iconify } from 'src/components/iconify'; import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section'; - import type { NavItemProps } from '../types'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-list.tsx b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-list.tsx index d5bb15c..0dc51d6 100644 --- a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-list.tsx +++ b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop-list.tsx @@ -1,14 +1,11 @@ import { useBoolean } from 'minimal-shared/hooks'; -import { useRef, useEffect, useCallback } from 'react'; -import { isEqualPath, isActiveLink, isExternalLink } from 'minimal-shared/utils'; - +import { isActiveLink, isEqualPath, isExternalLink } from 'minimal-shared/utils'; +import { useCallback, useEffect, useRef } from 'react'; import { usePathname } from 'src/routes/hooks'; - -import { NavItem } from './nav-desktop-item'; -import { Nav, NavLi, NavUl, NavDropdown } from '../components'; -import { NavItemDashboard } from './nav-desktop-item-dashboard'; - +import { Nav, NavDropdown, NavLi, NavUl } from '../components'; import type { NavListProps, NavSubListProps } from '../types'; +import { NavItem } from './nav-desktop-item'; +import { NavItemDashboard } from './nav-desktop-item-dashboard'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop.tsx b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop.tsx index 7231b4c..f4426b1 100644 --- a/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop.tsx +++ b/03_source/frontend/src/layouts/main/nav/desktop/nav-desktop.tsx @@ -1,7 +1,6 @@ import { Nav, NavUl } from '../components'; -import { NavList } from './nav-desktop-list'; - import type { NavMainProps } from '../types'; +import { NavList } from './nav-desktop-list'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-item.tsx b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-item.tsx index 75a62c7..4cc453c 100644 --- a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-item.tsx +++ b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-item.tsx @@ -1,13 +1,9 @@ -import type { CSSObject } from '@mui/material/styles'; - -import { varAlpha, mergeClasses } from 'minimal-shared/utils'; - -import { styled } from '@mui/material/styles'; import ButtonBase from '@mui/material/ButtonBase'; - +import type { CSSObject } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; +import { mergeClasses, varAlpha } from 'minimal-shared/utils'; import { Iconify } from 'src/components/iconify'; import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section'; - import type { NavItemProps } from '../types'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-list.tsx b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-list.tsx index 77f9716..7ef1a60 100644 --- a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-list.tsx +++ b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile-list.tsx @@ -1,20 +1,14 @@ -import { useRef, useCallback } from 'react'; -import { useBoolean } from 'minimal-shared/hooks'; -import { varAlpha, isActiveLink, isExternalLink } from 'minimal-shared/utils'; - import Collapse from '@mui/material/Collapse'; - -import { paths } from 'src/routes/paths'; -import { usePathname } from 'src/routes/hooks'; - -import { CONFIG } from 'src/global-config'; - +import { useBoolean } from 'minimal-shared/hooks'; +import { isActiveLink, isExternalLink, varAlpha } from 'minimal-shared/utils'; +import { useCallback, useRef } from 'react'; import { navSectionClasses, NavSectionVertical } from 'src/components/nav-section'; - +import { CONFIG } from 'src/global-config'; +import { usePathname } from 'src/routes/hooks'; +import { paths } from 'src/routes/paths'; import { NavLi } from '../components'; -import { NavItem } from './nav-mobile-item'; - import type { NavListProps } from '../types'; +import { NavItem } from './nav-mobile-item'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile.tsx b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile.tsx index 17df78c..f39cce2 100644 --- a/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile.tsx +++ b/03_source/frontend/src/layouts/main/nav/mobile/nav-mobile.tsx @@ -1,20 +1,15 @@ -import { useEffect } from 'react'; - import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Drawer from '@mui/material/Drawer'; - -import { paths } from 'src/routes/paths'; -import { usePathname } from 'src/routes/hooks'; - +import { useEffect } from 'react'; import { Logo } from 'src/components/logo'; import { Scrollbar } from 'src/components/scrollbar'; - -import { Nav, NavUl } from '../components'; -import { NavList } from './nav-mobile-list'; +import { usePathname } from 'src/routes/hooks'; +import { paths } from 'src/routes/paths'; import { SignInButton } from '../../../components/sign-in-button'; - +import { Nav, NavUl } from '../components'; import type { NavMainProps } from '../types'; +import { NavList } from './nav-mobile-list'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/main/nav/types.ts b/03_source/frontend/src/layouts/main/nav/types.ts index 6713bba..d4d449d 100644 --- a/03_source/frontend/src/layouts/main/nav/types.ts +++ b/03_source/frontend/src/layouts/main/nav/types.ts @@ -1,5 +1,5 @@ -import type { Theme, SxProps } from '@mui/material/styles'; import type { ButtonBaseProps } from '@mui/material/ButtonBase'; +import type { SxProps, Theme } from '@mui/material/styles'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/layouts/nav-config-account.tsx b/03_source/frontend/src/layouts/nav-config-account.tsx index 379bbcb..3bbf382 100644 --- a/03_source/frontend/src/layouts/nav-config-account.tsx +++ b/03_source/frontend/src/layouts/nav-config-account.tsx @@ -1,5 +1,4 @@ import { Iconify } from 'src/components/iconify'; - import type { AccountDrawerProps } from './components/account-drawer'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/locales/all-langs.ts b/03_source/frontend/src/locales/all-langs.ts index b08b753..397de89 100644 --- a/03_source/frontend/src/locales/all-langs.ts +++ b/03_source/frontend/src/locales/all-langs.ts @@ -2,6 +2,7 @@ import { arSA as arSACore, frFR as frFRCore, + jaJP as jaJPCore, viVN as viVNCore, zhCN as zhCNCore, zhHK as zhHKCore, @@ -11,6 +12,7 @@ import { arSD as arSDDataGrid, enUS as enUSDataGrid, frFR as frFRDataGrid, + jaJP as jaJPDataGrid, viVN as viVNDataGrid, zhCN as zhCNDataGrid, zhHK as zhHKDataGrid, @@ -19,6 +21,7 @@ import { import { enUS as enUSDate, frFR as frFRDate, + jaJP as jaJPDate, viVN as viVNDate, zhCN as zhCNDate, zhHK as zhHKDate, @@ -94,6 +97,16 @@ export const allLangs = [ }, }, }, + { + value: 'jp', + label: 'Japanese', + countryCode: 'JP', + adapterLocale: 'ja', + numberFormat: { code: 'ja-JP', currency: 'JPY' }, + systemValue: { + components: { ...jaJPCore.components, ...jaJPDate.components, ...jaJPDataGrid.components }, + }, + }, ]; /** diff --git a/03_source/frontend/src/locales/langs/hk/common.json b/03_source/frontend/src/locales/langs/hk/common.json index f374fbc..5b7eaac 100755 --- a/03_source/frontend/src/locales/langs/hk/common.json +++ b/03_source/frontend/src/locales/langs/hk/common.json @@ -58,5 +58,18 @@ "draft": "草稿", "Dashboard": "儀表板", "Apply": "應用", + "Name": "名稱", + "Phone number": "電話", + "Company": "公司", + "Role": "角色", + "Status": "狀態", + "New user": "新用戶", + "All": "全部", + "Active": "活躍", + "Pending": "等待", + "Banned": "已封鎖", + "Rejected": "已反對", + "Quick update": "快速更新", + "Address": "地址", "hello": "world" } diff --git a/03_source/frontend/src/locales/langs/jp/common.json b/03_source/frontend/src/locales/langs/jp/common.json new file mode 100755 index 0000000..2d8afc1 --- /dev/null +++ b/03_source/frontend/src/locales/langs/jp/common.json @@ -0,0 +1,74 @@ +{ + "demo": { + "lang": "日本語", + "description": "あなたの次のプロジェクトの起点はMUIに基づいています。簡単なカスタマイズで、より速く、より良いアプリケーションを構築することができます。" + }, + "new-product": "新製品", + "back": "戻る", + "App": "アプリケーション", + "Ecommerce": "電子商取引", + "Analytics": "分析", + "Banking": "銀行", + "Booking": "予約", + "File": "ファイル", + "Course": "コース", + "User": "ユーザー", + "Product": "製品", + "Order": "注文", + "Invoice": "請求書", + "Blog": "ブログ", + "Job": "仕事", + "Tour": "ツアー", + "manager": "マネージャー", + "Mail": "メール", + "Chat": "チャット", + "Calendar": "カレンダー", + "Kanban": "かんばん", + "Permission": "権限", + "Level": "レベル", + "Disabled": "無効", + "Label": "ラベル", + "Caption": "キャプション", + "Params": "パラメーター", + "link": "リンク", + "Blank": "空白", + "File-manager": "ファイルマネージャー", + "External-link": "外部リンク", + "Profile": "プロフィール", + "Cards": "カード", + "List": "リスト", + "Create": "作成", + "Edit": "編集", + "Account": "アカウント", + "Details": "詳細", + "Create-at": "作成日", + "Category": "カテゴリー", + "Stock": "在庫", + "Price": "価格", + "Publish": "公開", + "in stock": "在庫あり", + "low stock": "在庫少", + "out of stock": "在庫なし", + "In stock": "在庫あり", + "Low stock": "在庫少", + "Out of stock": "在庫なし", + "Published": "公開済み", + "Draft": "下書き", + "published": "公開済み", + "draft": "下書き", + "Dashboard": "ダッシュボード", + "Apply": "適用", + "Name": "名前", + "Phone number": "電話番号", + "Company": "会社", + "Role": "役割", + "Status": "ステータス", + "New user": "新規ユーザー", + "All": "すべて", + "Active": "アクティブ", + "Pending": "保留中", + "Banned": "禁止", + "Rejected": "却下", + "Quick update": "クイックアップデート", + "hello": "world" +} diff --git a/03_source/frontend/src/locales/langs/jp/navbar.json b/03_source/frontend/src/locales/langs/jp/navbar.json new file mode 100755 index 0000000..4be6887 --- /dev/null +++ b/03_source/frontend/src/locales/langs/jp/navbar.json @@ -0,0 +1,12 @@ +{ + "app": "アプリケーション", + "job": "仕事", + "user": "ユーザー", + "travel": "旅行", + "invoice": "請求書", + "blog": { + "title": "ブログ", + "caption": "カスタムキーボードショートカットを設定します。" + }, + "subheader": "サブヘッダー" +} diff --git a/03_source/frontend/src/locales/locales-config.ts b/03_source/frontend/src/locales/locales-config.ts index 0c05564..a3b4e51 100644 --- a/03_source/frontend/src/locales/locales-config.ts +++ b/03_source/frontend/src/locales/locales-config.ts @@ -1,7 +1,7 @@ // ---------------------------------------------------------------------- export const fallbackLng = 'en'; -export const languages = ['en', 'fr', 'vi', 'cn', 'ar', 'hk']; +export const languages = ['en', 'fr', 'vi', 'cn', 'ar', 'hk', 'jp']; export const defaultNS = 'common'; export type LanguageValue = (typeof languages)[number]; diff --git a/03_source/frontend/src/pages/dashboard/user/edit.tsx b/03_source/frontend/src/pages/dashboard/user/edit.tsx index 9c45842..fe6374b 100644 --- a/03_source/frontend/src/pages/dashboard/user/edit.tsx +++ b/03_source/frontend/src/pages/dashboard/user/edit.tsx @@ -1,8 +1,6 @@ -import { useParams } from 'src/routes/hooks'; - -import { CONFIG } from 'src/global-config'; import { _userList } from 'src/_mock/_user'; - +import { CONFIG } from 'src/global-config'; +import { useParams } from 'src/routes/hooks'; import { UserEditView } from 'src/sections/user/view'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/pages/dashboard/user/list.tsx b/03_source/frontend/src/pages/dashboard/user/list.tsx index 31dfae1..a22e360 100644 --- a/03_source/frontend/src/pages/dashboard/user/list.tsx +++ b/03_source/frontend/src/pages/dashboard/user/list.tsx @@ -1,5 +1,4 @@ import { CONFIG } from 'src/global-config'; - import { UserListView } from 'src/sections/user/view'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/pages/dashboard/user/profile.tsx b/03_source/frontend/src/pages/dashboard/user/profile.tsx index 6ef3738..051ccec 100644 --- a/03_source/frontend/src/pages/dashboard/user/profile.tsx +++ b/03_source/frontend/src/pages/dashboard/user/profile.tsx @@ -1,5 +1,4 @@ import { CONFIG } from 'src/global-config'; - import { UserProfileView } from 'src/sections/user/view'; // ---------------------------------------------------------------------- diff --git a/03_source/frontend/src/sections/product/product-table-toolbar.tsx b/03_source/frontend/src/sections/product/product-table-toolbar.tsx index 2f510a0..1a9c745 100644 --- a/03_source/frontend/src/sections/product/product-table-toolbar.tsx +++ b/03_source/frontend/src/sections/product/product-table-toolbar.tsx @@ -95,14 +95,7 @@ export function ProductTableToolbar({ filters, options }: Props) { onChange={handleChangeStock} onClose={handleFilterStock} input={} - renderValue={(selected) => { - return selected - .map((value) => { - console.log(t(value)); - return t(value); - }) - .join(', '); - }} + renderValue={(selected) => selected.map((value) => t(value)).join(', ')} inputProps={{ id: 'filter-stock-select' }} sx={{ textTransform: 'capitalize' }} > diff --git a/03_source/frontend/src/sections/product/view/product-list-view.tsx b/03_source/frontend/src/sections/product/view/product-list-view.tsx index 266d4b2..76ef0f9 100644 --- a/03_source/frontend/src/sections/product/view/product-list-view.tsx +++ b/03_source/frontend/src/sections/product/view/product-list-view.tsx @@ -1,3 +1,4 @@ +// src/sections/product/view/product-list-view.tsx import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; diff --git a/03_source/frontend/src/sections/user/user-new-edit-form.tsx b/03_source/frontend/src/sections/user/user-new-edit-form.tsx index 13db2a7..00439e4 100644 --- a/03_source/frontend/src/sections/user/user-new-edit-form.tsx +++ b/03_source/frontend/src/sections/user/user-new-edit-form.tsx @@ -1,27 +1,23 @@ -import type { IUserItem } from 'src/types/user'; - -import { z as zod } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm, Controller } from 'react-hook-form'; -import { isValidPhoneNumber } from 'react-phone-number-input/input'; - import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; +import FormControlLabel from '@mui/material/FormControlLabel'; import Grid from '@mui/material/Grid'; import Stack from '@mui/material/Stack'; -import Button from '@mui/material/Button'; import Switch from '@mui/material/Switch'; import Typography from '@mui/material/Typography'; -import FormControlLabel from '@mui/material/FormControlLabel'; - -import { paths } from 'src/routes/paths'; -import { useRouter } from 'src/routes/hooks'; - -import { fData } from 'src/utils/format-number'; - +import { Controller, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { isValidPhoneNumber } from 'react-phone-number-input/input'; +import { Field, Form, schemaHelper } from 'src/components/hook-form'; import { Label } from 'src/components/label'; import { toast } from 'src/components/snackbar'; -import { Form, Field, schemaHelper } from 'src/components/hook-form'; +import { useRouter } from 'src/routes/hooks'; +import { paths } from 'src/routes/paths'; +import type { IUserItem } from 'src/types/user'; +import { fData } from 'src/utils/format-number'; +import { z as zod } from 'zod'; // ---------------------------------------------------------------------- @@ -57,6 +53,8 @@ type Props = { }; export function UserNewEditForm({ currentUser }: Props) { + const { t } = useTranslation(); + const router = useRouter(); const defaultValues: NewUserSchemaType = { @@ -234,7 +232,7 @@ export function UserNewEditForm({ currentUser }: Props) { - + diff --git a/03_source/frontend/src/sections/user/user-quick-edit-form.tsx b/03_source/frontend/src/sections/user/user-quick-edit-form.tsx index 79bfe88..ba39cc5 100644 --- a/03_source/frontend/src/sections/user/user-quick-edit-form.tsx +++ b/03_source/frontend/src/sections/user/user-quick-edit-form.tsx @@ -1,23 +1,20 @@ -import type { IUserItem } from 'src/types/user'; - -import { z as zod } from 'zod'; -import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { isValidPhoneNumber } from 'react-phone-number-input/input'; - -import Box from '@mui/material/Box'; import Alert from '@mui/material/Alert'; +import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; -import MenuItem from '@mui/material/MenuItem'; -import DialogTitle from '@mui/material/DialogTitle'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; - +import DialogTitle from '@mui/material/DialogTitle'; +import MenuItem from '@mui/material/MenuItem'; +import { useForm } from 'react-hook-form'; +import { isValidPhoneNumber } from 'react-phone-number-input/input'; import { USER_STATUS_OPTIONS } from 'src/_mock'; - +import { Field, Form, schemaHelper } from 'src/components/hook-form'; import { toast } from 'src/components/snackbar'; -import { Form, Field, schemaHelper } from 'src/components/hook-form'; +import { useTranslate } from 'src/locales'; +import type { IUserItem } from 'src/types/user'; +import { z as zod } from 'zod'; // ---------------------------------------------------------------------- @@ -53,6 +50,8 @@ type Props = { }; export function UserQuickEditForm({ currentUser, open, onClose }: Props) { + const { t } = useTranslate(); + const defaultValues: UserQuickEditSchemaType = { name: '', email: '', @@ -113,7 +112,7 @@ export function UserQuickEditForm({ currentUser, open, onClose }: Props) { }, }} > - Quick update + {t('Quick update')}
diff --git a/03_source/frontend/src/sections/user/user-table-row.tsx b/03_source/frontend/src/sections/user/user-table-row.tsx index 23aceac..99e1083 100644 --- a/03_source/frontend/src/sections/user/user-table-row.tsx +++ b/03_source/frontend/src/sections/user/user-table-row.tsx @@ -1,27 +1,23 @@ -import type { IUserItem } from 'src/types/user'; - -import { useBoolean, usePopover } from 'minimal-shared/hooks'; - -import Box from '@mui/material/Box'; -import Link from '@mui/material/Link'; -import Stack from '@mui/material/Stack'; -import Button from '@mui/material/Button'; import Avatar from '@mui/material/Avatar'; -import Tooltip from '@mui/material/Tooltip'; -import MenuList from '@mui/material/MenuList'; -import MenuItem from '@mui/material/MenuItem'; -import TableRow from '@mui/material/TableRow'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; -import TableCell from '@mui/material/TableCell'; import IconButton from '@mui/material/IconButton'; - -import { RouterLink } from 'src/routes/components'; - -import { Label } from 'src/components/label'; -import { Iconify } from 'src/components/iconify'; +import Link from '@mui/material/Link'; +import MenuItem from '@mui/material/MenuItem'; +import MenuList from '@mui/material/MenuList'; +import Stack from '@mui/material/Stack'; +import TableCell from '@mui/material/TableCell'; +import TableRow from '@mui/material/TableRow'; +import Tooltip from '@mui/material/Tooltip'; +import { useBoolean, usePopover } from 'minimal-shared/hooks'; +import { useTranslation } from 'react-i18next'; import { ConfirmDialog } from 'src/components/custom-dialog'; import { CustomPopover } from 'src/components/custom-popover'; - +import { Iconify } from 'src/components/iconify'; +import { Label } from 'src/components/label'; +import { RouterLink } from 'src/routes/components'; +import type { IUserItem } from 'src/types/user'; import { UserQuickEditForm } from './user-quick-edit-form'; // ---------------------------------------------------------------------- @@ -38,6 +34,7 @@ export function UserTableRow({ row, selected, editHref, onSelectRow, onDeleteRow const menuActions = usePopover(); const confirmDialog = useBoolean(); const quickEditForm = useBoolean(); + const { t } = useTranslation(); const renderQuickEditForm = () => ( - + (_userList); + useEffect(() => { + setTableData(users); + }, [users]); + const filters = useSetState({ name: '', role: [], status: 'all' }); const { state: currentFilters, setState: updateFilters } = filters; @@ -131,7 +136,7 @@ export function UserListView() { confirmDialog.onFalse(); }} > - Delete + {t('Delete')} } /> @@ -154,7 +159,7 @@ export function UserListView() { variant="contained" startIcon={} > - New user + {t('New user')} } sx={{ mb: { xs: 3, md: 5 } }} @@ -176,7 +181,7 @@ export function UserListView() { key={tab.value} iconPosition="end" value={tab.value} - label={tab.label} + label={t(tab.label)} icon={