diff --git a/03_source/cms_backend/src/app/api/product/saveProduct/route.ts b/03_source/cms_backend/src/app/api/product/saveProduct/route.ts new file mode 100644 index 0000000..0adacae --- /dev/null +++ b/03_source/cms_backend/src/app/api/product/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.update({ + 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/frontend/src/actions/product.ts b/03_source/frontend/src/actions/product.ts index 7605140..6371dd9 100644 --- a/03_source/frontend/src/actions/product.ts +++ b/03_source/frontend/src/actions/product.ts @@ -4,7 +4,7 @@ import type { IProductItem } from 'src/types/product'; import useSWR from 'swr'; import { useMemo } from 'react'; -import { fetcher, endpoints } from 'src/lib/axios'; +import axiosInstance, { fetcher, endpoints } from 'src/lib/axios'; // ---------------------------------------------------------------------- @@ -90,3 +90,74 @@ export function useSearchProducts(query: string) { return memoizedValue; } + +// ---------------------------------------------------------------------- + +type SaveProductData = { + 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 | 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; + + // 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; +} diff --git a/03_source/frontend/src/components/mega-menu/components/nav-dropdown-content.tsx b/03_source/frontend/src/components/mega-menu/components/nav-dropdown-content.tsx index 17bbc19..543dd63 100644 --- a/03_source/frontend/src/components/mega-menu/components/nav-dropdown-content.tsx +++ b/03_source/frontend/src/components/mega-menu/components/nav-dropdown-content.tsx @@ -6,11 +6,11 @@ import Typography from '@mui/material/Typography'; import { RouterLink } from 'src/routes/components'; +import { NavUl } from './nav-elements'; import { Iconify } from '../../iconify'; import { NavSubList } from './nav-sub-list'; import { megaMenuClasses } from '../styles'; import { NavCarousel } from './nav-carousel'; -import { NavUl } from './nav-elements'; import type { NavListProps } from '../types'; diff --git a/03_source/frontend/src/pages/dashboard/product/edit.tsx b/03_source/frontend/src/pages/dashboard/product/edit.tsx index f78e867..ed9d3a6 100644 --- a/03_source/frontend/src/pages/dashboard/product/edit.tsx +++ b/03_source/frontend/src/pages/dashboard/product/edit.tsx @@ -13,6 +13,7 @@ export default function Page() { const { id = '' } = useParams(); const { product } = useGetProduct(id); + console.log({ id }); return ( <> diff --git a/03_source/frontend/src/sections/calendar/hooks/use-calendar.ts b/03_source/frontend/src/sections/calendar/hooks/use-calendar.ts index d65d332..0a3ba0a 100644 --- a/03_source/frontend/src/sections/calendar/hooks/use-calendar.ts +++ b/03_source/frontend/src/sections/calendar/hooks/use-calendar.ts @@ -1,7 +1,7 @@ import type FullCalendar from '@fullcalendar/react'; import type { EventResizeDoneArg } from '@fullcalendar/interaction/index.js'; -import type { EventDropArg, DateSelectArg, EventClickArg } from '@fullcalendar/core/index.js'; import type { ICalendarView, ICalendarRange, ICalendarEvent } from 'src/types/calendar'; +import type { EventDropArg, DateSelectArg, EventClickArg } from '@fullcalendar/core/index.js'; import { useRef, useState, useCallback } from 'react'; diff --git a/03_source/frontend/src/sections/calendar/view/calendar-view.tsx b/03_source/frontend/src/sections/calendar/view/calendar-view.tsx index 373db90..df438d6 100644 --- a/03_source/frontend/src/sections/calendar/view/calendar-view.tsx +++ b/03_source/frontend/src/sections/calendar/view/calendar-view.tsx @@ -2,13 +2,13 @@ import type { Theme, SxProps } from '@mui/material/styles'; import type { ICalendarEvent, ICalendarFilters } from 'src/types/calendar'; import Calendar from '@fullcalendar/react'; +import { useEffect, startTransition } from 'react'; import listPlugin from '@fullcalendar/list/index.js'; import dayGridPlugin from '@fullcalendar/daygrid/index.js'; -import { useEffect, startTransition } from 'react'; import timeGridPlugin from '@fullcalendar/timegrid/index.js'; import timelinePlugin from '@fullcalendar/timeline/index.js'; -import interactionPlugin from '@fullcalendar/interaction/index.js'; import { useBoolean, useSetState } from 'minimal-shared/hooks'; +import interactionPlugin from '@fullcalendar/interaction/index.js'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; diff --git a/03_source/frontend/src/sections/product/product-new-edit-form.tsx b/03_source/frontend/src/sections/product/product-new-edit-form.tsx index a011fa8..51c2105 100644 --- a/03_source/frontend/src/sections/product/product-new-edit-form.tsx +++ b/03_source/frontend/src/sections/product/product-new-edit-form.tsx @@ -23,6 +23,7 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import { paths } from 'src/routes/paths'; import { useRouter } from 'src/routes/hooks'; +import { saveProduct } from 'src/actions/product'; import { _tags, PRODUCT_SIZE_OPTIONS, @@ -44,7 +45,7 @@ export const NewProductSchema = zod.object({ description: schemaHelper .editor({ message: 'Description is required!' }) .min(100, { message: 'Description must be at least 100 characters' }) - .max(500, { message: 'Description must be less than 500 characters' }), + .max(50000, { message: 'Description must be less than 50000 characters' }), images: schemaHelper.files({ message: 'Images is required!' }), code: zod.string().min(1, { message: 'Product code is required!' }), sku: zod.string().min(1, { message: 'Product sku is required!' }), @@ -127,6 +128,8 @@ export function ProductNewEditForm({ currentProduct }: Props) { const values = watch(); + // const saveProduct = useSaveProduct('e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01'); + const onSubmit = handleSubmit(async (data) => { const updatedData = { ...data, @@ -136,7 +139,14 @@ export function ProductNewEditForm({ currentProduct }: Props) { try { await new Promise((resolve) => setTimeout(resolve, 500)); reset(); + + if (currentProduct) { + console.log('save product'); + await saveProduct(currentProduct.id, values); + } + toast.success(currentProduct ? 'Update success!' : 'Create success!'); + router.push(paths.dashboard.product.root); console.info('DATA', updatedData); } catch (error) {