feat: add total products sold to revenue summary and update dashboard display for marketing roles
This commit is contained in:
parent
6a196002fb
commit
6e7a4044b6
@ -13,6 +13,7 @@
|
||||
use App\Models\Expense;
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Payroll;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
@ -154,6 +155,9 @@ public function getRevenueSummary(?User $user = null): array
|
||||
$gross = $totalRevenue - $totalCogs;
|
||||
$net = $gross - $payrollTotal - $expenseTotal;
|
||||
|
||||
$orderIds = (clone $baseQuery)->pluck('id');
|
||||
$totalProductsSold = (int) OrderItem::whereIn('order_id', $orderIds)->sum('quantity');
|
||||
|
||||
return [
|
||||
'total_revenue' => $totalRevenue,
|
||||
'total_discount' => (int) $stats->total_discount,
|
||||
@ -164,6 +168,7 @@ public function getRevenueSummary(?User $user = null): array
|
||||
'payroll_total' => $payrollTotal,
|
||||
'expense_total' => $expenseTotal,
|
||||
'total_orders' => (int) $stats->total_orders,
|
||||
'total_products_sold' => $totalProductsSold,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -483,6 +483,7 @@ export default function Analysis({
|
||||
monthlyRetailRevenue,
|
||||
}: AnalysisProps) {
|
||||
const { can, hasAnyRole, hasRole } = useCan();
|
||||
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||
const [startDate, setStartDate] = useState(initialFilters.start_date ?? '');
|
||||
const [endDate, setEndDate] = useState(initialFilters.end_date ?? '');
|
||||
const [selectedPreset, setSelectedPreset] = useState('');
|
||||
@ -591,8 +592,8 @@ export default function Analysis({
|
||||
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, topProducts: 6, topCustomers: 7 };
|
||||
}
|
||||
|
||||
if (hasRole('marketing')) {
|
||||
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 };
|
||||
if (isMarketing) {
|
||||
return { statCards: 1, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 };
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -753,7 +754,7 @@ export default function Analysis({
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{can('analysis.revenue') && (
|
||||
{can('analysis.revenue') && !isMarketing && (
|
||||
<Card className="py-0" style={{ order: sectionOrder.revenue ?? 99 }}>
|
||||
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
||||
@ -911,7 +912,7 @@ export default function Analysis({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{can('analysis.revenue') && (
|
||||
{can('analysis.revenue') && !isMarketing && (
|
||||
<Card className="py-0" style={{ order: 3 }}>
|
||||
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
|
||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">
|
||||
|
||||
@ -47,7 +47,8 @@ export function TransactionCardRow({
|
||||
onUpdateStatus,
|
||||
onPrint,
|
||||
}: TransactionCardRowParams) {
|
||||
const { can } = useCan();
|
||||
const { can, hasAnyRole } = useCan();
|
||||
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||
const [refundDialogOpen, setRefundDialogOpen] = useState(false);
|
||||
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
||||
const variantCount = transaction.items_count ?? 0;
|
||||
@ -188,18 +189,22 @@ export function TransactionCardRow({
|
||||
</span>
|
||||
{formatCurrency(transaction.total_amount)}
|
||||
</span>
|
||||
<span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}>
|
||||
<span className="font-normal text-muted-foreground">
|
||||
Laba:{' '}
|
||||
{!isMarketing && (
|
||||
<span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}>
|
||||
<span className="font-normal text-muted-foreground">
|
||||
Laba:{' '}
|
||||
</span>
|
||||
{formatCurrency(transaction.profit)}
|
||||
</span>
|
||||
{formatCurrency(transaction.profit)}
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
HPP:{' '}
|
||||
)}
|
||||
{!isMarketing && (
|
||||
<span>
|
||||
<span className="text-muted-foreground">
|
||||
HPP:{' '}
|
||||
</span>
|
||||
{formatCurrency(transaction.cogs)}
|
||||
</span>
|
||||
{formatCurrency(transaction.cogs)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{transaction.photo_url && (
|
||||
|
||||
@ -53,6 +53,7 @@ type DashboardProps = {
|
||||
payroll_total: number;
|
||||
expense_total: number;
|
||||
total_orders: number;
|
||||
total_products_sold: number;
|
||||
};
|
||||
expenseSummary: {
|
||||
total: number;
|
||||
@ -97,7 +98,8 @@ export default function Dashboard({
|
||||
isOnLeave,
|
||||
canCheckIn,
|
||||
}: DashboardProps) {
|
||||
const { can, hasRole } = useCan();
|
||||
const { can, hasRole, hasAnyRole } = useCan();
|
||||
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
|
||||
const { auth } = usePage().props as { auth: { user?: { username?: string } } };
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
const [showCamera, setShowCamera] = useState(false);
|
||||
@ -336,37 +338,46 @@ export default function Dashboard({
|
||||
mainLabel="Total"
|
||||
mainValue={`Rp${formatRupiah(revenueSummary.total_revenue)}`}
|
||||
subLabel={`${revenueSummary.total_orders} transaksi selesai`}
|
||||
items={[
|
||||
{
|
||||
label: 'Kotor',
|
||||
value: `Rp${formatRupiah(revenueSummary.gross)}`,
|
||||
},
|
||||
{
|
||||
label: 'Bersih',
|
||||
value: `Rp${formatRupiah(revenueSummary.net)}`,
|
||||
},
|
||||
{
|
||||
label: 'Gaji',
|
||||
value: `Rp${formatRupiah(revenueSummary.payroll_total)}`,
|
||||
},
|
||||
{
|
||||
label: 'Pengeluaran',
|
||||
value: `Rp${formatRupiah(revenueSummary.expense_total)}`,
|
||||
},
|
||||
{
|
||||
label: 'Potongan',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_deduction)}`,
|
||||
},
|
||||
{
|
||||
label: 'Diskon',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_discount)}`,
|
||||
},
|
||||
{
|
||||
label: 'HPP',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_cogs)}`,
|
||||
},
|
||||
]}
|
||||
cols={visibleStatsCount === 1 ? 4 : visibleStatsCount === 2 ? 3 : 2}
|
||||
items={
|
||||
isMarketing
|
||||
? [
|
||||
{
|
||||
label: 'Produk Terjual',
|
||||
value: revenueSummary.total_products_sold.toLocaleString('id-ID'),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: 'Kotor',
|
||||
value: `Rp${formatRupiah(revenueSummary.gross)}`,
|
||||
},
|
||||
{
|
||||
label: 'Bersih',
|
||||
value: `Rp${formatRupiah(revenueSummary.net)}`,
|
||||
},
|
||||
{
|
||||
label: 'Gaji',
|
||||
value: `Rp${formatRupiah(revenueSummary.payroll_total)}`,
|
||||
},
|
||||
{
|
||||
label: 'Pengeluaran',
|
||||
value: `Rp${formatRupiah(revenueSummary.expense_total)}`,
|
||||
},
|
||||
{
|
||||
label: 'Potongan',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_deduction)}`,
|
||||
},
|
||||
{
|
||||
label: 'Diskon',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_discount)}`,
|
||||
},
|
||||
{
|
||||
label: 'HPP',
|
||||
value: `Rp${formatRupiah(revenueSummary.total_cogs)}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
cols={isMarketing ? 2 : visibleStatsCount === 1 ? 4 : visibleStatsCount === 2 ? 3 : 2}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user