feat: refacotor ui
This commit is contained in:
parent
687f59a9fa
commit
7beee4c3a1
BIN
public/images/logos/logo.png
Normal file
BIN
public/images/logos/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@ -3,34 +3,7 @@ import type { SVGAttributes } from 'react'
|
||||
|
||||
const Logo = (props: SVGAttributes<SVGElement>) => {
|
||||
return (
|
||||
<svg width='1.4583em' height='1em' viewBox='0 0 35 24' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M0.00188479 0V7.47707C0.00188479 7.47707 -0.145285 9.83135 2.161 11.8242L14.9358 23.9961L21.5792 23.9107L20.5136 10.7809L17.9947 7.82497L10.0778 0H0.00188479Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
opacity='0.06'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M8.39807 17.9307L13.6581 3.53127L18.059 7.91564L8.39807 17.9307Z'
|
||||
fill='#161616'
|
||||
/>
|
||||
<path
|
||||
opacity='0.06'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M8.81183 17.3645L15.2093 5.06165L18.0926 7.94695L8.81183 17.3645Z'
|
||||
fill='#161616'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M8.47955 17.8436L25.8069 0H34.9091V7.50963C34.9091 7.50963 34.7195 10.0128 33.4463 11.3517L21.5808 24H14.9387L8.47955 17.8436Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
<img src={'/images/logos/logo.png'} width={38} height={38} className='rounded' />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ const colorSchemes = (skin: Skin): Theme['colorSchemes'] => {
|
||||
light: {
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#7367F0',
|
||||
main: '#36175e',
|
||||
light: '#8F85F3',
|
||||
dark: '#675DD8',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',
|
||||
@ -161,7 +161,7 @@ const colorSchemes = (skin: Skin): Theme['colorSchemes'] => {
|
||||
dark: {
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#7367F0',
|
||||
main: '#36175e',
|
||||
light: '#8F85F3',
|
||||
dark: '#675DD8',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',
|
||||
|
||||
@ -3,12 +3,11 @@ 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 ProductInformation from '@views/apps/ecommerce/products/add/ProductInformation'
|
||||
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 eCommerceProductsAdd = () => {
|
||||
return (
|
||||
@ -27,9 +26,6 @@ const eCommerceProductsAdd = () => {
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProductVariants />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProductInventory />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Component Imports
|
||||
import ProductCard from '@views/apps/ecommerce/products/list/ProductCard'
|
||||
import ProductListTable from '@views/apps/ecommerce/products/list/ProductListTable'
|
||||
|
||||
// Data Imports
|
||||
@ -29,9 +28,6 @@ const eCommerceProductsList = async () => {
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProductCard />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProductListTable />
|
||||
</Grid>
|
||||
|
||||
@ -67,7 +67,7 @@ const Layout = async (props: ChildrenType & { params: Promise<{ lang: Locale }>
|
||||
<i className='tabler-arrow-up' />
|
||||
</Button>
|
||||
</ScrollToTop>
|
||||
<Customizer dir={direction} />
|
||||
{/* <Customizer dir={direction} /> */}
|
||||
</AuthGuard>
|
||||
</Providers>
|
||||
)
|
||||
|
||||
@ -30,9 +30,9 @@ import '@assets/iconify-icons/generated-icons.css'
|
||||
import { ReactQueryProvider } from '../../providers/ReactQueryProvider'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Vuexy - MUI Next.js Admin Dashboard Template',
|
||||
title: 'APSKEL',
|
||||
description:
|
||||
'Vuexy - MUI Next.js Admin Dashboard Template - is the most developer friendly & highly customizable Admin Dashboard Template based on MUI v5.'
|
||||
'Apsekel'
|
||||
}
|
||||
|
||||
const RootLayout = async (props: ChildrenType & { params: Promise<{ lang: Locale }> }) => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 192 KiB |
@ -4,9 +4,10 @@ import FooterContent from './FooterContent'
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<LayoutFooter>
|
||||
<FooterContent />
|
||||
</LayoutFooter>
|
||||
// <LayoutFooter>
|
||||
// <FooterContent />
|
||||
// </LayoutFooter>
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { CircularProgress } from '@mui/material'
|
||||
export default function Loading({ size = 60 }: { size?: number }) {
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center bg-white/70'>
|
||||
<CircularProgress size={size} color='primary' />
|
||||
<CircularProgress size={size} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,9 +4,10 @@ import FooterContent from './FooterContent'
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<LayoutFooter>
|
||||
<FooterContent />
|
||||
</LayoutFooter>
|
||||
// <LayoutFooter>
|
||||
// <FooterContent />
|
||||
// </LayoutFooter>
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ const primaryColorConfig: PrimaryColorConfig[] = [
|
||||
{
|
||||
name: 'primary-1',
|
||||
light: '#8F85F3',
|
||||
main: '#7367F0',
|
||||
main: '#36175e',
|
||||
dark: '#675DD8'
|
||||
},
|
||||
{
|
||||
|
||||
@ -54,7 +54,7 @@ export type Config = {
|
||||
}
|
||||
|
||||
const themeConfig: Config = {
|
||||
templateName: 'Vuexy',
|
||||
templateName: 'APSKEL',
|
||||
homePageUrl: '/dashboards/crm',
|
||||
settingsCookieName: 'vuexy-mui-next-demo-1',
|
||||
mode: 'system', // 'system', 'light', 'dark'
|
||||
|
||||
52
src/services/mutations/ingredients.ts
Normal file
52
src/services/mutations/ingredients.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { CustomerRequest } from '../../types/services/customer'
|
||||
import { api } from '../api'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
export const useIngredientsMutation = () => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const createCustomer = useMutation({
|
||||
mutationFn: async (newCustomer: CustomerRequest) => {
|
||||
const response = await api.post('/customers', newCustomer)
|
||||
return response.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Customer created successfully!')
|
||||
queryClient.invalidateQueries({ queryKey: ['customers'] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed')
|
||||
}
|
||||
})
|
||||
|
||||
const updateCustomer = useMutation({
|
||||
mutationFn: async ({ id, payload }: { id: string; payload: CustomerRequest }) => {
|
||||
const response = await api.put(`/customers/${id}`, payload)
|
||||
return response.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Customer updated successfully!')
|
||||
queryClient.invalidateQueries({ queryKey: ['customers'] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed')
|
||||
}
|
||||
})
|
||||
|
||||
const deleteCustomer = useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
const response = await api.delete(`/customers/${id}`)
|
||||
return response.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Customer deleted successfully!')
|
||||
queryClient.invalidateQueries({ queryKey: ['customers'] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed')
|
||||
}
|
||||
})
|
||||
|
||||
return { createCustomer, updateCustomer, deleteCustomer }
|
||||
}
|
||||
36
src/services/queries/ingredients.ts
Normal file
36
src/services/queries/ingredients.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Ingredients } from '../../types/services/ingredient'
|
||||
import { api } from '../api'
|
||||
|
||||
interface IngredientsQueryParams {
|
||||
page?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
}
|
||||
|
||||
export function useIngredients(params: IngredientsQueryParams = {}) {
|
||||
const { page = 1, limit = 10, search = '', ...filters } = params
|
||||
|
||||
return useQuery<Ingredients>({
|
||||
queryKey: ['ingredients', { page, limit, search, ...filters }],
|
||||
queryFn: async () => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
queryParams.append('page', page.toString())
|
||||
queryParams.append('limit', limit.toString())
|
||||
|
||||
if (search) {
|
||||
queryParams.append('search', search)
|
||||
}
|
||||
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
queryParams.append(key, value.toString())
|
||||
}
|
||||
})
|
||||
|
||||
const res = await api.get(`/ingredients?${queryParams.toString()}`)
|
||||
return res.data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
38
src/types/services/ingredient.ts
Normal file
38
src/types/services/ingredient.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export type Unit = {
|
||||
id: string
|
||||
organization_id: string
|
||||
outlet_id: string
|
||||
name: string
|
||||
abbreviation: string
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type IngredientItem = {
|
||||
id: string
|
||||
organization_id: string
|
||||
outlet_id: string
|
||||
name: string
|
||||
unit_id: string
|
||||
cost: number
|
||||
stock: number
|
||||
is_semi_finished: boolean
|
||||
is_active: boolean
|
||||
metadata: Record<string, unknown> // bisa diganti jika metadata memiliki struktur spesifik
|
||||
created_at: string
|
||||
updated_at: string
|
||||
unit: Unit
|
||||
}
|
||||
|
||||
export type Pagination = {
|
||||
page: number
|
||||
limit: number
|
||||
total_count: number
|
||||
total_pages: number
|
||||
}
|
||||
|
||||
export type Ingredients = {
|
||||
data: IngredientItem[]
|
||||
pagination: Pagination
|
||||
}
|
||||
@ -181,10 +181,6 @@ const CustomerListTable = () => {
|
||||
header: 'Phone',
|
||||
cell: ({ row }) => <Typography>{row.original.phone || '-'}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('address', {
|
||||
header: 'Address',
|
||||
cell: ({ row }) => <Typography>{row.original.address || '-'}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('is_active', {
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
@ -196,6 +192,10 @@ const CustomerListTable = () => {
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('address', {
|
||||
header: 'Address',
|
||||
cell: ({ row }) => <Typography>{row.original.address || '-'}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('actions', {
|
||||
header: 'Actions',
|
||||
cell: ({ row }) => (
|
||||
|
||||
@ -31,9 +31,9 @@ import { Box, Chip, CircularProgress } from '@mui/material'
|
||||
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
|
||||
import Loading from '../../../../../components/layout/shared/Loading'
|
||||
import { useUnitsMutation } from '../../../../../services/mutations/units'
|
||||
import { useUnits } from '../../../../../services/queries/units'
|
||||
import { Unit } from '../../../../../types/services/unit'
|
||||
import { formatDate } from '../../../../../utils/transform'
|
||||
import { useIngredients } from '../../../../../services/queries/ingredients'
|
||||
import { IngredientItem } from '../../../../../types/services/ingredient'
|
||||
import { formatCurrency } from '../../../../../utils/transform'
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
@ -44,7 +44,7 @@ declare module '@tanstack/table-core' {
|
||||
}
|
||||
}
|
||||
|
||||
type UnitWithActionsType = Unit & {
|
||||
type IngredientWithActionsType = IngredientItem & {
|
||||
actions?: string
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ const DebouncedInput = ({
|
||||
}
|
||||
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<UnitWithActionsType>()
|
||||
const columnHelper = createColumnHelper<IngredientWithActionsType>()
|
||||
|
||||
const ProductIngredientTable = () => {
|
||||
// States
|
||||
@ -102,17 +102,18 @@ const ProductIngredientTable = () => {
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
const [unitId, setUnitId] = useState('')
|
||||
const [openConfirm, setOpenConfirm] = useState(false)
|
||||
const [currentUnit, setCurrentUnit] = useState<Unit>()
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
// Fetch products with pagination and search
|
||||
const { data, isLoading, error, isFetching } = useUnits({
|
||||
const { data, isLoading, error, isFetching } = useIngredients({
|
||||
page: currentPage,
|
||||
limit: pageSize
|
||||
limit: pageSize,
|
||||
search
|
||||
})
|
||||
|
||||
const { mutate: deleteUnit, isPending: isDeleting } = useUnitsMutation().deleteUnit
|
||||
|
||||
const units = data?.data ?? []
|
||||
const ingredients = data?.data ?? []
|
||||
const totalCount = data?.pagination.total_count ?? 0
|
||||
|
||||
const handlePageChange = useCallback((event: unknown, newPage: number) => {
|
||||
@ -132,7 +133,7 @@ const ProductIngredientTable = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const columns = useMemo<ColumnDef<UnitWithActionsType, any>[]>(
|
||||
const columns = useMemo<ColumnDef<IngredientWithActionsType, any>[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'select',
|
||||
@ -169,9 +170,17 @@ const ProductIngredientTable = () => {
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('abbreviation', {
|
||||
header: 'Abbreviation',
|
||||
cell: ({ row }) => <Typography>{row.original.abbreviation || '-'}</Typography>
|
||||
columnHelper.accessor('cost', {
|
||||
header: 'Cost',
|
||||
cell: ({ row }) => <Typography>{formatCurrency(row.original.cost) || '-'}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('stock', {
|
||||
header: 'Stock',
|
||||
cell: ({ row }) => <Typography>{row.original.stock}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('unit', {
|
||||
header: 'Unit',
|
||||
cell: ({ row }) => <Typography>{row.original.unit.name}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('is_active', {
|
||||
header: 'Status',
|
||||
@ -184,9 +193,16 @@ const ProductIngredientTable = () => {
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('created_at', {
|
||||
header: 'Created Date',
|
||||
cell: ({ row }) => <Typography>{formatDate(row.original.created_at)}</Typography>
|
||||
columnHelper.accessor('is_semi_finished', {
|
||||
header: 'Semi Finished',
|
||||
cell: ({ row }) => (
|
||||
<Chip
|
||||
label={row.original.is_semi_finished ? 'Active' : 'Inactive'}
|
||||
variant='tonal'
|
||||
color={row.original.is_semi_finished ? 'success' : 'error'}
|
||||
size='small'
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('actions', {
|
||||
header: 'Actions',
|
||||
@ -194,7 +210,6 @@ const ProductIngredientTable = () => {
|
||||
<div className='flex items-center'>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setCurrentUnit(row.original)
|
||||
setEditUnitOpen(!editUnitOpen)
|
||||
}}
|
||||
>
|
||||
@ -228,7 +243,7 @@ const ProductIngredientTable = () => {
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: units as Unit[],
|
||||
data: ingredients as IngredientItem[],
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter
|
||||
@ -253,16 +268,16 @@ const ProductIngredientTable = () => {
|
||||
<Card>
|
||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||
<DebouncedInput
|
||||
value={'search'}
|
||||
onChange={value => console.log(value)}
|
||||
value={search}
|
||||
onChange={value => setSearch(value as string)}
|
||||
placeholder='Search Product'
|
||||
className='max-sm:is-full'
|
||||
/>
|
||||
<div className='flex max-sm:flex-col items-start sm:items-center gap-4 max-sm:is-full'>
|
||||
<CustomTextField
|
||||
select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={e => table.setPageSize(Number(e.target.value))}
|
||||
value={pageSize}
|
||||
onChange={handlePageSizeChange}
|
||||
className='flex-auto max-sm:is-full sm:is-[70px]'
|
||||
>
|
||||
<MenuItem value='10'>10</MenuItem>
|
||||
|
||||
@ -8,7 +8,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
// MUI Imports
|
||||
import Button from '@mui/material/Button'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import Checkbox from '@mui/material/Checkbox'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Divider from '@mui/material/Divider'
|
||||
@ -36,12 +35,12 @@ import OptionMenu from '@core/components/option-menu'
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
import { Box, CircularProgress } from '@mui/material'
|
||||
import AddStockDrawer from './AddStockDrawer'
|
||||
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
|
||||
import Loading from '../../../../../components/layout/shared/Loading'
|
||||
import { useInventoriesMutation } from '../../../../../services/mutations/inventories'
|
||||
import { useInventories } from '../../../../../services/queries/inventories'
|
||||
import { Inventory } from '../../../../../types/services/inventory'
|
||||
import AddStockDrawer from './AddStockDrawer'
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
@ -165,14 +164,6 @@ const StockListTable = () => {
|
||||
header: 'Product',
|
||||
cell: ({ row }) => <Typography>{row.original.product_id}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('quantity', {
|
||||
header: 'Quantity',
|
||||
cell: ({ row }) => <Typography>{row.original.quantity}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('reorder_level', {
|
||||
header: 'Reorder Level',
|
||||
cell: ({ row }) => <Typography>{row.original.reorder_level}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('is_low_stock', {
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
@ -184,6 +175,14 @@ const StockListTable = () => {
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('quantity', {
|
||||
header: 'Quantity',
|
||||
cell: ({ row }) => <Typography>{row.original.quantity}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('reorder_level', {
|
||||
header: 'Reorder Level',
|
||||
cell: ({ row }) => <Typography>{row.original.reorder_level}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('actions', {
|
||||
header: 'Actions',
|
||||
cell: ({ row }) => (
|
||||
@ -239,8 +238,6 @@ const StockListTable = () => {
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader title='Filters' />
|
||||
{/* <TableFilters setData={() => {}} productData={[]} /> */}
|
||||
<Divider />
|
||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||
<DebouncedInput
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user