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 }) {
|
||||
const { isAuthenticated } = useAuth()
|
||||
|
||||
console.log('isAuthenticated', isAuthenticated)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
redirect(getLocalizedUrl('/login', locale))
|
||||
|
||||
@ -6,13 +6,15 @@ import chatReducer from '@/redux-store/slices/chat'
|
||||
import calendarReducer from '@/redux-store/slices/calendar'
|
||||
import kanbanReducer from '@/redux-store/slices/kanban'
|
||||
import emailReducer from '@/redux-store/slices/email'
|
||||
import productReducer from '@/redux-store/slices/product'
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
chatReducer,
|
||||
calendarReducer,
|
||||
kanbanReducer,
|
||||
emailReducer
|
||||
emailReducer,
|
||||
productReducer
|
||||
},
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
<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}
|
||||
rowsPerPage={table.getState().pagination.pageSize}
|
||||
page={table.getState().pagination.pageIndex}
|
||||
|
||||
@ -1,8 +1,30 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Button from '@mui/material/Button'
|
||||
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 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 (
|
||||
<div className='flex flex-wrap sm:items-center justify-between max-sm:flex-col gap-6'>
|
||||
<div>
|
||||
@ -16,7 +38,9 @@ const ProductAddHeader = () => {
|
||||
Discard
|
||||
</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>
|
||||
)
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import type { BoxProps } from '@mui/material/Box'
|
||||
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 List from '@mui/material/List'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import type { BoxProps } from '@mui/material/Box'
|
||||
|
||||
// Third-party Imports
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
@ -24,6 +24,9 @@ import CustomAvatar from '@core/components/mui/Avatar'
|
||||
|
||||
// Styled Component Imports
|
||||
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 = {
|
||||
name: string
|
||||
@ -46,9 +49,27 @@ const Dropzone = styled(AppReactDropzone)<BoxProps>(({ theme }) => ({
|
||||
}))
|
||||
|
||||
const ProductImage = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { mutate, isPending } = useFilesMutation.uploadFile()
|
||||
|
||||
// States
|
||||
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
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles: File[]) => {
|
||||
@ -129,7 +150,9 @@ const ProductImage = () => {
|
||||
<Button color='error' variant='tonal' onClick={handleRemoveAllFiles}>
|
||||
Remove All
|
||||
</Button>
|
||||
<Button variant='contained'>Upload Files</Button>
|
||||
<Button variant='contained' onClick={handleUpload} disabled={isPending}>
|
||||
{isPending ? 'Uploading...' : 'Upload'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@ -24,6 +24,11 @@ import CustomTextField from '@core/components/mui/TextField'
|
||||
// Style Imports
|
||||
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 }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
@ -114,6 +119,13 @@ const EditorToolbar = ({ editor }: { editor: Editor | null }) => {
|
||||
}
|
||||
|
||||
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({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
@ -128,24 +140,57 @@ const ProductInformation = () => {
|
||||
immediatelyRender: false,
|
||||
content: `
|
||||
<p>
|
||||
Keep your account secure with authentication step.
|
||||
${description}
|
||||
</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 (
|
||||
<Card>
|
||||
<CardHeader title='Product Information' />
|
||||
<CardContent>
|
||||
<Grid container spacing={6} className='mbe-6'>
|
||||
<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 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 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>
|
||||
<Typography className='mbe-1'>Description (Optional)</Typography>
|
||||
|
||||
@ -1,48 +1,56 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
|
||||
// Component Imports
|
||||
import CustomIconButton from '@core/components/mui/IconButton'
|
||||
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 = () => {
|
||||
// States
|
||||
const [vendor, setVendor] = useState('')
|
||||
const [category, setCategory] = useState('')
|
||||
const [collection, setCollection] = useState('')
|
||||
const [status, setStatus] = useState('')
|
||||
const dispatch = useDispatch()
|
||||
const { category_id, printer_type } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||
|
||||
const { data: categoriesApi } = useCategoriesQuery.getCategories()
|
||||
|
||||
const handleSelectChange = (field: any, value: any) => {
|
||||
dispatch(setProductField({ field, value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Organize' />
|
||||
<CardContent>
|
||||
<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'>
|
||||
<CustomTextField
|
||||
select
|
||||
fullWidth
|
||||
label='Category'
|
||||
value={category}
|
||||
onChange={e => setCategory(e.target.value)}
|
||||
value={category_id}
|
||||
onChange={e => handleSelectChange('category_id', e.target.value)}
|
||||
>
|
||||
<MenuItem value='Household'>Household</MenuItem>
|
||||
<MenuItem value='Office'>Office</MenuItem>
|
||||
<MenuItem value='Electronics'>Electronics</MenuItem>
|
||||
<MenuItem value='Management'>Management</MenuItem>
|
||||
<MenuItem value='Automotive'>Automotive</MenuItem>
|
||||
{categoriesApi?.categories.length ? (
|
||||
categoriesApi?.categories.map((item: Category, index: number) => (
|
||||
<MenuItem key={index} value={item.id}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled value=''>
|
||||
Loading categories...
|
||||
</MenuItem>
|
||||
)}
|
||||
</CustomTextField>
|
||||
<CustomIconButton variant='tonal' color='primary' className='min-is-fit'>
|
||||
<i className='tabler-plus' />
|
||||
@ -51,20 +59,18 @@ const ProductOrganize = () => {
|
||||
<CustomTextField
|
||||
select
|
||||
fullWidth
|
||||
label='Collection'
|
||||
value={collection}
|
||||
onChange={e => setCollection(e.target.value)}
|
||||
label='Printer Type'
|
||||
value={printer_type}
|
||||
onChange={e => handleSelectChange('printer_type', 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>
|
||||
<MenuItem value={`kitchen`}>Kitchen</MenuItem>
|
||||
</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='Inactive'>Inactive</MenuItem>
|
||||
<MenuItem value='Scheduled'>Scheduled</MenuItem>
|
||||
</CustomTextField>
|
||||
<CustomTextField fullWidth label='Enter Tags' placeholder='Fashion, Trending, Summer' />
|
||||
<CustomTextField fullWidth label='Enter Tags' placeholder='Fashion, Trending, Summer' /> */}
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@ -11,15 +13,39 @@ import Typography from '@mui/material/Typography'
|
||||
// Component Imports
|
||||
import Form from '@components/Form'
|
||||
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 { price, cost } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const handleInputChange = (field: any, value: any) => {
|
||||
dispatch(setProductField({ field, value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Pricing' />
|
||||
<CardContent>
|
||||
<Form>
|
||||
<CustomTextField fullWidth label='Base Price' placeholder='Enter Base Price' className='mbe-6' />
|
||||
<CustomTextField fullWidth label='Discounted Price' placeholder='$499' className='mbe-6' />
|
||||
<CustomTextField
|
||||
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' />
|
||||
<Divider className='mlb-2' />
|
||||
<div className='flex items-center justify-between'>
|
||||
|
||||
@ -1,30 +1,42 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { SyntheticEvent } from 'react'
|
||||
|
||||
// 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 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
|
||||
import CustomIconButton from '@core/components/mui/IconButton'
|
||||
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 = () => {
|
||||
// States
|
||||
const [count, setCount] = useState(1)
|
||||
const dispatch = useDispatch()
|
||||
const { variants } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||
|
||||
const deleteForm = (e: SyntheticEvent) => {
|
||||
e.preventDefault()
|
||||
const handleAddVariant = () => {
|
||||
dispatch(setProductField({ field: 'variants', value: [...variants, { name: '', cost: 0, price_modifier: 0 }] }))
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
e.target.closest('.repeater-item').remove()
|
||||
const handleRemoveVariant = (index: number) => {
|
||||
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 (
|
||||
@ -32,21 +44,40 @@ const ProductVariants = () => {
|
||||
<CardHeader title='Product Variants' />
|
||||
<CardContent>
|
||||
<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 container spacing={6}>
|
||||
<Grid size={{ xs: 12, sm: 4 }}>
|
||||
<CustomTextField select fullWidth label='Options' defaultValue='Size'>
|
||||
<MenuItem value='Size'>Size</MenuItem>
|
||||
<MenuItem value='Color'>Color</MenuItem>
|
||||
<MenuItem value='Weight'>Weight</MenuItem>
|
||||
<MenuItem value='Smell'>Smell</MenuItem>
|
||||
</CustomTextField>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Variant Name'
|
||||
placeholder='Hot'
|
||||
name='name'
|
||||
value={variant.name}
|
||||
onChange={e => handleInputChange(index, e)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 8 }} alignSelf='end'>
|
||||
<div className='flex items-center gap-6'>
|
||||
<CustomTextField fullWidth placeholder='Enter Variant Value' />
|
||||
<CustomIconButton onClick={deleteForm} className='min-is-fit'>
|
||||
<Grid size={{ xs: 12, sm: 4 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
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' />
|
||||
</CustomIconButton>
|
||||
</div>
|
||||
@ -55,7 +86,7 @@ const ProductVariants = () => {
|
||||
</Grid>
|
||||
))}
|
||||
<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
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user