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>) => {
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' />
)
}

View File

@ -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)',

View File

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

View File

@ -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>

View File

@ -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>
)

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'

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',
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 }) => (

View File

@ -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>

View File

@ -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