feat: add total products sold to revenue summary and update dashboard display for marketing roles

This commit is contained in:
Yoga Pangestu 2026-08-23 20:12:20 +07:00
parent 6a196002fb
commit 6e7a4044b6
4 changed files with 69 additions and 47 deletions

View File

@ -13,6 +13,7 @@
use App\Models\Expense; use App\Models\Expense;
use App\Models\LeaveRequest; use App\Models\LeaveRequest;
use App\Models\Order; use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Payroll; use App\Models\Payroll;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
@ -154,6 +155,9 @@ public function getRevenueSummary(?User $user = null): array
$gross = $totalRevenue - $totalCogs; $gross = $totalRevenue - $totalCogs;
$net = $gross - $payrollTotal - $expenseTotal; $net = $gross - $payrollTotal - $expenseTotal;
$orderIds = (clone $baseQuery)->pluck('id');
$totalProductsSold = (int) OrderItem::whereIn('order_id', $orderIds)->sum('quantity');
return [ return [
'total_revenue' => $totalRevenue, 'total_revenue' => $totalRevenue,
'total_discount' => (int) $stats->total_discount, 'total_discount' => (int) $stats->total_discount,
@ -164,6 +168,7 @@ public function getRevenueSummary(?User $user = null): array
'payroll_total' => $payrollTotal, 'payroll_total' => $payrollTotal,
'expense_total' => $expenseTotal, 'expense_total' => $expenseTotal,
'total_orders' => (int) $stats->total_orders, 'total_orders' => (int) $stats->total_orders,
'total_products_sold' => $totalProductsSold,
]; ];
} }

View File

@ -483,6 +483,7 @@ export default function Analysis({
monthlyRetailRevenue, monthlyRetailRevenue,
}: AnalysisProps) { }: AnalysisProps) {
const { can, hasAnyRole, hasRole } = useCan(); const { can, hasAnyRole, hasRole } = useCan();
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
const [startDate, setStartDate] = useState(initialFilters.start_date ?? ''); const [startDate, setStartDate] = useState(initialFilters.start_date ?? '');
const [endDate, setEndDate] = useState(initialFilters.end_date ?? ''); const [endDate, setEndDate] = useState(initialFilters.end_date ?? '');
const [selectedPreset, setSelectedPreset] = useState(''); 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 }; return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, topProducts: 6, topCustomers: 7 };
} }
if (hasRole('marketing')) { if (isMarketing) {
return { statCards: 1, revenue: 2, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 }; return { statCards: 1, revenueByChannel: 3, orderPie: 4, totalOrder: 5, revenueTrend: 6, topProducts: 7, topCustomers: 8 };
} }
return {}; return {};
@ -753,7 +754,7 @@ export default function Analysis({
</Card> </Card>
)} )}
{can('analysis.revenue') && ( {can('analysis.revenue') && !isMarketing && (
<Card className="py-0" style={{ order: sectionOrder.revenue ?? 99 }}> <Card className="py-0" style={{ order: sectionOrder.revenue ?? 99 }}>
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row"> <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!"> <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> </div>
)} )}
{can('analysis.revenue') && ( {can('analysis.revenue') && !isMarketing && (
<Card className="py-0" style={{ order: 3 }}> <Card className="py-0" style={{ order: 3 }}>
<CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row"> <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!"> <div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:py-0!">

View File

@ -47,7 +47,8 @@ export function TransactionCardRow({
onUpdateStatus, onUpdateStatus,
onPrint, onPrint,
}: TransactionCardRowParams) { }: TransactionCardRowParams) {
const { can } = useCan(); const { can, hasAnyRole } = useCan();
const isMarketing = hasAnyRole(['marketing-offline', 'marketing-online']);
const [refundDialogOpen, setRefundDialogOpen] = useState(false); const [refundDialogOpen, setRefundDialogOpen] = useState(false);
const [cancelDialogOpen, setCancelDialogOpen] = useState(false); const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
const variantCount = transaction.items_count ?? 0; const variantCount = transaction.items_count ?? 0;
@ -188,18 +189,22 @@ export function TransactionCardRow({
</span> </span>
{formatCurrency(transaction.total_amount)} {formatCurrency(transaction.total_amount)}
</span> </span>
<span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}> {!isMarketing && (
<span className="font-normal text-muted-foreground"> <span className={transaction.profit >= 0 ? 'text-green-600' : 'text-red-600'}>
Laba:{' '} <span className="font-normal text-muted-foreground">
Laba:{' '}
</span>
{formatCurrency(transaction.profit)}
</span> </span>
{formatCurrency(transaction.profit)} )}
</span> {!isMarketing && (
<span> <span>
<span className="text-muted-foreground"> <span className="text-muted-foreground">
HPP:{' '} HPP:{' '}
</span>
{formatCurrency(transaction.cogs)}
</span> </span>
{formatCurrency(transaction.cogs)} )}
</span>
</div> </div>
{transaction.photo_url && ( {transaction.photo_url && (

View File

@ -53,6 +53,7 @@ type DashboardProps = {
payroll_total: number; payroll_total: number;
expense_total: number; expense_total: number;
total_orders: number; total_orders: number;
total_products_sold: number;
}; };
expenseSummary: { expenseSummary: {
total: number; total: number;
@ -97,7 +98,8 @@ export default function Dashboard({
isOnLeave, isOnLeave,
canCheckIn, canCheckIn,
}: DashboardProps) { }: 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 { auth } = usePage().props as { auth: { user?: { username?: string } } };
const [currentTime, setCurrentTime] = useState(new Date()); const [currentTime, setCurrentTime] = useState(new Date());
const [showCamera, setShowCamera] = useState(false); const [showCamera, setShowCamera] = useState(false);
@ -336,37 +338,46 @@ export default function Dashboard({
mainLabel="Total" mainLabel="Total"
mainValue={`Rp${formatRupiah(revenueSummary.total_revenue)}`} mainValue={`Rp${formatRupiah(revenueSummary.total_revenue)}`}
subLabel={`${revenueSummary.total_orders} transaksi selesai`} subLabel={`${revenueSummary.total_orders} transaksi selesai`}
items={[ items={
{ isMarketing
label: 'Kotor', ? [
value: `Rp${formatRupiah(revenueSummary.gross)}`, {
}, label: 'Produk Terjual',
{ value: revenueSummary.total_products_sold.toLocaleString('id-ID'),
label: 'Bersih', },
value: `Rp${formatRupiah(revenueSummary.net)}`, ]
}, : [
{ {
label: 'Gaji', label: 'Kotor',
value: `Rp${formatRupiah(revenueSummary.payroll_total)}`, value: `Rp${formatRupiah(revenueSummary.gross)}`,
}, },
{ {
label: 'Pengeluaran', label: 'Bersih',
value: `Rp${formatRupiah(revenueSummary.expense_total)}`, value: `Rp${formatRupiah(revenueSummary.net)}`,
}, },
{ {
label: 'Potongan', label: 'Gaji',
value: `Rp${formatRupiah(revenueSummary.total_deduction)}`, value: `Rp${formatRupiah(revenueSummary.payroll_total)}`,
}, },
{ {
label: 'Diskon', label: 'Pengeluaran',
value: `Rp${formatRupiah(revenueSummary.total_discount)}`, value: `Rp${formatRupiah(revenueSummary.expense_total)}`,
}, },
{ {
label: 'HPP', label: 'Potongan',
value: `Rp${formatRupiah(revenueSummary.total_cogs)}`, value: `Rp${formatRupiah(revenueSummary.total_deduction)}`,
}, },
]} {
cols={visibleStatsCount === 1 ? 4 : visibleStatsCount === 2 ? 3 : 2} 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}
/> />
)} )}