feat: refacotor ui

This commit is contained in:
ferdiansyah783 2025-08-08 01:49:00 +07:00
parent 687f59a9fa
commit 7beee4c3a1
19 changed files with 196 additions and 91 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -3,34 +3,7 @@ import type { SVGAttributes } from 'react'
const Logo = (props: SVGAttributes<SVGElement>) => { const Logo = (props: SVGAttributes<SVGElement>) => {
return ( return (
<svg width='1.4583em' height='1em' viewBox='0 0 35 24' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}> <img src={'/images/logos/logo.png'} width={38} height={38} className='rounded' />
<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>
) )
} }

View File

@ -9,7 +9,7 @@ const colorSchemes = (skin: Skin): Theme['colorSchemes'] => {
light: { light: {
palette: { palette: {
primary: { primary: {
main: '#7367F0', main: '#36175e',
light: '#8F85F3', light: '#8F85F3',
dark: '#675DD8', dark: '#675DD8',
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)', lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',
@ -161,7 +161,7 @@ const colorSchemes = (skin: Skin): Theme['colorSchemes'] => {
dark: { dark: {
palette: { palette: {
primary: { primary: {
main: '#7367F0', main: '#36175e',
light: '#8F85F3', light: '#8F85F3',
dark: '#675DD8', dark: '#675DD8',
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)', lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',

View File

@ -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 eCommerceProductsAdd = () => { const eCommerceProductsAdd = () => {
return ( return (
@ -27,9 +26,6 @@ const eCommerceProductsAdd = () => {
<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 }}>

View File

@ -2,7 +2,6 @@
import Grid from '@mui/material/Grid2' import Grid from '@mui/material/Grid2'
// Component Imports // Component Imports
import ProductCard from '@views/apps/ecommerce/products/list/ProductCard'
import ProductListTable from '@views/apps/ecommerce/products/list/ProductListTable' import ProductListTable from '@views/apps/ecommerce/products/list/ProductListTable'
// Data Imports // Data Imports
@ -29,9 +28,6 @@ const eCommerceProductsList = async () => {
return ( return (
<Grid container spacing={6}> <Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<ProductCard />
</Grid>
<Grid size={{ xs: 12 }}> <Grid size={{ xs: 12 }}>
<ProductListTable /> <ProductListTable />
</Grid> </Grid>

View File

@ -67,7 +67,7 @@ const Layout = async (props: ChildrenType & { params: Promise<{ lang: Locale }>
<i className='tabler-arrow-up' /> <i className='tabler-arrow-up' />
</Button> </Button>
</ScrollToTop> </ScrollToTop>
<Customizer dir={direction} /> {/* <Customizer dir={direction} /> */}
</AuthGuard> </AuthGuard>
</Providers> </Providers>
) )

View File

@ -30,9 +30,9 @@ import '@assets/iconify-icons/generated-icons.css'
import { ReactQueryProvider } from '../../providers/ReactQueryProvider' import { ReactQueryProvider } from '../../providers/ReactQueryProvider'
export const metadata = { export const metadata = {
title: 'Vuexy - MUI Next.js Admin Dashboard Template', title: 'APSKEL',
description: 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 }> }) => { 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

View File

@ -4,9 +4,10 @@ import FooterContent from './FooterContent'
const Footer = () => { const Footer = () => {
return ( return (
<LayoutFooter> // <LayoutFooter>
<FooterContent /> // <FooterContent />
</LayoutFooter> // </LayoutFooter>
<></>
) )
} }

View File

@ -3,7 +3,7 @@ import { CircularProgress } from '@mui/material'
export default function Loading({ size = 60 }: { size?: number }) { export default function Loading({ size = 60 }: { size?: number }) {
return ( return (
<div className='fixed inset-0 z-50 flex items-center justify-center bg-white/70'> <div className='fixed inset-0 z-50 flex items-center justify-center bg-white/70'>
<CircularProgress size={size} color='primary' /> <CircularProgress size={size} />
</div> </div>
) )
} }

View File

@ -4,9 +4,10 @@ import FooterContent from './FooterContent'
const Footer = () => { const Footer = () => {
return ( return (
<LayoutFooter> // <LayoutFooter>
<FooterContent /> // <FooterContent />
</LayoutFooter> // </LayoutFooter>
<></>
) )
} }

View File

@ -10,7 +10,7 @@ const primaryColorConfig: PrimaryColorConfig[] = [
{ {
name: 'primary-1', name: 'primary-1',
light: '#8F85F3', light: '#8F85F3',
main: '#7367F0', main: '#36175e',
dark: '#675DD8' dark: '#675DD8'
}, },
{ {

View File

@ -54,7 +54,7 @@ export type Config = {
} }
const themeConfig: Config = { const themeConfig: Config = {
templateName: 'Vuexy', templateName: 'APSKEL',
homePageUrl: '/dashboards/crm', homePageUrl: '/dashboards/crm',
settingsCookieName: 'vuexy-mui-next-demo-1', settingsCookieName: 'vuexy-mui-next-demo-1',
mode: 'system', // 'system', 'light', 'dark' mode: 'system', // 'system', 'light', 'dark'

View 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 }
}

View 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
}
})
}

View 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
}

View File

@ -181,10 +181,6 @@ const CustomerListTable = () => {
header: 'Phone', header: 'Phone',
cell: ({ row }) => <Typography>{row.original.phone || '-'}</Typography> cell: ({ row }) => <Typography>{row.original.phone || '-'}</Typography>
}), }),
columnHelper.accessor('address', {
header: 'Address',
cell: ({ row }) => <Typography>{row.original.address || '-'}</Typography>
}),
columnHelper.accessor('is_active', { columnHelper.accessor('is_active', {
header: 'Status', header: 'Status',
cell: ({ row }) => ( cell: ({ row }) => (
@ -196,6 +192,10 @@ const CustomerListTable = () => {
/> />
) )
}), }),
columnHelper.accessor('address', {
header: 'Address',
cell: ({ row }) => <Typography>{row.original.address || '-'}</Typography>
}),
columnHelper.accessor('actions', { columnHelper.accessor('actions', {
header: 'Actions', header: 'Actions',
cell: ({ row }) => ( cell: ({ row }) => (

View File

@ -31,9 +31,9 @@ import { Box, Chip, CircularProgress } from '@mui/material'
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
import Loading from '../../../../../components/layout/shared/Loading' import Loading from '../../../../../components/layout/shared/Loading'
import { useUnitsMutation } from '../../../../../services/mutations/units' import { useUnitsMutation } from '../../../../../services/mutations/units'
import { useUnits } from '../../../../../services/queries/units' import { useIngredients } from '../../../../../services/queries/ingredients'
import { Unit } from '../../../../../types/services/unit' import { IngredientItem } from '../../../../../types/services/ingredient'
import { formatDate } from '../../../../../utils/transform' import { formatCurrency } from '../../../../../utils/transform'
declare module '@tanstack/table-core' { declare module '@tanstack/table-core' {
interface FilterFns { interface FilterFns {
@ -44,7 +44,7 @@ declare module '@tanstack/table-core' {
} }
} }
type UnitWithActionsType = Unit & { type IngredientWithActionsType = IngredientItem & {
actions?: string actions?: string
} }
@ -91,7 +91,7 @@ const DebouncedInput = ({
} }
// Column Definitions // Column Definitions
const columnHelper = createColumnHelper<UnitWithActionsType>() const columnHelper = createColumnHelper<IngredientWithActionsType>()
const ProductIngredientTable = () => { const ProductIngredientTable = () => {
// States // States
@ -102,17 +102,18 @@ const ProductIngredientTable = () => {
const [pageSize, setPageSize] = useState(10) const [pageSize, setPageSize] = useState(10)
const [unitId, setUnitId] = useState('') const [unitId, setUnitId] = useState('')
const [openConfirm, setOpenConfirm] = useState(false) const [openConfirm, setOpenConfirm] = useState(false)
const [currentUnit, setCurrentUnit] = useState<Unit>() const [search, setSearch] = useState('')
// Fetch products with pagination and search // Fetch products with pagination and search
const { data, isLoading, error, isFetching } = useUnits({ const { data, isLoading, error, isFetching } = useIngredients({
page: currentPage, page: currentPage,
limit: pageSize limit: pageSize,
search
}) })
const { mutate: deleteUnit, isPending: isDeleting } = useUnitsMutation().deleteUnit const { mutate: deleteUnit, isPending: isDeleting } = useUnitsMutation().deleteUnit
const units = data?.data ?? [] const ingredients = data?.data ?? []
const totalCount = data?.pagination.total_count ?? 0 const totalCount = data?.pagination.total_count ?? 0
const handlePageChange = useCallback((event: unknown, newPage: number) => { 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', id: 'select',
@ -169,9 +170,17 @@ const ProductIngredientTable = () => {
</div> </div>
) )
}), }),
columnHelper.accessor('abbreviation', { columnHelper.accessor('cost', {
header: 'Abbreviation', header: 'Cost',
cell: ({ row }) => <Typography>{row.original.abbreviation || '-'}</Typography> 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', { columnHelper.accessor('is_active', {
header: 'Status', header: 'Status',
@ -184,9 +193,16 @@ const ProductIngredientTable = () => {
/> />
) )
}), }),
columnHelper.accessor('created_at', { columnHelper.accessor('is_semi_finished', {
header: 'Created Date', header: 'Semi Finished',
cell: ({ row }) => <Typography>{formatDate(row.original.created_at)}</Typography> 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', { columnHelper.accessor('actions', {
header: 'Actions', header: 'Actions',
@ -194,7 +210,6 @@ const ProductIngredientTable = () => {
<div className='flex items-center'> <div className='flex items-center'>
<IconButton <IconButton
onClick={() => { onClick={() => {
setCurrentUnit(row.original)
setEditUnitOpen(!editUnitOpen) setEditUnitOpen(!editUnitOpen)
}} }}
> >
@ -228,7 +243,7 @@ const ProductIngredientTable = () => {
) )
const table = useReactTable({ const table = useReactTable({
data: units as Unit[], data: ingredients as IngredientItem[],
columns, columns,
filterFns: { filterFns: {
fuzzy: fuzzyFilter fuzzy: fuzzyFilter
@ -253,16 +268,16 @@ const ProductIngredientTable = () => {
<Card> <Card>
<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'
/> />
<div className='flex max-sm:flex-col items-start sm:items-center gap-4 max-sm:is-full'> <div className='flex max-sm:flex-col items-start sm:items-center gap-4 max-sm:is-full'>
<CustomTextField <CustomTextField
select select
value={table.getState().pagination.pageSize} value={pageSize}
onChange={e => table.setPageSize(Number(e.target.value))} onChange={handlePageSizeChange}
className='flex-auto max-sm:is-full sm:is-[70px]' className='flex-auto max-sm:is-full sm:is-[70px]'
> >
<MenuItem value='10'>10</MenuItem> <MenuItem value='10'>10</MenuItem>

View File

@ -8,7 +8,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
// MUI Imports // MUI Imports
import Button from '@mui/material/Button' import Button from '@mui/material/Button'
import Card from '@mui/material/Card' import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import Checkbox from '@mui/material/Checkbox' import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip' import Chip from '@mui/material/Chip'
import Divider from '@mui/material/Divider' import Divider from '@mui/material/Divider'
@ -36,12 +35,12 @@ import OptionMenu from '@core/components/option-menu'
// Style Imports // Style Imports
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 AddStockDrawer from './AddStockDrawer'
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
import Loading from '../../../../../components/layout/shared/Loading' import Loading from '../../../../../components/layout/shared/Loading'
import { useInventoriesMutation } from '../../../../../services/mutations/inventories' import { useInventoriesMutation } from '../../../../../services/mutations/inventories'
import { useInventories } from '../../../../../services/queries/inventories' import { useInventories } from '../../../../../services/queries/inventories'
import { Inventory } from '../../../../../types/services/inventory' import { Inventory } from '../../../../../types/services/inventory'
import AddStockDrawer from './AddStockDrawer'
declare module '@tanstack/table-core' { declare module '@tanstack/table-core' {
interface FilterFns { interface FilterFns {
@ -165,14 +164,6 @@ const StockListTable = () => {
header: 'Product', header: 'Product',
cell: ({ row }) => <Typography>{row.original.product_id}</Typography> 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', { columnHelper.accessor('is_low_stock', {
header: 'Status', header: 'Status',
cell: ({ row }) => ( 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', { columnHelper.accessor('actions', {
header: 'Actions', header: 'Actions',
cell: ({ row }) => ( cell: ({ row }) => (
@ -239,8 +238,6 @@ const StockListTable = () => {
return ( return (
<> <>
<Card> <Card>
<CardHeader title='Filters' />
{/* <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