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