From b424d119fc1f0b2cbe9bc2e423f275d4320d22a3 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 9 Mar 2026 18:57:18 +0700 Subject: [PATCH] feat: Create Dashboard page with comprehensive business statistics, including egg production, order counts, revenue, expenses, and top customers. --- app/Filament/Pages/Dashboard.php | 134 +++++++++++ .../views/filament/pages/dashboard.blade.php | 226 ++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 app/Filament/Pages/Dashboard.php create mode 100644 resources/views/filament/pages/dashboard.blade.php diff --git a/app/Filament/Pages/Dashboard.php b/app/Filament/Pages/Dashboard.php new file mode 100644 index 0000000..9489283 --- /dev/null +++ b/app/Filament/Pages/Dashboard.php @@ -0,0 +1,134 @@ + number_format((float) $val, $val == floor($val) ? 0 : 2, ',', '.'); + $now = now(); + $today = $now->format('Y-m-d'); + + // 1. Egg Production by Unit - Single optimized query with joins + $totalEggsByUnit = EggCollectionItem::query() + ->join('egg_collections', 'egg_collection_items.egg_collection_id', 'egg_collections.id') + ->join('units', 'egg_collection_items.unit_id', 'units.id') + ->whereNull('egg_collections.deleted_at') + ->where('egg_collections.production_date', $today) + ->selectRaw('units.name as unit_name, + sum(case when is_broken = 0 then quantity else 0 end) as total_good, + sum(case when is_broken = 1 then quantity else 0 end) as total_broken') + ->groupBy('units.name') + ->get() + ->map(fn ($item) => [ + 'unit_name' => $item->unit_name, + 'total_good' => $formatNumber($item->total_good), + 'total_broken' => $formatNumber($item->total_broken), + ]); + + // 2. Orders Stats - Combined count and sum + $orderStats = Order::whereDate('order_date', $today) + ->selectRaw('count(*) as count, sum(total_amount) as total') + ->first(); + $orderCount = (int) ($orderStats->count ?? 0); + $orderRevenue = (float) ($orderStats->total ?? 0); + + // 3. Delayed Goods Stats - Combined sum and paid_amount + $delayedStatsByUnit = DelayedGood::query() + ->join('units', 'delayed_goods.unit_id', 'units.id') + ->whereDate('delayed_goods.stored_date', $today) + ->selectRaw('units.name as unit_name, sum(quantity) as total') + ->groupBy('units.name') + ->get() + ->map(fn ($item) => [ + 'unit_name' => $item->unit_name, + 'total' => $formatNumber($item->total), + ]); + + $delayedStatsTotals = DelayedGood::whereDate('stored_date', $today) + ->selectRaw('sum(total_amount) as total, sum(paid_amount) as paid') + ->first(); + + $delayedRevenue = (float) ($delayedStatsTotals->total ?? 0); + $paidToday = (float) ($delayedStatsTotals->paid ?? 0); + $totalRevenue = $orderRevenue + $delayedRevenue; + + // 4. Expenses & Financials Today + $totalExpenses = (float) Expense::whereDate('expense_date', $today)->sum('amount'); + $monthlyPayroll = (float) Payroll::where('period_month', $now->format('Y-m'))->sum('total_salary'); + $totalFeedPurchase = (float) FeedPurchase::whereDate('purchase_date', $today)->sum('total_price'); + + $grossProfit = $totalRevenue - $totalFeedPurchase; + $netProfit = $grossProfit - $totalExpenses - $monthlyPayroll; + $totalReceivablesToday = $delayedRevenue - $paidToday; + + $mainStats = [ + ['label' => 'Pesanan', 'desc' => 'Jumlah transaksi', 'value' => $orderCount, 'prefix' => ''], + ['label' => 'Laba Bersih', 'desc' => 'Setelah dikurangi pengeluaran harian', 'value' => $formatNumber($netProfit), 'prefix' => 'Rp'], + ['label' => 'Piutang', 'desc' => 'Piutang barang tertunda', 'value' => $formatNumber($totalReceivablesToday), 'prefix' => 'Rp'], + ['label' => 'Pendapatan', 'desc' => 'Akumulasi pemasukan', 'value' => $formatNumber($totalRevenue), 'prefix' => 'Rp'], + ['label' => 'Belanja Pakan', 'desc' => 'Baru dibeli', 'value' => $formatNumber($totalFeedPurchase), 'prefix' => 'Rp'], + ]; + + // 5. Top Customers Today - Optimized summing + $topCustomers = Customer::query() + ->select('id', 'name', 'phone_number') + ->withSum(['orders' => fn ($q) => $q->whereDate('order_date', $today)], 'total_amount') + ->withSum(['delayedGoods' => fn ($q) => $q->whereDate('stored_date', $today)], 'total_amount') + ->get() + ->map(function ($customer) { + $customer->total_spent = ($customer->orders_sum_total_amount ?? 0) + ($customer->delayed_goods_sum_total_amount ?? 0); + + return $customer; + }) + ->filter(fn ($c) => $c->total_spent > 0) + ->sortByDesc('total_spent') + ->take(5) + ->values(); + + // 6. Distribution Charts Data + $revenueSplitData = [ + 'labels' => ['Penjualan Langsung', 'Barang Tertunda'], + 'data' => [$orderRevenue, $delayedRevenue], + ]; + + $expenseDistributionData = [ + 'labels' => ['Belanja Pakan', 'Pengeluaran Umum'], + 'data' => [$totalFeedPurchase, $totalExpenses], + ]; + + $salesByUnit = Order::whereDate('order_date', $today) + ->join('units', 'orders.unit_id', 'units.id') + ->selectRaw('units.name, sum(total_amount) as total') + ->groupBy('units.name') + ->get(); + + return [ + 'mainStats' => $mainStats, + 'totalEggsByUnit' => $totalEggsByUnit, + 'totalDelayedByUnit' => $delayedStatsByUnit, + 'topCustomers' => $topCustomers, + 'revenueSplit' => $revenueSplitData, + 'expenseDistribution' => $expenseDistributionData, + 'salesByUnit' => [ + 'labels' => $salesByUnit->pluck('name')->values(), + 'data' => $salesByUnit->pluck('total')->map(fn ($v) => (float) $v)->values(), + ], + 'formatNumber' => $formatNumber, + ]; + } +} diff --git a/resources/views/filament/pages/dashboard.blade.php b/resources/views/filament/pages/dashboard.blade.php new file mode 100644 index 0000000..0d0270f --- /dev/null +++ b/resources/views/filament/pages/dashboard.blade.php @@ -0,0 +1,226 @@ + +
+ {{-- Main Stats Grid --}} +
+ @foreach ($mainStats as $stat) +
+
+ {{ $stat['label'] }} +
+ @if ($stat['prefix']) + {{ $stat['prefix'] }} + @endif + {{ $stat['value'] }} +
+ {{ $stat['desc'] }} +
+
+ @endforeach +
+ + {{-- Distribution Row --}} +
+
+

+ + Sumber Pemasukan +

+
+ +
+
+
+

+ + Alokasi Pengeluaran +

+
+ +
+
+
+

+ + Penjualan per Satuan +

+
+ +
+
+
+ + {{-- Units Breakdown --}} +
+ {{-- Production --}} +
+
+

Produksi (Unit)

+ Hari Ini +
+
+ @forelse($totalEggsByUnit as $item) +
+ {{ $item['unit_name'] }} +
+
+ Bagus + {{ $item['total_good'] }} +
+
+ Pecah + {{ $item['total_broken'] }} +
+
+
+ @empty +
Belum ada data produksi
+ @endforelse +
+
+ + {{-- Delayed --}} +
+
+

Tertunda (Unit)

+ Hari Ini +
+
+ @forelse($totalDelayedByUnit as $item) +
+ {{ $item['unit_name'] }} + {{ $item['total'] }} +
+ @empty +
Tidak ada barang tertunda hari + ini
+ @endforelse +
+
+
+ + +
+ + {{-- Top Customers Today --}} +
+
+ +

Top Pelanggan Hari Ini

+
+
+ @forelse ($topCustomers as $customer) +
+
+ {{ $customer->name }} + {{ $customer->phone ?? 'Telp tidak ada' }} +
+
+ Transaksi + Rp + {{ $formatNumber($customer->total_spent) }} +
+
+ @empty +
Belum ada transaksi hari ini +
+ @endforelse +
+
+
+
+ + + + + +