diff --git a/src/entities/ViewProduct/ViewProduct.tsx b/src/entities/ViewProduct/ViewProduct.tsx index f6e58ae90..67fbceb6e 100644 --- a/src/entities/ViewProduct/ViewProduct.tsx +++ b/src/entities/ViewProduct/ViewProduct.tsx @@ -7,12 +7,13 @@ import { addToCart, dellFromCart, getCountInCartById, TProductInCartWithCount } import { useAppSelector } from "../../store/hooks"; import { useForm } from "react-hook-form"; import { Product, TUpdateProductParams } from "../ViewProductList/model/types/types"; +import { productDelete } from "../../store/slices/saga/deleteProductSaga"; export interface IViewProductProps { product: Product; isLast: boolean; isEditMode: boolean; - onSaveChanges: (product: TUpdateProductParams) => void; + onSaveChanges: (product: Product) => void; nextPage: () => void; } @@ -25,7 +26,7 @@ export const ViewProduct: FC = ({ product, isLast, isEditMode const containerRef = useRef(); const [isGroupOpen, setIsGroupOpen] = useState(false); - const { register, handleSubmit } = useForm({ + const { register, handleSubmit } = useForm({ defaultValues: { ...product } }); @@ -43,19 +44,13 @@ export const ViewProduct: FC = ({ product, isLast, isEditMode } }, [containerRef]); + const handleDelete = () => { + dispatch(productDelete(product.id)); + } + return (
{ - - const updateProduct : TUpdateProductParams = { - id: data.id, - name: data.name, - createdAt: data.createdAt, - updatedAt: data.updatedAt, - price: data.price, - commandId: data.commandId, - categoryId: data.category.id - } - onSaveChanges(updateProduct); + onSaveChanges(data); }) } > @@ -99,7 +94,10 @@ export const ViewProduct: FC = ({ product, isLast, isEditMode {isEditMode ? - + <> + + + :
{ setIsOpenAddProductModal(true); } - const handleSaveChanges = (product: TUpdateProductParams) => { - putProductApi(product) + const handleSaveChanges = (product: Product) => { + dispatcher(productUpdate(product)); } return ( @@ -68,7 +69,7 @@ export const ViewProductList = ({ isEditMode }: IViewProductListProps) => { } { - productList.map((p, index) => + productList.map((p, index) => { export const putProductApi = async( product: TUpdateProductParams) => { const response = await axiosInstance.put(`/products/${product.id}`, product); } + +export const deleteProductApi = async( id: string) => { + const response = await axiosInstance.delete(`/products/${id}`); +} diff --git a/src/entities/ViewProductList/ui/AddProductModal/AddProductModal.tsx b/src/entities/ViewProductList/ui/AddProductModal/AddProductModal.tsx index 1b18b8c77..a6923f29c 100644 --- a/src/entities/ViewProductList/ui/AddProductModal/AddProductModal.tsx +++ b/src/entities/ViewProductList/ui/AddProductModal/AddProductModal.tsx @@ -7,6 +7,9 @@ import { LabelWrapper } from "../../../../shared/LabelWrapper"; import { z } from 'zod'; import s from './AddProductModal.modal.sass'; import { zodResolver } from '@hookform/resolvers/zod'; +import axiosInstance from "../../../../shared/axiosHelper/axiosHelper"; +import { useAppDispatch } from "../../../../store/hooks"; +import { productAdd } from "../../../../store/slices/saga/addProductSaga"; const schema = z.object({ name: z.string().min(3, { message: 'Минимальная длина имени товара 3 символа' }), @@ -28,6 +31,8 @@ export interface IAddProductModalProps { } export const AddProductModal = ({ isOpen, category, onAddCategory, onClose }: IAddProductModalProps) => { + const dispatcher = useAppDispatch(); + const { register, handleSubmit, @@ -54,12 +59,26 @@ export const AddProductModal = ({ isOpen, category, onAddCategory, onClose }: IA const newProduct: TAddProductParams = { name: data.name, price: data.price, - categoryId: category ? category.id : "unknown", + categoryId: category ? category.id : null, + photo: data.photo, + desc: data.desc, + oldPrice: data.oldPrice, } - addProductApi(newProduct); + dispatcher(productAdd(newProduct)) + onClose(); } + const handleChangePhoto = async (e: React.ChangeEvent) => { + const [file] = e.target.files; + const formData = new FormData(); + formData.append('file', file); + + const response = await axiosInstance.post('/upload', formData); + return response.data.url; + }; + + return ( <> {/* Модальное окно */} @@ -73,7 +92,11 @@ export const AddProductModal = ({ isOpen, category, onAddCategory, onClose }: IA {errors.name &&

{errors.name.message}

} - + ) => { + handleChangePhoto(e).then((url) => { + setValue("photo", url); + }); + }} /> {errors.photo &&

{errors.photo.message}

} diff --git a/src/store/mainStore.ts b/src/store/mainStore.ts index 3e05c0be6..b6d429f5e 100644 --- a/src/store/mainStore.ts +++ b/src/store/mainStore.ts @@ -7,6 +7,9 @@ import { takeEvery } from "redux-saga/effects"; import { doProfileRegisterSaga, PROFILE_REGISTER } from "./slices/saga/authAndProfileSaga"; import { doProfileUpdateSaga, PROFILE_UPDATE } from "./slices/saga/profileUpdateSaga"; import { getProductSaga, PRODUCT_GET } from "./slices/saga/getProductSaga"; +import { PRODUCT_UPDATE, updateProductSaga } from "./slices/saga/updateProductSaga"; +import { PRODUCT_DELETE, deleteProductSaga } from "./slices/saga/deleteProductSaga"; +import { PRODUCT_ADD, addProductSaga } from "./slices/saga/addProductSaga"; const sagaMiddleware = createSagaMiddleware(); @@ -14,6 +17,9 @@ function* sagas() { yield takeEvery(PROFILE_REGISTER, doProfileRegisterSaga); yield takeEvery(PROFILE_UPDATE, doProfileUpdateSaga); yield takeEvery(PRODUCT_GET, getProductSaga); + yield takeEvery(PRODUCT_UPDATE, updateProductSaga); + yield takeEvery(PRODUCT_DELETE, deleteProductSaga); + yield takeEvery(PRODUCT_ADD, addProductSaga); } const store = configureStore({ diff --git a/src/store/slices/productInCartSlice.ts b/src/store/slices/productInCartSlice.ts index 077bf2053..0b137d738 100644 --- a/src/store/slices/productInCartSlice.ts +++ b/src/store/slices/productInCartSlice.ts @@ -19,7 +19,6 @@ const productInCartSlice = createSlice( initialState, reducers: { addToCart(state, action: PayloadAction) { - //let found = state.productList.find(p => p.id == action.payload.id); let found = false; state.productList.forEach(p => { if (p.id == action.payload.id) { diff --git a/src/store/slices/productSlice.ts b/src/store/slices/productSlice.ts index 8ff4d0a5b..a757126b0 100644 --- a/src/store/slices/productSlice.ts +++ b/src/store/slices/productSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { Product, Sorting } from "../../entities/ViewProductList/model/types/types"; +import { Product, Sorting, TUpdateProductParams } from "../../entities/ViewProductList/model/types/types"; type TError = { isError: boolean; @@ -45,8 +45,8 @@ const productSlice = createSlice( name: 'product', initialState, reducers: { - addToProductList(state, action: PayloadAction) { - state.productList.push(action.payload); + addToTopOfProductList(state, action: PayloadAction) { + state.productList.unshift(action.payload); }, addProductsToList(state, action: PayloadAction) { state.productList.push(...action.payload); @@ -54,6 +54,15 @@ const productSlice = createSlice( cleanProductList(state) { state.productList = []; }, + updateProductList(state, action: PayloadAction) { + const foundIndex = state.productList.findIndex(p => p.id == action.payload.id); + if (foundIndex != -1) { + state.productList[foundIndex] = action.payload; + }; + }, + deleteProduct(state, action: PayloadAction) { + state.productList = state.productList.filter(p => p.id != action.payload); + }, setCurrentPage(state, action: PayloadAction) { state.pagination.currentPage = action.payload; }, @@ -70,4 +79,5 @@ const productSlice = createSlice( export default productSlice.reducer; -export const { addToProductList, addProductsToList, cleanProductList, setCurrentPage, setIsLoading, setError } = productSlice.actions; \ No newline at end of file +export const { addToTopOfProductList, addProductsToList, cleanProductList, deleteProduct, + updateProductList, setCurrentPage, setIsLoading, setError } = productSlice.actions; \ No newline at end of file diff --git a/src/store/slices/saga/addProductSaga.ts b/src/store/slices/saga/addProductSaga.ts new file mode 100644 index 000000000..5d2b2d4aa --- /dev/null +++ b/src/store/slices/saga/addProductSaga.ts @@ -0,0 +1,29 @@ +import { call, put } from "redux-saga/effects"; +import { isTErrorResponse, TServerError } from "../../../shared/fetchHelpers/typeGuards"; +import { createAction } from "@reduxjs/toolkit"; +import { UNKNOWN_ERROR_MESSAGE } from "./constant"; +import { Category, Product, TAddProductParams, TUpdateProductParams } from "../../../entities/ViewProductList/model/types/types"; +import { addProductApi, putProductApi } from "../../../entities/ViewProductList/api/request"; +import { setError, cleanProductList } from "../productSlice"; +import { productGet } from "./getProductSaga"; + +// Saga Effects. Add product +export function* addProductSaga(data: { type: string, payload: TAddProductParams }): any { + try { + yield addProductApi(data.payload); + yield put(cleanProductList()) + yield call(productGet, { pageSize: 10, pageNumber: 1, sorting: { type: 'ASC', field: 'id' } }); + } catch (error: unknown) { + if (isTErrorResponse(error)) { + let allErrors = ""; + error.response.data.errors.forEach((e: TServerError) => allErrors += e.message); + + yield put(setError({ isError: true, errorMessage: allErrors })) + } else { + yield put(setError({ isError: true, errorMessage: UNKNOWN_ERROR_MESSAGE })) + } + } +} + +export const PRODUCT_ADD = 'product/addProduct'; +export const productAdd = createAction(PRODUCT_ADD); \ No newline at end of file diff --git a/src/store/slices/saga/deleteProductSaga.ts b/src/store/slices/saga/deleteProductSaga.ts new file mode 100644 index 000000000..8b11c4c5b --- /dev/null +++ b/src/store/slices/saga/deleteProductSaga.ts @@ -0,0 +1,27 @@ +import { put } from "redux-saga/effects"; +import { isTErrorResponse, TServerError } from "../../../shared/fetchHelpers/typeGuards"; +import { createAction } from "@reduxjs/toolkit"; +import { UNKNOWN_ERROR_MESSAGE } from "./constant"; +import { deleteProductApi } from "../../../entities/ViewProductList/api/request"; +import { deleteProduct, setError } from "../productSlice"; + +// Saga Effects. Delete product +export function* deleteProductSaga(data: { type: string, payload: string }): any { + try { + + yield deleteProductApi(data.payload); + yield put(deleteProduct(data.payload)); + } catch (error: unknown) { + if (isTErrorResponse(error)) { + let allErrors = ""; + error.response.data.errors.forEach((e: TServerError) => allErrors += e.message); + + yield put(setError({ isError: true, errorMessage: allErrors })) + } else { + yield put(setError({ isError: true, errorMessage: UNKNOWN_ERROR_MESSAGE })) + } + } +} + +export const PRODUCT_DELETE = 'product/deleteProduct'; +export const productDelete = createAction(PRODUCT_DELETE); \ No newline at end of file diff --git a/src/store/slices/saga/getProductSaga.ts b/src/store/slices/saga/getProductSaga.ts index 2ff6c2fff..124b4917e 100644 --- a/src/store/slices/saga/getProductSaga.ts +++ b/src/store/slices/saga/getProductSaga.ts @@ -2,9 +2,9 @@ import { put } from "redux-saga/effects"; import { isTErrorResponse, TServerError } from "../../../shared/fetchHelpers/typeGuards"; import { createAction } from "@reduxjs/toolkit"; import { UNKNOWN_ERROR_MESSAGE } from "./constant"; -import { Product, Sorting, TProductRaw } from "../../../entities/ViewProductList/model/types/types"; +import { Product, Sorting } from "../../../entities/ViewProductList/model/types/types"; import { getProductsApi } from "../../../entities/ViewProductList/api/request"; -import { addProductsToList, addToProductList, setError, setIsLoading } from "../productSlice"; +import { addProductsToList, setError, setIsLoading } from "../productSlice"; import { RawProductDto, TProductGetRaw } from "../../../entities/ViewProductList/model/types/productTypes"; export type TGetProductSagaProps = { @@ -13,7 +13,6 @@ export type TGetProductSagaProps = { sorting: Sorting; } - // Saga Effects. get product export function* getProductSaga(data: { type: string, payload: TGetProductSagaProps }): any { try { diff --git a/src/store/slices/saga/updateProductSaga.ts b/src/store/slices/saga/updateProductSaga.ts new file mode 100644 index 000000000..9a3f93a25 --- /dev/null +++ b/src/store/slices/saga/updateProductSaga.ts @@ -0,0 +1,38 @@ +import { put } from "redux-saga/effects"; +import { isTErrorResponse, TServerError } from "../../../shared/fetchHelpers/typeGuards"; +import { createAction } from "@reduxjs/toolkit"; +import { UNKNOWN_ERROR_MESSAGE } from "./constant"; +import { Category, Product, TUpdateProductParams } from "../../../entities/ViewProductList/model/types/types"; +import { putProductApi } from "../../../entities/ViewProductList/api/request"; +import { setError, updateProductList } from "../productSlice"; + +// Saga Effects. Update product +export function* updateProductSaga(data: { type: string, payload: Product }): any { + try { + + const updateProduct: TUpdateProductParams = { + id: data.payload.id, + name: data.payload.name, + createdAt: data.payload.createdAt, + updatedAt: data.payload.updatedAt, + price: data.payload.price, + categoryId: data.payload.category.id, + commandId: data.payload.commandId, + } + + yield putProductApi(updateProduct); + yield put(updateProductList(data.payload)); + } catch (error: unknown) { + if (isTErrorResponse(error)) { + let allErrors = ""; + error.response.data.errors.forEach((e: TServerError) => allErrors += e.message); + + yield put(setError({ isError: true, errorMessage: allErrors })) + } else { + yield put(setError({ isError: true, errorMessage: UNKNOWN_ERROR_MESSAGE })) + } + } +} + +export const PRODUCT_UPDATE = 'product/updateProduct'; +export const productUpdate = createAction(PRODUCT_UPDATE); \ No newline at end of file