fix: add product
This commit is contained in:
parent
0fde122ac4
commit
799837e82e
@ -0,0 +1,49 @@
|
|||||||
|
// MUI Imports
|
||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
|
|
||||||
|
// Component Imports
|
||||||
|
import ProductAddHeader from '@views/apps/ecommerce/products/add/ProductAddHeader'
|
||||||
|
import ProductInformation from '@views/apps/ecommerce/products/add/ProductInformation'
|
||||||
|
import ProductImage from '@views/apps/ecommerce/products/add/ProductImage'
|
||||||
|
import ProductVariants from '@views/apps/ecommerce/products/add/ProductVariants'
|
||||||
|
import ProductInventory from '@views/apps/ecommerce/products/add/ProductInventory'
|
||||||
|
import ProductPricing from '@views/apps/ecommerce/products/add/ProductPricing'
|
||||||
|
import ProductOrganize from '@views/apps/ecommerce/products/add/ProductOrganize'
|
||||||
|
|
||||||
|
const eCommerceProductsEdit = () => {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductAddHeader />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 8 }}>
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductInformation />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductImage />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductVariants />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductInventory />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductPricing />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ProductOrganize />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eCommerceProductsEdit
|
||||||
@ -14,8 +14,6 @@ import { getLocalizedUrl } from '../utils/i18n'
|
|||||||
export default function AuthGuard({ children, locale }: ChildrenType & { locale: Locale }) {
|
export default function AuthGuard({ children, locale }: ChildrenType & { locale: Locale }) {
|
||||||
const { isAuthenticated } = useAuth()
|
const { isAuthenticated } = useAuth()
|
||||||
|
|
||||||
console.log('isAuthenticated', isAuthenticated)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
redirect(getLocalizedUrl('/login', locale))
|
redirect(getLocalizedUrl('/login', locale))
|
||||||
|
|||||||
@ -6,13 +6,15 @@ import chatReducer from '@/redux-store/slices/chat'
|
|||||||
import calendarReducer from '@/redux-store/slices/calendar'
|
import calendarReducer from '@/redux-store/slices/calendar'
|
||||||
import kanbanReducer from '@/redux-store/slices/kanban'
|
import kanbanReducer from '@/redux-store/slices/kanban'
|
||||||
import emailReducer from '@/redux-store/slices/email'
|
import emailReducer from '@/redux-store/slices/email'
|
||||||
|
import productReducer from '@/redux-store/slices/product'
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
chatReducer,
|
chatReducer,
|
||||||
calendarReducer,
|
calendarReducer,
|
||||||
kanbanReducer,
|
kanbanReducer,
|
||||||
emailReducer
|
emailReducer,
|
||||||
|
productReducer
|
||||||
},
|
},
|
||||||
middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
|
middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
|
||||||
})
|
})
|
||||||
|
|||||||
47
src/redux-store/slices/product.ts
Normal file
47
src/redux-store/slices/product.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Third-party Imports
|
||||||
|
import type { Draft, PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
// Type Imports
|
||||||
|
|
||||||
|
// Data Imports
|
||||||
|
import { ProductRequest } from '../../types/services/product'
|
||||||
|
|
||||||
|
const initialState: { productRequest: ProductRequest } = {
|
||||||
|
productRequest: {
|
||||||
|
category_id: '',
|
||||||
|
sku: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
barcode: '',
|
||||||
|
price: 0,
|
||||||
|
cost: 0,
|
||||||
|
printer_type: '',
|
||||||
|
image_url: '',
|
||||||
|
variants: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productSlice = createSlice({
|
||||||
|
name: 'product',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setProductField: <K extends keyof ProductRequest>(
|
||||||
|
state: Draft<{ productRequest: ProductRequest }>,
|
||||||
|
action: PayloadAction<{ field: K; value: ProductRequest[K] }>
|
||||||
|
) => {
|
||||||
|
const { field, value } = action.payload
|
||||||
|
state.productRequest[field] = value
|
||||||
|
},
|
||||||
|
setProduct: (state, action: PayloadAction<ProductRequest>) => {
|
||||||
|
state.productRequest = action.payload
|
||||||
|
},
|
||||||
|
resetProduct: state => {
|
||||||
|
state.productRequest = initialState.productRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setProductField, setProduct, resetProduct } = productSlice.actions
|
||||||
|
|
||||||
|
export default productSlice.reducer
|
||||||
25
src/services/mutations/files.ts
Normal file
25
src/services/mutations/files.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query'
|
||||||
|
import { api } from '../api'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
export const useFilesMutation = {
|
||||||
|
uploadFile: () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (newFile: FormData) => {
|
||||||
|
const response = await api.post('/files/upload', newFile, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.data.data
|
||||||
|
},
|
||||||
|
onSuccess: data => {
|
||||||
|
toast.success('File uploaded successfully!')
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response.data.errors[0].cause)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/services/mutations/products.ts
Normal file
21
src/services/mutations/products.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query'
|
||||||
|
import { api } from '../api'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { ProductRequest } from '../../types/services/product'
|
||||||
|
|
||||||
|
export const useProductsMutation = {
|
||||||
|
createProduct: () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (newProduct: ProductRequest) => {
|
||||||
|
const response = await api.post('/products', newProduct)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: data => {
|
||||||
|
toast.success('Product created successfully!')
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response.data.errors[0].cause)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,3 +35,22 @@ export type Products = {
|
|||||||
limit: number
|
limit: number
|
||||||
total_pages: number
|
total_pages: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProductVariantRequest = {
|
||||||
|
name: string
|
||||||
|
price_modifier: number
|
||||||
|
cost: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductRequest = {
|
||||||
|
category_id: string
|
||||||
|
sku: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
barcode: string
|
||||||
|
price: number
|
||||||
|
cost: number
|
||||||
|
printer_type: string
|
||||||
|
image_url: string
|
||||||
|
variants: ProductVariantRequest[]
|
||||||
|
}
|
||||||
|
|||||||
@ -329,7 +329,16 @@ const CourseTable = ({ courseData }: { courseData?: Course[] }) => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
component={() => <TablePaginationComponent table={table} />}
|
component={() => (
|
||||||
|
<TablePaginationComponent
|
||||||
|
pageIndex={table.getState().pagination.pageIndex + 1}
|
||||||
|
pageSize={table.getState().pagination.pageSize}
|
||||||
|
totalCount={table.getFilteredRowModel().rows.length}
|
||||||
|
onPageChange={(_, page) => {
|
||||||
|
table.setPageIndex(page - 1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
count={table.getFilteredRowModel().rows.length}
|
count={table.getFilteredRowModel().rows.length}
|
||||||
rowsPerPage={table.getState().pagination.pageSize}
|
rowsPerPage={table.getState().pagination.pageSize}
|
||||||
page={table.getState().pagination.pageIndex}
|
page={table.getState().pagination.pageIndex}
|
||||||
|
|||||||
@ -1,8 +1,30 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
|
import { useProductsMutation } from '../../../../../services/mutations/products'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../../../../redux-store'
|
||||||
|
import { CircularProgress } from '@mui/material'
|
||||||
|
import { resetProduct } from '../../../../../redux-store/slices/product'
|
||||||
|
|
||||||
const ProductAddHeader = () => {
|
const ProductAddHeader = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { mutate, isPending } = useProductsMutation.createProduct()
|
||||||
|
const { productRequest } = useSelector((state: RootState) => state.productReducer)
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const { cost, price, ...rest } = productRequest
|
||||||
|
const newProductRequest = { ...rest, cost: Number(cost), price: Number(price) }
|
||||||
|
|
||||||
|
mutate(newProductRequest, {
|
||||||
|
onSuccess: () => {
|
||||||
|
dispatch(resetProduct())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap sm:items-center justify-between max-sm:flex-col gap-6'>
|
<div className='flex flex-wrap sm:items-center justify-between max-sm:flex-col gap-6'>
|
||||||
<div>
|
<div>
|
||||||
@ -16,7 +38,9 @@ const ProductAddHeader = () => {
|
|||||||
Discard
|
Discard
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='tonal'>Save Draft</Button>
|
<Button variant='tonal'>Save Draft</Button>
|
||||||
<Button variant='contained'>Publish Product</Button>
|
<Button variant='contained' disabled={isPending} onClick={handleSubmit}>
|
||||||
|
Publish Product {isPending && <CircularProgress color='inherit' size={16} className='ml-2' />}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,16 +4,16 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Card from '@mui/material/Card'
|
import type { BoxProps } from '@mui/material/Box'
|
||||||
import CardHeader from '@mui/material/CardHeader'
|
|
||||||
import CardContent from '@mui/material/CardContent'
|
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
|
import Card from '@mui/material/Card'
|
||||||
|
import CardContent from '@mui/material/CardContent'
|
||||||
|
import CardHeader from '@mui/material/CardHeader'
|
||||||
import IconButton from '@mui/material/IconButton'
|
import IconButton from '@mui/material/IconButton'
|
||||||
import List from '@mui/material/List'
|
import List from '@mui/material/List'
|
||||||
import ListItem from '@mui/material/ListItem'
|
import ListItem from '@mui/material/ListItem'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { styled } from '@mui/material/styles'
|
import { styled } from '@mui/material/styles'
|
||||||
import type { BoxProps } from '@mui/material/Box'
|
|
||||||
|
|
||||||
// Third-party Imports
|
// Third-party Imports
|
||||||
import { useDropzone } from 'react-dropzone'
|
import { useDropzone } from 'react-dropzone'
|
||||||
@ -24,6 +24,9 @@ import CustomAvatar from '@core/components/mui/Avatar'
|
|||||||
|
|
||||||
// Styled Component Imports
|
// Styled Component Imports
|
||||||
import AppReactDropzone from '@/libs/styles/AppReactDropzone'
|
import AppReactDropzone from '@/libs/styles/AppReactDropzone'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useFilesMutation } from '../../../../../services/mutations/files'
|
||||||
|
import { setProductField } from '../../../../../redux-store/slices/product'
|
||||||
|
|
||||||
type FileProp = {
|
type FileProp = {
|
||||||
name: string
|
name: string
|
||||||
@ -46,9 +49,27 @@ const Dropzone = styled(AppReactDropzone)<BoxProps>(({ theme }) => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const ProductImage = () => {
|
const ProductImage = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { mutate, isPending } = useFilesMutation.uploadFile()
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [files, setFiles] = useState<File[]>([])
|
const [files, setFiles] = useState<File[]>([])
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
if (!files.length) return
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', files[0])
|
||||||
|
formData.append('file_type', 'image')
|
||||||
|
formData.append('description', 'Product image')
|
||||||
|
|
||||||
|
mutate(formData, {
|
||||||
|
onSuccess: data => {
|
||||||
|
dispatch(setProductField({ field: 'image_url', value: data.file_url }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
onDrop: (acceptedFiles: File[]) => {
|
onDrop: (acceptedFiles: File[]) => {
|
||||||
@ -129,7 +150,9 @@ const ProductImage = () => {
|
|||||||
<Button color='error' variant='tonal' onClick={handleRemoveAllFiles}>
|
<Button color='error' variant='tonal' onClick={handleRemoveAllFiles}>
|
||||||
Remove All
|
Remove All
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='contained'>Upload Files</Button>
|
<Button variant='contained' onClick={handleUpload} disabled={isPending}>
|
||||||
|
{isPending ? 'Uploading...' : 'Upload'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -24,6 +24,11 @@ import CustomTextField from '@core/components/mui/TextField'
|
|||||||
// Style Imports
|
// Style Imports
|
||||||
import '@/libs/styles/tiptapEditor.css'
|
import '@/libs/styles/tiptapEditor.css'
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../../../../redux-store'
|
||||||
|
import { setProductField } from '@/redux-store/slices/product'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
const EditorToolbar = ({ editor }: { editor: Editor | null }) => {
|
const EditorToolbar = ({ editor }: { editor: Editor | null }) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null
|
return null
|
||||||
@ -114,6 +119,13 @@ const EditorToolbar = ({ editor }: { editor: Editor | null }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductInformation = () => {
|
const ProductInformation = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { name, sku, barcode, description } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
|
|
||||||
|
const handleInputChange = (field: any, value: any) => {
|
||||||
|
dispatch(setProductField({ field, value }))
|
||||||
|
}
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
@ -128,24 +140,57 @@ const ProductInformation = () => {
|
|||||||
immediatelyRender: false,
|
immediatelyRender: false,
|
||||||
content: `
|
content: `
|
||||||
<p>
|
<p>
|
||||||
Keep your account secure with authentication step.
|
${description}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) return
|
||||||
|
|
||||||
|
const updateListener = () => {
|
||||||
|
const html = editor.getHTML()
|
||||||
|
dispatch(setProductField({ field: 'description', value: html }))
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.on('update', updateListener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
editor.off('update', updateListener)
|
||||||
|
}
|
||||||
|
}, [editor])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader title='Product Information' />
|
<CardHeader title='Product Information' />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Grid container spacing={6} className='mbe-6'>
|
<Grid container spacing={6} className='mbe-6'>
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<CustomTextField fullWidth label='Product Name' placeholder='iPhone 14' />
|
<CustomTextField
|
||||||
|
fullWidth
|
||||||
|
label='Product Name'
|
||||||
|
placeholder='iPhone 14'
|
||||||
|
value={name}
|
||||||
|
onChange={e => handleInputChange('name', e.target.value)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 6 }}>
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
<CustomTextField fullWidth label='SKU' placeholder='FXSK123U' />
|
<CustomTextField
|
||||||
|
fullWidth
|
||||||
|
label='SKU'
|
||||||
|
placeholder='FXSK123U'
|
||||||
|
value={sku}
|
||||||
|
onChange={e => handleInputChange('sku', e.target.value)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 6 }}>
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
<CustomTextField fullWidth label='Barcode' placeholder='0123-4567' />
|
<CustomTextField
|
||||||
|
fullWidth
|
||||||
|
label='Barcode'
|
||||||
|
placeholder='0123-4567'
|
||||||
|
value={barcode}
|
||||||
|
onChange={e => handleInputChange('barcode', e.target.value)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Typography className='mbe-1'>Description (Optional)</Typography>
|
<Typography className='mbe-1'>Description (Optional)</Typography>
|
||||||
|
|||||||
@ -1,48 +1,56 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
// React Imports
|
// React Imports
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Card from '@mui/material/Card'
|
import Card from '@mui/material/Card'
|
||||||
import CardHeader from '@mui/material/CardHeader'
|
|
||||||
import CardContent from '@mui/material/CardContent'
|
import CardContent from '@mui/material/CardContent'
|
||||||
|
import CardHeader from '@mui/material/CardHeader'
|
||||||
import MenuItem from '@mui/material/MenuItem'
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
|
|
||||||
// Component Imports
|
// Component Imports
|
||||||
import CustomIconButton from '@core/components/mui/IconButton'
|
import CustomIconButton from '@core/components/mui/IconButton'
|
||||||
import CustomTextField from '@core/components/mui/TextField'
|
import CustomTextField from '@core/components/mui/TextField'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../../../../redux-store'
|
||||||
|
import { setProductField } from '../../../../../redux-store/slices/product'
|
||||||
|
import { useCategoriesQuery } from '../../../../../services/queries/categories'
|
||||||
|
import { Category } from '../../../../../types/services/category'
|
||||||
|
|
||||||
const ProductOrganize = () => {
|
const ProductOrganize = () => {
|
||||||
// States
|
const dispatch = useDispatch()
|
||||||
const [vendor, setVendor] = useState('')
|
const { category_id, printer_type } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
const [category, setCategory] = useState('')
|
|
||||||
const [collection, setCollection] = useState('')
|
const { data: categoriesApi } = useCategoriesQuery.getCategories()
|
||||||
const [status, setStatus] = useState('')
|
|
||||||
|
const handleSelectChange = (field: any, value: any) => {
|
||||||
|
dispatch(setProductField({ field, value }))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader title='Organize' />
|
<CardHeader title='Organize' />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={e => e.preventDefault()} className='flex flex-col gap-6'>
|
<form onSubmit={e => e.preventDefault()} className='flex flex-col gap-6'>
|
||||||
<CustomTextField select fullWidth label='Vendor' value={vendor} onChange={e => setVendor(e.target.value)}>
|
|
||||||
<MenuItem value={`Men's Clothing`}>Men's Clothing</MenuItem>
|
|
||||||
<MenuItem value={`Women's Clothing`}>Women's Clothing</MenuItem>
|
|
||||||
<MenuItem value={`Kid's Clothing`}>Kid's Clothing</MenuItem>
|
|
||||||
</CustomTextField>
|
|
||||||
<div className='flex items-end gap-4'>
|
<div className='flex items-end gap-4'>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
select
|
select
|
||||||
fullWidth
|
fullWidth
|
||||||
label='Category'
|
label='Category'
|
||||||
value={category}
|
value={category_id}
|
||||||
onChange={e => setCategory(e.target.value)}
|
onChange={e => handleSelectChange('category_id', e.target.value)}
|
||||||
>
|
>
|
||||||
<MenuItem value='Household'>Household</MenuItem>
|
{categoriesApi?.categories.length ? (
|
||||||
<MenuItem value='Office'>Office</MenuItem>
|
categoriesApi?.categories.map((item: Category, index: number) => (
|
||||||
<MenuItem value='Electronics'>Electronics</MenuItem>
|
<MenuItem key={index} value={item.id}>
|
||||||
<MenuItem value='Management'>Management</MenuItem>
|
{item.name}
|
||||||
<MenuItem value='Automotive'>Automotive</MenuItem>
|
</MenuItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<MenuItem disabled value=''>
|
||||||
|
Loading categories...
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</CustomTextField>
|
</CustomTextField>
|
||||||
<CustomIconButton variant='tonal' color='primary' className='min-is-fit'>
|
<CustomIconButton variant='tonal' color='primary' className='min-is-fit'>
|
||||||
<i className='tabler-plus' />
|
<i className='tabler-plus' />
|
||||||
@ -51,20 +59,18 @@ const ProductOrganize = () => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
select
|
select
|
||||||
fullWidth
|
fullWidth
|
||||||
label='Collection'
|
label='Printer Type'
|
||||||
value={collection}
|
value={printer_type}
|
||||||
onChange={e => setCollection(e.target.value)}
|
onChange={e => handleSelectChange('printer_type', e.target.value)}
|
||||||
>
|
>
|
||||||
<MenuItem value={`Men's Clothing`}>Men's Clothing</MenuItem>
|
<MenuItem value={`kitchen`}>Kitchen</MenuItem>
|
||||||
<MenuItem value={`Women's Clothing`}>Women's Clothing</MenuItem>
|
|
||||||
<MenuItem value={`Kid's Clothing`}>Kid's Clothing</MenuItem>
|
|
||||||
</CustomTextField>
|
</CustomTextField>
|
||||||
<CustomTextField select fullWidth label='Status' value={status} onChange={e => setStatus(e.target.value)}>
|
{/* <CustomTextField select fullWidth label='Status' value={status} onChange={e => setStatus(e.target.value)}>
|
||||||
<MenuItem value='Published'>Published</MenuItem>
|
<MenuItem value='Published'>Published</MenuItem>
|
||||||
<MenuItem value='Inactive'>Inactive</MenuItem>
|
<MenuItem value='Inactive'>Inactive</MenuItem>
|
||||||
<MenuItem value='Scheduled'>Scheduled</MenuItem>
|
<MenuItem value='Scheduled'>Scheduled</MenuItem>
|
||||||
</CustomTextField>
|
</CustomTextField>
|
||||||
<CustomTextField fullWidth label='Enter Tags' placeholder='Fashion, Trending, Summer' />
|
<CustomTextField fullWidth label='Enter Tags' placeholder='Fashion, Trending, Summer' /> */}
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Card from '@mui/material/Card'
|
import Card from '@mui/material/Card'
|
||||||
import CardHeader from '@mui/material/CardHeader'
|
import CardHeader from '@mui/material/CardHeader'
|
||||||
@ -11,15 +13,39 @@ import Typography from '@mui/material/Typography'
|
|||||||
// Component Imports
|
// Component Imports
|
||||||
import Form from '@components/Form'
|
import Form from '@components/Form'
|
||||||
import CustomTextField from '@core/components/mui/TextField'
|
import CustomTextField from '@core/components/mui/TextField'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../../../../redux-store'
|
||||||
|
import { setProductField } from '../../../../../redux-store/slices/product'
|
||||||
|
|
||||||
const ProductPricing = () => {
|
const ProductPricing = () => {
|
||||||
|
const { price, cost } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const handleInputChange = (field: any, value: any) => {
|
||||||
|
dispatch(setProductField({ field, value }))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader title='Pricing' />
|
<CardHeader title='Pricing' />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form>
|
<Form>
|
||||||
<CustomTextField fullWidth label='Base Price' placeholder='Enter Base Price' className='mbe-6' />
|
<CustomTextField
|
||||||
<CustomTextField fullWidth label='Discounted Price' placeholder='$499' className='mbe-6' />
|
fullWidth
|
||||||
|
label='Base Price'
|
||||||
|
placeholder='Enter Base Price'
|
||||||
|
className='mbe-6'
|
||||||
|
value={price}
|
||||||
|
onChange={e => handleInputChange('price', e.target.value)}
|
||||||
|
/>
|
||||||
|
<CustomTextField
|
||||||
|
fullWidth
|
||||||
|
label='Cost'
|
||||||
|
placeholder='$499'
|
||||||
|
className='mbe-6'
|
||||||
|
value={cost}
|
||||||
|
onChange={e => handleInputChange('cost', e.target.value)}
|
||||||
|
/>
|
||||||
<FormControlLabel control={<Checkbox defaultChecked />} label='Charge tax on this product' />
|
<FormControlLabel control={<Checkbox defaultChecked />} label='Charge tax on this product' />
|
||||||
<Divider className='mlb-2' />
|
<Divider className='mlb-2' />
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
|
|||||||
@ -1,30 +1,42 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
// React Imports
|
// React Imports
|
||||||
import { useState } from 'react'
|
|
||||||
import type { SyntheticEvent } from 'react'
|
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Grid from '@mui/material/Grid2'
|
|
||||||
import Card from '@mui/material/Card'
|
|
||||||
import CardHeader from '@mui/material/CardHeader'
|
|
||||||
import CardContent from '@mui/material/CardContent'
|
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import MenuItem from '@mui/material/MenuItem'
|
import Card from '@mui/material/Card'
|
||||||
|
import CardContent from '@mui/material/CardContent'
|
||||||
|
import CardHeader from '@mui/material/CardHeader'
|
||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
|
|
||||||
// Components Imports
|
// Components Imports
|
||||||
import CustomIconButton from '@core/components/mui/IconButton'
|
import CustomIconButton from '@core/components/mui/IconButton'
|
||||||
import CustomTextField from '@core/components/mui/TextField'
|
import CustomTextField from '@core/components/mui/TextField'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../../../../redux-store'
|
||||||
|
import { setProductField } from '../../../../../redux-store/slices/product'
|
||||||
|
|
||||||
const ProductVariants = () => {
|
const ProductVariants = () => {
|
||||||
// States
|
const dispatch = useDispatch()
|
||||||
const [count, setCount] = useState(1)
|
const { variants } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
|
|
||||||
const deleteForm = (e: SyntheticEvent) => {
|
const handleAddVariant = () => {
|
||||||
e.preventDefault()
|
dispatch(setProductField({ field: 'variants', value: [...variants, { name: '', cost: 0, price_modifier: 0 }] }))
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
const handleRemoveVariant = (index: number) => {
|
||||||
e.target.closest('.repeater-item').remove()
|
const updated = variants.filter((_, i) => i !== index)
|
||||||
|
dispatch(setProductField({ field: 'variants', value: updated }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputChange = (index: number, e: any) => {
|
||||||
|
const { name, value } = e.target
|
||||||
|
const updated = [...variants]
|
||||||
|
updated[index] = {
|
||||||
|
...updated[index],
|
||||||
|
[name]: name === 'name' ? value : Number(value)
|
||||||
|
}
|
||||||
|
dispatch(setProductField({ field: 'variants', value: updated }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -32,21 +44,40 @@ const ProductVariants = () => {
|
|||||||
<CardHeader title='Product Variants' />
|
<CardHeader title='Product Variants' />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={6}>
|
||||||
{Array.from(Array(count).keys()).map((item, index) => (
|
{variants.map((variant, index) => (
|
||||||
<Grid key={index} size={{ xs: 12 }} className='repeater-item'>
|
<Grid key={index} size={{ xs: 12 }} className='repeater-item'>
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={6}>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<CustomTextField select fullWidth label='Options' defaultValue='Size'>
|
<CustomTextField
|
||||||
<MenuItem value='Size'>Size</MenuItem>
|
fullWidth
|
||||||
<MenuItem value='Color'>Color</MenuItem>
|
label='Variant Name'
|
||||||
<MenuItem value='Weight'>Weight</MenuItem>
|
placeholder='Hot'
|
||||||
<MenuItem value='Smell'>Smell</MenuItem>
|
name='name'
|
||||||
</CustomTextField>
|
value={variant.name}
|
||||||
|
onChange={e => handleInputChange(index, e)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 8 }} alignSelf='end'>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<div className='flex items-center gap-6'>
|
<CustomTextField
|
||||||
<CustomTextField fullWidth placeholder='Enter Variant Value' />
|
fullWidth
|
||||||
<CustomIconButton onClick={deleteForm} className='min-is-fit'>
|
label='Price Modifier'
|
||||||
|
placeholder='0'
|
||||||
|
name='price_modifier'
|
||||||
|
value={variant.price_modifier}
|
||||||
|
onChange={e => handleInputChange(index, e)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<CustomTextField
|
||||||
|
fullWidth
|
||||||
|
label='Cost'
|
||||||
|
placeholder='0'
|
||||||
|
name='cost'
|
||||||
|
value={variant.cost}
|
||||||
|
onChange={e => handleInputChange(index, e)}
|
||||||
|
/>
|
||||||
|
<CustomIconButton onClick={() => handleRemoveVariant(index)} className='min-is-fit'>
|
||||||
<i className='tabler-x' />
|
<i className='tabler-x' />
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +86,7 @@ const ProductVariants = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<Button variant='contained' onClick={() => setCount(count + 1)} startIcon={<i className='tabler-plus' />}>
|
<Button variant='contained' onClick={handleAddVariant} startIcon={<i className='tabler-plus' />}>
|
||||||
Add Another Option
|
Add Another Option
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user