From 51f2e65bd24261c4d0961239bc0ab83679401a3a Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 23 Apr 2026 19:58:02 +0700 Subject: [PATCH] feat: add payroll calculations to analysis and visualize revenue vs profit trends --- app/Http/Controllers/AnalysisController.php | 54 +++++++++++- .../charts/revenue-vs-profit-chart.tsx | 88 +++++++++++++++++++ resources/js/pages/analysis.tsx | 9 +- resources/js/types/dashboard.ts | 4 +- 4 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 resources/js/components/charts/revenue-vs-profit-chart.tsx diff --git a/app/Http/Controllers/AnalysisController.php b/app/Http/Controllers/AnalysisController.php index 62e7ac9..3d868d5 100644 --- a/app/Http/Controllers/AnalysisController.php +++ b/app/Http/Controllers/AnalysisController.php @@ -8,6 +8,7 @@ use App\Models\Expense; use App\Models\Order; use App\Models\OrderItem; +use App\Models\Payroll; use App\Models\Purchase; use Illuminate\Support\Facades\DB; use Inertia\Inertia; @@ -20,6 +21,7 @@ public function __invoke() 'total_sales' => $this->getStats(fn () => Order::count()), 'total_revenue' => $this->getStats(fn () => Order::sum('total')), 'total_expenses' => $this->getStats(fn () => Expense::sum('amount')), + 'total_payrolls' => $this->getStats(fn () => Payroll::sum('total_salary')), 'cogs' => $this->getStats(fn () => Order::sum('hpp')), 'total_discount' => $this->getStats(fn () => Order::sum('discount')), 'total_purchases' => $this->getStats(fn () => Purchase::sum('total')), @@ -29,7 +31,11 @@ public function __invoke() $stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']); $stats['gross_profit'] = $this->calculateDiff($stats['total_revenue'], $stats['cogs']); - $stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']); + $stats['net_profit'] = [ + 'value' => $stats['gross_profit']['value'] - $stats['total_expenses']['value'] - $stats['total_payrolls']['value'], + 'yesterday' => null, + 'change' => null, + ]; $stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']); // Revenue vs Purchases per month (this year) @@ -131,9 +137,55 @@ public function __invoke() ]; }); + // Revenue & Profit per month + $ordersByMonth = DB::table('orders') + ->select( + DB::raw('MONTH(created_at) as month'), + DB::raw('SUM(total) as revenue'), + DB::raw('SUM(hpp) as cogs') + ) + ->groupBy(DB::raw('MONTH(created_at)')) + ->get(); + + $expensesByMonth = DB::table('expenses') + ->select( + DB::raw('MONTH(created_at) as month'), + DB::raw('SUM(amount) as total') + ) + ->groupBy(DB::raw('MONTH(created_at)')) + ->get(); + + $payrollsByMonth = DB::table('payrolls') + ->select( + DB::raw('MONTH(period_month) as month'), + DB::raw('SUM(total_salary) as total') + ) + ->whereNull('deleted_at') + ->groupBy(DB::raw('MONTH(period_month)')) + ->get(); + + $revenueVsProfit = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $expensesByMonth, $payrollsByMonth, $monthNames) { + $orderFound = $ordersByMonth->firstWhere('month', $month); + $expenseFound = $expensesByMonth->firstWhere('month', $month); + $payrollFound = $payrollsByMonth->firstWhere('month', $month); + + $revenue = $orderFound ? (float) $orderFound->revenue : 0; + $cogs = $orderFound ? (float) $orderFound->cogs : 0; + $expense = $expenseFound ? (float) $expenseFound->total : 0; + $payroll = $payrollFound ? (float) $payrollFound->total : 0; + $profit = $revenue - $cogs - $expense - $payroll; + + return [ + 'month' => $monthNames[$month - 1], + 'revenue' => $revenue, + 'profit' => $profit, + ]; + }); + return Inertia::render('analysis', [ 'stats' => $stats, 'salesByMonth' => $salesByMonth, + 'revenueVsProfit' => $revenueVsProfit, 'salesByHour' => $salesByHour, 'paymentMethods' => $paymentMethods, 'orderStatuses' => $orderStatuses, diff --git a/resources/js/components/charts/revenue-vs-profit-chart.tsx b/resources/js/components/charts/revenue-vs-profit-chart.tsx new file mode 100644 index 0000000..a08610a --- /dev/null +++ b/resources/js/components/charts/revenue-vs-profit-chart.tsx @@ -0,0 +1,88 @@ +"use client" + +import { CartesianGrid, Line, LineChart, XAxis } from "recharts" + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from "@/components/ui/card" +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart" +import { formatCurrency } from "@/lib/formatters" + +export const description = "Perbandingan Pendapatan vs Laba Bersih per bulan." + +const chartConfig = { + revenue: { + label: "Pendapatan", + color: "var(--chart-1)", + }, + profit: { + label: "Laba Bersih", + color: "var(--chart-2)", + }, +} satisfies ChartConfig + +export function RevenueVsProfitChart({ data }: { data: any[] }) { + return ( + + + Pendapatan vs Laba Bersih + Perbandingan Pendapatan vs Laba Bersih per bulan. + + + + + + value.slice(0, 3)} + /> + [ + formatCurrency(Number(value)), + ` - ${chartConfig[name as keyof typeof chartConfig]?.label ?? name}`, + ]} + /> + } + /> + + + + + + + ) +} diff --git a/resources/js/pages/analysis.tsx b/resources/js/pages/analysis.tsx index ac6d99b..238ca8d 100644 --- a/resources/js/pages/analysis.tsx +++ b/resources/js/pages/analysis.tsx @@ -4,11 +4,12 @@ import type { AnalysisPageProps } from '@/types'; import { Head, usePage } from '@inertiajs/react'; import { CustomBarChart } from '../components/charts/bar-chart'; import { CustomPieChart } from '../components/charts/pie-chart'; -import { SalesByHourChart } from '../components/charts/sales-by-hour-chart'; +import { RevenueVsProfitChart } from '../components/charts/revenue-vs-profit-chart'; import { RevenueVsPurchasesChart } from '../components/charts/revenue-vs-purchases-chart'; +import { SalesByHourChart } from '../components/charts/sales-by-hour-chart'; export default function Analisa() { - const { stats, salesByMonth, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } = + const { stats, salesByMonth, revenueVsProfit, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } = usePage().props; return ( @@ -33,6 +34,7 @@ export default function Analisa() { { title: "Total Diskon", stat: stats.total_discount }, { title: "Laba Kotor", stat: stats.gross_profit }, { title: "Laba Bersih", stat: stats.net_profit }, + { title: "Total Gaji", stat: stats.total_payrolls }, { title: "Total Pembelian", stat: stats.total_purchases }, { title: "Produk Terjual", stat: stats.products_sold, isCurrency: false }, { title: "Total Pelanggan", stat: stats.total_customers, isCurrency: false }, @@ -51,6 +53,9 @@ export default function Analisa() { + + +