fix: add product

This commit is contained in:
ferdiansyah783
2025-08-05 21:31:53 +07:00
parent 0fde122ac4
commit 799837e82e
14 changed files with 393 additions and 68 deletions
@@ -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&apos;s Clothing</MenuItem>
<MenuItem value={`Women's Clothing`}>Women&apos;s Clothing</MenuItem>
<MenuItem value={`Kid's Clothing`}>Kid&apos;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&apos;s Clothing</MenuItem>
<MenuItem value={`Women's Clothing`}>Women&apos;s Clothing</MenuItem>
<MenuItem value={`Kid's Clothing`}>Kid&apos;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>