Merge pull request 'efril' (#18) from efril into main
Reviewed-on: https://gits.altru.id/ferdiansyah/pos-dashboard-v2/pulls/18
This commit is contained in:
commit
116f6b8009
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,3 +39,5 @@ next-env.d.ts
|
|||||||
# icon generated file
|
# icon generated file
|
||||||
src/assets/iconify-icons/generated-icons.js
|
src/assets/iconify-icons/generated-icons.js
|
||||||
src/assets/iconify-icons/generated-icons.css
|
src/assets/iconify-icons/generated-icons.css
|
||||||
|
|
||||||
|
*.backup
|
||||||
|
|||||||
@ -55,14 +55,6 @@ const DailyPOSReport = () => {
|
|||||||
totalOrders: products?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0
|
totalOrders: products?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate profit loss product summary
|
|
||||||
const profitLossProductSummary = {
|
|
||||||
totalRevenue: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.revenue || 0), 0) || 0,
|
|
||||||
totalCost: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.cost || 0), 0) || 0,
|
|
||||||
totalGrossProfit: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.gross_profit || 0), 0) || 0,
|
|
||||||
totalQuantity: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.quantity_sold || 0), 0) || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const categorySummary = {
|
const categorySummary = {
|
||||||
totalRevenue: category?.data?.reduce((sum, item) => sum + (item?.total_revenue || 0), 0) || 0,
|
totalRevenue: category?.data?.reduce((sum, item) => sum + (item?.total_revenue || 0), 0) || 0,
|
||||||
orderCount: category?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0,
|
orderCount: category?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0,
|
||||||
@ -340,126 +332,62 @@ const DailyPOSReport = () => {
|
|||||||
{/* Performance Summary */}
|
{/* Performance Summary */}
|
||||||
<div className='p-8'>
|
<div className='p-8'>
|
||||||
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
||||||
1. Ringkasan
|
Ringkasan
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='grid grid-cols-2 gap-6'>
|
<div className='space-y-4'>
|
||||||
<div className='space-y-4'>
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
<span className='text-gray-700'>Total Penjualan</span>
|
||||||
<span className='text-gray-700'>Total Penjualan (termasuk rasik)</span>
|
<span className='font-semibold text-gray-800'>
|
||||||
<span className='font-semibold text-gray-800'>
|
{formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
|
||||||
{formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
|
||||||
<span className='text-gray-700'>Total Terjual</span>
|
|
||||||
<span className='font-semibold text-gray-800'>{productSummary.totalQuantitySold}</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
|
||||||
<span className='text-gray-700'>HPP</span>
|
|
||||||
<span className='font-semibold text-gray-800'>
|
|
||||||
{formatCurrency(profitLoss?.summary.total_cost ?? 0)} |{' '}
|
|
||||||
{(((profitLoss?.summary.total_cost ?? 0) / (profitLoss?.summary.total_revenue || 1)) * 100).toFixed(
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex justify-between items-center py-2 border-b-2 border-gray-300'>
|
|
||||||
<span className='text-lg font-semibold text-green-800'>Laba Kotor</span>
|
|
||||||
<span className='text-lg font-bold text-green-800'>
|
|
||||||
{formatCurrency(profitLoss?.summary.gross_profit ?? 0)} |{' '}
|
|
||||||
{(profitLoss?.summary.gross_profit_margin ?? 0).toFixed(0)}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
<div className='space-y-4'>
|
<span className='text-gray-700'>Total Diskon</span>
|
||||||
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
<span className='font-semibold text-gray-800'>
|
||||||
<span className='text-gray-700'>Biaya lain²</span>
|
{formatCurrency(profitLoss?.summary.total_discount ?? 0)}
|
||||||
<span className='font-semibold text-gray-800'>
|
</span>
|
||||||
{formatCurrency(profitLoss?.summary.total_tax ?? 0)} |{' '}
|
</div>
|
||||||
{(((profitLoss?.summary.total_tax ?? 0) / (profitLoss?.summary.total_revenue || 1)) * 100).toFixed(0)}
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
%
|
<span className='text-gray-700'>Total Pajak</span>
|
||||||
</span>
|
<span className='font-semibold text-gray-800'>{formatCurrency(profitLoss?.summary.total_tax ?? 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center py-2 border-b-2 border-gray-300'>
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
<span className='text-lg font-bold text-blue-800'>Laba/Rugi</span>
|
<span className='text-gray-700 font-bold'>Total</span>
|
||||||
<span className='text-lg font-bold text-blue-800'>
|
<span className='font-bold text-gray-800'>{formatCurrency(profitLoss?.summary.total_revenue ?? 0)}</span>
|
||||||
{formatCurrency(profitLoss?.summary.net_profit ?? 0)} |{' '}
|
|
||||||
{(profitLoss?.summary.net_profit_margin ?? 0).toFixed(0)}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Profit Loss Product Table */}
|
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-lg font-medium mb-6' style={{ color: '#36175e' }}>
|
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
||||||
Laba Rugi Per Produk
|
Invoice
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
<div className='space-y-4'>
|
||||||
<table className='w-full'>
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
<thead>
|
<span className='text-gray-700'>Total Invoice</span>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<span className='font-semibold text-gray-800'>{profitLoss?.summary.total_orders ?? 0}</span>
|
||||||
<th className='text-left p-3 font-semibold'>Produk</th>
|
</div>
|
||||||
<th className='text-center p-3 font-semibold'>Qty</th>
|
<div className='flex justify-between items-center py-2 border-b border-gray-200'>
|
||||||
<th className='text-right p-3 font-semibold'>Pendapatan</th>
|
<span className='text-gray-700'>Rata-rata Tagihan Per Invoice </span>
|
||||||
<th className='text-right p-3 font-semibold'>HPP</th>
|
<span className='font-semibold text-gray-800'>
|
||||||
<th className='text-right p-3 font-semibold'>Laba Kotor</th>
|
{formatCurrency(profitLoss?.summary.average_profit ?? 0)}
|
||||||
<th className='text-center p-3 font-semibold'>Margin (%)</th>
|
</span>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{profitLoss?.product_data?.map((item, index) => (
|
|
||||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
|
||||||
<td className='p-3 font-medium text-gray-800'>{item.product_name}</td>
|
|
||||||
<td className='p-3 text-center text-gray-700'>{item.quantity_sold}</td>
|
|
||||||
<td className='p-3 text-right font-semibold text-gray-800'>{formatCurrency(item.revenue)}</td>
|
|
||||||
<td className='p-3 text-right font-semibold text-red-600'>{formatCurrency(item.cost)}</td>
|
|
||||||
<td className='p-3 text-right font-semibold text-green-600'>{formatCurrency(item.gross_profit)}</td>
|
|
||||||
<td className='p-3 text-center font-semibold' style={{ color: '#36175e' }}>
|
|
||||||
{(item.gross_profit_margin ?? 0).toFixed(1)}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)) || []}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
|
||||||
<td className='p-3 font-bold'>TOTAL</td>
|
|
||||||
<td className='p-3 text-center font-bold'>{profitLossProductSummary.totalQuantity}</td>
|
|
||||||
<td className='p-3 text-right font-bold'>{formatCurrency(profitLossProductSummary.totalRevenue)}</td>
|
|
||||||
<td className='p-3 text-right font-bold'>{formatCurrency(profitLossProductSummary.totalCost)}</td>
|
|
||||||
<td className='p-3 text-right font-bold'>
|
|
||||||
{formatCurrency(profitLossProductSummary.totalGrossProfit)}
|
|
||||||
</td>
|
|
||||||
<td className='p-3 text-center font-bold'>
|
|
||||||
{profitLossProductSummary.totalRevenue > 0
|
|
||||||
? (
|
|
||||||
(profitLossProductSummary.totalGrossProfit / profitLossProductSummary.totalRevenue) *
|
|
||||||
100
|
|
||||||
).toFixed(1)
|
|
||||||
: 0}
|
|
||||||
%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment Method Summary */}
|
{/* Payment Method Summary */}
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
||||||
2. Ringkasan Metode Pembayaran
|
Ringkasan Metode Pembayaran
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
||||||
<table className='w-full'>
|
<table className='w-full'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<tr className='text-gray-800 border-b-2 border-gray-300'>
|
||||||
<th className='text-left p-3 font-semibold'>Metode Pembayaran</th>
|
<th className='text-left p-3 font-semibold'>Metode Pembayaran</th>
|
||||||
<th className='text-center p-3 font-semibold'>Tipe</th>
|
<th className='text-center p-3 font-semibold'>Tipe</th>
|
||||||
<th className='text-center p-3 font-semibold'>Jumlah Order</th>
|
<th className='text-center p-3 font-semibold'>Jumlah Order</th>
|
||||||
@ -493,7 +421,7 @@ const DailyPOSReport = () => {
|
|||||||
)) || []}
|
)) || []}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<tr className='text-gray-800 border-t-2 border-gray-300'>
|
||||||
<td className='p-3 font-bold'>TOTAL</td>
|
<td className='p-3 font-bold'>TOTAL</td>
|
||||||
<td className='p-3'></td>
|
<td className='p-3'></td>
|
||||||
<td className='p-3 text-center font-bold'>{paymentAnalytics?.summary.total_orders ?? 0}</td>
|
<td className='p-3 text-center font-bold'>{paymentAnalytics?.summary.total_orders ?? 0}</td>
|
||||||
@ -510,18 +438,17 @@ const DailyPOSReport = () => {
|
|||||||
{/* Category Summary */}
|
{/* Category Summary */}
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
||||||
2. Ringkasan Kategori
|
Ringkasan Kategori
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
||||||
<table className='w-full'>
|
<table className='w-full'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<tr className='text-gray-800 border-b-2 border-gray-300'>
|
||||||
<th className='text-left p-3 font-semibold'>Nama</th>
|
<th className='text-left p-3 font-semibold'>Nama</th>
|
||||||
<th className='text-center p-3 font-semibold'>Total Produk</th>
|
<th className='text-center p-3 font-semibold'>Total Produk</th>
|
||||||
<th className='text-center p-3 font-semibold'>Qty</th>
|
<th className='text-center p-3 font-semibold'>Qty</th>
|
||||||
<th className='text-right p-3 font-semibold'>Jumlah Order</th>
|
<th className='text-right p-3 font-semibold'>Pendapatan</th>
|
||||||
<th className='text-center p-3 font-semibold'>Pendapatan</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -530,7 +457,6 @@ const DailyPOSReport = () => {
|
|||||||
<td className='p-3 font-medium text-gray-800'>{c.category_name}</td>
|
<td className='p-3 font-medium text-gray-800'>{c.category_name}</td>
|
||||||
<td className='p-3 text-center text-gray-700'>{c.product_count}</td>
|
<td className='p-3 text-center text-gray-700'>{c.product_count}</td>
|
||||||
<td className='p-3 text-center text-gray-700'>{c.total_quantity}</td>
|
<td className='p-3 text-center text-gray-700'>{c.total_quantity}</td>
|
||||||
<td className='p-3 text-center text-gray-700'>{c.order_count}</td>
|
|
||||||
<td className='p-3 text-right font-semibold' style={{ color: '#36175e' }}>
|
<td className='p-3 text-right font-semibold' style={{ color: '#36175e' }}>
|
||||||
{formatCurrency(c.total_revenue)}
|
{formatCurrency(c.total_revenue)}
|
||||||
</td>
|
</td>
|
||||||
@ -538,11 +464,10 @@ const DailyPOSReport = () => {
|
|||||||
)) || []}
|
)) || []}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<tr className='text-gray-800 border-t-2 border-gray-300'>
|
||||||
<td className='p-3 font-bold'>TOTAL</td>
|
<td className='p-3 font-bold'>TOTAL</td>
|
||||||
<td className='p-3 text-center font-bold'>{categorySummary?.productCount ?? 0}</td>
|
<td className='p-3 text-center font-bold'>{categorySummary?.productCount ?? 0}</td>
|
||||||
<td className='p-3 text-center font-bold'>{categorySummary?.totalQuantity ?? 0}</td>
|
<td className='p-3 text-center font-bold'>{categorySummary?.totalQuantity ?? 0}</td>
|
||||||
<td className='p-3 text-center font-bold'>{categorySummary?.orderCount ?? 0}</td>
|
|
||||||
<td className='p-3 text-right font-bold'>{formatCurrency(categorySummary?.totalRevenue ?? 0)}</td>
|
<td className='p-3 text-right font-bold'>{formatCurrency(categorySummary?.totalRevenue ?? 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@ -553,49 +478,164 @@ const DailyPOSReport = () => {
|
|||||||
{/* Transaction Summary */}
|
{/* Transaction Summary */}
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
<h3 className='text-xl font-bold mb-6' style={{ color: '#36175e' }}>
|
||||||
4. Ringkasan Item
|
Ringkasan Item
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-hidden'>
|
<div className='bg-gray-50 rounded-lg border border-gray-200 overflow-visible'>
|
||||||
<table className='w-full'>
|
<div className='overflow-x-auto'>
|
||||||
<thead>
|
<table className='w-full table-fixed' style={{ minWidth: '100%' }}>
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
<colgroup>
|
||||||
<th className='text-left p-3 font-semibold'>Produk</th>
|
<col style={{ width: '40%' }} />
|
||||||
<th className='text-left p-3 font-semibold'>Kategori</th>
|
<col style={{ width: '15%' }} />
|
||||||
<th className='text-center p-3 font-semibold'>Qty</th>
|
<col style={{ width: '15%' }} />
|
||||||
<th className='text-right p-3 font-semibold'>Order</th>
|
<col style={{ width: '15%' }} />
|
||||||
<th className='text-right p-3 font-semibold'>Pendapatan</th>
|
<col style={{ width: '15%' }} />
|
||||||
<th className='text-right p-3 font-semibold'>Rata Rata</th>
|
</colgroup>
|
||||||
</tr>
|
<thead>
|
||||||
</thead>
|
<tr className='text-gray-800 border-b-2 border-gray-300'>
|
||||||
<tbody>
|
<th className='text-left p-3 font-semibold border-r border-gray-300'>Produk</th>
|
||||||
{products?.data?.map((item, index) => (
|
<th className='text-center p-3 font-semibold border-r border-gray-300'>Qty</th>
|
||||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
<th className='text-center p-3 font-semibold border-r border-gray-300'>Order</th>
|
||||||
<td className='p-3 font-medium text-gray-800'>{item.product_name}</td>
|
<th className='text-right p-3 font-semibold border-r border-gray-300'>Pendapatan</th>
|
||||||
<td className='p-3 font-medium text-gray-800'>{item.category_name}</td>
|
<th className='text-right p-3 font-semibold'>Rata Rata</th>
|
||||||
<td className='p-3 text-center text-gray-700'>{item.quantity_sold}</td>
|
|
||||||
<td className='p-3 text-center text-gray-700'>{item.order_count ?? 0}</td>
|
|
||||||
<td className='p-3 text-right font-semibold text-gray-800'>{formatCurrency(item.revenue)}</td>
|
|
||||||
<td className='p-3 text-right font-medium text-gray-800'>{formatCurrency(item.average_price)}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)) || []}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
<tfoot>
|
{(() => {
|
||||||
<tr style={{ backgroundColor: '#36175e' }} className='text-white'>
|
// Group products by category
|
||||||
<td className='p-3 font-bold'>TOTAL</td>
|
const groupedProducts =
|
||||||
<td className='p-3 text-center'></td>
|
products?.data?.reduce(
|
||||||
<td className='p-3 text-center font-bold'>{productSummary.totalQuantitySold ?? 0}</td>
|
(acc, item) => {
|
||||||
<td className='p-3 text-right font-bold'>{productSummary.totalOrders ?? 0}</td>
|
const categoryName = item.category_name || 'Tidak Berkategori'
|
||||||
<td className='p-3 text-right font-bold'>{formatCurrency(productSummary.totalRevenue ?? 0)}</td>
|
if (!acc[categoryName]) {
|
||||||
<td className='p-3 text-right font-bold'></td>
|
acc[categoryName] = []
|
||||||
</tr>
|
}
|
||||||
</tfoot>
|
acc[categoryName].push(item)
|
||||||
</table>
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, any[]>
|
||||||
|
) || {}
|
||||||
|
|
||||||
|
const rows: JSX.Element[] = []
|
||||||
|
let globalIndex = 0
|
||||||
|
|
||||||
|
// Sort categories alphabetically
|
||||||
|
Object.keys(groupedProducts)
|
||||||
|
.sort()
|
||||||
|
.forEach(categoryName => {
|
||||||
|
const categoryProducts = groupedProducts[categoryName]
|
||||||
|
|
||||||
|
// Category header row
|
||||||
|
rows.push(
|
||||||
|
<tr
|
||||||
|
key={`category-${categoryName}`}
|
||||||
|
className='bg-gray-100 border-b border-gray-300'
|
||||||
|
style={{ pageBreakInside: 'avoid' }}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className='p-3 font-bold text-gray-900 border-r border-gray-300'
|
||||||
|
style={{ color: '#36175e' }}
|
||||||
|
>
|
||||||
|
{categoryName.toUpperCase()}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 border-r border-gray-300'></td>
|
||||||
|
<td className='p-3 border-r border-gray-300'></td>
|
||||||
|
<td className='p-3 border-r border-gray-300'></td>
|
||||||
|
<td className='p-3'></td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Product rows for this category
|
||||||
|
categoryProducts.forEach((item, index) => {
|
||||||
|
globalIndex++
|
||||||
|
rows.push(
|
||||||
|
<tr
|
||||||
|
key={`product-${item.product_name}-${index}`}
|
||||||
|
className={`${globalIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'} border-b border-gray-200`}
|
||||||
|
style={{ pageBreakInside: 'avoid' }}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className='p-3 pl-6 font-medium text-gray-800 border-r border-gray-200'
|
||||||
|
style={{ wordWrap: 'break-word' }}
|
||||||
|
>
|
||||||
|
{item.product_name}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-center text-gray-700 border-r border-gray-200'>
|
||||||
|
{item.quantity_sold}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-center text-gray-700 border-r border-gray-200'>
|
||||||
|
{item.order_count ?? 0}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-right font-semibold text-gray-800 border-r border-gray-200'>
|
||||||
|
{formatCurrency(item.revenue)}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-right font-medium text-gray-800'>
|
||||||
|
{formatCurrency(item.average_price)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Category subtotal row
|
||||||
|
const categoryTotalQty = categoryProducts.reduce(
|
||||||
|
(sum, item) => sum + (item.quantity_sold || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const categoryTotalOrders = categoryProducts.reduce(
|
||||||
|
(sum, item) => sum + (item.order_count || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const categoryTotalRevenue = categoryProducts.reduce(
|
||||||
|
(sum, item) => sum + (item.revenue || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
rows.push(
|
||||||
|
<tr
|
||||||
|
key={`subtotal-${categoryName}`}
|
||||||
|
className='bg-gray-200 border-b-2 border-gray-400'
|
||||||
|
style={{ pageBreakInside: 'avoid' }}
|
||||||
|
>
|
||||||
|
<td className='p-3 pl-6 font-semibold text-gray-800 border-r border-gray-400'>
|
||||||
|
Subtotal {categoryName}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-center font-semibold text-gray-800 border-r border-gray-400'>
|
||||||
|
{categoryTotalQty}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-center font-semibold text-gray-800 border-r border-gray-400'>
|
||||||
|
{categoryTotalOrders}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-right font-semibold text-gray-800 border-r border-gray-400'>
|
||||||
|
{formatCurrency(categoryTotalRevenue)}
|
||||||
|
</td>
|
||||||
|
<td className='p-3'></td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return rows
|
||||||
|
})()}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr className='text-gray-800 border-t-2 border-gray-300' style={{ pageBreakInside: 'avoid' }}>
|
||||||
|
<td className='p-3 font-bold border-r border-gray-300'>TOTAL KESELURUHAN</td>
|
||||||
|
<td className='p-3 text-center font-bold border-r border-gray-300'>
|
||||||
|
{productSummary.totalQuantitySold ?? 0}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-center font-bold border-r border-gray-300'>
|
||||||
|
{productSummary.totalOrders ?? 0}
|
||||||
|
</td>
|
||||||
|
<td className='p-3 text-right font-bold border-r border-gray-300'>
|
||||||
|
{formatCurrency(productSummary.totalRevenue ?? 0)}
|
||||||
|
</td>
|
||||||
|
<td className='p-3'></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Profit Loss Product Table */}
|
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className='px-8 py-6 border-t-2 border-gray-200 mt-8'>
|
<div className='px-8 py-6 border-t-2 border-gray-200 mt-8'>
|
||||||
<div className='flex justify-between items-center text-sm text-gray-600'>
|
<div className='flex justify-between items-center text-sm text-gray-600'>
|
||||||
|
|||||||
@ -7,9 +7,6 @@ import type { FC } from 'react'
|
|||||||
import { Box, Typography, Divider } from '@mui/material'
|
import { Box, Typography, Divider } from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
// Component Imports
|
|
||||||
import Logo from '@core/svg/Logo'
|
|
||||||
|
|
||||||
// Type Imports
|
// Type Imports
|
||||||
import type { Outlet } from '@/types/services/outlet'
|
import type { Outlet } from '@/types/services/outlet'
|
||||||
|
|
||||||
@ -39,75 +36,28 @@ const ReportHeader: FC<ReportHeaderProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
<Box sx={{ p: theme.spacing(8, 8, 6) }}>
|
<Box sx={{ p: theme.spacing(8, 8, 6) }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
<Box sx={{ textAlign: 'center' }}>
|
||||||
{/* Left Section - Brand & Outlet Info */}
|
<Typography
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
variant='h4'
|
||||||
<Logo />
|
component='h3'
|
||||||
<Box>
|
sx={{
|
||||||
<Typography
|
fontWeight: 700,
|
||||||
variant='h3'
|
color: '#222222'
|
||||||
component='h1'
|
}}
|
||||||
sx={{
|
>
|
||||||
fontWeight: 700,
|
{reportTitle}
|
||||||
color: brandColor || theme.palette.primary.main
|
</Typography>
|
||||||
}}
|
{periode && (
|
||||||
>
|
|
||||||
{brandName}
|
|
||||||
</Typography>
|
|
||||||
{outlet?.name && (
|
|
||||||
<Typography
|
|
||||||
variant='h5'
|
|
||||||
component='h2'
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
mt: 1,
|
|
||||||
color: '#222222'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{outlet.name}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{outlet?.address && (
|
|
||||||
<Typography
|
|
||||||
variant='body2'
|
|
||||||
sx={{
|
|
||||||
mt: 1,
|
|
||||||
color: '#222222'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{outlet.address}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Right Section - Report Info */}
|
|
||||||
<Box sx={{ textAlign: 'right' }}>
|
|
||||||
<Typography
|
<Typography
|
||||||
variant='h4'
|
variant='body2'
|
||||||
component='h3'
|
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 700,
|
color: '#222222',
|
||||||
color: '#222222'
|
mt: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{reportTitle}
|
{periode}
|
||||||
</Typography>
|
</Typography>
|
||||||
{periode && (
|
)}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 2, mt: 2 }}>
|
|
||||||
<Typography variant='body2' sx={{ color: '#222222' }}>
|
|
||||||
{periode}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{reportSubtitle && (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 2, mt: 2 }}>
|
|
||||||
<Typography variant='body2' sx={{ color: '#222222' }}>
|
|
||||||
{reportSubtitle}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider
|
<Divider
|
||||||
|
|||||||
@ -19,22 +19,25 @@ const PaymentMethodReport = ({ payments }: { payments: PaymentDataItem[] }) => {
|
|||||||
<h2 className='text-xl font-semibold text-gray-900'>Payment Methods</h2>
|
<h2 className='text-xl font-semibold text-gray-900'>Payment Methods</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
{payments && payments.map(method => (
|
{payments &&
|
||||||
<div key={method.payment_method_id} className='border-b border-gray-200 pb-4 last:border-b-0'>
|
payments.map(method => (
|
||||||
<div className='flex justify-between items-center mb-2'>
|
<div key={method.payment_method_id} className='border-b border-gray-200 pb-4 last:border-b-0'>
|
||||||
<span className='text-sm font-medium text-gray-900'>{method.payment_method_name}</span>
|
<div className='flex justify-between items-center mb-2'>
|
||||||
<span className='text-sm text-gray-600'>{method.percentage.toFixed(1)}%</span>
|
<span className='text-sm font-medium text-gray-900' translate='no'>
|
||||||
|
{method.payment_method_name}
|
||||||
|
</span>
|
||||||
|
<span className='text-sm text-gray-600'>{method.percentage.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar
|
||||||
|
percentage={method.percentage}
|
||||||
|
color={method.payment_method_type === 'cash' ? 'bg-green-500' : 'bg-blue-500'}
|
||||||
|
/>
|
||||||
|
<div className='flex justify-between items-center mt-2 text-xs text-gray-600'>
|
||||||
|
<span>{formatCurrency(method.total_amount)}</span>
|
||||||
|
<span>{method.order_count} orders</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar
|
))}
|
||||||
percentage={method.percentage}
|
|
||||||
color={method.payment_method_type === 'cash' ? 'bg-green-500' : 'bg-blue-500'}
|
|
||||||
/>
|
|
||||||
<div className='flex justify-between items-center mt-2 text-xs text-gray-600'>
|
|
||||||
<span>{formatCurrency(method.total_amount)}</span>
|
|
||||||
<span>{method.order_count} orders</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user