feat: analytic

This commit is contained in:
ferdiansyah783 2025-08-08 17:59:39 +07:00
parent 7beee4c3a1
commit 67b747f0ff
34 changed files with 1227 additions and 2834 deletions

View File

@ -1,72 +0,0 @@
// MUI Imports
import Grid from '@mui/material/Grid2'
// Components Imports
import WebsiteAnalyticsSlider from '@views/dashboards/analytics/WebsiteAnalyticsSlider'
import LineAreaDailySalesChart from '@views/dashboards/analytics/LineAreaDailySalesChart'
import SalesOverview from '@views/dashboards/analytics/SalesOverview'
import EarningReports from '@views/dashboards/analytics/EarningReports'
import SupportTracker from '@views/dashboards/analytics/SupportTracker'
import SalesByCountries from '@views/dashboards/analytics/SalesByCountries'
import TotalEarning from '@views/dashboards/analytics/TotalEarning'
import MonthlyCampaignState from '@views/dashboards/analytics/MonthlyCampaignState'
import SourceVisits from '@views/dashboards/analytics/SourceVisits'
import ProjectsTable from '@views/dashboards/analytics/ProjectsTable'
/**
* ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the
* ! `.env` file found at root of your project and also update the API endpoints like `/pages/profile` in below example.
* ! Also, remove the above server action import and the action itself from the `src/app/server/actions.ts` file to clean up unused code
* ! because we've used the server action for getting our static data.
*/
/* const getProfileData = async () => {
// Vars
const res = await fetch(`${process.env.API_URL}/pages/profile`)
if (!res.ok) {
throw new Error('Failed to fetch profileData')
}
return res.json()
} */
const DashboardAnalytics = async () => {
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, lg: 6 }}>
<WebsiteAnalyticsSlider />
</Grid>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<LineAreaDailySalesChart />
</Grid>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<SalesOverview />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<EarningReports />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<SupportTracker />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<SalesByCountries />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<TotalEarning />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<MonthlyCampaignState />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<SourceVisits />
</Grid>
<Grid size={{ xs: 12, lg: 8 }}>
<ProjectsTable projectTable={[]} />
</Grid>
</Grid>
)
}
export default DashboardAnalytics

View File

@ -1,356 +0,0 @@
'use client'
import React, { useEffect, useState } from 'react'
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
BarChart,
Bar,
PieChart,
Pie,
Cell
} from 'recharts'
import { useSalesAnalytics } from '../../../../../../services/queries/analytics'
import Loading from '../../../../../../components/layout/shared/Loading'
import PickerBasic from '../../../../../../components/date-picker/PickerBasic'
import { formatDateDDMMYYYY } from '../../../../../../utils/transform'
// Tabler icons component
const TablerIcon = ({ name = '', className = '' }) => <i className={`tabler-${name} ${className}`} />
const DashboardCRM = () => {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const [dateFrom, setDateFrom] = useState<Date | null>(monthAgo)
const [dateTo, setDateTo] = useState<Date | null>(today)
const { data: analytics, isLoading } = useSalesAnalytics({
date_from: formatDateDDMMYYYY(dateFrom!),
date_to: formatDateDDMMYYYY(dateTo!)
})
useEffect(() => {
if (analytics) {
setDateFrom(new Date(analytics.date_from))
setDateTo(new Date(analytics.date_to))
}
}, [analytics])
// Format currency
const formatCurrency = (amount: any) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(amount)
}
// Format date
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
// Format long date
const formatLongDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
// Prepare chart data
const chartData = analytics?.data.map((item: any) => ({
date: formatDate(item.date),
sales: item.sales,
orders: item.orders,
items: item.items,
averageOrderValue: item.orders > 0 ? Math.round(item.sales / item.orders) : 0
}))
// Colors for charts
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
// Metric cards data
const metrics = [
{
title: 'Total Sales',
value: formatCurrency(analytics?.summary.total_sales),
icon: 'currency-dollar',
color: 'text-green-600',
bgColor: 'bg-green-50',
change: '+12.5%',
changeColor: 'text-green-600'
},
{
title: 'Total Orders',
value: analytics?.summary.total_orders.toLocaleString(),
icon: 'shopping-cart',
color: 'text-blue-600',
bgColor: 'bg-blue-50',
change: '+8.2%',
changeColor: 'text-blue-600'
},
{
title: 'Total Items',
value: analytics?.summary.total_items.toLocaleString(),
icon: 'package',
color: 'text-purple-600',
bgColor: 'bg-purple-50',
change: '+15.1%',
changeColor: 'text-purple-600'
},
{
title: 'Average Order Value',
value: formatCurrency(analytics?.summary.average_order_value),
icon: 'trending-up',
color: 'text-orange-600',
bgColor: 'bg-orange-50',
change: '+4.3%',
changeColor: 'text-orange-600'
}
]
// Performance data for pie chart
const performanceData = analytics?.data.map((item: any, index: any) => ({
name: formatDate(item.date),
value: item.sales,
color: colors[index % colors.length]
}))
if (isLoading) return <Loading />
return (
<div className='mx-auto space-y-6'>
{/* Header */}
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8'>
<div>
<h1 className='text-4xl font-bold text-gray-800 mb-2'>Sales Analytics Dashboard</h1>
<PickerBasic dateFrom={dateFrom} dateTo={dateTo} onChangeDateFrom={setDateFrom} onChangeDateTo={setDateTo} />
</div>
<div className='flex gap-3'>
<div className='flex items-center gap-2 bg-blue-50 px-4 py-2 rounded-full'>
<TablerIcon name='calendar' className='text-blue-600 text-sm' />
<span className='text-blue-700 font-medium'>Grouped by {analytics?.group_by}</span>
</div>
<div className='flex items-center gap-2 bg-purple-50 px-4 py-2 rounded-full'>
<TablerIcon name='chart-line' className='text-purple-600 text-sm' />
<span className='text-purple-700 font-medium'>{analytics?.data.length} data points</span>
</div>
</div>
</div>
{/* Metrics Cards */}
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6'>
{metrics.map((metric, index) => (
<div
key={index}
className='bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100'
>
<div className='p-6'>
<div className='flex items-start justify-between'>
<div className='flex-1'>
<p className='text-gray-500 text-sm font-medium mb-2'>{metric.title}</p>
<h3 className='text-2xl font-bold text-gray-800 mb-3'>{metric.value}</h3>
<div className='flex items-center gap-1'>
<TablerIcon name='trending-up' className={`text-xs ${metric.changeColor}`} />
<span className={`text-sm font-medium ${metric.changeColor}`}>{metric.change}</span>
</div>
</div>
<div className={`p-4 rounded-xl ${metric.bgColor}`}>
<TablerIcon name={metric.icon} className={`text-2xl ${metric.color}`} />
</div>
</div>
</div>
</div>
))}
</div>
{/* Charts Section */}
<div className='grid grid-cols-1 lg:grid-cols-3 gap-6'>
{/* Sales Trend Chart */}
<div className='lg:col-span-2 bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-line' className='text-blue-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Sales Trend</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='date' stroke='#6b7280' />
<YAxis tickFormatter={value => `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' />
<Tooltip
formatter={(value, name) => [
name === 'sales' ? formatCurrency(value) : value.toLocaleString(),
name === 'sales' ? 'Sales' : name === 'orders' ? 'Orders' : 'Items'
]}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Line
type='monotone'
dataKey='sales'
stroke='#3B82F6'
strokeWidth={3}
dot={{ fill: '#3B82F6', strokeWidth: 2, r: 5 }}
activeDot={{ r: 7, stroke: '#3B82F6', strokeWidth: 2 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Sales Distribution */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-pie' className='text-purple-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Sales Distribution</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={performanceData}
cx='50%'
cy='50%'
outerRadius={90}
dataKey='value'
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{performanceData?.map((entry: any, index: any) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
formatter={value => [formatCurrency(value), 'Sales']}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Orders & Items Chart */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-bar' className='text-green-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Orders & Items Analysis</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='date' stroke='#6b7280' />
<YAxis stroke='#6b7280' />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Bar dataKey='orders' fill='#10B981' name='Orders' radius={[4, 4, 0, 0]} />
<Bar dataKey='items' fill='#3B82F6' name='Items' radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Detailed Data Table */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='table' className='text-indigo-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Daily Performance Details</h2>
</div>
<div className='overflow-x-auto'>
<table className='w-full'>
<thead>
<tr className='bg-gray-50 border-b border-gray-200'>
<th className='text-left py-4 px-6 font-semibold text-gray-700'>Date</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Sales</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Orders</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Items</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Avg Order Value</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Net Sales</th>
</tr>
</thead>
<tbody>
{analytics?.data.map((row: any, index: any) => (
<tr key={index} className='border-b border-gray-100 hover:bg-gray-50 transition-colors'>
<td className='py-4 px-6 font-medium text-gray-800'>{formatLongDate(row.date)}</td>
<td className='py-4 px-6 text-right font-semibold text-green-600'>{formatCurrency(row.sales)}</td>
<td className='py-4 px-6 text-right text-gray-700'>{row.orders.toLocaleString()}</td>
<td className='py-4 px-6 text-right text-gray-700'>{row.items.toLocaleString()}</td>
<td className='py-4 px-6 text-right font-medium text-gray-800'>
{formatCurrency(row.orders > 0 ? Math.round(row.sales / row.orders) : 0)}
</td>
<td className='py-4 px-6 text-right font-semibold text-gray-800'>
{formatCurrency(row.net_sales)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Summary Footer */}
<div className='bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-100'>
<div className='p-6'>
<div className='grid grid-cols-2 md:grid-cols-4 gap-6'>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{formatCurrency(analytics?.summary.net_sales)}</h3>
<p className='text-gray-600 font-medium'>Net Sales</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{analytics?.summary.total_orders}</h3>
<p className='text-gray-600 font-medium'>Total Orders</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{formatCurrency(analytics?.summary.total_tax)}</h3>
<p className='text-gray-600 font-medium'>Total Tax</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>
{formatCurrency(analytics?.summary.total_discount)}
</h3>
<p className='text-gray-600 font-medium'>Total Discount</p>
</div>
</div>
</div>
</div>
</div>
)
}
export default DashboardCRM

View File

@ -1,348 +0,0 @@
'use client'
import React, { useEffect, useState } from 'react'
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
BarChart,
Bar,
PieChart,
Pie,
Cell
} from 'recharts'
import PickerBasic from '../../../../../../../components/date-picker/PickerBasic'
import Loading from '../../../../../../../components/layout/shared/Loading'
import { useSalesAnalytics } from '../../../../../../../services/queries/analytics'
import { formatDateDDMMYYYY } from '../../../../../../../utils/transform'
// Tabler icons component
const TablerIcon = ({ name = '', className = '' }) => <i className={`tabler-${name} ${className}`} />
const EcomerceOrderReport = () => {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const [dateFrom, setDateFrom] = useState<Date | null>(monthAgo)
const [dateTo, setDateTo] = useState<Date | null>(today)
const { data: analytics, isLoading } = useSalesAnalytics({
date_from: formatDateDDMMYYYY(dateFrom!),
date_to: formatDateDDMMYYYY(dateTo!)
})
useEffect(() => {
if (analytics) {
setDateFrom(new Date(analytics.date_from))
setDateTo(new Date(analytics.date_to))
}
}, [analytics])
// Format currency
const formatCurrency = (amount: any) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(amount)
}
// Format date
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
// Format long date
const formatLongDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
// Prepare chart data
const chartData = analytics?.data.map((item: any) => ({
date: formatDate(item.date),
sales: item.sales,
orders: item.orders,
items: item.items,
averageOrderValue: item.orders > 0 ? Math.round(item.sales / item.orders) : 0
}))
// Colors for charts
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
// Metric cards data
const metrics = [
{
title: 'Total Sales',
value: formatCurrency(analytics?.summary.total_sales),
icon: 'currency-dollar',
color: 'text-green-600',
bgColor: 'bg-green-50',
change: '+12.5%',
changeColor: 'text-green-600'
},
{
title: 'Total Orders',
value: analytics?.summary.total_orders.toLocaleString(),
icon: 'shopping-cart',
color: 'text-blue-600',
bgColor: 'bg-blue-50',
change: '+8.2%',
changeColor: 'text-blue-600'
},
{
title: 'Total Items',
value: analytics?.summary.total_items.toLocaleString(),
icon: 'package',
color: 'text-purple-600',
bgColor: 'bg-purple-50',
change: '+15.1%',
changeColor: 'text-purple-600'
},
{
title: 'Average Order Value',
value: formatCurrency(analytics?.summary.average_order_value),
icon: 'trending-up',
color: 'text-orange-600',
bgColor: 'bg-orange-50',
change: '+4.3%',
changeColor: 'text-orange-600'
}
]
// Performance data for pie chart
const performanceData = analytics?.data.map((item: any, index: any) => ({
name: formatDate(item.date),
value: item.sales,
color: colors[index % colors.length]
}))
if (isLoading) return <Loading />
return (
<div className='mx-auto space-y-6'>
{/* Header */}
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8 bg-white shadow p-6 rounded-xl'>
<div>
<h1 className='text-4xl font-bold text-gray-800 mb-2'>Sales Analytics</h1>
</div>
<div className='flex gap-3'>
<PickerBasic dateFrom={dateFrom} dateTo={dateTo} onChangeDateFrom={setDateFrom} onChangeDateTo={setDateTo} />
</div>
</div>
{/* Metrics Cards */}
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6'>
{metrics.map((metric, index) => (
<div
key={index}
className='bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100'
>
<div className='p-6'>
<div className='flex items-start justify-between'>
<div className='flex-1'>
<p className='text-gray-500 text-sm font-medium mb-2'>{metric.title}</p>
<h3 className='text-2xl font-bold text-gray-800 mb-3'>{metric.value}</h3>
<div className='flex items-center gap-1'>
<TablerIcon name='trending-up' className={`text-xs ${metric.changeColor}`} />
<span className={`text-sm font-medium ${metric.changeColor}`}>{metric.change}</span>
</div>
</div>
<div className={`p-4 rounded-xl ${metric.bgColor}`}>
<TablerIcon name={metric.icon} className={`text-2xl ${metric.color}`} />
</div>
</div>
</div>
</div>
))}
</div>
{/* Charts Section */}
<div className='grid grid-cols-1 lg:grid-cols-3 gap-6'>
{/* Sales Trend Chart */}
<div className='lg:col-span-2 bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-line' className='text-blue-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Sales Trend</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='date' stroke='#6b7280' />
<YAxis tickFormatter={value => `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' />
<Tooltip
formatter={(value, name) => [
name === 'sales' ? formatCurrency(value) : value.toLocaleString(),
name === 'sales' ? 'Sales' : name === 'orders' ? 'Orders' : 'Items'
]}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Line
type='monotone'
dataKey='sales'
stroke='#3B82F6'
strokeWidth={3}
dot={{ fill: '#3B82F6', strokeWidth: 2, r: 5 }}
activeDot={{ r: 7, stroke: '#3B82F6', strokeWidth: 2 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Sales Distribution */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-pie' className='text-purple-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Sales Distribution</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={performanceData}
cx='50%'
cy='50%'
outerRadius={90}
dataKey='value'
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{performanceData?.map((entry: any, index: any) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
formatter={value => [formatCurrency(value), 'Sales']}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Orders & Items Chart */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-bar' className='text-green-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Orders & Items Analysis</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='date' stroke='#6b7280' />
<YAxis stroke='#6b7280' />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Bar dataKey='orders' fill='#10B981' name='Orders' radius={[4, 4, 0, 0]} />
<Bar dataKey='items' fill='#3B82F6' name='Items' radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Detailed Data Table */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='table' className='text-indigo-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Daily Performance Details</h2>
</div>
<div className='overflow-x-auto'>
<table className='w-full'>
<thead>
<tr className='bg-gray-50 border-b border-gray-200'>
<th className='text-left py-4 px-6 font-semibold text-gray-700'>Date</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Sales</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Orders</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Items</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Avg Order Value</th>
<th className='text-right py-4 px-6 font-semibold text-gray-700'>Net Sales</th>
</tr>
</thead>
<tbody>
{analytics?.data.map((row: any, index: any) => (
<tr key={index} className='border-b border-gray-100 hover:bg-gray-50 transition-colors'>
<td className='py-4 px-6 font-medium text-gray-800'>{formatLongDate(row.date)}</td>
<td className='py-4 px-6 text-right font-semibold text-green-600'>{formatCurrency(row.sales)}</td>
<td className='py-4 px-6 text-right text-gray-700'>{row.orders.toLocaleString()}</td>
<td className='py-4 px-6 text-right text-gray-700'>{row.items.toLocaleString()}</td>
<td className='py-4 px-6 text-right font-medium text-gray-800'>
{formatCurrency(row.orders > 0 ? Math.round(row.sales / row.orders) : 0)}
</td>
<td className='py-4 px-6 text-right font-semibold text-gray-800'>
{formatCurrency(row.net_sales)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Summary Footer */}
<div className='bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-100'>
<div className='p-6'>
<div className='grid grid-cols-2 md:grid-cols-4 gap-6'>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{formatCurrency(analytics?.summary.net_sales)}</h3>
<p className='text-gray-600 font-medium'>Net Sales</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{analytics?.summary.total_orders}</h3>
<p className='text-gray-600 font-medium'>Total Orders</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{formatCurrency(analytics?.summary.total_tax)}</h3>
<p className='text-gray-600 font-medium'>Total Tax</p>
</div>
<div className='text-center'>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>
{formatCurrency(analytics?.summary.total_discount)}
</h3>
<p className='text-gray-600 font-medium'>Total Discount</p>
</div>
</div>
</div>
</div>
</div>
)
}
export default EcomerceOrderReport

View File

@ -1,7 +0,0 @@
import EcommerceDashboard from '../../apps/ecommerce/dashboard/page'
const DashboardECommerce = () => {
return <EcommerceDashboard />
}
export default DashboardECommerce

View File

@ -1,397 +0,0 @@
'use client'
import React, { useState, useEffect } from 'react'
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
LineChart,
Line
} from 'recharts'
import { useProductSalesAnalytics } from '../../../../../../../services/queries/analytics'
import Loading from '../../../../../../../components/layout/shared/Loading'
import { formatDateDDMMYYYY } from '../../../../../../../utils/transform'
import PickerBasic from '../../../../../../../components/date-picker/PickerBasic'
// Tabler icons component
const TablerIcon = ({ name = '', className = '' }) => <i className={`tabler-${name} ${className}`} />
const EcomerceProductReport = () => {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const [dateFrom, setDateFrom] = useState<Date | null>(monthAgo)
const [dateTo, setDateTo] = useState<Date | null>(today)
const [selectedCategory, setSelectedCategory] = useState('all')
const [sortBy, setSortBy] = useState('revenue')
const [sortOrder, setSortOrder] = useState('desc')
const { data: analytics, isLoading } = useProductSalesAnalytics({
date_from: formatDateDDMMYYYY(dateFrom!),
date_to: formatDateDDMMYYYY(dateTo!)
})
useEffect(() => {
if (analytics) {
setDateFrom(new Date(analytics.date_from))
setDateTo(new Date(analytics.date_to))
}
}, [analytics])
// Format currency
const formatCurrency = (amount: any) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(amount)
}
// Calculate summary metrics
const summary = {
totalProducts: analytics?.data.length,
totalQuantitySold: analytics?.data.reduce((sum, item) => sum + item.quantity_sold, 0),
totalRevenue: analytics?.data.reduce((sum, item) => sum + item.revenue, 0),
totalOrders: analytics?.data.reduce((sum, item) => sum + item.order_count, 0),
averageOrderValue: analytics?.data
? analytics!.data.reduce((sum, item) => sum + item.revenue, 0) /
analytics!.data.reduce((sum, item) => sum + item.order_count, 0)
: 0
}
// Get unique categories
const categories = ['all', ...new Set(analytics?.data.map(item => item.category_name))]
// Filter and sort data
const filteredData = analytics?.data
.filter(item => selectedCategory === 'all' || item.category_name === selectedCategory)
.sort((a: any, b: any) => {
const multiplier = sortOrder === 'desc' ? -1 : 1
return (a[sortBy] - b[sortBy]) * multiplier
})
// Prepare chart data
const chartData = filteredData?.slice(0, 10).map(item => ({
name: item.product_name.length > 15 ? item.product_name.substring(0, 15) + '...' : item.product_name,
fullName: item.product_name,
revenue: item.revenue,
quantity: item.quantity_sold,
orders: item.order_count,
avgPrice: item.average_price
}))
// Category performance data
const categoryData = categories
.filter(cat => cat !== 'all')
.map(category => {
const categoryItems = analytics?.data.filter(item => item.category_name === category)
return {
name: category,
revenue: categoryItems?.reduce((sum, item) => sum + item.revenue, 0),
quantity: categoryItems?.reduce((sum, item) => sum + item.quantity_sold, 0),
products: categoryItems?.length
}
})
// Colors for charts
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#06B6D4', '#84CC16', '#F97316']
// Metric cards data
const metrics = [
{
title: 'Total Revenue',
value: formatCurrency(summary.totalRevenue),
icon: 'currency-dollar',
color: 'text-green-600',
bgColor: 'bg-green-50',
subtitle: 'From all products'
},
{
title: 'Products Sold',
value: summary.totalQuantitySold?.toLocaleString(),
icon: 'package',
color: 'text-blue-600',
bgColor: 'bg-blue-50',
subtitle: `${summary.totalProducts} different products`
},
{
title: 'Total Orders',
value: summary.totalOrders?.toLocaleString(),
icon: 'shopping-cart',
color: 'text-purple-600',
bgColor: 'bg-purple-50',
subtitle: 'Completed orders'
},
{
title: 'Avg Order Value',
value: formatCurrency(summary.averageOrderValue || 0),
icon: 'trending-up',
color: 'text-orange-600',
bgColor: 'bg-orange-50',
subtitle: 'Per order average'
}
]
const handleSort = (field: any) => {
if (sortBy === field) {
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc')
} else {
setSortBy(field)
setSortOrder('desc')
}
}
if (isLoading) return <Loading />
return (
<div className='mx-auto space-y-6'>
{/* Header */}
<div className='flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8 bg-white shadow p-6 rounded-xl'>
<div>
<h1 className='text-4xl font-bold text-gray-800 mb-2'>Product Analytics</h1>
</div>
{/* Filters */}
<div className='flex flex-col sm:flex-row gap-4'>
<PickerBasic dateFrom={dateFrom} dateTo={dateTo} onChangeDateFrom={setDateFrom} onChangeDateTo={setDateTo} />
</div>
</div>
{/* Summary Cards */}
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6'>
{metrics.map((metric, index) => (
<div
key={index}
className='bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100'
>
<div className='p-6'>
<div className='flex items-start justify-between'>
<div className='flex-1'>
<p className='text-gray-500 text-sm font-medium mb-2'>{metric.title}</p>
<h3 className='text-2xl font-bold text-gray-800 mb-1'>{metric.value}</h3>
<p className='text-gray-500 text-xs'>{metric.subtitle}</p>
</div>
<div className={`p-4 rounded-xl ${metric.bgColor}`}>
<TablerIcon name={metric.icon} className={`text-2xl ${metric.color}`} />
</div>
</div>
</div>
</div>
))}
</div>
{/* Charts Section */}
<div className='grid grid-cols-1 xl:grid-cols-3 gap-6'>
{/* Product Revenue Chart */}
<div className='xl:col-span-2 bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-bar' className='text-blue-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Top Products by Revenue</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 20, bottom: 60 }}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='name' stroke='#6b7280' angle={-45} textAnchor='end' height={100} fontSize={12} />
<YAxis tickFormatter={value => `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' />
<Tooltip
formatter={(value, name) => [
name === 'revenue' ? formatCurrency(value) : value.toLocaleString(),
name === 'revenue' ? 'Revenue' : name === 'quantity' ? 'Quantity Sold' : 'Orders'
]}
labelFormatter={label => {
const item = chartData?.find(d => d.name === label)
return item ? item.fullName : label
}}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Bar dataKey='revenue' fill='#3B82F6' radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Category Distribution */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-pie' className='text-purple-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Category Distribution</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={categoryData}
cx='50%'
cy='50%'
outerRadius={80}
dataKey='revenue'
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{categoryData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
))}
</Pie>
<Tooltip
formatter={value => [formatCurrency(value), 'Revenue']}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Quantity vs Orders Chart */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center gap-3 mb-6'>
<TablerIcon name='chart-line' className='text-green-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Quantity Sold vs Order Count</h2>
</div>
<div className='h-80'>
<ResponsiveContainer width='100%' height='100%'>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray='3 3' stroke='#f0f0f0' />
<XAxis dataKey='name' stroke='#6b7280' angle={-45} textAnchor='end' height={100} fontSize={12} />
<YAxis stroke='#6b7280' />
<Tooltip
labelFormatter={label => {
const item = chartData?.find(d => d.name === label)
return item ? item.fullName : label
}}
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
}}
/>
<Bar dataKey='quantity' fill='#10B981' name='Quantity Sold' radius={[4, 4, 0, 0]} />
<Bar dataKey='orders' fill='#F59E0B' name='Order Count' radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Detailed Products Table */}
<div className='bg-white rounded-xl shadow-lg border border-gray-100'>
<div className='p-6'>
<div className='flex items-center justify-between mb-6'>
<div className='flex items-center gap-3'>
<TablerIcon name='table' className='text-indigo-600 text-2xl' />
<h2 className='text-xl font-semibold text-gray-800'>Product Performance Details</h2>
</div>
<div className='text-sm text-gray-500'>Showing {filteredData?.length} products</div>
</div>
<div className='overflow-x-auto'>
<table className='w-full'>
<thead>
<tr className='bg-gray-50 border-b border-gray-200'>
<th className='text-left py-4 px-6 font-semibold text-gray-700'>Product</th>
<th className='text-left py-4 px-6 font-semibold text-gray-700'>Category</th>
<th
className='text-right py-4 px-6 font-semibold text-gray-700 cursor-pointer hover:text-blue-600 transition-colors'
onClick={() => handleSort('quantity_sold')}
>
<div className='flex items-center justify-end gap-1'>
Quantity Sold
<TablerIcon
name={sortBy === 'quantity_sold' && sortOrder === 'desc' ? 'chevron-down' : 'chevron-up'}
className='text-xs'
/>
</div>
</th>
<th
className='text-right py-4 px-6 font-semibold text-gray-700 cursor-pointer hover:text-blue-600 transition-colors'
onClick={() => handleSort('revenue')}
>
<div className='flex items-center justify-end gap-1'>
Revenue
<TablerIcon
name={sortBy === 'revenue' && sortOrder === 'desc' ? 'chevron-down' : 'chevron-up'}
className='text-xs'
/>
</div>
</th>
<th
className='text-right py-4 px-6 font-semibold text-gray-700 cursor-pointer hover:text-blue-600 transition-colors'
onClick={() => handleSort('average_price')}
>
<div className='flex items-center justify-end gap-1'>
Avg Price
<TablerIcon
name={sortBy === 'average_price' && sortOrder === 'desc' ? 'chevron-down' : 'chevron-up'}
className='text-xs'
/>
</div>
</th>
<th
className='text-right py-4 px-6 font-semibold text-gray-700 cursor-pointer hover:text-blue-600 transition-colors'
onClick={() => handleSort('order_count')}
>
<div className='flex items-center justify-end gap-1'>
Orders
<TablerIcon
name={sortBy === 'order_count' && sortOrder === 'desc' ? 'chevron-down' : 'chevron-up'}
className='text-xs'
/>
</div>
</th>
</tr>
</thead>
<tbody>
{filteredData?.map((product, index) => (
<tr key={product.product_id} className='border-b border-gray-100 hover:bg-gray-50 transition-colors'>
<td className='py-4 px-6'>
<div className='font-medium text-gray-800'>{product.product_name}</div>
</td>
<td className='py-4 px-6'>
<span className='inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800'>
{product.category_name}
</span>
</td>
<td className='py-4 px-6 text-right font-semibold text-gray-800'>
{product.quantity_sold.toLocaleString()}
</td>
<td className='py-4 px-6 text-right font-semibold text-green-600'>
{formatCurrency(product.revenue)}
</td>
<td className='py-4 px-6 text-right text-gray-700'>{formatCurrency(product.average_price)}</td>
<td className='py-4 px-6 text-right text-gray-700'>{product.order_count.toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}
export default EcomerceProductReport

View File

@ -1,355 +0,0 @@
import React from 'react';
const TablerIcon = ({ name = '', className = '' }) => <i className={`tabler-${name} ${className}`} />;
const PaymentMethodAnalytics = () => {
// Default data structure for demonstration
const defaultData = {
"organization_id": "4ecf5cfb-9ac1-4fbd-94e4-9f149f07460a",
"outlet_id": "d5b38fc4-8df7-4d54-99e2-b8825977f5ca",
"date_from": "2025-07-05T00:00:00+07:00",
"date_to": "2025-08-05T23:59:59.999999999+07:00",
"group_by": "day",
"summary": {
"total_amount": 212000,
"total_orders": 6,
"total_payments": 6,
"average_order_value": 35333.333333333336
},
"data": [
{
"payment_method_id": "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905",
"payment_method_name": "CASH",
"payment_method_type": "cash",
"total_amount": 212000,
"order_count": 6,
"payment_count": 6,
"percentage": 100
}
]
};
const analyticsData = defaultData;
const { summary, data: paymentMethods } = analyticsData;
// Format currency
const formatCurrency = (amount: any) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(amount);
};
// Format date
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
// Get icon for payment method
const getPaymentMethodIcon = (type: any) => {
switch (type.toLowerCase()) {
case 'cash':
return <TablerIcon name="cash" className="w-5 h-5" />;
case 'card':
case 'credit_card':
case 'debit_card':
return <TablerIcon name="credit-card" className="w-5 h-5" />;
default:
return <TablerIcon name="coins" className="w-5 h-5" />;
}
};
// Get color classes for payment method type
const getPaymentMethodColors = (type: any) => {
switch (type.toLowerCase()) {
case 'cash':
return {
chip: 'bg-green-100 text-green-800 border-green-200',
progress: 'bg-green-500'
};
case 'card':
case 'credit_card':
case 'debit_card':
return {
chip: 'bg-blue-100 text-blue-800 border-blue-200',
progress: 'bg-blue-500'
};
default:
return {
chip: 'bg-gray-100 text-gray-800 border-gray-200',
progress: 'bg-gray-500'
};
}
};
// Progress Bar Component
const ProgressBar = ({ value, className, color }: any) => {
return (
<div className={`w-full bg-gray-200 rounded-full h-2 ${className}`}>
<div
className={`h-2 rounded-full transition-all duration-300 ${color}`}
style={{ width: `${Math.min(value, 100)}%` }}
></div>
</div>
);
};
// Chip Component
const Chip = ({ label, colorClasses }: any) => {
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${colorClasses}`}>
{label}
</span>
);
};
return (
<div className="mx-auto space-y-6 bg-gray-50 min-h-screen">
{/* Header */}
<div className="flex items-center gap-3 mb-6">
<div className="p-2 bg-blue-600 rounded-lg">
<TablerIcon name="trending-up" className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">
Payment Analytics
</h1>
<div className="flex items-center gap-2 mt-1">
<TablerIcon name="calendar" className="w-4 h-4 text-gray-500" />
<p className="text-sm text-gray-600">
{formatDate(analyticsData.date_from)} - {formatDate(analyticsData.date_to)}
</p>
</div>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6 bg-gradient-to-br from-blue-50 to-blue-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-blue-600 mb-1">
Total Revenue
</p>
<p className="text-2xl font-bold text-blue-900">
{formatCurrency(summary.total_amount)}
</p>
</div>
<div className="p-3 bg-blue-600 rounded-full">
<TablerIcon name="receipt" className="w-6 h-6 text-white" />
</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6 bg-gradient-to-br from-green-50 to-green-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-green-600 mb-1">
Total Orders
</p>
<p className="text-2xl font-bold text-green-900">
{summary.total_orders.toLocaleString()}
</p>
</div>
<div className="p-3 bg-green-600 rounded-full">
<TablerIcon name="cash" className="w-6 h-6 text-white" />
</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6 bg-gradient-to-br from-purple-50 to-purple-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-purple-600 mb-1">
Total Payments
</p>
<p className="text-2xl font-bold text-purple-900">
{summary.total_payments.toLocaleString()}
</p>
</div>
<div className="p-3 bg-purple-600 rounded-full">
<TablerIcon name="credit-card" className="w-6 h-6 text-white" />
</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6 bg-gradient-to-br from-orange-50 to-orange-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-orange-600 mb-1">
Avg Order Value
</p>
<p className="text-2xl font-bold text-orange-900">
{formatCurrency(summary.average_order_value)}
</p>
</div>
<div className="p-3 bg-orange-600 rounded-full">
<TablerIcon name="trending-up" className="w-6 h-6 text-white" />
</div>
</div>
</div>
</div>
</div>
{/* Payment Methods Breakdown */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100">
<div className="p-6">
<div className="flex items-center gap-2 mb-6">
<TablerIcon name="credit-card" className="w-6 h-6 text-blue-600" />
<h2 className="text-xl font-bold text-gray-900">
Payment Methods Breakdown
</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-gray-50 border-b border-gray-200">
<th className="text-left p-4 font-semibold text-gray-900">Payment Method</th>
<th className="text-left p-4 font-semibold text-gray-900">Type</th>
<th className="text-right p-4 font-semibold text-gray-900">Amount</th>
<th className="text-right p-4 font-semibold text-gray-900">Orders</th>
<th className="text-right p-4 font-semibold text-gray-900">Payments</th>
<th className="text-right p-4 font-semibold text-gray-900">Percentage</th>
<th className="text-left p-4 font-semibold text-gray-900">Usage</th>
</tr>
</thead>
<tbody>
{paymentMethods.map((method, index) => {
const colors = getPaymentMethodColors(method.payment_method_type);
return (
<tr key={method.payment_method_id || index} className="border-b border-gray-100 hover:bg-gray-50 transition-colors">
<td className="p-4">
<div className="flex items-center gap-3">
{getPaymentMethodIcon(method.payment_method_type)}
<span className="font-medium text-gray-900">
{method.payment_method_name}
</span>
</div>
</td>
<td className="p-4">
<Chip
label={method.payment_method_type.toUpperCase()}
colorClasses={colors.chip}
/>
</td>
<td className="p-4 text-right font-semibold text-gray-900">
{formatCurrency(method.total_amount)}
</td>
<td className="p-4 text-right text-gray-700">
{method.order_count.toLocaleString()}
</td>
<td className="p-4 text-right text-gray-700">
{method.payment_count.toLocaleString()}
</td>
<td className="p-4 text-right">
<span className="font-bold text-blue-600">
{method.percentage.toFixed(1)}%
</span>
</td>
<td className="p-4">
<div className="flex items-center gap-2">
<ProgressBar
value={method.percentage}
className="flex-1"
color={colors.progress}
/>
<span className="text-xs text-gray-500 min-w-fit">
{method.percentage.toFixed(1)}%
</span>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{paymentMethods.length === 0 && (
<div className="text-center py-12">
<TablerIcon name="credit-card" className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">
No payment method data available for the selected period.
</p>
</div>
)}
</div>
</div>
{/* Additional Stats */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100">
<div className="p-6">
<h3 className="text-lg font-bold text-gray-900 mb-4">
Key Insights
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-3">
<div className="flex items-center justify-between p-4 bg-blue-50 rounded-lg border border-blue-100">
<div className="flex items-center gap-3">
<TablerIcon name="cash" className="w-5 h-5 text-blue-600" />
<span className="text-sm font-medium text-blue-900">
Most Used Payment Method
</span>
</div>
<span className="font-bold text-blue-900">
{paymentMethods.length > 0 ? paymentMethods[0].payment_method_name : 'N/A'}
</span>
</div>
<div className="flex items-center justify-between p-4 bg-green-50 rounded-lg border border-green-100">
<div className="flex items-center gap-3">
<TablerIcon name="trending-up" className="w-5 h-5 text-green-600" />
<span className="text-sm font-medium text-green-900">
Revenue per Payment
</span>
</div>
<span className="font-bold text-green-900">
{formatCurrency(summary.total_payments > 0 ? summary.total_amount / summary.total_payments : 0)}
</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 bg-purple-50 rounded-lg border border-purple-100">
<div className="flex items-center gap-3">
<TablerIcon name="receipt" className="w-5 h-5 text-purple-600" />
<span className="text-sm font-medium text-purple-900">
Payment Success Rate
</span>
</div>
<span className="font-bold text-purple-900">
{((summary.total_payments / summary.total_orders) * 100).toFixed(1)}%
</span>
</div>
<div className="flex items-center justify-between p-4 bg-orange-50 rounded-lg border border-orange-100">
<div className="flex items-center gap-3">
<TablerIcon name="coins" className="w-5 h-5 text-orange-600" />
<span className="text-sm font-medium text-orange-900">
Payment Methods Used
</span>
</div>
<span className="font-bold text-orange-900">
{paymentMethods.length}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default PaymentMethodAnalytics;

View File

@ -0,0 +1,116 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder'
import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs'
// Server Action Imports
import Loading from '../../../../../../components/layout/shared/Loading'
import { useSalesAnalytics } from '../../../../../../services/queries/analytics'
const DashboardOrder = () => {
const { data, isLoading } = useSalesAnalytics()
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
const transformSalesData = (data: any) => {
return [
{
type: 'items',
avatarIcon: 'tabler-shopping-cart',
date: data.map((d: any) => formatDate(d.date)),
series: [{ data: data.map((d: any) => d.items) }]
},
{
type: 'orders',
avatarIcon: 'tabler-chart-bar',
date: data.map((d: any) => formatDate(d.date)),
series: [{ data: data.map((d: any) => d.orders) }]
},
{
type: 'sales',
avatarIcon: 'tabler-currency-dollar',
date: data.map((d: any) => formatDate(d.date)),
series: [{ data: data.map((d: any) => d.sales) }]
}
]
}
if (isLoading) return <Loading />
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Items'
value={data?.summary.total_items as number}
avatarIcon={'tabler-package'}
avatarColor='primary'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Orders'
value={data?.summary.total_orders as number}
avatarIcon={'tabler-shopping-cart'}
avatarColor='info'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Average Orders'
value={data?.summary.average_order_value as number}
avatarIcon={'tabler-trending-up'}
avatarColor='warning'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Sales'
value={data?.summary.total_sales as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='success'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, lg: 12 }}>
<EarningReportsWithTabs data={transformSalesData(data?.data)} />
</Grid>
{/* <Grid size={{ xs: 12, md: 6, lg: 4 }}>
<RadarSalesChart />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<SalesByCountries />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ProjectStatus />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ActiveProjects />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<LastTransaction />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<ActivityTimeline />
</Grid> */}
</Grid>
)
}
export default DashboardOrder

View File

@ -0,0 +1,99 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder'
import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs'
// Server Action Imports
import Loading from '../../../../../../components/layout/shared/Loading'
import { useDashboardAnalytics } from '../../../../../../services/queries/analytics'
import { DashboardReport, PaymentDataItem, ProductData, RecentSale } from '../../../../../../types/services/analytic'
const DashboardOverview = () => {
const { data, isLoading } = useDashboardAnalytics()
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
const transformSalesData = (data: DashboardReport) => {
return [
{
type: 'products',
avatarIcon: 'tabler-package',
date: data.top_products.map((d: ProductData) => d.product_name),
series: [{ data: data.top_products.map((d: ProductData) => d.revenue) }]
},
{
type: 'orders',
avatarIcon: 'tabler-shopping-cart',
date: data.recent_sales.map((d: RecentSale) => formatDate(d.date)),
series: [{ data: data.recent_sales.map((d: RecentSale) => d.net_sales) }]
},
{
type: 'payments',
avatarIcon: 'tabler-credit-card-pay',
date: data.payment_methods.map((d: PaymentDataItem) => d.payment_method_name),
series: [{ data: data.payment_methods.map((d: PaymentDataItem) => d.total_amount) }]
},
]
}
if (isLoading) return <Loading />
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Customers'
value={data?.overview.total_customers as number}
avatarIcon={'tabler-users'}
avatarColor='primary'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Orders'
value={data?.overview.total_orders as number}
avatarIcon={'tabler-package'}
avatarColor='info'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Average Orders'
value={data?.overview.average_order_value as number}
avatarIcon={'tabler-trending-up'}
avatarColor='warning'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Sales'
value={data?.overview.total_sales as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='success'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, lg: 12 }}>
<EarningReportsWithTabs data={transformSalesData(data!)} />
</Grid>
</Grid>
)
}
export default DashboardOverview

View File

@ -0,0 +1,98 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder'
import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs'
// Server Action Imports
import Loading from '../../../../../../components/layout/shared/Loading'
import { usePaymentAnalytics } from '../../../../../../services/queries/analytics'
import { PaymentDataItem } from '../../../../../../types/services/analytic'
const DashboardPayment = () => {
const { data, isLoading } = usePaymentAnalytics()
const transformSalesData = (data: PaymentDataItem[]) => {
return [
{
type: 'payment',
avatarIcon: 'tabler-package',
date: data.map((d: PaymentDataItem) => d.payment_method_name),
series: [{ data: data.map((d: PaymentDataItem) => d.total_amount) }]
}
]
}
if (isLoading) return <Loading />
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Orders'
value={data?.summary.total_orders as number}
avatarIcon={'tabler-shopping-cart'}
avatarColor='primary'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Payment'
value={data?.summary.total_payments as number}
avatarIcon={'tabler-package'}
avatarColor='info'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Average Orders'
value={data?.summary.average_order_value as number}
avatarIcon={'tabler-trending-up'}
avatarColor='warning'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Amount'
value={data?.summary.total_amount as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='success'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, lg: 12 }}>
<EarningReportsWithTabs data={transformSalesData(data!.data)} />
</Grid>
{/* <Grid size={{ xs: 12, md: 6, lg: 4 }}>
<RadarSalesChart />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<SalesByCountries />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ProjectStatus />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ActiveProjects />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<LastTransaction />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<ActivityTimeline />
</Grid> */}
</Grid>
)
}
export default DashboardPayment

View File

@ -0,0 +1,115 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder'
import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs'
// Server Action Imports
import Loading from '../../../../../../components/layout/shared/Loading'
import { useProductSalesAnalytics } from '../../../../../../services/queries/analytics'
const DashboardProduct = () => {
const { data, isLoading } = useProductSalesAnalytics()
const summary = {
totalProducts: data?.data.length,
totalQuantitySold: data?.data.reduce((sum, item) => sum + item.quantity_sold, 0),
totalRevenue: data?.data.reduce((sum, item) => sum + item.revenue, 0),
totalOrders: data?.data.reduce((sum, item) => sum + item.order_count, 0),
averageOrderValue: data?.data
? data!.data.reduce((sum, item) => sum + item.revenue, 0) /
data!.data.reduce((sum, item) => sum + item.order_count, 0)
: 0
}
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
const transformSalesData = (data: any) => {
return [
{
type: 'products',
avatarIcon: 'tabler-package',
date: data.map((d: any) => d.product_name),
series: [{ data: data.map((d: any) => d.revenue) }]
}
]
}
if (isLoading) return <Loading />
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Orders'
value={summary.totalOrders as number}
avatarIcon={'tabler-shopping-cart'}
avatarColor='primary'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Product Sold'
value={summary.totalQuantitySold as number}
avatarIcon={'tabler-package'}
avatarColor='info'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Average Orders'
value={summary.averageOrderValue as number}
avatarIcon={'tabler-trending-up'}
avatarColor='warning'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Sales'
value={summary.totalRevenue as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='success'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, lg: 12 }}>
<EarningReportsWithTabs data={transformSalesData(data?.data)} />
</Grid>
{/* <Grid size={{ xs: 12, md: 6, lg: 4 }}>
<RadarSalesChart />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<SalesByCountries />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ProjectStatus />
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<ActiveProjects />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<LastTransaction />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<ActivityTimeline />
</Grid> */}
</Grid>
)
}
export default DashboardProduct

View File

@ -0,0 +1,117 @@
'use client'
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder'
import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs'
// Server Action Imports
import Loading from '../../../../../../components/layout/shared/Loading'
import { useProfitLossAnalytics } from '../../../../../../services/queries/analytics'
import {
ProductDataReport,
ProfitLossReport
} from '../../../../../../types/services/analytic'
function formatMetricName(metric: string): string {
const nameMap: { [key: string]: string } = {
revenue: 'Revenue',
cost: 'Cost',
gross_profit: 'Gross Profit',
gross_profit_margin: 'Gross Profit Margin (%)',
tax: 'Tax',
discount: 'Discount',
net_profit: 'Net Profit',
net_profit_margin: 'Net Profit Margin (%)',
orders: 'Orders'
}
return nameMap[metric] || metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
}
const DashboardProfitLoss = () => {
const { data, isLoading } = useProfitLossAnalytics()
const formatDate = (dateString: any) => {
return new Date(dateString).toLocaleDateString('id-ID', {
month: 'short',
day: 'numeric'
})
}
const metrics = ['revenue', 'cost', 'gross_profit', 'net_profit']
const transformSalesData = (data: ProfitLossReport) => {
return [
{
type: 'products',
avatarIcon: 'tabler-package',
date: data.product_data.map((d: ProductDataReport) => d.product_name),
series: [{ data: data.product_data.map((d: ProductDataReport) => d.revenue) }]
},
// {
// type: 'profits',
// avatarIcon: 'tabler-currency-dollar',
// date: data.data.map((d: DailyData) => formatDate(d.date)),
// series: metrics.map(metric => ({
// name: formatMetricName(metric as string),
// data: data.data.map((item: any) => item[metric] as number)
// }))
// }
]
}
if (isLoading) return <Loading />
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Cost'
value={data?.summary.total_cost as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='primary'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Total Rvenue'
value={data?.summary.total_revenue as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='info'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Gross Profit'
value={data?.summary.gross_profit as number}
avatarIcon={'tabler-trending-up'}
avatarColor='warning'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<DistributedBarChartOrder
isLoading={isLoading}
title='Net Profit'
value={data?.summary.net_profit as number}
avatarIcon={'tabler-currency-dollar'}
avatarColor='success'
avatarSkin='light'
/>
</Grid>
<Grid size={{ xs: 12, lg: 12 }}>
<EarningReportsWithTabs data={transformSalesData(data!)} />
</Grid>
</Grid>
)
}
export default DashboardProfitLoss

View File

@ -81,17 +81,11 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => {
menuSectionStyles={menuSectionStyles(verticalNavOptions, theme)} menuSectionStyles={menuSectionStyles(verticalNavOptions, theme)}
> >
<SubMenu label={dictionary['navigation'].dashboards} icon={<i className='tabler-smart-home' />}> <SubMenu label={dictionary['navigation'].dashboards} icon={<i className='tabler-smart-home' />}>
<MenuItem href={`/${locale}/dashboards/crm`}>{dictionary['navigation'].crm}</MenuItem> <MenuItem href={`/${locale}/dashboards/overview`}>{dictionary['navigation'].overview}</MenuItem>
<MenuItem href={`/${locale}/dashboards/analytics`}>{dictionary['navigation'].analytics}</MenuItem> <MenuItem href={`/${locale}/dashboards/profit-loss`}>{dictionary['navigation'].profitloss}</MenuItem>
<SubMenu label={dictionary['navigation'].eCommerce}> <MenuItem href={`/${locale}/dashboards/products`}>{dictionary['navigation'].products}</MenuItem>
<MenuItem href={`/${locale}/dashboards/ecommerce/order`}>{dictionary['navigation'].orders}</MenuItem> <MenuItem href={`/${locale}/dashboards/orders`}>{dictionary['navigation'].orders}</MenuItem>
<MenuItem href={`/${locale}/dashboards/ecommerce/product`}>{dictionary['navigation'].products}</MenuItem> <MenuItem href={`/${locale}/dashboards/payment-methods`}>{dictionary['navigation'].paymentMethods}</MenuItem>
</SubMenu>
<SubMenu label={dictionary['navigation'].finance}>
<MenuItem href={`/${locale}/dashboards/finance/payment-method`}>
{dictionary['navigation'].paymentMethods}
</MenuItem>
</SubMenu>
</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-shopping-cart' />}>

View File

@ -21,6 +21,8 @@
"add": "يضيف", "add": "يضيف",
"addjustment": "تعديل", "addjustment": "تعديل",
"category": "فئة", "category": "فئة",
"overview": "نظرة عامة",
"profitloss": "الربح والخسارة",
"finance": "مالية", "finance": "مالية",
"paymentMethods": "طرق الدفع", "paymentMethods": "طرق الدفع",
"organization": "المنظمة", "organization": "المنظمة",

View File

@ -21,6 +21,8 @@
"add": "Add", "add": "Add",
"addjustment": "Addjustment", "addjustment": "Addjustment",
"category": "Category", "category": "Category",
"overview": "Overview",
"profitloss": "Profit Loss",
"units": "Units", "units": "Units",
"finance": "Finance", "finance": "Finance",
"paymentMethods": "Payment Methods", "paymentMethods": "Payment Methods",

View File

@ -21,6 +21,8 @@
"add": "Ajouter", "add": "Ajouter",
"addjustment": "Ajustement", "addjustment": "Ajustement",
"category": "Catégorie", "category": "Catégorie",
"overview": "Aperçu",
"profitloss": "Profit et perte",
"finance": "Finance", "finance": "Finance",
"paymentMethods": "Méthodes de paiement", "paymentMethods": "Méthodes de paiement",
"organization": "Organisation", "organization": "Organisation",

View File

@ -4,12 +4,14 @@ import { configureStore } from '@reduxjs/toolkit'
import productReducer from '@/redux-store/slices/product' import productReducer from '@/redux-store/slices/product'
import customerReducer from '@/redux-store/slices/customer' import customerReducer from '@/redux-store/slices/customer'
import paymentMethodReducer from '@/redux-store/slices/paymentMethod' import paymentMethodReducer from '@/redux-store/slices/paymentMethod'
import ingredientReducer from '@/redux-store/slices/ingredient'
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
productReducer, productReducer,
customerReducer, customerReducer,
paymentMethodReducer paymentMethodReducer,
ingredientReducer
}, },
middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }) middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
}) })

View File

@ -0,0 +1,52 @@
// Third-party Imports
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
// Type Imports
// Data Imports
import { IngredientItem } from '../../types/services/ingredient'
const initialState: { currentIngredient: IngredientItem } = {
currentIngredient: {
id: '',
organization_id: '',
outlet_id: '',
name: '',
unit_id: '',
cost: 0,
stock: 0,
is_semi_finished: false,
is_active: true,
metadata: {},
created_at: '',
updated_at: '',
unit: {
id: '',
organization_id: '',
outlet_id: '',
name: '',
abbreviation: '',
is_active: false,
created_at: '',
updated_at: ''
}
}
}
export const ingredientSlice = createSlice({
name: 'ingredient',
initialState,
reducers: {
setIngredient: (state, action: PayloadAction<IngredientItem>) => {
state.currentIngredient = action.payload
},
resetIngredient: state => {
state.currentIngredient = initialState.currentIngredient
}
}
})
export const { setIngredient, resetIngredient } = ingredientSlice.actions
export default ingredientSlice.reducer

View File

@ -1,52 +1,52 @@
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import { CustomerRequest } from '../../types/services/customer'
import { api } from '../api'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { IngredientRequest } from '../../types/services/ingredient'
import { api } from '../api'
export const useIngredientsMutation = () => { export const useIngredientsMutation = () => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const createCustomer = useMutation({ const createIngredient = useMutation({
mutationFn: async (newCustomer: CustomerRequest) => { mutationFn: async (newIngredient: IngredientRequest) => {
const response = await api.post('/customers', newCustomer) const response = await api.post('/ingredients', newIngredient)
return response.data return response.data
}, },
onSuccess: () => { onSuccess: () => {
toast.success('Customer created successfully!') toast.success('Ingredient created successfully!')
queryClient.invalidateQueries({ queryKey: ['customers'] }) queryClient.invalidateQueries({ queryKey: ['ingredients'] })
}, },
onError: (error: any) => { onError: (error: any) => {
toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed')
} }
}) })
const updateCustomer = useMutation({ const updateIngredient = useMutation({
mutationFn: async ({ id, payload }: { id: string; payload: CustomerRequest }) => { mutationFn: async ({ id, payload }: { id: string; payload: IngredientRequest }) => {
const response = await api.put(`/customers/${id}`, payload) const response = await api.put(`/ingredients/${id}`, payload)
return response.data return response.data
}, },
onSuccess: () => { onSuccess: () => {
toast.success('Customer updated successfully!') toast.success('Ingredient updated successfully!')
queryClient.invalidateQueries({ queryKey: ['customers'] }) queryClient.invalidateQueries({ queryKey: ['ingredients'] })
}, },
onError: (error: any) => { onError: (error: any) => {
toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed')
} }
}) })
const deleteCustomer = useMutation({ const deleteIngredient = useMutation({
mutationFn: async (id: string) => { mutationFn: async (id: string) => {
const response = await api.delete(`/customers/${id}`) const response = await api.delete(`/ingredients/${id}`)
return response.data return response.data
}, },
onSuccess: () => { onSuccess: () => {
toast.success('Customer deleted successfully!') toast.success('Ingredient deleted successfully!')
queryClient.invalidateQueries({ queryKey: ['customers'] }) queryClient.invalidateQueries({ queryKey: ['ingredients'] })
}, },
onError: (error: any) => { onError: (error: any) => {
toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed')
} }
}) })
return { createCustomer, updateCustomer, deleteCustomer } return { createIngredient, updateIngredient, deleteIngredient }
} }

View File

@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { ProductSalesReport, SalesReport } from '../../types/services/analytic' import { DashboardReport, PaymentReport, ProductSalesReport, ProfitLossReport, SalesReport } from '../../types/services/analytic'
import { api } from '../api' import { api } from '../api'
import { formatDateDDMMYYYY } from '../../utils/transform' import { formatDateDDMMYYYY } from '../../utils/transform'
@ -10,11 +10,11 @@ interface AnalyticQueryParams {
export function useSalesAnalytics(params: AnalyticQueryParams = {}) { export function useSalesAnalytics(params: AnalyticQueryParams = {}) {
const today = new Date() const today = new Date()
const sevenDaysAgo = new Date() const monthAgo = new Date()
sevenDaysAgo.setDate(today.getDate() - 30) monthAgo.setDate(today.getDate() - 30)
const defaultDateTo = formatDateDDMMYYYY(today) const defaultDateTo = formatDateDDMMYYYY(today)
const defaultDateFrom = formatDateDDMMYYYY(sevenDaysAgo) const defaultDateFrom = formatDateDDMMYYYY(monthAgo)
const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params
@ -40,11 +40,11 @@ export function useSalesAnalytics(params: AnalyticQueryParams = {}) {
export function useProductSalesAnalytics(params: AnalyticQueryParams = {}) { export function useProductSalesAnalytics(params: AnalyticQueryParams = {}) {
const today = new Date() const today = new Date()
const sevenDaysAgo = new Date() const monthAgo = new Date()
sevenDaysAgo.setDate(today.getDate() - 30) monthAgo.setDate(today.getDate() - 30)
const defaultDateTo = formatDateDDMMYYYY(today) const defaultDateTo = formatDateDDMMYYYY(today)
const defaultDateFrom = formatDateDDMMYYYY(sevenDaysAgo) const defaultDateFrom = formatDateDDMMYYYY(monthAgo)
const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params
@ -67,3 +67,93 @@ export function useProductSalesAnalytics(params: AnalyticQueryParams = {}) {
} }
}) })
} }
export function usePaymentAnalytics(params: AnalyticQueryParams = {}) {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const defaultDateTo = formatDateDDMMYYYY(today)
const defaultDateFrom = formatDateDDMMYYYY(monthAgo)
const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params
return useQuery<PaymentReport>({
queryKey: ['analytics-payment-methods', { date_from, date_to, ...filters }],
queryFn: async () => {
const queryParams = new URLSearchParams()
queryParams.append('date_from', date_from)
queryParams.append('date_to', date_to)
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, value.toString())
}
})
const res = await api.get(`/analytics/payment-methods?${queryParams.toString()}`)
return res.data.data
}
})
}
export function useDashboardAnalytics(params: AnalyticQueryParams = {}) {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const defaultDateTo = formatDateDDMMYYYY(today)
const defaultDateFrom = formatDateDDMMYYYY(monthAgo)
const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params
return useQuery<DashboardReport>({
queryKey: ['analytics-dashboard', { date_from, date_to, ...filters }],
queryFn: async () => {
const queryParams = new URLSearchParams()
queryParams.append('date_from', date_from)
queryParams.append('date_to', date_to)
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, value.toString())
}
})
const res = await api.get(`/analytics/dashboard?${queryParams.toString()}`)
return res.data.data
}
})
}
export function useProfitLossAnalytics(params: AnalyticQueryParams = {}) {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 30)
const defaultDateTo = formatDateDDMMYYYY(today)
const defaultDateFrom = formatDateDDMMYYYY(monthAgo)
const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params
return useQuery<ProfitLossReport>({
queryKey: ['analytics-profit-loss', { date_from, date_to, ...filters }],
queryFn: async () => {
const queryParams = new URLSearchParams()
queryParams.append('date_from', date_from)
queryParams.append('date_to', date_to)
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, value.toString())
}
})
const res = await api.get(`/analytics/profit-loss?${queryParams.toString()}`)
return res.data.data
}
})
}

View File

@ -46,3 +46,112 @@ export interface ProductSalesReport {
date_to: string date_to: string
data: ProductData[] data: ProductData[]
} }
export type PaymentDataItem = {
payment_method_id: string
payment_method_name: string
payment_method_type: string
total_amount: number
order_count: number
payment_count: number
percentage: number
}
export type SummaryData = {
total_amount: number
total_orders: number
total_payments: number
average_order_value: number
}
export type PaymentReport = {
organization_id: string
outlet_id: string
date_from: string
date_to: string
group_by: string
summary: SummaryData
data: PaymentDataItem[]
}
export type Overview = {
total_sales: number
total_orders: number
average_order_value: number
total_customers: number
voided_orders: number
refunded_orders: number
}
export type RecentSale = {
date: string
sales: number
orders: number
items: number
tax: number
discount: number
net_sales: number
}
export type DashboardReport = {
organization_id: string
outlet_id: string
date_from: string
date_to: string
overview: Overview
top_products: ProductData[]
payment_methods: PaymentDataItem[]
recent_sales: RecentSale[]
}
export interface ProfitLossReport {
organization_id: string;
date_from: string; // ISO date string with timezone
date_to: string; // ISO date string with timezone
group_by: string;
summary: Summary;
data: DailyData[];
product_data: ProductDataReport[];
}
export interface Summary {
total_revenue: number;
total_cost: number;
gross_profit: number;
gross_profit_margin: number;
total_tax: number;
total_discount: number;
net_profit: number;
net_profit_margin: number;
total_orders: number;
average_profit: number;
profitability_ratio: number;
}
export interface DailyData {
date: string; // ISO date string with timezone
revenue: number;
cost: number;
gross_profit: number;
gross_profit_margin: number;
tax: number;
discount: number;
net_profit: number;
net_profit_margin: number;
orders: number;
}
export interface ProductDataReport {
product_id: string;
product_name: string;
category_id: string;
category_name: string;
quantity_sold: number;
revenue: number;
cost: number;
gross_profit: number;
gross_profit_margin: number;
average_price: number;
average_cost: number;
profit_per_unit: number;
}

View File

@ -36,3 +36,12 @@ export type Ingredients = {
data: IngredientItem[] data: IngredientItem[]
pagination: Pagination pagination: Pagination
} }
export type IngredientRequest = {
name: string
unit_id: string
cost: number
stock: number
is_semi_finished: boolean
outlet_id: string
}

View File

@ -6,6 +6,15 @@ export const formatCurrency = (amount: number) => {
}).format(amount) }).format(amount)
} }
export const formatShortCurrency = (num: number): string => {
if (num >= 1_000_000) {
return (num / 1_000_000).toFixed(2) + 'M'
} else if (num >= 1_000) {
return (num / 1_000).toFixed(2) + 'k'
}
return num.toString()
}
export const formatDate = (dateString: string) => { export const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', { return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric', year: 'numeric',
@ -17,8 +26,8 @@ export const formatDate = (dateString: string) => {
} }
export const formatDateDDMMYYYY = (date: Date) => { export const formatDateDDMMYYYY = (date: Date) => {
const day = String(date.getDate()).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0')
const month = String(date.getMonth() + 1).padStart(2, '0') const month = String(date.getMonth() + 1).padStart(2, '0')
const year = date.getFullYear() const year = date.getFullYear()
return `${day}-${month}-${year}` return `${day}-${month}-${year}`
} }

View File

@ -1,413 +0,0 @@
'use client'
// React Imports
import { useEffect, useMemo, useState } from 'react'
// Next Imports
import Link from 'next/link'
import { useParams } from 'next/navigation'
// MUI Imports
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip'
import MenuItem from '@mui/material/MenuItem'
import Rating from '@mui/material/Rating'
import type { TextFieldProps } from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
// Third-party Imports
import type { RankingInfo } from '@tanstack/match-sorter-utils'
import { rankItem } from '@tanstack/match-sorter-utils'
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getFacetedMinMaxValues,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table'
import classnames from 'classnames'
// Type Imports
import type { ReviewType } from '@/types/apps/ecommerceTypes'
import type { Locale } from '@configs/i18n'
// Component Imports
import CustomAvatar from '@core/components/mui/Avatar'
import CustomTextField from '@core/components/mui/TextField'
import OptionMenu from '@core/components/option-menu'
// Util Imports
import { getLocalizedUrl } from '@/utils/i18n'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
declare module '@tanstack/table-core' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
type ReviewWithActionsType = ReviewType & {
actions?: string
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number
onChange: (value: string | number) => void
debounce?: number
} & Omit<TextFieldProps, 'onChange'>) => {
// States
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value)
}, debounce)
return () => clearTimeout(timeout)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
}
// Column Definitions
const columnHelper = createColumnHelper<ReviewWithActionsType>()
const ManageReviewsTable = ({ reviewsData }: { reviewsData?: ReviewType[] }) => {
// States
const [status, setStatus] = useState<ReviewType['status']>('All')
const [rowSelection, setRowSelection] = useState({})
const [allData, setAllData] = useState(...[reviewsData])
const [data, setData] = useState(allData)
const [globalFilter, setGlobalFilter] = useState('')
// Hooks
const { lang: locale } = useParams()
const columns = useMemo<ColumnDef<ReviewWithActionsType, any>[]>(
() => [
{
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler()
}}
/>
),
cell: ({ row }) => (
<Checkbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
)
},
columnHelper.accessor('product', {
header: 'Product',
cell: ({ row }) => (
<div className='flex items-center gap-4'>
<img src={row.original.productImage} width={38} height={38} className='rounded bg-actionHover' />
<div className='flex flex-col items-start'>
<Typography className='font-medium' color='text.primary'>
{row.original.product}
</Typography>
<Typography variant='body2' className='text-wrap'>
{row.original.companyName}
</Typography>
</div>
</div>
)
}),
columnHelper.accessor('reviewer', {
header: 'Reviewer',
cell: ({ row }) => (
<div className='flex items-center gap-4'>
<CustomAvatar src={row.original.avatar} size={34} />
<div className='flex flex-col items-start'>
<Typography
component={Link}
href={getLocalizedUrl('/apps/ecommerce/customers/details/879861', locale as Locale)}
color='primary.main'
className='font-medium'
>
{row.original.reviewer}
</Typography>
<Typography variant='body2'>{row.original.email}</Typography>
</div>
</div>
)
}),
columnHelper.accessor('head', {
header: 'Review',
sortingFn: (rowA, rowB) => rowA.original.review - rowB.original.review,
cell: ({ row }) => (
<div className='flex flex-col gap-1'>
<Rating
name='product-review'
readOnly
value={row.original.review}
emptyIcon={<i className='tabler-star-filled' />}
/>
<Typography className='font-medium' color='text.primary'>
{row.original.head}
</Typography>
<Typography variant='body2' className='text-wrap'>
{row.original.para}
</Typography>
</div>
)
}),
columnHelper.accessor('date', {
header: 'Date',
sortingFn: (rowA, rowB) => {
const dateA = new Date(rowA.original.date)
const dateB = new Date(rowB.original.date)
return dateA.getTime() - dateB.getTime()
},
cell: ({ row }) => {
const date = new Date(row.original.date).toLocaleDateString('en-US', {
month: 'short',
day: '2-digit',
year: 'numeric'
})
return <Typography>{date}</Typography>
}
}),
columnHelper.accessor('status', {
header: 'Status',
cell: ({ row }) => (
<div className='flex items-center gap-3'>
<Chip
label={row.original.status}
variant='tonal'
color={row.original.status === 'Published' ? 'success' : 'warning'}
size='small'
/>
</div>
)
}),
columnHelper.accessor('actions', {
header: 'Actions',
cell: ({ row }) => (
<OptionMenu
iconButtonProps={{ size: 'medium' }}
iconClassName='text-textSecondary'
options={[
{
text: 'View',
icon: 'tabler-eye',
href: getLocalizedUrl('/apps/ecommerce/orders/details/5434', locale as Locale),
linkProps: { className: 'flex items-center gap-2 is-full plb-2 pli-4' }
},
{
text: 'Delete',
icon: 'tabler-trash',
menuItemProps: {
onClick: () => setAllData(allData?.filter(review => review.id !== row.original.id)),
className: 'flex items-center'
}
}
]}
/>
),
enableSorting: false
})
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[data]
)
const table = useReactTable({
data: data as ReviewType[],
columns,
filterFns: {
fuzzy: fuzzyFilter
},
state: {
rowSelection,
globalFilter
},
initialState: {
pagination: {
pageSize: 10
}
},
enableRowSelection: true, //enable row selection for all rows
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
globalFilterFn: fuzzyFilter,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
onGlobalFilterChange: setGlobalFilter,
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues()
})
useEffect(() => {
const filteredData = allData?.filter(review => {
if (status !== 'All' && review.status !== status) return false
return true
})
setData(filteredData)
}, [status, allData, setData])
return (
<>
<Card>
<div className='flex flex-wrap justify-between gap-4 p-6'>
<DebouncedInput
value={globalFilter ?? ''}
onChange={value => setGlobalFilter(String(value))}
placeholder='Search Product'
className='max-sm:is-full'
/>
<div className='flex max-sm:flex-col sm:items-center gap-4 max-sm:is-full'>
<CustomTextField
select
value={table.getState().pagination.pageSize}
onChange={e => table.setPageSize(Number(e.target.value))}
className='sm:is-[140px] flex-auto is-full'
>
<MenuItem value='10'>10</MenuItem>
<MenuItem value='25'>25</MenuItem>
<MenuItem value='50'>50</MenuItem>
</CustomTextField>
<CustomTextField
select
fullWidth
value={status}
onChange={e => setStatus(e.target.value)}
className='is-full sm:is-[140px] flex-auto'
>
<MenuItem value='All'>All</MenuItem>
<MenuItem value='Published'>Published</MenuItem>
<MenuItem value='Pending'>Pending</MenuItem>
</CustomTextField>
<Button
variant='tonal'
className='max-sm:is-full'
startIcon={<i className='tabler-upload' />}
color='secondary'
>
Export
</Button>
</div>
</div>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
)}
</table>
</div>
{/* <TablePagination
component={() => <TablePaginationComponent table={table} />}
count={table.getFilteredRowModel().rows.length}
rowsPerPage={table.getState().pagination.pageSize}
page={table.getState().pagination.pageIndex}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
/> */}
</Card>
</>
)
}
export default ManageReviewsTable

View File

@ -1,127 +0,0 @@
'use client'
// Next Imports
import dynamic from 'next/dynamic'
// MUI Imports
import Grid from '@mui/material/Grid2'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Chip from '@mui/material/Chip'
import Typography from '@mui/material/Typography'
import { useTheme } from '@mui/material/styles'
// Third-party Imports
import type { ApexOptions } from 'apexcharts'
// Styled Component Imports
const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts'))
// Vars
const series = [{ data: [32, 52, 72, 94, 116, 94, 72] }]
const ReviewsStatistics = () => {
// Hook
const theme = useTheme()
// Vars
const successLightOpacity = 'var(--mui-palette-success-lightOpacity)'
const options: ApexOptions = {
chart: {
parentHeightOffset: 0,
toolbar: { show: false }
},
plotOptions: {
bar: {
borderRadius: 6,
distributed: true,
columnWidth: '40%'
}
},
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
colors: [
successLightOpacity,
successLightOpacity,
successLightOpacity,
successLightOpacity,
'var(--mui-palette-success-main)',
successLightOpacity,
successLightOpacity
],
states: {
hover: {
filter: { type: 'none' }
},
active: {
filter: { type: 'none' }
}
},
grid: {
show: false,
padding: {
top: -30,
left: 0,
right: 0,
bottom: -12
}
},
xaxis: {
categories: ['M', 'T', 'W', 'T', 'F', 'S', 'S'],
axisTicks: { show: false },
axisBorder: { show: false },
tickPlacement: 'on',
labels: {
style: {
colors: 'var(--mui-palette-text-disabled)',
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.body2.fontSize as string
}
}
},
yaxis: { show: false },
responsive: [
{
breakpoint: 600,
options: {
chart: {
width: 275
}
}
}
]
}
return (
<Card>
<CardContent>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6 }}>
<div className='bs-full flex flex-col items-start justify-between gap-6'>
<div className='flex flex-col items-start gap-2'>
<Typography variant='h5'>Reviews statistics</Typography>
<div className='flex items-center gap-2'>
<Typography>12 New reviews</Typography>
<Chip label='+8.4%' variant='tonal' size='small' color='success' />
</div>
</div>
<div className='flex flex-col items-start gap-2'>
<Typography color='text.primary'>
<span className='text-success'>87%</span> Positive reviews
</Typography>
<Typography variant='body2'>Weekly Report</Typography>
</div>
</div>
</Grid>
<Grid size={{ xs: 12, sm: 6 }} className='flex justify-center'>
<AppReactApexCharts type='bar' width='100%' height={156} series={series} options={options} />
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default ReviewsStatistics

View File

@ -1,72 +0,0 @@
'use client'
// MUI Imports
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Chip from '@mui/material/Chip'
import Divider from '@mui/material/Divider'
import LinearProgress from '@mui/material/LinearProgress'
import Typography from '@mui/material/Typography'
import useMediaQuery from '@mui/material/useMediaQuery'
import { useTheme } from '@mui/material/styles'
type DataType = {
rating: number
value: number
}
// Vars
const totalReviewsData: DataType[] = [
{ rating: 5, value: 109 },
{ rating: 4, value: 40 },
{ rating: 3, value: 18 },
{ rating: 2, value: 12 },
{ rating: 1, value: 8 }
]
const TotalReviews = () => {
// Hooks
const theme = useTheme()
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'))
return (
<Card className='bs-full'>
<CardContent>
<div className='flex max-sm:flex-col items-center gap-6'>
<div className='flex flex-col items-start gap-2 is-full sm:is-6/12'>
<div className='flex items-center gap-2'>
<Typography variant='h3' color='primary.main'>
4.89
</Typography>
<i className='tabler-star-filled text-[32px] text-primary' />
</div>
<Typography className='font-medium' color='text.primary'>
Total 187 reviews
</Typography>
<Typography>All reviews are from genuine customers</Typography>
<Chip label='+5 This week' variant='tonal' size='small' color='primary' />
</div>
<Divider orientation={isSmallScreen ? 'horizontal' : 'vertical'} flexItem />
<div className='flex flex-col gap-3 is-full sm:is-6/12'>
{totalReviewsData.map((item, index) => (
<div key={index} className='flex items-center gap-2'>
<Typography variant='body2' className='text-nowrap'>
{item.rating} Star
</Typography>
<LinearProgress
color='primary'
value={Math.floor((item.value / 185) * 100)}
variant='determinate'
className='bs-2 is-full'
/>
<Typography variant='body2'>{item.value}</Typography>
</div>
))}
</div>
</div>
</CardContent>
</Card>
)
}
export default TotalReviews

View File

@ -0,0 +1,265 @@
// React Imports
import { useEffect, useMemo, useState } from 'react'
// MUI Imports
import Button from '@mui/material/Button'
import Divider from '@mui/material/Divider'
import Drawer from '@mui/material/Drawer'
import IconButton from '@mui/material/IconButton'
import Switch from '@mui/material/Switch'
import Typography from '@mui/material/Typography'
// Third-party Imports
import PerfectScrollbar from 'react-perfect-scrollbar'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from '../../../../../redux-store'
import { useIngredientsMutation } from '../../../../../services/mutations/ingredients'
import { IngredientRequest } from '../../../../../types/services/ingredient'
import { Autocomplete, CircularProgress } from '@mui/material'
import { useOutlets } from '../../../../../services/queries/outlets'
import { useDebounce } from 'use-debounce'
import { useUnits } from '../../../../../services/queries/units'
import { resetIngredient } from '../../../../../redux-store/slices/ingredient'
type Props = {
open: boolean
handleClose: () => void
}
// Vars
const initialData = {
name: '',
unit_id: '',
cost: 0,
stock: 0,
is_semi_finished: false,
outlet_id: ''
}
const AddProductIngredientDrawer = (props: Props) => {
const dispatch = useDispatch()
// Props
const { open, handleClose } = props
const { createIngredient, updateIngredient } = useIngredientsMutation()
const { currentIngredient } = useSelector((state: RootState) => state.ingredientReducer)
// States
const [formData, setFormData] = useState<IngredientRequest>(initialData)
const [outletInput, setOutletInput] = useState('')
const [outletDebouncedInput] = useDebounce(outletInput, 500)
const [unitInput, setUnitInput] = useState('')
const [unitDebouncedInput] = useDebounce(unitInput, 500)
const { data: outlets, isLoading: outletsLoading } = useOutlets({
search: outletDebouncedInput
})
const { data: units, isLoading: unitsLoading } = useUnits({
search: unitDebouncedInput
})
const outletOptions = useMemo(() => outlets?.outlets || [], [outlets])
const unitOptions = useMemo(() => units?.data || [], [units])
useEffect(() => {
if (currentIngredient.id) {
setFormData(currentIngredient)
}
}, [currentIngredient])
const handleSubmit = (e: any) => {
e.preventDefault()
const { stock, cost, ...rest } = formData
const payload = {
...rest,
stock: Number(stock),
cost: Number(cost)
}
if (currentIngredient.id) {
updateIngredient.mutate(
{ id: currentIngredient.id, payload },
{
onSuccess: () => {
handleReset()
}
}
)
} else {
createIngredient.mutate(payload, {
onSuccess: () => {
handleReset()
}
})
}
}
const handleReset = () => {
handleClose()
dispatch(resetIngredient())
setFormData(initialData)
}
const handleInputChange = (e: any) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
>
<div className='flex items-center justify-between pli-6 plb-5'>
<Typography variant='h5'>{currentIngredient.id ? 'Edit' : 'Add'} Ingredient</Typography>
<IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-2xl' />
</IconButton>
</div>
<Divider />
<PerfectScrollbar options={{ wheelPropagation: false, suppressScrollX: true }}>
<div className='p-6'>
<form onSubmit={handleSubmit} className='flex flex-col gap-5'>
<Typography color='text.primary' className='font-medium'>
Basic Information
</Typography>
<CustomTextField
fullWidth
label='Name'
name='name'
placeholder='John Doe'
value={formData.name}
onChange={handleInputChange}
/>
<Autocomplete
options={outletOptions}
loading={outletsLoading}
getOptionLabel={option => option.name}
value={outletOptions.find(p => p.id === formData.outlet_id) || null}
onInputChange={(event, newOutlettInput) => {
setOutletInput(newOutlettInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
outlet_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Outlet'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{outletsLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<Autocomplete
options={unitOptions}
loading={unitsLoading}
getOptionLabel={option => option.name}
value={unitOptions.find(p => p.id === formData.unit_id) || null}
onInputChange={(event, newUnitInput) => {
setUnitInput(newUnitInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
unit_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Unit'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{unitsLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<CustomTextField
fullWidth
type='number'
label='Cost'
name='cost'
placeholder='$499'
value={formData.cost}
onChange={handleInputChange}
/>
<CustomTextField
label='Stock'
type='number'
fullWidth
placeholder='100'
name='stock'
value={formData.stock}
onChange={handleInputChange}
/>
<div className='flex items-center'>
<div className='flex flex-col items-start gap-1'>
<Typography color='text.primary' className='font-medium'>
Semi Finished
</Typography>
</div>
<Switch
checked={formData.is_semi_finished}
name='is_semi_finished'
onChange={e => setFormData({ ...formData, is_semi_finished: e.target.checked })}
/>
</div>
<div className='flex items-center gap-4'>
<Button
variant='contained'
type='submit'
disabled={createIngredient.isPending || updateIngredient.isPending}
>
{currentIngredient.id
? updateIngredient.isPending
? 'Updating...'
: 'Update'
: createIngredient.isPending
? 'Creating...'
: 'Create'}
</Button>
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
Discard
</Button>
</div>
</form>
</div>
</PerfectScrollbar>
</Drawer>
)
}
export default AddProductIngredientDrawer

View File

@ -34,6 +34,10 @@ import { useUnitsMutation } from '../../../../../services/mutations/units'
import { useIngredients } from '../../../../../services/queries/ingredients' import { useIngredients } from '../../../../../services/queries/ingredients'
import { IngredientItem } from '../../../../../types/services/ingredient' import { IngredientItem } from '../../../../../types/services/ingredient'
import { formatCurrency } from '../../../../../utils/transform' import { formatCurrency } from '../../../../../utils/transform'
import AddProductIngredientDrawer from './AddProductIngredientDrawer'
import { useIngredientsMutation } from '../../../../../services/mutations/ingredients'
import { useDispatch } from 'react-redux'
import { setIngredient } from '../../../../../redux-store/slices/ingredient'
declare module '@tanstack/table-core' { declare module '@tanstack/table-core' {
interface FilterFns { interface FilterFns {
@ -94,13 +98,15 @@ const DebouncedInput = ({
const columnHelper = createColumnHelper<IngredientWithActionsType>() const columnHelper = createColumnHelper<IngredientWithActionsType>()
const ProductIngredientTable = () => { const ProductIngredientTable = () => {
const dispatch = useDispatch()
// States // States
const [addUnitOpen, setAddUnitOpen] = useState(false) const [addIngredientOpen, setAddIngredientOpen] = useState(false)
const [editUnitOpen, setEditUnitOpen] = useState(false) const [editUnitOpen, setEditUnitOpen] = useState(false)
const [rowSelection, setRowSelection] = useState({}) const [rowSelection, setRowSelection] = useState({})
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(10) const [pageSize, setPageSize] = useState(10)
const [unitId, setUnitId] = useState('') const [ingredientId, setIngredientId] = useState('')
const [openConfirm, setOpenConfirm] = useState(false) const [openConfirm, setOpenConfirm] = useState(false)
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
@ -111,7 +117,7 @@ const ProductIngredientTable = () => {
search search
}) })
const { mutate: deleteUnit, isPending: isDeleting } = useUnitsMutation().deleteUnit const { deleteIngredient } = useIngredientsMutation()
const ingredients = data?.data ?? [] const ingredients = data?.data ?? []
const totalCount = data?.pagination.total_count ?? 0 const totalCount = data?.pagination.total_count ?? 0
@ -128,7 +134,7 @@ const ProductIngredientTable = () => {
}, []) }, [])
const handleDelete = () => { const handleDelete = () => {
deleteUnit(unitId, { deleteIngredient.mutate(ingredientId, {
onSuccess: () => setOpenConfirm(false) onSuccess: () => setOpenConfirm(false)
}) })
} }
@ -210,7 +216,8 @@ const ProductIngredientTable = () => {
<div className='flex items-center'> <div className='flex items-center'>
<IconButton <IconButton
onClick={() => { onClick={() => {
setEditUnitOpen(!editUnitOpen) setAddIngredientOpen(!editUnitOpen)
dispatch(setIngredient(row.original))
}} }}
> >
<i className='tabler-edit text-textSecondary' /> <i className='tabler-edit text-textSecondary' />
@ -225,7 +232,7 @@ const ProductIngredientTable = () => {
icon: 'tabler-trash', icon: 'tabler-trash',
menuItemProps: { menuItemProps: {
onClick: () => { onClick: () => {
setUnitId(row.original.id) setIngredientId(row.original.id)
setOpenConfirm(true) setOpenConfirm(true)
} }
} }
@ -287,7 +294,7 @@ const ProductIngredientTable = () => {
<Button <Button
variant='contained' variant='contained'
className='max-sm:is-full' className='max-sm:is-full'
onClick={() => setAddUnitOpen(!addUnitOpen)} onClick={() => setAddIngredientOpen(!addIngredientOpen)}
startIcon={<i className='tabler-plus' />} startIcon={<i className='tabler-plus' />}
> >
Add Ingredient Add Ingredient
@ -389,17 +396,15 @@ const ProductIngredientTable = () => {
/> />
</Card> </Card>
{/* <AddUnitDrawer open={addUnitOpen} handleClose={() => setAddUnitOpen(!addUnitOpen)} /> <AddProductIngredientDrawer open={addIngredientOpen} handleClose={() => setAddIngredientOpen(!addIngredientOpen)} />
<EditUnitDrawer open={editUnitOpen} handleClose={() => setEditUnitOpen(!editUnitOpen)} data={currentUnit!} /> */}
<ConfirmDeleteDialog <ConfirmDeleteDialog
open={openConfirm} open={openConfirm}
onClose={() => setOpenConfirm(false)} onClose={() => setOpenConfirm(false)}
onConfirm={handleDelete} onConfirm={handleDelete}
isLoading={isDeleting} isLoading={deleteIngredient.isPending}
title='Delete Unit' title='Delete Ingredient'
message='Are you sure you want to delete this Unit? This action cannot be undone.' message='Are you sure you want to delete this Ingredient? This action cannot be undone.'
/> />
</> </>
) )

View File

@ -1,24 +0,0 @@
// MUI Imports
import Grid from '@mui/material/Grid2'
// Types Imports
import type { CardStatsHorizontalWithAvatarProps } from '@/types/pages/widgetTypes'
// Component Imports
import CardStatsHorizontalWithAvatar from '@components/card-statistics/HorizontalWithAvatar'
const HorizontalStatisticsCard = ({ data }: { data?: CardStatsHorizontalWithAvatarProps[] }) => {
return (
data && (
<Grid container spacing={6}>
{data.map((item, index) => (
<Grid key={index} size={{ xs: 12, sm: 6, md: 3 }}>
<CardStatsHorizontalWithAvatar {...item} avatarSkin='light' />
</Grid>
))}
</Grid>
)
)
}
export default HorizontalStatisticsCard

View File

@ -1,101 +0,0 @@
// React Imports
import type { ReactNode } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
type DataType = {
description: string
value: string
icon: ReactNode
}
// Vars
const data: DataType[] = [
{
description: 'Create & validate your referral link and get',
value: '$50',
icon: (
<svg xmlns='http://www.w3.org/2000/svg' width='42' height='42' viewBox='0 0 43 42' fill='none'>
<path
opacity='0.2'
fillRule='evenodd'
clipRule='evenodd'
d='M35.5943 24.3473L30.4428 18.1621C30.6396 21.952 29.7045 26.3652 26.817 31.4019L31.7389 35.3394C31.9139 35.4784 32.1215 35.5704 32.342 35.6067C32.5625 35.643 32.7887 35.6224 32.999 35.5468C33.2093 35.4712 33.3968 35.3432 33.5438 35.1748C33.6908 35.0065 33.7923 34.8034 33.8389 34.5848L35.8568 25.4629C35.9055 25.2695 35.907 25.0672 35.8614 24.8731C35.8157 24.679 35.7241 24.4987 35.5943 24.3473ZM7.63806 24.4457L12.7896 18.277C12.5927 22.0668 13.5279 26.4801 16.4154 31.5004L11.4935 35.4379C11.3196 35.5769 11.1132 35.6693 10.8937 35.7065C10.6743 35.7437 10.4489 35.7245 10.2389 35.6507C10.0289 35.5769 9.84115 35.4509 9.69326 35.2845C9.54537 35.1181 9.44223 34.9168 9.39353 34.6996L7.37556 25.5613C7.32691 25.3679 7.32536 25.1657 7.37104 24.9716C7.41671 24.7775 7.50828 24.5971 7.63806 24.4457Z'
fill='currentColor'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M20.2132 2.47353C20.6217 2.13596 21.1351 1.95117 21.6653 1.95117C22.1971 1.95117 22.7121 2.13707 23.1212 2.47657C24.7301 3.7867 28.2128 7.0357 30.1373 12.0381C30.798 13.7554 31.2701 15.6673 31.4253 17.7625L36.4572 23.8008C36.6883 24.0724 36.8515 24.3951 36.9332 24.7424C37.0142 25.0868 37.0128 25.4453 36.9291 25.7889L34.9138 34.9151L34.9131 34.9182C34.8273 35.3009 34.6455 35.6555 34.385 35.9487C34.1244 36.2418 33.7936 36.4639 33.4236 36.5939C33.0535 36.724 32.6565 36.7579 32.2698 36.6923C31.8831 36.6267 31.5195 36.4638 31.2131 36.219L31.2126 36.2186L26.5646 32.5002H16.7662L12.1182 36.2186L12.1177 36.219C11.8113 36.4638 11.4477 36.6267 11.061 36.6923C10.6743 36.7579 10.2773 36.724 9.90727 36.5939C9.53726 36.4639 9.20641 36.2418 8.94584 35.9487C8.68527 35.6555 8.50355 35.3009 8.41775 34.9182L8.41706 34.9151L6.40177 25.7889C6.31804 25.4453 6.31658 25.0868 6.39762 24.7424C6.47936 24.395 6.64268 24.072 6.87401 23.8004L11.8088 17.8912C11.9513 15.7424 12.4328 13.7842 13.1145 12.029C15.058 7.02537 18.5854 3.77698 20.2132 2.47353ZM29.4558 18.3076C29.4446 18.2387 29.4406 18.169 29.4438 18.0996C29.3181 16.1202 28.879 14.3374 28.2707 12.7563C26.5219 8.21065 23.3318 5.22661 21.8544 4.02428L21.8446 4.01626L21.8446 4.01619C21.7943 3.97418 21.7309 3.95117 21.6653 3.95117C21.5998 3.95117 21.5363 3.97418 21.486 4.01619L21.4697 4.02954C19.9771 5.22365 16.7444 8.20744 14.9788 12.7531C14.3468 14.3804 13.8969 16.2219 13.7896 18.2718C13.7898 18.3084 13.788 18.345 13.7842 18.3815C13.6245 21.8411 14.4416 25.8898 16.9985 30.5002H26.3263C28.8486 25.8569 29.6366 21.7836 29.4558 18.3076ZM34.9245 25.0857L31.4177 20.8775C31.1755 24.0045 30.2142 27.4702 28.197 31.2448L32.4615 34.6565C32.5029 34.6896 32.5521 34.7116 32.6043 34.7204C32.6566 34.7293 32.7102 34.7247 32.7602 34.7071C32.8102 34.6896 32.8549 34.6596 32.8901 34.6199C32.9251 34.5806 32.9496 34.533 32.9613 34.4817L32.9615 34.4807L34.9788 25.3455C34.9809 25.3361 34.9831 25.3266 34.9855 25.3172C34.9951 25.2789 34.9954 25.2389 34.9864 25.2004C34.9773 25.162 34.9592 25.1263 34.9335 25.0963L34.9245 25.0858L34.9245 25.0857ZM11.8405 20.9734L8.40561 25.0865L8.39739 25.0964L8.39732 25.0963C8.37163 25.1263 8.3535 25.162 8.34445 25.2004C8.33541 25.2389 8.33572 25.2789 8.34535 25.3172C8.34772 25.3266 8.34995 25.3361 8.35204 25.3455L10.3693 34.4807L10.3695 34.4817C10.3812 34.533 10.4057 34.5806 10.4407 34.6199C10.4759 34.6596 10.5206 34.6896 10.5706 34.7071C10.6206 34.7247 10.6743 34.7293 10.7265 34.7204C10.7788 34.7116 10.8279 34.6896 10.8693 34.6565L15.1281 31.2495C13.0909 27.5131 12.1056 24.0779 11.8405 20.9734ZM18.0404 36.7502C18.0404 36.1979 18.4881 35.7502 19.0404 35.7502H24.2904C24.8427 35.7502 25.2904 36.1979 25.2904 36.7502C25.2904 37.3025 24.8427 37.7502 24.2904 37.7502H19.0404C18.4881 37.7502 18.0404 37.3025 18.0404 36.7502ZM23.6342 15.7502C23.6342 16.8375 22.7527 17.719 21.6654 17.719C20.5781 17.719 19.6967 16.8375 19.6967 15.7502C19.6967 14.6629 20.5781 13.7815 21.6654 13.7815C22.7527 13.7815 23.6342 14.6629 23.6342 15.7502Z'
fill='currentColor'
/>
</svg>
)
},
{
description: 'For every new signup you get',
value: '10%',
icon: (
<svg xmlns='http://www.w3.org/2000/svg' width='42' height='42' viewBox='0 0 42 42' fill='none'>
<path
opacity='0.2'
d='M9.1875 6.25H32.8125C32.8954 6.25 32.9749 6.28292 33.0335 6.34153L33.739 5.63603L33.0335 6.34153C33.0921 6.40013 33.125 6.47962 33.125 6.5625V35.4375C33.125 35.5204 33.0921 35.5999 33.0335 35.6585L33.7406 36.3656L33.0335 35.6585C32.9749 35.7171 32.8954 35.75 32.8125 35.75H9.1875C9.10462 35.75 9.02513 35.7171 8.96653 35.6585L8.25942 36.3656L8.96653 35.6585C8.90792 35.5999 8.875 35.5204 8.875 35.4375V6.5625C8.875 6.47962 8.90792 6.40014 8.96653 6.34153C9.02514 6.28292 9.10462 6.25 9.1875 6.25ZM17.5277 27.5092C18.5555 28.1959 19.7639 28.5625 21 28.5625C22.6576 28.5625 24.2473 27.904 25.4194 26.7319C26.5915 25.5598 27.25 23.9701 27.25 22.3125C27.25 21.0764 26.8834 19.868 26.1967 18.8402C25.5099 17.8124 24.5338 17.0113 23.3918 16.5383C22.2497 16.0652 20.9931 15.9414 19.7807 16.1826C18.5683 16.4237 17.4547 17.019 16.5806 17.8931C15.7065 18.7672 15.1112 19.8808 14.8701 21.0932C14.6289 22.3056 14.7527 23.5622 15.2258 24.7043C15.6988 25.8463 16.4999 26.8224 17.5277 27.5092Z'
fill='currentColor'
stroke='currentColor'
strokeWidth='2'
/>
<path
d='M21 27.5625C23.8995 27.5625 26.25 25.212 26.25 22.3125C26.25 19.413 23.8995 17.0625 21 17.0625C18.1005 17.0625 15.75 19.413 15.75 22.3125C15.75 25.212 18.1005 27.5625 21 27.5625ZM21 27.5625C19.4718 27.5625 17.9646 27.9183 16.5977 28.6017C15.2309 29.2852 14.0419 30.2774 13.125 31.5M21 27.5625C22.5282 27.5625 24.0354 27.9183 25.4023 28.6017C26.7691 29.2852 27.9581 30.2774 28.875 31.5M15.75 10.5H26.25M34.125 6.5625V35.4375C34.125 36.1624 33.5374 36.75 32.8125 36.75H9.1875C8.46263 36.75 7.875 36.1624 7.875 35.4375V6.5625C7.875 5.83763 8.46263 5.25 9.1875 5.25H32.8125C33.5374 5.25 34.125 5.83763 34.125 6.5625Z'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
)
},
{
description: 'Get other friends to generate link and get',
value: '$100',
icon: (
<svg xmlns='http://www.w3.org/2000/svg' width='42' height='42' viewBox='0 0 43 42' fill='none'>
<path
opacity='0.2'
d='M34.8347 5.89001L4.25347 14.5033C3.99315 14.5745 3.76109 14.7242 3.58892 14.932C3.41674 15.1398 3.31281 15.3956 3.29129 15.6647C3.26977 15.9337 3.3317 16.2028 3.46865 16.4353C3.60559 16.6679 3.8109 16.8526 4.0566 16.9642L18.1003 23.6088C18.3754 23.7362 18.5964 23.9571 18.7238 24.2322L25.3683 38.2759C25.48 38.5216 25.6647 38.7269 25.8972 38.8639C26.1298 39.0008 26.3989 39.0628 26.6679 39.0412C26.9369 39.0197 27.1927 38.9158 27.4006 38.7436C27.6084 38.5714 27.7581 38.3394 27.8293 38.0791L36.4425 7.49782C36.5078 7.27466 36.5118 7.03804 36.4542 6.81279C36.3966 6.58753 36.2794 6.38192 36.115 6.21751C35.9506 6.0531 35.745 5.93594 35.5198 5.87832C35.2945 5.8207 35.0579 5.82474 34.8347 5.89001Z'
fill='currentColor'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M35.7676 4.90951C35.3704 4.80791 34.9532 4.81452 34.5595 4.92862L3.98975 13.5387L3.98553 13.5398C3.52858 13.6657 3.12129 13.929 2.81886 14.294C2.5155 14.6602 2.33239 15.1109 2.29448 15.5849C2.25656 16.0589 2.36567 16.533 2.60696 16.9428C2.84676 17.35 3.20553 17.6739 3.63489 17.871L17.6727 24.5127L17.6727 24.5127L17.6801 24.5162C17.7402 24.544 17.7885 24.5923 17.8164 24.6524L17.8163 24.6524L17.8199 24.6599L24.4616 38.6978C24.6587 39.1271 24.9826 39.4858 25.3898 39.7256C25.7995 39.9669 26.2736 40.076 26.7476 40.0381C27.2216 40.0001 27.6724 39.817 28.0385 39.5137C28.4036 39.2113 28.6668 38.804 28.7927 38.347L28.7939 38.3428L37.4023 7.77853L37.4039 7.77315C37.518 7.37938 37.5246 6.96221 37.423 6.56497C37.3209 6.16591 37.1134 5.80166 36.8221 5.5104C36.5309 5.21914 36.1666 5.01159 35.7676 4.90951ZM35.1058 6.85256L34.8347 5.89001L35.1154 6.8498C35.1664 6.83489 35.2205 6.83396 35.2719 6.84713C35.3234 6.86029 35.3704 6.88705 35.4079 6.92461C35.4455 6.96218 35.4723 7.00915 35.4854 7.06061C35.4986 7.11207 35.4977 7.16612 35.4827 7.2171L35.4827 7.21709L35.48 7.22671L26.8667 37.808L26.8667 37.808L26.8647 37.8153C26.8477 37.8773 26.8121 37.9326 26.7626 37.9736C26.7131 38.0146 26.6522 38.0393 26.5881 38.0444C26.5241 38.0496 26.46 38.0348 26.4046 38.0022C26.3493 37.9696 26.3053 37.9207 26.2787 37.8622L26.2722 37.8483L19.7287 24.0181L26.6496 17.0971C27.0402 16.7066 27.0402 16.0734 26.6496 15.6829C26.2591 15.2924 25.626 15.2924 25.2354 15.6829L18.3145 22.6038L4.48428 16.0603L4.47032 16.0538C4.41182 16.0272 4.36294 15.9833 4.33033 15.9279C4.29773 15.8725 4.28298 15.8085 4.28811 15.7444C4.29323 15.6803 4.31798 15.6194 4.35897 15.57C4.39996 15.5205 4.45521 15.4848 4.5172 15.4679L4.5172 15.4679L4.52458 15.4658L35.1058 6.85256Z'
fill='currentColor'
/>
</svg>
)
}
]
const IconStepsCard = () => {
return (
<Card>
<CardHeader title='How to use' subheader='Integrate your referral code in 3 easy steps.' className='pbe-6' />
<CardContent className='flex flex-col sm:flex-row items-center justify-around gap-6'>
{data.map((item, index) => (
<div key={index} className='flex flex-col items-center gap-2 max-is-[185px]'>
<div className='flex border border-dashed border-primary rounded-full p-3.5 text-primary'>{item.icon}</div>
<Typography className='text-wrap text-center'>{item.description}</Typography>
<Typography variant='h6' color='primary.main'>
{item.value}
</Typography>
</div>
))}
</CardContent>
</Card>
)
}
export default IconStepsCard

View File

@ -1,55 +0,0 @@
// MUI Imports
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
// Component Imports
import CustomIconButton from '@core/components/mui/IconButton'
import CustomTextField from '@core/components/mui/TextField'
const InviteAndShare = () => {
return (
<Card>
<CardContent className='flex flex-col gap-[1.11937rem]'>
<div>
<Typography variant='h5' className='mbe-5'>
Invite your friends
</Typography>
<div className='flex items-end gap-4'>
<CustomTextField
label="Enter friend's email address & invite them"
placeholder='Email Address'
className='flex-auto'
/>
<Button variant='contained' className='min-is-fit'>
Submit
</Button>
</div>
</div>
<div>
<Typography variant='h5' className='mbe-5'>
Share the referral link
</Typography>
<div className='flex items-end gap-4'>
<CustomTextField
label='Share referral link in social media'
placeholder='pixinvent.com/?ref=6479'
className='flex-auto'
/>
<div className='flex gap-2'>
<CustomIconButton variant='contained' className='bg-facebook text-white'>
<i className='tabler-brand-facebook' />
</CustomIconButton>
<CustomIconButton variant='contained' className='bg-twitter text-white'>
<i className='tabler-brand-twitter' />
</CustomIconButton>
</div>
</div>
</div>
</CardContent>
</Card>
)
}
export default InviteAndShare

View File

@ -1,278 +0,0 @@
'use client'
// React Imports
import { useMemo, useState } from 'react'
// Next Imports
import Link from 'next/link'
import { useParams } from 'next/navigation'
// MUI Imports
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip'
import MenuItem from '@mui/material/MenuItem'
import Typography from '@mui/material/Typography'
// Third-party Imports
import type { RankingInfo } from '@tanstack/match-sorter-utils'
import { rankItem } from '@tanstack/match-sorter-utils'
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getFacetedMinMaxValues,
getFacetedRowModel,
getFacetedUniqueValues,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table'
import classnames from 'classnames'
// Type Imports
import type { ReferralsType } from '@/types/apps/ecommerceTypes'
import type { Locale } from '@configs/i18n'
import type { ThemeColor } from '@core/types'
// Component Imports
import CustomAvatar from '@core/components/mui/Avatar'
import CustomTextField from '@core/components/mui/TextField'
// Util Imports
import { getLocalizedUrl } from '@/utils/i18n'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
declare module '@tanstack/table-core' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
type userStatusType = {
[key: string]: ThemeColor
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
// Vars
const userStatusObj: userStatusType = {
Rejected: 'error',
Unpaid: 'warning',
Paid: 'success'
}
// Column Definitions
const columnHelper = createColumnHelper<ReferralsType>()
const ReferredUsersTable = ({ referralsData }: { referralsData?: ReferralsType[] }) => {
// States
const [rowSelection, setRowSelection] = useState({})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [data, setData] = useState(...[referralsData])
// Hooks
const { lang: locale } = useParams()
const columns = useMemo<ColumnDef<ReferralsType, any>[]>(
() => [
{
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler()
}}
/>
),
cell: ({ row }) => (
<Checkbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
)
},
columnHelper.accessor('user', {
header: 'Users',
cell: ({ row }) => (
<div className='flex items-center gap-4'>
<CustomAvatar src={row.original.avatar} size={34} />
<div className='flex flex-col items-start'>
<Typography
component={Link}
href={getLocalizedUrl('/apps/ecommerce/customers/details/879861', locale as Locale)}
color='text.primary'
className='font-medium hover:text-primary'
>
{row.original.user}
</Typography>
<Typography variant='body2'>{row.original.email}</Typography>
</div>
</div>
)
}),
columnHelper.accessor('referredId', {
header: 'Referred ID',
cell: ({ row }) => <Typography>{row.original.referredId}</Typography>
}),
columnHelper.accessor('status', {
header: 'Status',
cell: ({ row }) => (
<Chip variant='tonal' label={row.original.status} size='small' color={userStatusObj[row.original.status]} />
)
}),
columnHelper.accessor('value', {
header: 'Value',
cell: ({ row }) => <Typography>{row.original.value}</Typography>
}),
columnHelper.accessor('earning', {
header: 'Earning',
cell: ({ row }) => <Typography color='text.primary'>{row.original.earning}</Typography>
})
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const table = useReactTable({
data: data as ReferralsType[],
columns,
filterFns: {
fuzzy: fuzzyFilter
},
state: {
rowSelection
},
initialState: {
pagination: {
pageSize: 10
}
},
enableRowSelection: true, //enable row selection for all rows
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
globalFilterFn: fuzzyFilter,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues()
})
return (
<>
<Card>
<CardContent className='flex flex-wrap justify-between items-center gap-4'>
<Typography variant='h5'>Referred Users</Typography>
<div className='flex flex-wrap items-center gap-4'>
<CustomTextField
select
value={table.getState().pagination.pageSize}
onChange={e => table.setPageSize(Number(e.target.value))}
className='flex-auto is-[70px]'
>
<MenuItem value='10'>10</MenuItem>
<MenuItem value='25'>25</MenuItem>
<MenuItem value='50'>50</MenuItem>
</CustomTextField>
<Button variant='tonal' startIcon={<i className='tabler-upload' />} color='secondary'>
Export
</Button>
</div>
</CardContent>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
)}
</table>
</div>
{/* <TablePagination
component={() => <TablePaginationComponent table={table} />}
count={table.getExpandedRowModel().rows.length}
rowsPerPage={table.getState().pagination.pageSize}
page={table.getState().pagination.pageIndex}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
/> */}
</Card>
</>
)
}
export default ReferredUsersTable

View File

@ -1,123 +1,56 @@
'use client' 'use client'
// Next Imports // Next Imports
import dynamic from 'next/dynamic'
// MUI Imports // MUI Imports
import Card from '@mui/material/Card' import Card from '@mui/material/Card'
import Typography from '@mui/material/Typography'
import CardContent from '@mui/material/CardContent' import CardContent from '@mui/material/CardContent'
import CardHeader from '@mui/material/CardHeader'
import { useTheme } from '@mui/material/styles'
// Third-party Imports // Third-party Imports
import type { ApexOptions } from 'apexcharts' import classnames from 'classnames'
import CustomAvatar, { CustomAvatarProps } from '../../../@core/components/mui/Avatar'
import { ThemeColor } from '../../../@core/types'
import { Skeleton, Typography } from '@mui/material'
import { formatShortCurrency } from '../../../utils/transform'
// Styled Component Imports type Props = {
const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) title: string
value: number
isLoading: boolean
avatarIcon: string
avatarSkin?: CustomAvatarProps['skin']
avatarSize?: number
avatarColor?: ThemeColor
}
// Vars const DistributedBarChartOrder = ({
const series = [{ data: [77, 55, 23, 43, 77, 55, 89] }] title,
value,
isLoading,
avatarIcon,
avatarSkin,
avatarColor
}: Props) => {
const DistributedBarChartOrder = () => { if (isLoading) {
// Hooks return <Skeleton sx={{ bgcolor: 'grey.100' }} variant='rectangular' width={300} height={118} />
const theme = useTheme()
// Vars
const actionSelectedColor = 'var(--mui-palette-action-selected)'
const options: ApexOptions = {
chart: {
type: 'bar',
stacked: false,
parentHeightOffset: 0,
toolbar: { show: false },
sparkline: { enabled: true }
},
tooltip: { enabled: false },
legend: { show: false },
dataLabels: { enabled: false },
colors: ['var(--mui-palette-primary-main)'],
states: {
hover: {
filter: { type: 'none' }
},
active: {
filter: { type: 'none' }
}
},
plotOptions: {
bar: {
borderRadius: 3,
horizontal: false,
columnWidth: '32%',
colors: {
backgroundBarRadius: 5,
backgroundBarColors: [
actionSelectedColor,
actionSelectedColor,
actionSelectedColor,
actionSelectedColor,
actionSelectedColor
]
}
}
},
grid: {
show: false,
padding: {
left: -3,
right: 5,
top: 15,
bottom: 18
}
},
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false }
},
yaxis: { show: false },
responsive: [
{
breakpoint: 1350,
options: {
plotOptions: {
bar: { columnWidth: '45%' }
}
}
},
{
breakpoint: theme.breakpoints.values.lg,
options: {
plotOptions: {
bar: { columnWidth: '20%' }
}
}
},
{
breakpoint: 600,
options: {
plotOptions: {
bar: { columnWidth: '15%' }
}
}
}
]
} }
return ( return (
<Card> <Card>
<CardHeader title='Order' subheader='Last Week' className='pbe-0' /> <CardContent>
<CardContent className='flex flex-col'> <div className='flex items-start justify-between'>
<AppReactApexCharts type='bar' height={84} width='100%' options={options} series={series} /> <div className='flex-1'>
<div className='flex items-center justify-between flex-wrap gap-x-4 gap-y-0.5'> <Typography variant='h6' color='text.disabled'>
<Typography variant='h4' color='text.primary'> {title}
124k </Typography>
</Typography> <Typography color='text.primary' variant='h4'>
<Typography variant='body2' color='success.main'> {formatShortCurrency(value)}
+12.6% </Typography>
</Typography> </div>
<CustomAvatar variant='rounded' skin={avatarSkin} size={52} color={avatarColor}>
<i className={classnames(avatarIcon, 'text-[28px]')} />
</CustomAvatar>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -1,31 +1,33 @@
'use client' 'use client'
// React Imports // React Imports
import { useState } from 'react'
import type { SyntheticEvent } from 'react' import type { SyntheticEvent } from 'react'
import { useState } from 'react'
// Next Imports // Next Imports
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
// MUI Imports // MUI Imports
import Card from '@mui/material/Card' import TabContext from '@mui/lab/TabContext'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Tab from '@mui/material/Tab'
import TabList from '@mui/lab/TabList' import TabList from '@mui/lab/TabList'
import TabPanel from '@mui/lab/TabPanel' import TabPanel from '@mui/lab/TabPanel'
import TabContext from '@mui/lab/TabContext' import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import CardHeader from '@mui/material/CardHeader'
import Tab from '@mui/material/Tab'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import type { Theme } from '@mui/material/styles' import type { Theme } from '@mui/material/styles'
import { useTheme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles'
// Third Party Imports // Third Party Imports
import classnames from 'classnames'
import type { ApexOptions } from 'apexcharts' import type { ApexOptions } from 'apexcharts'
import classnames from 'classnames'
// Components Imports // Components Imports
import OptionMenu from '@core/components/option-menu'
import CustomAvatar from '@core/components/mui/Avatar' import CustomAvatar from '@core/components/mui/Avatar'
import OptionMenu from '@core/components/option-menu'
import Loading from '../../../components/layout/shared/Loading'
import { formatShortCurrency } from '../../../utils/transform'
// Styled Component Imports // Styled Component Imports
const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts'))
@ -33,39 +35,14 @@ const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexChart
type ApexChartSeries = NonNullable<ApexOptions['series']> type ApexChartSeries = NonNullable<ApexOptions['series']>
type ApexChartSeriesData = Exclude<ApexChartSeries[0], number> type ApexChartSeriesData = Exclude<ApexChartSeries[0], number>
type TabCategory = 'orders' | 'sales' | 'profit' | 'income'
type TabType = { type TabType = {
type: TabCategory type: string
avatarIcon: string avatarIcon: string
date: any
series: ApexChartSeries series: ApexChartSeries
} }
// Vars const renderTabs = (tabData: TabType[], value: string) => {
const tabData: TabType[] = [
{
type: 'orders',
avatarIcon: 'tabler-shopping-cart',
series: [{ data: [28, 10, 46, 38, 15, 30, 35, 28, 8] }]
},
{
type: 'sales',
avatarIcon: 'tabler-chart-bar',
series: [{ data: [35, 25, 15, 40, 42, 25, 48, 8, 30] }]
},
{
type: 'profit',
avatarIcon: 'tabler-currency-dollar',
series: [{ data: [10, 22, 27, 33, 42, 32, 27, 22, 8] }]
},
{
type: 'income',
avatarIcon: 'tabler-chart-pie-2',
series: [{ data: [5, 9, 12, 18, 20, 25, 30, 36, 48] }]
}
]
const renderTabs = (value: TabCategory) => {
return tabData.map((item, index) => ( return tabData.map((item, index) => (
<Tab <Tab
key={index} key={index}
@ -90,7 +67,7 @@ const renderTabs = (value: TabCategory) => {
)) ))
} }
const renderTabPanels = (value: TabCategory, theme: Theme, options: ApexOptions, colors: string[]) => { const renderTabPanels = (tabData: TabType[], theme: Theme, options: ApexOptions, colors: string[]) => {
return tabData.map((item, index) => { return tabData.map((item, index) => {
const max = Math.max(...((item.series[0] as ApexChartSeriesData).data as number[])) const max = Math.max(...((item.series[0] as ApexChartSeriesData).data as number[]))
const seriesIndex = ((item.series[0] as ApexChartSeriesData).data as number[]).indexOf(max) const seriesIndex = ((item.series[0] as ApexChartSeriesData).data as number[]).indexOf(max)
@ -101,7 +78,7 @@ const renderTabPanels = (value: TabCategory, theme: Theme, options: ApexOptions,
<TabPanel key={index} value={item.type} className='!p-0'> <TabPanel key={index} value={item.type} className='!p-0'>
<AppReactApexCharts <AppReactApexCharts
type='bar' type='bar'
height={233} height={360}
width='100%' width='100%'
options={{ ...options, colors: finalColors }} options={{ ...options, colors: finalColors }}
series={item.series} series={item.series}
@ -111,9 +88,9 @@ const renderTabPanels = (value: TabCategory, theme: Theme, options: ApexOptions,
}) })
} }
const EarningReportsWithTabs = () => { const EarningReportsWithTabs = ({ data }: { data: TabType[] }) => {
// States // States
const [value, setValue] = useState<TabCategory>('orders') const [value, setValue] = useState(data[0].type)
// Hooks // Hooks
const theme = useTheme() const theme = useTheme()
@ -121,7 +98,7 @@ const EarningReportsWithTabs = () => {
// Vars // Vars
const disabledText = 'var(--mui-palette-text-disabled)' const disabledText = 'var(--mui-palette-text-disabled)'
const handleChange = (event: SyntheticEvent, newValue: TabCategory) => { const handleChange = (event: SyntheticEvent, newValue: string) => {
setValue(newValue) setValue(newValue)
} }
@ -145,7 +122,7 @@ const EarningReportsWithTabs = () => {
tooltip: { enabled: false }, tooltip: { enabled: false },
dataLabels: { dataLabels: {
offsetY: -11, offsetY: -11,
formatter: val => `${val}k`, formatter: val => formatShortCurrency(Number(val)),
style: { style: {
fontWeight: 500, fontWeight: 500,
colors: ['var(--mui-palette-text-primary)'], colors: ['var(--mui-palette-text-primary)'],
@ -173,7 +150,7 @@ const EarningReportsWithTabs = () => {
xaxis: { xaxis: {
axisTicks: { show: false }, axisTicks: { show: false },
axisBorder: { color: 'var(--mui-palette-divider)' }, axisBorder: { color: 'var(--mui-palette-divider)' },
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'], categories: data.find(item => item.type === value)?.date,
labels: { labels: {
style: { style: {
colors: disabledText, colors: disabledText,
@ -185,7 +162,7 @@ const EarningReportsWithTabs = () => {
yaxis: { yaxis: {
labels: { labels: {
offsetX: -18, offsetX: -18,
formatter: val => `$${val}k`, formatter: val => `${formatShortCurrency(Number(val))}`,
style: { style: {
colors: disabledText, colors: disabledText,
fontFamily: theme.typography.fontFamily, fontFamily: theme.typography.fontFamily,
@ -235,31 +212,33 @@ const EarningReportsWithTabs = () => {
/> />
<CardContent> <CardContent>
<TabContext value={value}> <TabContext value={value}>
<TabList {data.length > 1 && (
variant='scrollable' <TabList
scrollButtons='auto' variant='scrollable'
onChange={handleChange} scrollButtons='auto'
aria-label='earning report tabs' onChange={handleChange}
className='!border-0 mbe-10' aria-label='earning report tabs'
sx={{ className='!border-0 mbe-10'
'& .MuiTabs-indicator': { display: 'none !important' }, sx={{
'& .MuiTab-root': { padding: '0 !important', border: '0 !important' } '& .MuiTabs-indicator': { display: 'none !important' },
}} '& .MuiTab-root': { padding: '0 !important', border: '0 !important' }
> }}
{renderTabs(value)} >
<Tab {renderTabs(data, value)}
disabled <Tab
value='add' disabled
label={ value='add'
<div className='flex flex-col items-center justify-center is-[110px] bs-[100px] border border-dashed rounded-xl'> label={
<CustomAvatar variant='rounded' size={34}> <div className='flex flex-col items-center justify-center is-[110px] bs-[100px] border border-dashed rounded-xl'>
<i className='tabler-plus text-textSecondary' /> <CustomAvatar variant='rounded' size={34}>
</CustomAvatar> <i className='tabler-plus text-textSecondary' />
</div> </CustomAvatar>
} </div>
/> }
</TabList> />
{renderTabPanels(value, theme, options, colors)} </TabList>
)}
{renderTabPanels(data, theme, options, colors)}
</TabContext> </TabContext>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -88,12 +88,12 @@ const statusObj: StatusObj = {
verified: { text: 'Verified', color: 'success' } verified: { text: 'Verified', color: 'success' }
} }
const LastTransaction = ({ serverMode }: { serverMode: SystemMode }) => { const LastTransaction = () => {
// Hooks // Hooks
const { mode } = useColorScheme() const { mode } = useColorScheme()
// Vars // Vars
const _mode = (mode === 'system' ? serverMode : mode) || serverMode const _mode = mode as SystemMode
return ( return (
<Card> <Card>