Sales Order Report
This commit is contained in:
parent
daa3c4e9a2
commit
0dc6e967bb
@ -0,0 +1,19 @@
|
|||||||
|
import ReportTitle from '@/components/report/ReportTitle'
|
||||||
|
import ReportSalesOrderContent from '@/views/apps/report/sales/sales-order/ReportSalesOrderContent'
|
||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
|
|
||||||
|
const SalesOrderReportPage = () => {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ReportTitle title='Laporan Penjualan Pesanan' />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<ReportSalesOrderContent />
|
||||||
|
</Grid>
|
||||||
|
z
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SalesOrderReportPage
|
||||||
@ -35,6 +35,11 @@ const ReportSalesList: React.FC = () => {
|
|||||||
title: 'Penjualan per Kategori Produk',
|
title: 'Penjualan per Kategori Produk',
|
||||||
iconClass: 'tabler-receipt-2',
|
iconClass: 'tabler-receipt-2',
|
||||||
link: getLocalizedUrl(`/apps/report/sales/sales-product-category`, locale as Locale)
|
link: getLocalizedUrl(`/apps/report/sales/sales-product-category`, locale as Locale)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Penjualan Pesanan',
|
||||||
|
iconClass: 'tabler-receipt-2',
|
||||||
|
link: getLocalizedUrl(`/apps/report/sales/sales-order`, locale as Locale)
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
// title: 'Penjualan Produk per Pelanggan',
|
// title: 'Penjualan Produk per Pelanggan',
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
|
import type { UserDataType } from '@components/card-statistics/HorizontalWithSubtitle'
|
||||||
|
import HorizontalWithSubtitle from '@components/card-statistics/HorizontalWithSubtitle'
|
||||||
|
import { ProfitLossReport, SalesReport } from '@/types/services/analytic'
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
const formatIDR = (amount: number) => {
|
||||||
|
return new Intl.NumberFormat('id-ID', {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}).format(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPercentage = (value: number) => {
|
||||||
|
return `${value.toFixed(1)}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportSalesOrderCardProps {
|
||||||
|
sales: SalesReport | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReportSalesOrderCard = ({ sales }: ReportSalesOrderCardProps) => {
|
||||||
|
if (!sales) {
|
||||||
|
return null // Will be handled by parent loading state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using actual data from API response with correct field names
|
||||||
|
const data: UserDataType[] = [
|
||||||
|
{
|
||||||
|
title: 'Penjualan',
|
||||||
|
stats: formatIDR(sales.summary.total_sales),
|
||||||
|
avatarIcon: 'tabler-trending-up',
|
||||||
|
avatarColor: 'success',
|
||||||
|
trend: 'positive',
|
||||||
|
trendNumber: '',
|
||||||
|
subtitle: 'Total Penjualan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Total Pesanan',
|
||||||
|
stats: sales.summary.total_orders.toString(),
|
||||||
|
avatarIcon: 'tabler-gauge',
|
||||||
|
avatarColor: 'success',
|
||||||
|
trend: 'positive',
|
||||||
|
trendNumber: '',
|
||||||
|
subtitle: 'Total Pesanan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Rata Rata',
|
||||||
|
stats: formatIDR(sales.summary.average_order_value),
|
||||||
|
avatarIcon: 'tabler-trending-up',
|
||||||
|
avatarColor: sales.summary.average_order_value >= 0 ? 'success' : 'error',
|
||||||
|
trend: sales.summary.average_order_value >= 0 ? 'positive' : 'negative',
|
||||||
|
trendNumber: '',
|
||||||
|
subtitle: 'Rata Rata Nilai Pesanan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Penjualan Bersih',
|
||||||
|
stats: formatIDR(sales.summary.net_sales),
|
||||||
|
avatarIcon: sales.summary.net_sales >= 0 ? 'tabler-trending-up' : 'tabler-trending-down',
|
||||||
|
avatarColor: sales.summary.net_sales >= 0 ? 'success' : 'error',
|
||||||
|
trend: sales.summary.net_sales >= 0 ? 'positive' : 'negative',
|
||||||
|
trendNumber: '',
|
||||||
|
subtitle: 'Net Profit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
{data.map((item, i) => (
|
||||||
|
<Grid key={i} size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
|
<HorizontalWithSubtitle {...item} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReportSalesOrderCard
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import DateRangePicker from '@/components/RangeDatePicker'
|
||||||
|
import { ReportItemHeader, ReportItemSubheader } from '@/components/report/ReportItem'
|
||||||
|
import { useSalesAnalytics } from '@/services/queries/analytics'
|
||||||
|
import { formatCurrency, formatDate, formatDateDDMMYYYY } from '@/utils/transform'
|
||||||
|
import { Button, Card, CardContent } from '@mui/material'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import ReportSalesOrderCard from './ReportSalesOrderCard'
|
||||||
|
|
||||||
|
const ReportSalesOrderContent = () => {
|
||||||
|
const [startDate, setStartDate] = useState<Date | null>(new Date())
|
||||||
|
const [endDate, setEndDate] = useState<Date | null>(new Date())
|
||||||
|
|
||||||
|
const { data: sales } = useSalesAnalytics({
|
||||||
|
date_from: formatDateDDMMYYYY(startDate!),
|
||||||
|
date_to: formatDateDDMMYYYY(endDate!)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ReportSalesOrderCard sales={sales} />
|
||||||
|
|
||||||
|
<Card className='mt-5'>
|
||||||
|
<div className='p-6 border-be'>
|
||||||
|
<div className='flex items-center justify-end gap-2'>
|
||||||
|
<Button
|
||||||
|
color='secondary'
|
||||||
|
variant='tonal'
|
||||||
|
startIcon={<i className='tabler-upload' />}
|
||||||
|
className='max-sm:is-full'
|
||||||
|
// onClick={handleExportPDF}
|
||||||
|
>
|
||||||
|
Ekspor
|
||||||
|
</Button>
|
||||||
|
<DateRangePicker
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onStartDateChange={setStartDate}
|
||||||
|
onEndDateChange={setEndDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CardContent>
|
||||||
|
<ReportItemHeader
|
||||||
|
title='Pesanan'
|
||||||
|
date={`${sales?.date_from.split('T')[0]} - ${sales?.date_to.split('T')[0]}`}
|
||||||
|
/>
|
||||||
|
<div className='bg-gray-50 border border-gray-200 overflow-hidden'>
|
||||||
|
<table className='w-full'>
|
||||||
|
<thead>
|
||||||
|
<tr className='text-gray-800 border-b-2 border-gray-300'>
|
||||||
|
<th className='text-left p-3 font-semibold'>Date</th>
|
||||||
|
<th className='text-center p-3 font-semibold'>Penjualan</th>
|
||||||
|
<th className='text-center p-3 font-semibold'>Pesanan</th>
|
||||||
|
<th className='text-right p-3 font-semibold'>Qty</th>
|
||||||
|
<th className='text-right p-3 font-semibold'>Pajak</th>
|
||||||
|
<th className='text-right p-3 font-semibold'>Diskon</th>
|
||||||
|
<th className='text-right p-3 font-semibold'>Pendapatan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sales?.data?.map((c, index) => (
|
||||||
|
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
|
<td className='p-3 font-medium text-gray-800'>{formatDate(c.date)}</td>
|
||||||
|
<td className='p-3 text-center text-gray-700'>{formatCurrency(c.sales)}</td>
|
||||||
|
<td className='p-3 text-center text-gray-700'>{c.orders}</td>
|
||||||
|
<td className='p-3 text-center text-gray-700'>{c.items}</td>
|
||||||
|
<td className='p-3 text-center text-gray-700'>{formatCurrency(c.tax)}</td>
|
||||||
|
<td className='p-3 text-center text-gray-700'>{formatCurrency(c.discount)}</td>
|
||||||
|
<td className='p-3 text-right font-semibold' style={{ color: '#36175e' }}>
|
||||||
|
{formatCurrency(c.net_sales)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)) || []}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<ReportItemSubheader title='' />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReportSalesOrderContent
|
||||||
Loading…
x
Reference in New Issue
Block a user