fix: user management & profit chart

This commit is contained in:
ferdiansyah783
2025-08-10 15:47:04 +07:00
parent de93de2e6d
commit 48570c018f
23 changed files with 989 additions and 599 deletions
@@ -2,64 +2,79 @@
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import type { TypographyProps } from '@mui/material/Typography'
// Type Imports
import type { ThemeColor } from '@core/types'
import classnames from 'classnames'
// Component Imports
import AddAddress from '@components/dialogs/add-edit-address'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
import CustomAvatar from '../../../../../@core/components/mui/Avatar'
import { Order } from '../../../../../types/services/order'
import { formatCurrency } from '../../../../../utils/transform'
// Vars
const data = {
firstName: 'Roker',
lastName: 'Terrace',
email: 'sbaser0@boston.com',
country: 'UK',
address1: 'Latheronwheel',
address2: 'KW5 8NW, London',
landmark: 'Near Water Plant',
city: 'London',
state: 'Capholim',
zipCode: '403114',
taxId: 'TAX-875623',
vatNumber: 'SDF754K77',
contact: '+1 (609) 972-22-22'
type PayementStatusType = {
text: string
color: ThemeColor
colorClassName: string
}
const BillingAddress = () => {
// Vars
const typographyProps = (children: string, color: ThemeColor, className: string): TypographyProps => ({
children,
color,
className
})
const statusChipColor: { [key: string]: PayementStatusType } = {
pending: {
color: 'warning',
text: 'Pending',
colorClassName: 'text-warning'
},
completed: {
color: 'success',
text: 'Paid',
colorClassName: 'text-success'
},
cancelled: {
color: 'error',
text: 'Cancelled',
colorClassName: 'text-error'
}
}
const BillingAddress = ({ data }: { data: Order }) => {
return (
<Card>
<CardContent className='flex flex-col gap-6'>
<div className='flex flex-col gap-2'>
<div className='flex justify-between items-center'>
<Typography variant='h5'>Billing Address</Typography>
<OpenDialogOnElementClick
element={Typography}
elementProps={typographyProps('Edit', 'primary', 'cursor-pointer font-medium')}
dialog={AddAddress}
dialogProps={{ type: 'Add address for billing address', data }}
/>
</div>
<div className='flex flex-col'>
<Typography>45 Roker Terrace</Typography>
<Typography>Latheronwheel</Typography>
<Typography>KW5 8NW, London</Typography>
<Typography>UK</Typography>
<Typography variant='h5'>
Payment Details ({data.payments.length} {data.payments.length === 1 ? 'Payment' : 'Payments'})
</Typography>
</div>
</div>
<div className='flex flex-col items-start gap-1'>
<Typography variant='h5'>Mastercard</Typography>
<Typography>Card Number: ******4291</Typography>
</div>
{data.payments.map((payment, index) => (
<div key={index}>
<div className='flex items-center gap-3'>
<CustomAvatar skin='light' color='secondary' size={40}>
<i className='tabler-credit-card' />
</CustomAvatar>
<div className='flex flex-col'>
<div className='font-medium flex items-center gap-3'>
<Typography color='text.primary'>{payment.payment_method_name}</Typography>
<div className='flex items-center gap-1'>
<i
className={classnames(
'tabler-circle-filled bs-1.5 is-1.5',
statusChipColor[payment.status].colorClassName
)}
/>
<Typography color={`${statusChipColor[payment.status].color}.main`} className='font-medium text-xs'>
{statusChipColor[payment.status].text}
</Typography>
</div>
</div>
<Typography color='text.secondary' className='font-medium'>
{formatCurrency(payment.amount)}
</Typography>
</div>
</div>
</div>
))}
</CardContent>
</Card>
)
@@ -1,21 +1,18 @@
// MUI Imports
import Avatar from '@mui/material/Avatar'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Avatar from '@mui/material/Avatar'
import Typography from '@mui/material/Typography'
import type { TypographyProps } from '@mui/material/Typography'
// Type Imports
import type { ThemeColor } from '@core/types'
import type { OrderType } from '@/types/apps/ecommerceTypes'
// Component Imports
import CustomAvatar from '@core/components/mui/Avatar'
import EditUserInfo from '@components/dialogs/edit-user-info'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
// Util Imports
import { getInitials } from '@/utils/getInitials'
import { Order } from '../../../../../types/services/order'
const getAvatar = (params: Pick<OrderType, 'avatar' | 'customer'>) => {
const { avatar, customer } = params
@@ -42,25 +39,17 @@ const userData = {
useAsBillingAddress: true
}
const CustomerDetails = ({ orderData }: { orderData?: OrderType }) => {
// Vars
const typographyProps = (children: string, color: ThemeColor, className: string): TypographyProps => ({
children,
color,
className
})
const CustomerDetails = ({ orderData }: { orderData?: Order }) => {
return (
<Card>
<CardContent className='flex flex-col gap-6'>
<Typography variant='h5'>Customer details</Typography>
<div className='flex items-center gap-3'>
{getAvatar({ avatar: orderData?.avatar ?? '', customer: orderData?.customer ?? '' })}
{getAvatar({ avatar: '', customer: orderData?.metadata.customer_name ?? '' })}
<div className='flex flex-col'>
<Typography color='text.primary' className='font-medium'>
{orderData?.customer}
{orderData?.metadata.customer_name}
</Typography>
<Typography>Customer ID: #47389</Typography>
</div>
</div>
<div className='flex items-center gap-3'>
@@ -68,24 +57,9 @@ const CustomerDetails = ({ orderData }: { orderData?: OrderType }) => {
<i className='tabler-shopping-cart' />
</CustomAvatar>
<Typography color='text.primary' className='font-medium'>
12 Orders
{orderData?.order_items.length} {orderData?.order_items.length === 1 ? 'Order' : 'Orders'}
</Typography>
</div>
<div className='flex flex-col gap-1'>
<div className='flex justify-between items-center'>
<Typography color='text.primary' className='font-medium'>
Contact info
</Typography>
<OpenDialogOnElementClick
element={Typography}
elementProps={typographyProps('Edit', 'primary', 'cursor-pointer font-medium')}
dialog={EditUserInfo}
dialogProps={{ data: userData }}
/>
</div>
<Typography>Email: {orderData?.email}</Typography>
<Typography>Mobile: +1 (609) 972-22-22</Typography>
</div>
</CardContent>
</Card>
)
@@ -1,16 +1,17 @@
// MUI Imports
import type { ButtonProps } from '@mui/material/Button'
import Button from '@mui/material/Button'
import Chip from '@mui/material/Chip'
import Typography from '@mui/material/Typography'
import type { ButtonProps } from '@mui/material/Button'
// Type Imports
import type { ThemeColor } from '@core/types'
import type { OrderType } from '@/types/apps/ecommerceTypes'
// Component Imports
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
import { Order } from '../../../../../types/services/order'
import { formatDate } from '../../../../../utils/transform'
type PayementStatusType = {
text: string
@@ -22,32 +23,32 @@ type StatusChipColorType = {
}
export const paymentStatus: { [key: number]: PayementStatusType } = {
1: { text: 'Paid', color: 'success' },
2: { text: 'Pending', color: 'warning' },
3: { text: 'Cancelled', color: 'secondary' },
4: { text: 'Failed', color: 'error' }
1: { text: 'paid', color: 'success' },
2: { text: 'pending', color: 'warning' },
3: { text: 'cancelled', color: 'secondary' },
4: { text: 'failed', color: 'error' }
}
export const statusChipColor: { [key: string]: StatusChipColorType } = {
Delivered: { color: 'success' },
'Out for Delivery': { color: 'primary' },
'Ready to Pickup': { color: 'info' },
Dispatched: { color: 'warning' }
'pending': { color: 'warning' },
'completed': { color: 'success' },
'partial': { color: 'secondary' },
'cancelled': { color: 'error' }
}
const OrderDetailHeader = ({ orderData, order }: { orderData?: OrderType; order: string }) => {
const OrderDetailHeader = ({ orderData }: { orderData?: Order }) => {
// Vars
const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({
children,
color,
variant
})
})
return (
<div className='flex flex-wrap justify-between sm:items-center max-sm:flex-col gap-y-4'>
<div className='flex flex-col items-start gap-1'>
<div className='flex items-center gap-2'>
<Typography variant='h5'>{`Order #${order}`}</Typography>
<Typography variant='h5'>{`Order #${orderData?.order_number}`}</Typography>
<Chip
variant='tonal'
label={orderData?.status}
@@ -56,12 +57,12 @@ const OrderDetailHeader = ({ orderData, order }: { orderData?: OrderType; order:
/>
<Chip
variant='tonal'
label={paymentStatus[orderData?.payment ?? 0].text}
color={paymentStatus[orderData?.payment ?? 0].color}
label={orderData?.payment_status || ''}
color={statusChipColor[orderData?.payment_status || ''].color}
size='small'
/>
</div>
<Typography>{`${new Date(orderData?.date ?? '').toDateString()}, ${orderData?.time} (ET)`}</Typography>
<Typography>{`${formatDate(orderData!.created_at)}`}</Typography>
</div>
<OpenDialogOnElementClick
element={Button}
@@ -1,37 +1,39 @@
'use client'
// React Imports
import { useState, useMemo } from 'react'
import { useMemo, useState } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import CardHeader from '@mui/material/CardHeader'
import Checkbox from '@mui/material/Checkbox'
import Typography from '@mui/material/Typography'
// Third-party Imports
import classnames from 'classnames'
import { rankItem } from '@tanstack/match-sorter-utils'
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
getFilteredRowModel,
getFacetedMinMaxValues,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel
getSortedRowModel,
useReactTable
} from '@tanstack/react-table'
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
import classnames from 'classnames'
// Component Imports
import Link from '@components/Link'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
import { ThemeColor } from '../../../../../@core/types'
import { Order, OrderItem } from '../../../../../types/services/order'
import { formatCurrency } from '../../../../../utils/transform'
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
@@ -47,57 +49,44 @@ const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
}
type dataType = {
productName: string
productImage: string
brand: string
price: number
product_name: string
status: string
unit_price: number
quantity: number
total: number
total_price: number
}
const orderData: dataType[] = [
{
productName: 'OnePlus 7 Pro',
productImage: '/images/apps/ecommerce/product-21.png',
brand: 'OnePluse',
price: 799,
quantity: 1,
total: 799
type PayementStatusType = {
text: string
color: ThemeColor
colorClassName: string
}
const statusChipColor: { [key: string]: PayementStatusType } = {
pending: {
color: 'warning',
text: 'Pending',
colorClassName: 'text-warning'
},
{
productName: 'Magic Mouse',
productImage: '/images/apps/ecommerce/product-22.png',
brand: 'Google',
price: 89,
quantity: 1,
total: 89
paid: {
color: 'success',
text: 'Paid',
colorClassName: 'text-success'
},
{
productName: 'Wooden Chair',
productImage: '/images/apps/ecommerce/product-23.png',
brand: 'Insofar',
price: 289,
quantity: 2,
total: 578
},
{
productName: 'Air Jorden',
productImage: '/images/apps/ecommerce/product-24.png',
brand: 'Nike',
price: 299,
quantity: 2,
total: 598
cancelled: {
color: 'error',
text: 'Cancelled',
colorClassName: 'text-error'
}
]
}
// Column Definitions
const columnHelper = createColumnHelper<dataType>()
const OrderTable = () => {
const OrderTable = ({ data }: { data: OrderItem[] }) => {
// States
const [rowSelection, setRowSelection] = useState({})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [data, setData] = useState(...[orderData])
const [globalFilter, setGlobalFilter] = useState('')
const columns = useMemo<ColumnDef<dataType, any>[]>(
@@ -124,31 +113,43 @@ const OrderTable = () => {
/>
)
},
columnHelper.accessor('productName', {
columnHelper.accessor('product_name', {
header: 'Product',
cell: ({ row }) => (
<div className='flex items-center gap-3'>
<img src={row.original.productImage} alt={row.original.productName} height={34} className='rounded' />
<div className='flex flex-col items-start'>
<Typography color='text.primary' className='font-medium'>
{row.original.productName}
{row.original.product_name}
</Typography>
<Typography variant='body2'>{row.original.brand}</Typography>
<div className='flex items-center gap-1'>
<i
className={classnames(
'tabler-circle-filled bs-2.5 is-2.5',
statusChipColor[row.original.status].colorClassName
)}
/>
<Typography
color={`${statusChipColor[row.original.status].color}.main`}
className='font-medium text-xs'
>
{statusChipColor[row.original.status].text}
</Typography>
</div>
</div>
</div>
)
}),
columnHelper.accessor('price', {
columnHelper.accessor('unit_price', {
header: 'Price',
cell: ({ row }) => <Typography>{`$${row.original.price}`}</Typography>
cell: ({ row }) => <Typography>{formatCurrency(row.original.unit_price)}</Typography>
}),
columnHelper.accessor('quantity', {
header: 'Qty',
cell: ({ row }) => <Typography>{`${row.original.quantity}`}</Typography>
}),
columnHelper.accessor('total', {
columnHelper.accessor('total_price', {
header: 'Total',
cell: ({ row }) => <Typography>{`$${row.original.total}`}</Typography>
cell: ({ row }) => <Typography>{formatCurrency(row.original.total_price)}</Typography>
})
],
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -156,7 +157,7 @@ const OrderTable = () => {
)
const table = useReactTable({
data: data as dataType[],
data: data as OrderItem[],
columns,
filterFns: {
fuzzy: fuzzyFilter
@@ -243,18 +244,11 @@ const OrderTable = () => {
)
}
const OrderDetailsCard = () => {
const OrderDetailsCard = ({ data }: { data: Order }) => {
return (
<Card>
<CardHeader
title='Order Details'
action={
<Typography component={Link} color='primary.main' className='font-medium'>
Edit
</Typography>
}
/>
<OrderTable />
<CardHeader title='Order Details' />
<OrderTable data={data.order_items} />
<CardContent className='flex justify-end'>
<div>
<div className='flex items-center gap-12'>
@@ -262,15 +256,15 @@ const OrderDetailsCard = () => {
Subtotal:
</Typography>
<Typography color='text.primary' className='font-medium'>
$2,093
{formatCurrency(data.subtotal)}
</Typography>
</div>
<div className='flex items-center gap-12'>
<Typography color='text.primary' className='min-is-[100px]'>
Shipping Fee:
Discount
</Typography>
<Typography color='text.primary' className='font-medium'>
$2
{formatCurrency(data.discount_amount)}
</Typography>
</div>
<div className='flex items-center gap-12'>
@@ -278,7 +272,7 @@ const OrderDetailsCard = () => {
Tax:
</Typography>
<Typography color='text.primary' className='font-medium'>
$28
{formatCurrency(data.tax_amount)}
</Typography>
</div>
<div className='flex items-center gap-12'>
@@ -286,7 +280,7 @@ const OrderDetailsCard = () => {
Total:
</Typography>
<Typography color='text.primary' className='font-medium'>
$2113
{formatCurrency(data.total_amount)}
</Typography>
</div>
</div>
@@ -1,43 +1,59 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Type Imports
import type { OrderType } from '@/types/apps/ecommerceTypes'
// Component Imports
import { redirect, useParams } from 'next/navigation'
import Loading from '../../../../../components/layout/shared/Loading'
import { useOrder } from '../../../../../services/queries/orders'
import BillingAddress from './BillingAddressCard'
import CustomerDetails from './CustomerDetailsCard'
import OrderDetailHeader from './OrderDetailHeader'
import OrderDetailsCard from './OrderDetailsCard'
import ShippingActivity from './ShippingActivityCard'
import CustomerDetails from './CustomerDetailsCard'
import ShippingAddress from './ShippingAddressCard'
import BillingAddress from './BillingAddressCard'
const OrderDetails = ({ orderData, order }: { orderData?: OrderType; order: string }) => {
const OrderDetails = () => {
const params = useParams()
const { data, isLoading } = useOrder(params.id as string)
if (isLoading) {
return <Loading />
}
if (!data) {
redirect('not-found')
}
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<OrderDetailHeader orderData={orderData} order={order} />
<OrderDetailHeader orderData={data} />
</Grid>
<Grid size={{ xs: 12, md: 8 }}>
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<OrderDetailsCard />
</Grid>
<Grid size={{ xs: 12 }}>
<ShippingActivity order={order} />
<OrderDetailsCard data={data} />
</Grid>
{/* <Grid size={{ xs: 12 }}>
<ShippingActivity order={data.order_number} />
</Grid> */}
</Grid>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<CustomerDetails orderData={orderData} />
<CustomerDetails orderData={data} />
</Grid>
{/* <Grid size={{ xs: 12 }}>
<ShippingAddress />
</Grid> */}
<Grid size={{ xs: 12 }}>
<ShippingAddress />
</Grid>
<Grid size={{ xs: 12 }}>
<BillingAddress />
<BillingAddress data={data} />
</Grid>
</Grid>
</Grid>
@@ -215,7 +215,7 @@ const OrderListTable = () => {
text: 'View',
icon: 'tabler-eye',
href: getLocalizedUrl(
`/apps/ecommerce/orders/details/${row.original.order_number}`,
`/apps/ecommerce/orders/${row.original.id}/details`,
locale as Locale
),
linkProps: { className: 'flex items-center gap-2 is-full plb-2 pli-4' }