Merge remote-tracking branch 'origin/main' into efril
This commit is contained in:
commit
054ca015fd
@ -352,7 +352,7 @@ const CreateOrganization = () => {
|
|||||||
required
|
required
|
||||||
label='Organization Name'
|
label='Organization Name'
|
||||||
placeholder='My Company Ltd.'
|
placeholder='My Company Ltd.'
|
||||||
value={formData.organization_name}
|
value={formData.organization_name || ''}
|
||||||
onChange={handleInputChange('organization_name')}
|
onChange={handleInputChange('organization_name')}
|
||||||
error={!!errors.organization_name}
|
error={!!errors.organization_name}
|
||||||
helperText={errors.organization_name}
|
helperText={errors.organization_name}
|
||||||
@ -395,7 +395,7 @@ const CreateOrganization = () => {
|
|||||||
required
|
required
|
||||||
label='Admin Name'
|
label='Admin Name'
|
||||||
placeholder='John Doe'
|
placeholder='John Doe'
|
||||||
value={formData.admin_name}
|
value={formData.admin_name || ''}
|
||||||
onChange={handleInputChange('admin_name')}
|
onChange={handleInputChange('admin_name')}
|
||||||
error={!!errors.admin_name}
|
error={!!errors.admin_name}
|
||||||
helperText={errors.admin_name}
|
helperText={errors.admin_name}
|
||||||
@ -408,7 +408,7 @@ const CreateOrganization = () => {
|
|||||||
label='Admin Email'
|
label='Admin Email'
|
||||||
placeholder='admin@mycompany.com'
|
placeholder='admin@mycompany.com'
|
||||||
type='email'
|
type='email'
|
||||||
value={formData.admin_email}
|
value={formData.admin_email || ''}
|
||||||
onChange={handleInputChange('admin_email')}
|
onChange={handleInputChange('admin_email')}
|
||||||
error={!!errors.admin_email}
|
error={!!errors.admin_email}
|
||||||
helperText={errors.admin_email}
|
helperText={errors.admin_email}
|
||||||
@ -421,7 +421,7 @@ const CreateOrganization = () => {
|
|||||||
type='password'
|
type='password'
|
||||||
label='Admin Password'
|
label='Admin Password'
|
||||||
placeholder='Minimum 6 characters'
|
placeholder='Minimum 6 characters'
|
||||||
value={formData.admin_password}
|
value={formData.admin_password || ''}
|
||||||
onChange={handleInputChange('admin_password')}
|
onChange={handleInputChange('admin_password')}
|
||||||
error={!!errors.admin_password}
|
error={!!errors.admin_password}
|
||||||
helperText={errors.admin_password}
|
helperText={errors.admin_password}
|
||||||
@ -443,7 +443,7 @@ const CreateOrganization = () => {
|
|||||||
required
|
required
|
||||||
label='Outlet Name'
|
label='Outlet Name'
|
||||||
placeholder='Main Store'
|
placeholder='Main Store'
|
||||||
value={formData.outlet_name}
|
value={formData.outlet_name || ''}
|
||||||
onChange={handleInputChange('outlet_name')}
|
onChange={handleInputChange('outlet_name')}
|
||||||
error={!!errors.outlet_name}
|
error={!!errors.outlet_name}
|
||||||
helperText={errors.outlet_name}
|
helperText={errors.outlet_name}
|
||||||
|
|||||||
@ -3,12 +3,11 @@ import Grid from '@mui/material/Grid2'
|
|||||||
|
|
||||||
// Component Imports
|
// Component Imports
|
||||||
import ProductAddHeader from '@views/apps/ecommerce/products/add/ProductAddHeader'
|
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 ProductImage from '@views/apps/ecommerce/products/add/ProductImage'
|
||||||
import ProductVariants from '@views/apps/ecommerce/products/add/ProductVariants'
|
import ProductInformation from '@views/apps/ecommerce/products/add/ProductInformation'
|
||||||
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'
|
import ProductOrganize from '@views/apps/ecommerce/products/add/ProductOrganize'
|
||||||
|
import ProductPricing from '@views/apps/ecommerce/products/add/ProductPricing'
|
||||||
|
import ProductVariants from '@views/apps/ecommerce/products/add/ProductVariants'
|
||||||
|
|
||||||
const eCommerceProductsEdit = () => {
|
const eCommerceProductsEdit = () => {
|
||||||
return (
|
return (
|
||||||
@ -27,9 +26,6 @@ const eCommerceProductsEdit = () => {
|
|||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<ProductVariants />
|
<ProductVariants />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<ProductInventory />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 4 }}>
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
|||||||
@ -14,12 +14,12 @@ const DashboardOverview = () => {
|
|||||||
|
|
||||||
if (isLoading) return <Loading />
|
if (isLoading) return <Loading />
|
||||||
|
|
||||||
const MetricCard = ({ iconClass, title, value, subtitle, bgColor = 'bg-blue-500' }: any) => (
|
const MetricCard = ({ iconClass, title, value, subtitle, bgColor = 'bg-blue-500', isCurrency = false }: any) => (
|
||||||
<div className='bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 p-6'>
|
<div className='bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 p-6'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<h3 className='text-sm font-medium text-gray-600 mb-2'>{title}</h3>
|
<h3 className='text-sm font-medium text-gray-600 mb-2'>{title}</h3>
|
||||||
<p className='text-2xl font-bold text-gray-900 mb-1'>{value}</p>
|
<p className='text-2xl font-bold text-gray-900 mb-1'>{isCurrency ? 'Rp ' + value : value}</p>
|
||||||
{subtitle && <p className='text-sm text-gray-500'>{subtitle}</p>}
|
{subtitle && <p className='text-sm text-gray-500'>{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className={`px-4 py-3 rounded-full ${bgColor} bg-opacity-10`}>
|
<div className={`px-4 py-3 rounded-full ${bgColor} bg-opacity-10`}>
|
||||||
@ -52,12 +52,6 @@ const DashboardOverview = () => {
|
|||||||
|
|
||||||
{/* Overview Metrics */}
|
{/* Overview Metrics */}
|
||||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8'>
|
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8'>
|
||||||
<MetricCard
|
|
||||||
iconClass='tabler-cash'
|
|
||||||
title='Total Sales'
|
|
||||||
value={formatShortCurrency(salesData.overview.total_sales)}
|
|
||||||
bgColor='bg-green-500'
|
|
||||||
/>
|
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-shopping-cart'
|
iconClass='tabler-shopping-cart'
|
||||||
title='Total Orders'
|
title='Total Orders'
|
||||||
@ -65,11 +59,19 @@ const DashboardOverview = () => {
|
|||||||
subtitle={`${salesData.overview.voided_orders} voided, ${salesData.overview.refunded_orders} refunded`}
|
subtitle={`${salesData.overview.voided_orders} voided, ${salesData.overview.refunded_orders} refunded`}
|
||||||
bgColor='bg-blue-500'
|
bgColor='bg-blue-500'
|
||||||
/>
|
/>
|
||||||
|
<MetricCard
|
||||||
|
iconClass='tabler-cash'
|
||||||
|
title='Total Sales'
|
||||||
|
value={formatShortCurrency(salesData.overview.total_sales)}
|
||||||
|
bgColor='bg-green-500'
|
||||||
|
isCurrency={true}
|
||||||
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-trending-up'
|
iconClass='tabler-trending-up'
|
||||||
title='Average Order Value'
|
title='Average Order Value'
|
||||||
value={formatShortCurrency(salesData.overview.average_order_value)}
|
value={formatShortCurrency(salesData.overview.average_order_value)}
|
||||||
bgColor='bg-purple-500'
|
bgColor='bg-purple-500'
|
||||||
|
isCurrency={true}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-users'
|
iconClass='tabler-users'
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const DashboardProfitloss = () => {
|
|||||||
function formatMetricName(metric: string): string {
|
function formatMetricName(metric: string): string {
|
||||||
const nameMap: { [key: string]: string } = {
|
const nameMap: { [key: string]: string } = {
|
||||||
revenue: 'Revenue',
|
revenue: 'Revenue',
|
||||||
net_profit: 'Net Profit',
|
net_profit: 'Net Profit'
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameMap[metric] || metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
return nameMap[metric] || metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||||
@ -61,15 +61,25 @@ const DashboardProfitloss = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const MetricCard = ({ iconClass, title, value, subtitle, bgColor = 'bg-blue-500', isNegative = false }: any) => (
|
const MetricCard = ({
|
||||||
|
iconClass,
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
subtitle,
|
||||||
|
bgColor = 'bg-blue-500',
|
||||||
|
isNegative = false,
|
||||||
|
isCurrency = false
|
||||||
|
}: any) => (
|
||||||
<div className='bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 p-6'>
|
<div className='bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 p-6'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<h3 className='text-sm font-medium text-gray-600 mb-2'>{title}</h3>
|
<h3 className='text-sm font-medium text-gray-600 mb-2'>{title}</h3>
|
||||||
<p className={`text-2xl font-bold mb-1 ${isNegative ? 'text-red-600' : 'text-gray-900'}`}>{value}</p>
|
<p className={`text-2xl font-bold mb-1 ${isNegative ? 'text-red-600' : 'text-gray-900'}`}>
|
||||||
|
{isCurrency ? 'Rp ' + value : value}
|
||||||
|
</p>
|
||||||
{subtitle && <p className='text-sm text-gray-500'>{subtitle}</p>}
|
{subtitle && <p className='text-sm text-gray-500'>{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className={`p-3 rounded-full ${bgColor} bg-opacity-10`}>
|
<div className={`px-4 py-3 rounded-full ${bgColor} bg-opacity-10`}>
|
||||||
<i className={`${iconClass} text-[32px] ${bgColor.replace('bg-', 'text-')}`}></i>
|
<i className={`${iconClass} text-[32px] ${bgColor.replace('bg-', 'text-')}`}></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,12 +105,14 @@ const DashboardProfitloss = () => {
|
|||||||
title='Total Revenue'
|
title='Total Revenue'
|
||||||
value={formatShortCurrency(profitData.summary.total_revenue)}
|
value={formatShortCurrency(profitData.summary.total_revenue)}
|
||||||
bgColor='bg-green-500'
|
bgColor='bg-green-500'
|
||||||
|
isCurrency={true}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-receipt'
|
iconClass='tabler-receipt'
|
||||||
title='Total Cost'
|
title='Total Cost'
|
||||||
value={formatShortCurrency(profitData.summary.total_cost)}
|
value={formatShortCurrency(profitData.summary.total_cost)}
|
||||||
bgColor='bg-red-500'
|
bgColor='bg-red-500'
|
||||||
|
isCurrency={true}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-trending-up'
|
iconClass='tabler-trending-up'
|
||||||
@ -109,6 +121,7 @@ const DashboardProfitloss = () => {
|
|||||||
subtitle={`Margin: ${formatPercentage(profitData.summary.gross_profit_margin)}`}
|
subtitle={`Margin: ${formatPercentage(profitData.summary.gross_profit_margin)}`}
|
||||||
bgColor='bg-blue-500'
|
bgColor='bg-blue-500'
|
||||||
isNegative={profitData.summary.gross_profit < 0}
|
isNegative={profitData.summary.gross_profit < 0}
|
||||||
|
isCurrency={true}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
iconClass='tabler-percentage'
|
iconClass='tabler-percentage'
|
||||||
@ -127,7 +140,7 @@ const DashboardProfitloss = () => {
|
|||||||
<h3 className='text-lg font-semibold text-gray-900'>Net Profit</h3>
|
<h3 className='text-lg font-semibold text-gray-900'>Net Profit</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className='text-3xl font-bold text-green-600 mb-2'>
|
<p className='text-3xl font-bold text-green-600 mb-2'>
|
||||||
{formatShortCurrency(profitData.summary.net_profit)}
|
Rp {formatShortCurrency(profitData.summary.net_profit)}
|
||||||
</p>
|
</p>
|
||||||
<p className='text-sm text-gray-600'>Margin: {formatPercentage(profitData.summary.net_profit_margin)}</p>
|
<p className='text-sm text-gray-600'>Margin: {formatPercentage(profitData.summary.net_profit_margin)}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -144,7 +157,7 @@ const DashboardProfitloss = () => {
|
|||||||
<h3 className='text-lg font-semibold text-gray-900'>Tax & Discount</h3>
|
<h3 className='text-lg font-semibold text-gray-900'>Tax & Discount</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className='text-xl font-bold text-orange-600 mb-1'>
|
<p className='text-xl font-bold text-orange-600 mb-1'>
|
||||||
{formatShortCurrency(profitData.summary.total_tax + profitData.summary.total_discount)}
|
Rp {formatShortCurrency(profitData.summary.total_tax + profitData.summary.total_discount)}
|
||||||
</p>
|
</p>
|
||||||
<p className='text-sm text-gray-600'>
|
<p className='text-sm text-gray-600'>
|
||||||
Tax: {formatShortCurrency(profitData.summary.total_tax)} | Discount:{' '}
|
Tax: {formatShortCurrency(profitData.summary.total_tax)} | Discount:{' '}
|
||||||
|
|||||||
@ -91,7 +91,7 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => {
|
|||||||
<MenuItem href={`/${locale}/dashboards/daily-report`}>{dictionary['navigation'].dailyReport}</MenuItem>
|
<MenuItem href={`/${locale}/dashboards/daily-report`}>{dictionary['navigation'].dailyReport}</MenuItem>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
<MenuSection label={dictionary['navigation'].appsPages}>
|
<MenuSection label={dictionary['navigation'].appsPages}>
|
||||||
<SubMenu label={dictionary['navigation'].eCommerce} icon={<i className='tabler-shopping-cart' />}>
|
<SubMenu label={dictionary['navigation'].eCommerce} icon={<i className='tabler-salad' />}>
|
||||||
{/* <MenuItem href={`/${locale}/apps/ecommerce/dashboard`}>{dictionary['navigation'].dashboard}</MenuItem> */}
|
{/* <MenuItem href={`/${locale}/apps/ecommerce/dashboard`}>{dictionary['navigation'].dashboard}</MenuItem> */}
|
||||||
<SubMenu label={dictionary['navigation'].products}>
|
<SubMenu label={dictionary['navigation'].products}>
|
||||||
<MenuItem href={`/${locale}/apps/ecommerce/products/list`}>{dictionary['navigation'].list}</MenuItem>
|
<MenuItem href={`/${locale}/apps/ecommerce/products/list`}>{dictionary['navigation'].list}</MenuItem>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboards": "لوحات القيادة",
|
"dashboards": "لوحات القيادة",
|
||||||
"analytics": "تحليلات",
|
"analytics": "تحليلات",
|
||||||
"eCommerce": "التجارة الإلكترونية",
|
"eCommerce": "تجزئة الكترونية",
|
||||||
"stock": "المخزون",
|
"stock": "المخزون",
|
||||||
"academy": "أكاديمية",
|
"academy": "أكاديمية",
|
||||||
"logistics": "اللوجستية",
|
"logistics": "اللوجستية",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboards": "Dashboards",
|
"dashboards": "Dashboards",
|
||||||
"analytics": "Analytics",
|
"analytics": "Analytics",
|
||||||
"eCommerce": "eCommerce",
|
"eCommerce": "Inventory",
|
||||||
"stock": "Stock",
|
"stock": "Stock",
|
||||||
"academy": "Academy",
|
"academy": "Academy",
|
||||||
"logistics": "Logistics",
|
"logistics": "Logistics",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboards": "Tableaux de bord",
|
"dashboards": "Tableaux de bord",
|
||||||
"analytics": "Analytique",
|
"analytics": "Analytique",
|
||||||
"eCommerce": "commerce électronique",
|
"eCommerce": "Inventaire",
|
||||||
"stock": "Stock",
|
"stock": "Stock",
|
||||||
"academy": "Académie",
|
"academy": "Académie",
|
||||||
"logistics": "Logistique",
|
"logistics": "Logistique",
|
||||||
|
|||||||
@ -13,7 +13,6 @@ const initialState: { productRequest: ProductRequest } = {
|
|||||||
sku: '',
|
sku: '',
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
barcode: '',
|
|
||||||
price: 0,
|
price: 0,
|
||||||
cost: 0,
|
cost: 0,
|
||||||
printer_type: '',
|
printer_type: '',
|
||||||
|
|||||||
@ -1,20 +1,71 @@
|
|||||||
// Third-party Imports
|
// Third-party Imports
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
import { ProductRecipe } from '../../types/services/productRecipe'
|
||||||
|
|
||||||
// Type Imports
|
// Type Imports
|
||||||
|
|
||||||
// Data Imports
|
// Data Imports
|
||||||
|
|
||||||
const initialState: { currentProductRecipe: any } = {
|
const initialState: { currentVariant: any, currentProductRecipe: ProductRecipe } = {
|
||||||
currentProductRecipe: {}
|
currentVariant: {},
|
||||||
|
currentProductRecipe: {
|
||||||
|
id: '',
|
||||||
|
organization_id: '',
|
||||||
|
outlet_id: null,
|
||||||
|
product_id: '',
|
||||||
|
variant_id: null,
|
||||||
|
ingredient_id: '',
|
||||||
|
quantity: 0,
|
||||||
|
created_at: '',
|
||||||
|
updated_at: '',
|
||||||
|
product: {
|
||||||
|
ID: '',
|
||||||
|
OrganizationID: '',
|
||||||
|
CategoryID: '',
|
||||||
|
SKU: '',
|
||||||
|
Name: '',
|
||||||
|
Description: null,
|
||||||
|
Price: 0,
|
||||||
|
Cost: 0,
|
||||||
|
BusinessType: '',
|
||||||
|
ImageURL: '',
|
||||||
|
PrinterType: '',
|
||||||
|
UnitID: null,
|
||||||
|
HasIngredients: false,
|
||||||
|
Metadata: {},
|
||||||
|
IsActive: false,
|
||||||
|
CreatedAt: '',
|
||||||
|
UpdatedAt: ''
|
||||||
|
},
|
||||||
|
ingredient: {
|
||||||
|
id: '',
|
||||||
|
organization_id: '',
|
||||||
|
outlet_id: null,
|
||||||
|
name: '',
|
||||||
|
unit_id: '',
|
||||||
|
cost: 0,
|
||||||
|
stock: 0,
|
||||||
|
is_semi_finished: false,
|
||||||
|
is_active: false,
|
||||||
|
metadata: {},
|
||||||
|
created_at: '',
|
||||||
|
updated_at: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const productRecipeSlice = createSlice({
|
export const productRecipeSlice = createSlice({
|
||||||
name: 'productRecipe',
|
name: 'productRecipe',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setProductRecipe: (state, action: PayloadAction<any>) => {
|
setProductVariant: (state, action: PayloadAction<any>) => {
|
||||||
|
state.currentVariant = action.payload
|
||||||
|
},
|
||||||
|
resetProductVariant: state => {
|
||||||
|
state.currentVariant = initialState.currentVariant
|
||||||
|
},
|
||||||
|
setProductRecipe: (state, action: PayloadAction<ProductRecipe>) => {
|
||||||
state.currentProductRecipe = action.payload
|
state.currentProductRecipe = action.payload
|
||||||
},
|
},
|
||||||
resetProductRecipe: state => {
|
resetProductRecipe: state => {
|
||||||
@ -23,6 +74,6 @@ export const productRecipeSlice = createSlice({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setProductRecipe, resetProductRecipe } = productRecipeSlice.actions
|
export const { setProductVariant, resetProductVariant, setProductRecipe, resetProductRecipe } = productRecipeSlice.actions
|
||||||
|
|
||||||
export default productRecipeSlice.reducer
|
export default productRecipeSlice.reducer
|
||||||
|
|||||||
@ -38,8 +38,23 @@ export const useProductRecipesMutation = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deleteProductRecipe = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
const response = await api.delete(`/product-recipes/${id}`)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Product Recipe deleted successfully!')
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['product-recipes'] })
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createProductRecipe,
|
createProductRecipe,
|
||||||
updateProductRecipe
|
updateProductRecipe,
|
||||||
|
deleteProductRecipe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Product, Products } from '../../types/services/product'
|
import { Product, Products } from '../../types/services/product'
|
||||||
import { api } from '../api'
|
import { api } from '../api'
|
||||||
import { ProductRecipe } from '../../types/services/productRecipe'
|
|
||||||
|
|
||||||
interface ProductsQueryParams {
|
export interface ProductsQueryParams {
|
||||||
page?: number
|
page?: number
|
||||||
limit?: number
|
limit?: number
|
||||||
search?: string
|
search?: string
|
||||||
// Add other filter parameters as needed
|
// Add other filter parameters as needed
|
||||||
category_id?: string
|
category_id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useProducts(params: ProductsQueryParams = {}) {
|
export function useProducts(params: ProductsQueryParams = {}) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface Inventory {
|
|||||||
quantity: number
|
quantity: number
|
||||||
reorder_level: number
|
reorder_level: number
|
||||||
is_low_stock: boolean
|
is_low_stock: boolean
|
||||||
|
product: any
|
||||||
updated_at: string // ISO 8601 timestamp
|
updated_at: string // ISO 8601 timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export type ProductRequest = {
|
|||||||
sku: string
|
sku: string
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
barcode: string
|
|
||||||
price: number
|
price: number
|
||||||
cost: number
|
cost: number
|
||||||
printer_type: string
|
printer_type: string
|
||||||
|
|||||||
@ -133,11 +133,22 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
const handleClickShowPassword = () => setIsPasswordShown(show => !show)
|
const handleClickShowPassword = () => setIsPasswordShown(show => !show)
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<FormData> = async (data: FormData) => {
|
const onSubmit: SubmitHandler<FormData> = async (data: FormData) => {
|
||||||
login.mutate(data)
|
login.mutate(data, {
|
||||||
|
onSuccess: (data: any) => {
|
||||||
|
if (data?.user?.role === 'admin') {
|
||||||
const redirectURL = searchParams.get('redirectTo') ?? '/dashboards/overview'
|
const redirectURL = searchParams.get('redirectTo') ?? '/dashboards/overview'
|
||||||
|
|
||||||
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
||||||
|
} else {
|
||||||
|
const redirectURL = searchParams.get('redirectTo') ?? '/sa/organizations/list'
|
||||||
|
|
||||||
|
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
setErrorState(error.response.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -243,7 +254,11 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<div className='flex justify-center items-center flex-wrap gap-2'>
|
<div className='flex justify-center items-center flex-wrap gap-2'>
|
||||||
<Typography>New on our platform?</Typography>
|
<Typography>New on our platform?</Typography>
|
||||||
<Typography component={Link} href={getLocalizedUrl('/organization', locale as Locale)} color='primary.main'>
|
<Typography
|
||||||
|
component={Link}
|
||||||
|
href={getLocalizedUrl('/organization', locale as Locale)}
|
||||||
|
color='primary.main'
|
||||||
|
>
|
||||||
Create an account
|
Create an account
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import type { SystemMode } from '@core/types'
|
|||||||
|
|
||||||
// Hook Imports
|
// Hook Imports
|
||||||
import { useImageVariant } from '@core/hooks/useImageVariant'
|
import { useImageVariant } from '@core/hooks/useImageVariant'
|
||||||
|
import { useAuth } from '../contexts/authContext'
|
||||||
|
|
||||||
// Styled Components
|
// Styled Components
|
||||||
const MaskImg = styled('img')({
|
const MaskImg = styled('img')({
|
||||||
@ -29,6 +30,8 @@ const MaskImg = styled('img')({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const NotFound = ({ mode }: { mode: SystemMode }) => {
|
const NotFound = ({ mode }: { mode: SystemMode }) => {
|
||||||
|
const { currentUser } = useAuth()
|
||||||
|
|
||||||
// Vars
|
// Vars
|
||||||
const darkImg = '/images/pages/misc-mask-dark.png'
|
const darkImg = '/images/pages/misc-mask-dark.png'
|
||||||
const lightImg = '/images/pages/misc-mask-light.png'
|
const lightImg = '/images/pages/misc-mask-light.png'
|
||||||
@ -48,7 +51,11 @@ const NotFound = ({ mode }: { mode: SystemMode }) => {
|
|||||||
<Typography variant='h4'>Page Not Found ⚠️</Typography>
|
<Typography variant='h4'>Page Not Found ⚠️</Typography>
|
||||||
<Typography>we couldn't find the page you are looking for.</Typography>
|
<Typography>we couldn't find the page you are looking for.</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Button href='/' component={Link} variant='contained'>
|
<Button
|
||||||
|
href={currentUser?.role === 'admin' ? '/' : '/sa/organizations/list'}
|
||||||
|
component={Link}
|
||||||
|
variant='contained'
|
||||||
|
>
|
||||||
Back To Home
|
Back To Home
|
||||||
</Button>
|
</Button>
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -43,11 +43,11 @@ const BillingAddress = ({ data }: { data: Order }) => {
|
|||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<Typography variant='h5'>
|
<Typography variant='h5'>
|
||||||
Payment Details ({data.payments.length} {data.payments.length === 1 ? 'Payment' : 'Payments'})
|
Payment Details ({data?.payments?.length ?? 0} {data?.payments?.length === 1 ? 'Payment' : 'Payments'})
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{data.payments.map((payment, index) => (
|
{data?.payments?.length ? data.payments.map((payment, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<div className='flex items-center gap-3'>
|
<div className='flex items-center gap-3'>
|
||||||
<CustomAvatar skin='light' color='secondary' size={40}>
|
<CustomAvatar skin='light' color='secondary' size={40}>
|
||||||
@ -74,7 +74,9 @@ const BillingAddress = ({ data }: { data: Order }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)) : (
|
||||||
|
<Typography variant='body2' className='text-secondary'>No payments found</Typography>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -276,10 +276,10 @@ const OrderDetailsCard = ({ data }: { data: Order }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-12'>
|
<div className='flex items-center gap-12'>
|
||||||
<Typography color='text.primary' className='font-medium min-is-[100px]'>
|
<Typography color='text.primary' className='font-semibold min-is-[100px]'>
|
||||||
Total:
|
Total:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color='text.primary' className='font-medium'>
|
<Typography color='text.primary' className='font-semibold'>
|
||||||
{formatCurrency(data.total_amount)}
|
{formatCurrency(data.total_amount)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -39,9 +39,6 @@ const OrderDetails = () => {
|
|||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<OrderDetailsCard data={data} />
|
<OrderDetailsCard data={data} />
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* <Grid size={{ xs: 12 }}>
|
|
||||||
<ShippingActivity order={data.order_number} />
|
|
||||||
</Grid> */}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 4 }}>
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
@ -49,9 +46,6 @@ const OrderDetails = () => {
|
|||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<CustomerDetails orderData={data} />
|
<CustomerDetails orderData={data} />
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* <Grid size={{ xs: 12 }}>
|
|
||||||
<ShippingAddress />
|
|
||||||
</Grid> */}
|
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<BillingAddress data={data} />
|
<BillingAddress data={data} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import OrderListTable from './OrderListTable'
|
|||||||
const OrderList = () => {
|
const OrderList = () => {
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={6}>
|
||||||
<Grid size={{ xs: 12 }}>
|
{/* <Grid size={{ xs: 12 }}>
|
||||||
<OrderCard />
|
<OrderCard />
|
||||||
</Grid>
|
</Grid> */}
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<OrderListTable />
|
<OrderListTable />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -55,7 +55,7 @@ const ProductAddHeader = () => {
|
|||||||
<Button variant='tonal' color='secondary'>
|
<Button variant='tonal' color='secondary'>
|
||||||
Discard
|
Discard
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='tonal'>Save Draft</Button>
|
{/* <Button variant='tonal'>Save Draft</Button> */}
|
||||||
<Button variant='contained' disabled={isEdit ? isUpdating : isCreating} onClick={handleSubmit}>
|
<Button variant='contained' disabled={isEdit ? isUpdating : isCreating} onClick={handleSubmit}>
|
||||||
{isEdit ? 'Update Product' : 'Publish Product'}
|
{isEdit ? 'Update Product' : 'Publish Product'}
|
||||||
{(isCreating || isUpdating) && <CircularProgress color='inherit' size={16} className='ml-2' />}
|
{(isCreating || isUpdating) && <CircularProgress color='inherit' size={16} className='ml-2' />}
|
||||||
|
|||||||
@ -126,13 +126,27 @@ const ProductInformation = () => {
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
||||||
const { data: product, isLoading, error } = useProductById(params?.id as string)
|
const { data: product, isLoading, error } = useProductById(params?.id as string)
|
||||||
const { name, sku, barcode, description } = useSelector((state: RootState) => state.productReducer.productRequest)
|
const { name, sku, description } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
|
|
||||||
|
console.log('desc', description)
|
||||||
|
|
||||||
const isEdit = !!params?.id
|
const isEdit = !!params?.id
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (product) {
|
if (product) {
|
||||||
dispatch(setProduct(product))
|
dispatch(
|
||||||
|
setProduct({
|
||||||
|
name: product.name,
|
||||||
|
sku: product.sku || '',
|
||||||
|
description: product.description || '',
|
||||||
|
price: product.price,
|
||||||
|
cost: product.cost,
|
||||||
|
category_id: product.category_id,
|
||||||
|
printer_type: product.printer_type,
|
||||||
|
image_url: product.image_url || '',
|
||||||
|
variants: product.variants || []
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [product, dispatch])
|
}, [product, dispatch])
|
||||||
|
|
||||||
@ -152,9 +166,11 @@ const ProductInformation = () => {
|
|||||||
Underline
|
Underline
|
||||||
],
|
],
|
||||||
immediatelyRender: false,
|
immediatelyRender: false,
|
||||||
content: `
|
content: params?.id
|
||||||
|
? description
|
||||||
|
: `
|
||||||
<p>
|
<p>
|
||||||
${description || ''}
|
${description}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
@ -181,7 +197,7 @@ const ProductInformation = () => {
|
|||||||
<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, sm: 6 }}>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label='Product Name'
|
label='Product Name'
|
||||||
@ -199,15 +215,6 @@ const ProductInformation = () => {
|
|||||||
onChange={e => handleInputChange('sku', e.target.value)}
|
onChange={e => handleInputChange('sku', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 6 }}>
|
|
||||||
<CustomTextField
|
|
||||||
fullWidth
|
|
||||||
label='Barcode'
|
|
||||||
placeholder='0123-4567'
|
|
||||||
value={barcode || ''}
|
|
||||||
onChange={e => handleInputChange('barcode', e.target.value)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Typography className='mbe-1'>Description (Optional)</Typography>
|
<Typography className='mbe-1'>Description (Optional)</Typography>
|
||||||
<Card className='p-0 border shadow-none'>
|
<Card className='p-0 border shadow-none'>
|
||||||
|
|||||||
@ -16,12 +16,22 @@ import { RootState } from '../../../../../redux-store'
|
|||||||
import { setProductField } from '../../../../../redux-store/slices/product'
|
import { setProductField } from '../../../../../redux-store/slices/product'
|
||||||
import { useCategories } from '../../../../../services/queries/categories'
|
import { useCategories } from '../../../../../services/queries/categories'
|
||||||
import { Category } from '../../../../../types/services/category'
|
import { Category } from '../../../../../types/services/category'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { Autocomplete, CircularProgress } from '@mui/material'
|
||||||
|
|
||||||
const ProductOrganize = () => {
|
const ProductOrganize = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { category_id, printer_type } = useSelector((state: RootState) => state.productReducer.productRequest)
|
const { category_id, printer_type } = useSelector((state: RootState) => state.productReducer.productRequest)
|
||||||
|
|
||||||
const { data: categoriesApi } = useCategories()
|
const [categoryInput, setCategoryInput] = useState('')
|
||||||
|
const [categoryDebouncedInput] = useDebounce(categoryInput, 500)
|
||||||
|
|
||||||
|
const { data: categoriesApi, isLoading: categoriesLoading } = useCategories({
|
||||||
|
search: categoryDebouncedInput
|
||||||
|
})
|
||||||
|
|
||||||
|
const categoryOptions = useMemo(() => categoriesApi?.categories || [], [categoriesApi])
|
||||||
|
|
||||||
const handleSelectChange = (field: any, value: any) => {
|
const handleSelectChange = (field: any, value: any) => {
|
||||||
dispatch(setProductField({ field, value }))
|
dispatch(setProductField({ field, value }))
|
||||||
@ -33,25 +43,36 @@ const ProductOrganize = () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={e => e.preventDefault()} className='flex flex-col gap-6'>
|
<form onSubmit={e => e.preventDefault()} className='flex flex-col gap-6'>
|
||||||
<div className='flex items-end gap-4'>
|
<div className='flex items-end gap-4'>
|
||||||
<CustomTextField
|
<Autocomplete
|
||||||
select
|
options={categoryOptions}
|
||||||
|
loading={categoriesLoading}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
getOptionLabel={option => option.name}
|
||||||
|
value={categoryOptions.find(p => p.id === category_id) || null}
|
||||||
|
onInputChange={(event, newCategoryInput) => {
|
||||||
|
setCategoryInput(newCategoryInput)
|
||||||
|
}}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
dispatch(setProductField({ field: 'category_id', value: newValue?.id || '' }))
|
||||||
|
}}
|
||||||
|
renderInput={params => (
|
||||||
|
<CustomTextField
|
||||||
|
{...params}
|
||||||
|
className=''
|
||||||
label='Category'
|
label='Category'
|
||||||
value={category_id}
|
fullWidth
|
||||||
onChange={e => handleSelectChange('category_id', e.target.value)}
|
InputProps={{
|
||||||
>
|
...params.InputProps,
|
||||||
{categoriesApi?.categories.length ? (
|
endAdornment: (
|
||||||
categoriesApi?.categories.map((item: Category, index: number) => (
|
<>
|
||||||
<MenuItem key={index} value={item.id}>
|
{categoriesLoading && <CircularProgress size={18} />}
|
||||||
{item.name}
|
{params.InputProps.endAdornment}
|
||||||
</MenuItem>
|
</>
|
||||||
))
|
)
|
||||||
) : (
|
}}
|
||||||
<MenuItem disabled value=''>
|
/>
|
||||||
Loading categories...
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
)}
|
||||||
</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' />
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
@ -65,12 +86,6 @@ const ProductOrganize = () => {
|
|||||||
>
|
>
|
||||||
<MenuItem value={`kitchen`}>Kitchen</MenuItem>
|
<MenuItem value={`kitchen`}>Kitchen</MenuItem>
|
||||||
</CustomTextField>
|
</CustomTextField>
|
||||||
{/* <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' /> */}
|
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// React Imports
|
// React Imports
|
||||||
import { useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
@ -19,12 +19,12 @@ import { Autocomplete } from '@mui/material'
|
|||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { RootState } from '../../../../../redux-store'
|
import { RootState } from '../../../../../redux-store'
|
||||||
import { resetProductRecipe } from '../../../../../redux-store/slices/productRecipe'
|
|
||||||
import { useProductRecipesMutation } from '../../../../../services/mutations/productRecipes'
|
import { useProductRecipesMutation } from '../../../../../services/mutations/productRecipes'
|
||||||
import { useIngredients } from '../../../../../services/queries/ingredients'
|
import { useIngredients } from '../../../../../services/queries/ingredients'
|
||||||
import { useOutlets } from '../../../../../services/queries/outlets'
|
import { useOutlets } from '../../../../../services/queries/outlets'
|
||||||
import { Product } from '../../../../../types/services/product'
|
import { Product } from '../../../../../types/services/product'
|
||||||
import { ProductRecipeRequest } from '../../../../../types/services/productRecipe'
|
import { ProductRecipeRequest } from '../../../../../types/services/productRecipe'
|
||||||
|
import { resetProductVariant } from '../../../../../redux-store/slices/productRecipe'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
@ -47,7 +47,7 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
// Props
|
// Props
|
||||||
const { open, handleClose, product } = props
|
const { open, handleClose, product } = props
|
||||||
|
|
||||||
const { currentProductRecipe } = useSelector((state: RootState) => state.productRecipeReducer)
|
const { currentVariant, currentProductRecipe } = useSelector((state: RootState) => state.productRecipeReducer)
|
||||||
|
|
||||||
const [outletInput, setOutletInput] = useState('')
|
const [outletInput, setOutletInput] = useState('')
|
||||||
const [outletDebouncedInput] = useDebounce(outletInput, 500)
|
const [outletDebouncedInput] = useDebounce(outletInput, 500)
|
||||||
@ -67,11 +67,27 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
|
|
||||||
const { createProductRecipe, updateProductRecipe } = useProductRecipesMutation()
|
const { createProductRecipe, updateProductRecipe } = useProductRecipesMutation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentProductRecipe.id) {
|
||||||
|
setFormData(currentProductRecipe)
|
||||||
|
}
|
||||||
|
}, [currentProductRecipe])
|
||||||
|
|
||||||
const handleSubmit = (e: any) => {
|
const handleSubmit = (e: any) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (currentProductRecipe.id) {
|
||||||
|
updateProductRecipe.mutate(
|
||||||
|
{ id: currentProductRecipe.id, payload: formData },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
handleReset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
createProductRecipe.mutate(
|
createProductRecipe.mutate(
|
||||||
{ ...formData, product_id: product.id, variant_id: currentProductRecipe.id || '' },
|
{ ...formData, product_id: product.id, variant_id: currentVariant.id || '' },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
handleReset()
|
handleReset()
|
||||||
@ -79,10 +95,11 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
handleClose()
|
handleClose()
|
||||||
dispatch(resetProductRecipe())
|
dispatch(resetProductVariant())
|
||||||
setFormData(initialData)
|
setFormData(initialData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,13 +111,15 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setTitleDrawer = (recipe: any) => {
|
const setTitleDrawer = (recipe: any) => {
|
||||||
|
const addOrEdit = currentProductRecipe.id ? 'Edit ' : 'Add '
|
||||||
|
|
||||||
let title = 'Original'
|
let title = 'Original'
|
||||||
|
|
||||||
if (recipe?.name) {
|
if (recipe?.name) {
|
||||||
title = recipe?.name
|
title = recipe?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
return title
|
return addOrEdit + title
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -113,7 +132,7 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
|
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-between pli-6 plb-5'>
|
<div className='flex items-center justify-between pli-6 plb-5'>
|
||||||
<Typography variant='h5'>{setTitleDrawer(currentProductRecipe)} Variant Ingredient</Typography>
|
<Typography variant='h5'>{setTitleDrawer(currentVariant)} Variant Ingredient</Typography>
|
||||||
<IconButton size='small' onClick={handleReset}>
|
<IconButton size='small' onClick={handleReset}>
|
||||||
<i className='tabler-x text-2xl' />
|
<i className='tabler-x text-2xl' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -192,9 +211,13 @@ const AddRecipeDrawer = (props: Props) => {
|
|||||||
type='submit'
|
type='submit'
|
||||||
disabled={createProductRecipe.isPending || updateProductRecipe.isPending}
|
disabled={createProductRecipe.isPending || updateProductRecipe.isPending}
|
||||||
>
|
>
|
||||||
{createProductRecipe.isPending
|
{currentProductRecipe.id
|
||||||
? 'Adding...'
|
? updateProductRecipe.isPending
|
||||||
: 'Add'}
|
? 'Updating...'
|
||||||
|
: 'Update'
|
||||||
|
: createProductRecipe.isPending
|
||||||
|
? 'Creating...'
|
||||||
|
: 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
|
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
|
||||||
Discard
|
Discard
|
||||||
|
|||||||
@ -16,33 +16,54 @@ import {
|
|||||||
TableContainer,
|
TableContainer,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useParams } from 'next/navigation'
|
import { useParams } from 'next/navigation'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import Loading from '../../../../../components/layout/shared/Loading'
|
import Loading from '../../../../../components/layout/shared/Loading'
|
||||||
import { setProductRecipe } from '../../../../../redux-store/slices/productRecipe'
|
|
||||||
import { useProductRecipesByProduct } from '../../../../../services/queries/productRecipes'
|
import { useProductRecipesByProduct } from '../../../../../services/queries/productRecipes'
|
||||||
import { useProductById } from '../../../../../services/queries/products'
|
import { useProductById } from '../../../../../services/queries/products'
|
||||||
import { ProductVariant } from '../../../../../types/services/product'
|
import { ProductVariant } from '../../../../../types/services/product'
|
||||||
import { formatCurrency } from '../../../../../utils/transform'
|
import { formatCurrency } from '../../../../../utils/transform'
|
||||||
import AddRecipeDrawer from './AddRecipeDrawer'
|
import AddRecipeDrawer from './AddRecipeDrawer'
|
||||||
|
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
|
||||||
|
import { useProductRecipesMutation } from '../../../../../services/mutations/productRecipes'
|
||||||
|
import { setProductRecipe, setProductVariant } from '../../../../../redux-store/slices/productRecipe'
|
||||||
|
import { ProductRecipe } from '../../../../../types/services/productRecipe'
|
||||||
|
|
||||||
const ProductDetail = () => {
|
const ProductDetail = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
||||||
const [openProductRecipe, setOpenProductRecipe] = useState(false)
|
const [openProductRecipe, setOpenProductRecipe] = useState(false)
|
||||||
|
const [openConfirm, setOpenConfirm] = useState(false)
|
||||||
|
const [productRecipeId, setProductRecipeId] = useState('')
|
||||||
|
|
||||||
const { data: product, isLoading, error } = useProductById(params?.id as string)
|
const { data: product, isLoading, error } = useProductById(params?.id as string)
|
||||||
const { data: productRecipe, isLoading: isLoadingProductRecipe } = useProductRecipesByProduct(params?.id as string)
|
const { data: productRecipe, isLoading: isLoadingProductRecipe } = useProductRecipesByProduct(params?.id as string)
|
||||||
|
|
||||||
const handleOpenProductRecipe = (recipe: any) => {
|
const { deleteProductRecipe } = useProductRecipesMutation()
|
||||||
|
|
||||||
|
const handleOpenProductRecipe = (variant: any) => {
|
||||||
|
setOpenProductRecipe(true)
|
||||||
|
dispatch(setProductVariant(variant))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOpenEditProductRecipe = (recipe: ProductRecipe) => {
|
||||||
setOpenProductRecipe(true)
|
setOpenProductRecipe(true)
|
||||||
dispatch(setProductRecipe(recipe))
|
dispatch(setProductRecipe(recipe))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteRecipe = () => {
|
||||||
|
deleteProductRecipe.mutate(productRecipeId, {
|
||||||
|
onSuccess: () => {
|
||||||
|
setOpenConfirm(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading || isLoadingProductRecipe) return <Loading />
|
if (isLoading || isLoadingProductRecipe) return <Loading />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -149,13 +170,14 @@ const ProductDetail = () => {
|
|||||||
Total Cost
|
Total Cost
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{productRecipe?.length &&
|
{productRecipe?.length ? (
|
||||||
productRecipe
|
productRecipe
|
||||||
.filter((item: any) => item.variant_id === null)
|
.filter((item: ProductRecipe) => item.variant_id === null)
|
||||||
.map((item: any, index: number) => (
|
.map((item: ProductRecipe, index: number) => (
|
||||||
<TableRow key={index} className='hover:bg-gray-50'>
|
<TableRow key={index} className='hover:bg-gray-50'>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className='flex items-center gap-3'>
|
<div className='flex items-center gap-3'>
|
||||||
@ -185,14 +207,42 @@ const ProductDetail = () => {
|
|||||||
<TableCell className='text-right font-medium'>
|
<TableCell className='text-right font-medium'>
|
||||||
{formatCurrency(item.ingredient.cost * item.quantity)}
|
{formatCurrency(item.ingredient.cost * item.quantity)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className='text-right'>
|
||||||
|
<Button size='small' color='info' onClick={() => handleOpenEditProductRecipe(item)}>
|
||||||
|
<Tooltip title='Edit'>
|
||||||
|
<i className='tabler-pencil' />
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
color='error'
|
||||||
|
onClick={() => {
|
||||||
|
setProductRecipeId(item.id)
|
||||||
|
setOpenConfirm(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title='Delete'>
|
||||||
|
<i className='tabler-trash' />
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} className='text-center'>
|
||||||
|
<Typography variant='body2' color='textSecondary'>
|
||||||
|
No ingredients found for this variant
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
||||||
{/* Variant Summary */}
|
{/* Variant Summary */}
|
||||||
{productRecipe?.length && (
|
{productRecipe?.length ? (
|
||||||
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
@ -215,6 +265,13 @@ const ProductDetail = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
||||||
|
<Typography variant='body2' className='flex items-center gap-2'>
|
||||||
|
<i className='tabler-list-numbers text-blue-600' />
|
||||||
|
<span className='font-semibold'>Total Ingredients:</span>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -294,7 +351,7 @@ const ProductDetail = () => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{productRecipe?.length &&
|
{productRecipe?.length ? (
|
||||||
productRecipe
|
productRecipe
|
||||||
.filter((item: any) => item.variant_id === variantData.id)
|
.filter((item: any) => item.variant_id === variantData.id)
|
||||||
.map((item: any, index: number) => (
|
.map((item: any, index: number) => (
|
||||||
@ -327,14 +384,42 @@ const ProductDetail = () => {
|
|||||||
<TableCell className='text-right font-medium'>
|
<TableCell className='text-right font-medium'>
|
||||||
{formatCurrency(item.ingredient.cost * item.quantity)}
|
{formatCurrency(item.ingredient.cost * item.quantity)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className='text-right'>
|
||||||
|
<Button size='small' color='info' onClick={() => handleOpenEditProductRecipe(item)}>
|
||||||
|
<Tooltip title='Edit'>
|
||||||
|
<i className='tabler-pencil' />
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
color='error'
|
||||||
|
onClick={() => {
|
||||||
|
setProductRecipeId(item.id)
|
||||||
|
setOpenConfirm(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title='Delete'>
|
||||||
|
<i className='tabler-trash' />
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} className='text-center'>
|
||||||
|
<Typography variant='body2' color='textSecondary'>
|
||||||
|
No ingredients found for this variant
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
||||||
{/* Variant Summary */}
|
{/* Variant Summary */}
|
||||||
{productRecipe?.length && (
|
{productRecipe?.length ? (
|
||||||
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
@ -357,6 +442,13 @@ const ProductDetail = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box className='mt-4 p-4 bg-blue-50 rounded-lg'>
|
||||||
|
<Typography variant='body2' className='flex items-center gap-2'>
|
||||||
|
<i className='tabler-list-numbers text-blue-600' />
|
||||||
|
<span className='font-semibold'>Total Ingredients:</span>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -376,6 +468,15 @@ const ProductDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AddRecipeDrawer open={openProductRecipe} handleClose={() => setOpenProductRecipe(false)} product={product!} />
|
<AddRecipeDrawer open={openProductRecipe} handleClose={() => setOpenProductRecipe(false)} product={product!} />
|
||||||
|
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
open={openConfirm}
|
||||||
|
onClose={() => setOpenConfirm(false)}
|
||||||
|
onConfirm={handleDeleteRecipe}
|
||||||
|
isLoading={deleteProductRecipe.isPending}
|
||||||
|
title='Delete Product Ingredient'
|
||||||
|
message='Are you sure you want to delete this product ingredient? This action cannot be undone.'
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ import { getLocalizedUrl } from '@/utils/i18n'
|
|||||||
import tableStyles from '@core/styles/table.module.css'
|
import tableStyles from '@core/styles/table.module.css'
|
||||||
import { Box, CircularProgress } from '@mui/material'
|
import { Box, CircularProgress } from '@mui/material'
|
||||||
import Loading from '../../../../../components/layout/shared/Loading'
|
import Loading from '../../../../../components/layout/shared/Loading'
|
||||||
import { useProducts } from '../../../../../services/queries/products'
|
import { ProductsQueryParams, useProducts } from '../../../../../services/queries/products'
|
||||||
import { Product } from '../../../../../types/services/product'
|
import { Product } from '../../../../../types/services/product'
|
||||||
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
|
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
|
||||||
import { useProductsMutation } from '../../../../../services/mutations/products'
|
import { useProductsMutation } from '../../../../../services/mutations/products'
|
||||||
@ -115,6 +115,11 @@ const ProductListTable = () => {
|
|||||||
const [productId, setProductId] = useState('')
|
const [productId, setProductId] = useState('')
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
|
const [filter, setFilter] = useState<ProductsQueryParams>({
|
||||||
|
is_active: '',
|
||||||
|
category_id: ''
|
||||||
|
})
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const { lang: locale } = useParams()
|
const { lang: locale } = useParams()
|
||||||
|
|
||||||
@ -122,7 +127,8 @@ const ProductListTable = () => {
|
|||||||
const { data, isLoading, error, isFetching } = useProducts({
|
const { data, isLoading, error, isFetching } = useProducts({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
search
|
search: search,
|
||||||
|
is_active: filter.is_active
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutate: deleteProduct, isPending: isDeleting } = useProductsMutation().deleteProduct
|
const { mutate: deleteProduct, isPending: isDeleting } = useProductsMutation().deleteProduct
|
||||||
@ -276,7 +282,7 @@ const ProductListTable = () => {
|
|||||||
<>
|
<>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader title='Filters' />
|
<CardHeader title='Filters' />
|
||||||
<TableFilters setData={() => {}} productData={[]} />
|
<TableFilters filter={filter} setFilter={setFilter} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||||
<DebouncedInput
|
<DebouncedInput
|
||||||
|
|||||||
@ -1,47 +1,45 @@
|
|||||||
// React Imports
|
// React Imports
|
||||||
import { useState, useEffect } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import Grid from '@mui/material/Grid2'
|
|
||||||
import CardContent from '@mui/material/CardContent'
|
import CardContent from '@mui/material/CardContent'
|
||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
import MenuItem from '@mui/material/MenuItem'
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
|
|
||||||
// Type Imports
|
// Type Imports
|
||||||
import type { ProductType } from '@/types/apps/ecommerceTypes'
|
|
||||||
|
|
||||||
// Component Imports
|
// Component Imports
|
||||||
import CustomTextField from '@core/components/mui/TextField'
|
import CustomTextField from '@core/components/mui/TextField'
|
||||||
|
import { ProductsQueryParams } from '../../../../../services/queries/products'
|
||||||
import { Product } from '../../../../../types/services/product'
|
import { Product } from '../../../../../types/services/product'
|
||||||
|
import { useCategories } from '../../../../../services/queries/categories'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
|
import { Autocomplete, CircularProgress } from '@mui/material'
|
||||||
|
|
||||||
type ProductStockType = { [key: string]: boolean }
|
type ProductStockType = { [key: string]: boolean }
|
||||||
|
|
||||||
// Vars
|
const TableFilters = ({
|
||||||
const productStockObj: ProductStockType = {
|
filter,
|
||||||
'In Stock': true,
|
setFilter
|
||||||
'Out of Stock': false
|
}: {
|
||||||
}
|
filter: ProductsQueryParams
|
||||||
|
setFilter: (data: ProductsQueryParams) => void
|
||||||
const TableFilters = ({ setData, productData }: { setData: (data: Product[]) => void; productData?: Product[] }) => {
|
}) => {
|
||||||
// States
|
// States
|
||||||
const [category, setCategory] = useState<Product['category_id']>('')
|
|
||||||
const [stock, setStock] = useState('')
|
const [stock, setStock] = useState('')
|
||||||
const [status, setStatus] = useState<Product['name']>('')
|
|
||||||
|
|
||||||
useEffect(
|
const [categoryInput, setCategoryInput] = useState('')
|
||||||
() => {
|
const [categoryDebouncedInput] = useDebounce(categoryInput, 500)
|
||||||
const filteredData = productData?.filter(product => {
|
|
||||||
if (category && product.category_id !== category) return false
|
|
||||||
if (stock && product.name !== stock) return false
|
|
||||||
if (status && product.name !== status) return false
|
|
||||||
|
|
||||||
return true
|
const { data: categoriesApi, isLoading: categoriesLoading } = useCategories({
|
||||||
|
search: categoryDebouncedInput
|
||||||
})
|
})
|
||||||
|
|
||||||
setData(filteredData ?? [])
|
const categoryOptions = useMemo(() => categoriesApi?.categories || [], [categoriesApi])
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const handleStatusChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
[category, stock, status, productData]
|
setFilter({ ...filter, is_active: e.target.value === 'Active' ? true : e.target.value === 'Inactive' ? false : '' })
|
||||||
)
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -51,37 +49,47 @@ const TableFilters = ({ setData, productData }: { setData: (data: Product[]) =>
|
|||||||
select
|
select
|
||||||
fullWidth
|
fullWidth
|
||||||
id='select-status'
|
id='select-status'
|
||||||
value={status}
|
value={filter.is_active ? 'Active' : filter.is_active === false ? 'Inactive' : ''}
|
||||||
onChange={e => setStatus(e.target.value)}
|
onChange={handleStatusChange}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
select: { displayEmpty: true }
|
select: { displayEmpty: true }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value=''>Select Status</MenuItem>
|
<MenuItem value=''>Select Status</MenuItem>
|
||||||
<MenuItem value='Scheduled'>Scheduled</MenuItem>
|
<MenuItem value='Active'>Active</MenuItem>
|
||||||
<MenuItem value='Published'>Publish</MenuItem>
|
|
||||||
<MenuItem value='Inactive'>Inactive</MenuItem>
|
<MenuItem value='Inactive'>Inactive</MenuItem>
|
||||||
</CustomTextField>
|
</CustomTextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<CustomTextField
|
<Autocomplete
|
||||||
select
|
options={categoryOptions}
|
||||||
|
loading={categoriesLoading}
|
||||||
fullWidth
|
fullWidth
|
||||||
id='select-category'
|
getOptionLabel={option => option.name}
|
||||||
value={category}
|
value={categoryOptions.find(p => p.id === filter.category_id) || null}
|
||||||
onChange={e => setCategory(e.target.value)}
|
onInputChange={(event, newCategoryInput) => {
|
||||||
slotProps={{
|
setCategoryInput(newCategoryInput)
|
||||||
select: { displayEmpty: true }
|
|
||||||
}}
|
}}
|
||||||
>
|
onChange={(event, newValue) => {
|
||||||
<MenuItem value=''>Select Category</MenuItem>
|
setFilter({ ...filter, category_id: newValue?.id || '' })
|
||||||
<MenuItem value='Accessories'>Accessories</MenuItem>
|
}}
|
||||||
<MenuItem value='Home Decor'>Home Decor</MenuItem>
|
renderInput={params => (
|
||||||
<MenuItem value='Electronics'>Electronics</MenuItem>
|
<CustomTextField
|
||||||
<MenuItem value='Shoes'>Shoes</MenuItem>
|
{...params}
|
||||||
<MenuItem value='Office'>Office</MenuItem>
|
fullWidth
|
||||||
<MenuItem value='Games'>Games</MenuItem>
|
placeholder='Search Category'
|
||||||
</CustomTextField>
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
endAdornment: (
|
||||||
|
<>
|
||||||
|
{categoriesLoading && <CircularProgress size={18} />}
|
||||||
|
{params.InputProps.endAdornment}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
|
|||||||
@ -103,11 +103,13 @@ const StockListTable = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
|
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
// Fetch products with pagination and search
|
// Fetch products with pagination and search
|
||||||
const { data, isLoading, error, isFetching } = useInventories({
|
const { data, isLoading, error, isFetching } = useInventories({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: pageSize
|
limit: pageSize,
|
||||||
|
search
|
||||||
})
|
})
|
||||||
|
|
||||||
const inventories = data?.inventory ?? []
|
const inventories = data?.inventory ?? []
|
||||||
@ -150,7 +152,7 @@ const StockListTable = () => {
|
|||||||
},
|
},
|
||||||
columnHelper.accessor('product_id', {
|
columnHelper.accessor('product_id', {
|
||||||
header: 'Product',
|
header: 'Product',
|
||||||
cell: ({ row }) => <Typography>{row.original.product_id}</Typography>
|
cell: ({ row }) => <Typography>{row.original.product?.name}</Typography>
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('quantity', {
|
columnHelper.accessor('quantity', {
|
||||||
header: 'Quantity',
|
header: 'Quantity',
|
||||||
@ -171,32 +173,6 @@ const StockListTable = () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
// columnHelper.accessor('actions', {
|
|
||||||
// header: 'Actions',
|
|
||||||
// cell: ({ row }) => (
|
|
||||||
// <div className='flex items-center'>
|
|
||||||
// <OptionMenu
|
|
||||||
// iconButtonProps={{ size: 'medium' }}
|
|
||||||
// iconClassName='text-textSecondary'
|
|
||||||
// options={[
|
|
||||||
// { text: 'Download', icon: 'tabler-download' },
|
|
||||||
// {
|
|
||||||
// text: 'Delete',
|
|
||||||
// icon: 'tabler-trash',
|
|
||||||
// menuItemProps: {
|
|
||||||
// onClick: () => {
|
|
||||||
// setOpenConfirm(true)
|
|
||||||
// setProductId(row.original.id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// { text: 'Duplicate', icon: 'tabler-copy' }
|
|
||||||
// ]}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// ),
|
|
||||||
// enableSorting: false
|
|
||||||
// })
|
|
||||||
],
|
],
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[]
|
[]
|
||||||
@ -226,13 +202,13 @@ const StockListTable = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader title='Filters' />
|
{/* <CardHeader title='Filters' /> */}
|
||||||
{/* <TableFilters setData={() => {}} productData={[]} /> */}
|
{/* <TableFilters setData={() => {}} productData={[]} /> */}
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||||
<DebouncedInput
|
<DebouncedInput
|
||||||
value={'search'}
|
value={search}
|
||||||
onChange={value => console.log(value)}
|
onChange={value => setSearch(String(value))}
|
||||||
placeholder='Search Product'
|
placeholder='Search Product'
|
||||||
className='max-sm:is-full'
|
className='max-sm:is-full'
|
||||||
/>
|
/>
|
||||||
@ -261,7 +237,7 @@ const StockListTable = () => {
|
|||||||
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
|
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
|
||||||
startIcon={<i className='tabler-plus' />}
|
startIcon={<i className='tabler-plus' />}
|
||||||
>
|
>
|
||||||
Adjust Inventory
|
Adjust Stock
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -107,11 +107,13 @@ const StockListTable = () => {
|
|||||||
const [openConfirm, setOpenConfirm] = useState(false)
|
const [openConfirm, setOpenConfirm] = useState(false)
|
||||||
const [productId, setProductId] = useState('')
|
const [productId, setProductId] = useState('')
|
||||||
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
|
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
// Fetch products with pagination and search
|
// Fetch products with pagination and search
|
||||||
const { data, isLoading, error, isFetching } = useInventories({
|
const { data, isLoading, error, isFetching } = useInventories({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: pageSize
|
limit: pageSize,
|
||||||
|
search
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutate: deleteInventory, isPending: isDeleting } = useInventoriesMutation().deleteInventory
|
const { mutate: deleteInventory, isPending: isDeleting } = useInventoriesMutation().deleteInventory
|
||||||
@ -162,7 +164,7 @@ const StockListTable = () => {
|
|||||||
},
|
},
|
||||||
columnHelper.accessor('product_id', {
|
columnHelper.accessor('product_id', {
|
||||||
header: 'Product',
|
header: 'Product',
|
||||||
cell: ({ row }) => <Typography>{row.original.product_id}</Typography>
|
cell: ({ row }) => <Typography>{row.original.product?.name}</Typography>
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('is_low_stock', {
|
columnHelper.accessor('is_low_stock', {
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
@ -241,8 +243,8 @@ const StockListTable = () => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||||
<DebouncedInput
|
<DebouncedInput
|
||||||
value={'search'}
|
value={search}
|
||||||
onChange={value => console.log(value)}
|
onChange={value => setSearch(value as string)}
|
||||||
placeholder='Search Product'
|
placeholder='Search Product'
|
||||||
className='max-sm:is-full'
|
className='max-sm:is-full'
|
||||||
/>
|
/>
|
||||||
@ -271,7 +273,7 @@ const StockListTable = () => {
|
|||||||
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
|
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
|
||||||
startIcon={<i className='tabler-plus' />}
|
startIcon={<i className='tabler-plus' />}
|
||||||
>
|
>
|
||||||
Add Inventory
|
Add Stock
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user