null, 'end' => null, ]; public array $selectedOutletIds = []; public array $revenueTrendChart = []; public array $hourlySalesChart = []; public array $categoryDistributionChart = []; public array $paymentMethodChart = []; public array $outletPerformanceChart = []; public array $memberGrowthChart = []; public array $dayOfWeekSalesChart = []; public array $customerTypeChart = []; public array $transactionTrendChart = []; public array $aovTrendChart = []; public array $expenseBreakdownChart = []; public array $revenueVsCostChart = []; public array $customerRetentionChart = []; public array $voucherUsageTrendChart = []; public array $countingData = []; public array $orders = []; public array $expenses = []; public array $topPerfumes = []; public array $topProducts = []; public array $topBottles = []; public function mount(): void { /** @var \App\Models\User $user */ $user = auth()->user(); $this->outlets = $user->outlets()->get()->pluck('name', 'id')->toArray(); } public function render(): View { /** @var \App\Models\User $user */ $user = auth()->user(); $outletIds = $this->selectedOutletIds; $period = $this->period; $range = $this->range; $dateQuery = match ($period) { '7' => now()->subDays(7), '30' => now()->subDays(30), '90' => now()->subDays(90), '365' => now()->subDays(365), default => null, }; $startDate = ! empty($range['start']) ? Carbon::parse($range['start'])->startOfDay() : null; $endDate = ! empty($range['end']) ? Carbon::parse($range['end'])->endOfDay() : null; if ($startDate && $endDate) { $dateQuery = null; } $filterDate = function ($q) use ($dateQuery, $startDate, $endDate) { if ($startDate && $endDate) { $q->whereBetween('created_at', [$startDate, $endDate]); } elseif ($dateQuery) { $q->where('created_at', '>=', $dateQuery); } }; $countingData = [ [ 'title' => 'Outlet', 'value' => Outlet::when($dateQuery || ($startDate && $endDate), $filterDate)->count(), 'description' => 'Total jumlah outlet atau cabang outlet yang terdaftar.', ], [ 'title' => 'Pegawai', 'value' => User::whereHas('employee') ->when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->withoutDeveloper() ->count(), 'description' => 'Jumlah karyawan aktif yang terdaftar di outlet terpilih.', ], [ 'title' => 'Tier', 'value' => Tier::when($dateQuery || ($startDate && $endDate), $filterDate)->count(), 'description' => 'Jumlah tingkatan loyalitas member (seperti Bronze, Silver, Gold).', ], [ 'title' => 'Voucher Aktif', 'value' => Voucher::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->active() ->count(), 'description' => 'Jumlah kode promo atau voucher yang masih berlaku saat ini.', ], [ 'title' => 'Member', 'value' => User::whereHas('customer') ->when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->count(), 'description' => 'Total pelanggan yang sudah mendaftar sebagai member resmi.', ], [ 'title' => 'Customer', 'value' => Customer::whereDoesntHave('user') ->when($dateQuery || ($startDate && $endDate), $filterDate) ->count(), 'description' => 'Jumlah pelanggan umum (guest) yang pernah melakukan transaksi.', ], [ 'title' => 'Kategori', 'value' => Category::when($dateQuery || ($startDate && $endDate), $filterDate)->count(), 'description' => 'Jumlah kategori barang (seperti Parfum, Sabun, dll).', ], [ 'title' => 'Merek', 'value' => Brand::when($dateQuery || ($startDate && $endDate), $filterDate)->count(), 'description' => 'Jumlah merek parfum yang terdaftar di sistem.', ], [ 'title' => 'Parfum', 'value' => Perfume::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->count(), 'description' => 'Jumlah varian bibit parfum yang tersedia dalam katalog.', ], [ 'title' => 'Produk', 'value' => Product::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->count(), 'description' => 'Jumlah item produk jadi (seperti aksesoris) yang terdaftar.', ], [ 'title' => 'Botol', 'value' => Bottle::when(! empty($outletIds), fn ($q) => $q->whereHas('outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->count(), 'description' => 'Jumlah jenis botol parfum yang tersedia.', ], ]; $orderQuery = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); $totalIncome = (clone $orderQuery)->sum('total'); $totalCogs = (clone $orderQuery)->sum('cogs'); $totalDiscount = (clone $orderQuery)->sum(DB::raw('discount + COALESCE(voucher_discount, 0)')); $expenseQuery = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); $totalExpense = $expenseQuery->sum('amount'); $payrollQuery = Payroll::when(! empty($outletIds), fn ($q) => $q->whereHas('user.outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->paid(); $totalPayroll = $payrollQuery->sum('total_salary'); $grossProfit = $totalIncome - $totalCogs - $totalDiscount; $netProfit = $grossProfit - $totalExpense - $totalPayroll; $totalOrdersCount = (clone $orderQuery)->count(); $totalItemsCount = OrderItem::whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate))->sum('quantity'); $aov = $totalOrdersCount > 0 ? $totalIncome / $totalOrdersCount : 0; $profitMargin = $totalIncome > 0 ? ($netProfit / $totalIncome) * 100 : 0; $itemsPerOrder = $totalOrdersCount > 0 ? $totalItemsCount / $totalOrdersCount : 0; // Total Asset Calculation $totalOutletAsset = (int) Perfume::query() ->join('outlet_perfume', 'perfumes.id', '=', 'outlet_perfume.perfume_id') ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_perfume.outlet_id', $outletIds)) ->sum(DB::raw('CAST(outlet_perfume.stock AS DECIMAL) * perfumes.cost_price')); $totalOutletAsset += (int) Product::query() ->join('outlet_product', 'products.id', '=', 'outlet_product.product_id') ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_product.outlet_id', $outletIds)) ->sum(DB::raw('CAST(outlet_product.stock AS DECIMAL) * products.cost_price')); $totalOutletAsset += (int) Bottle::query() ->join('bottle_outlet', 'bottles.id', '=', 'bottle_outlet.bottle_id') ->when(! empty($outletIds), fn ($q) => $q->whereIn('bottle_outlet.outlet_id', $outletIds)) ->sum(DB::raw('CAST(bottle_outlet.stock AS DECIMAL) * bottles.cost_price')); $totalWarehouseAsset = (int) Perfume::query() ->join('perfume_warehouse', 'perfumes.id', '=', 'perfume_warehouse.perfume_id') ->sum(DB::raw('CAST(perfume_warehouse.stock AS DECIMAL) * perfumes.cost_price')); $totalWarehouseAsset += (int) Product::query() ->join('product_warehouse', 'products.id', '=', 'product_warehouse.product_id') ->sum(DB::raw('CAST(product_warehouse.stock AS DECIMAL) * products.cost_price')); $totalWarehouseAsset += (int) Bottle::query() ->join('bottle_warehouse', 'bottles.id', '=', 'bottle_warehouse.bottle_id') ->sum(DB::raw('CAST(bottle_warehouse.stock AS DECIMAL) * bottles.cost_price')); $customerOrders = (clone $orderQuery)->select('customer_id', DB::raw('count(*) as count')) ->whereNotNull('customer_id') ->groupBy('customer_id') ->get(); $repeatCustomers = $customerOrders->filter(fn ($c) => $c->count > 1)->count(); $totalCustomers = $customerOrders->count(); $loyaltyRate = $totalCustomers > 0 ? ($repeatCustomers / $totalCustomers) * 100 : 0; $orders = array_filter([ [ 'title' => 'Average Order Value (AOV)', 'value' => formatCurrencyNumber($aov, 'Rp'), 'description' => 'Rata-rata nilai belanja pelanggan dalam satu kali checkout.', ], $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Profit Margin', 'value' => round($profitMargin, 1).'%', 'description' => 'Persentase keuntungan bersih dibandingkan dengan total pendapatan.', ] : null, $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Aset Outlet', 'value' => formatCurrencyNumber($totalOutletAsset, 'Rp'), 'description' => 'Total nilai aset dari stok barang yang ada di outlet (Parfum, Produk, Botol) berdasarkan harga modal.', ] : null, $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Aset Gudang', 'value' => formatCurrencyNumber($totalWarehouseAsset, 'Rp'), 'description' => 'Total nilai aset dari stok barang yang ada di gudang (Parfum, Produk, Botol) berdasarkan harga modal.', ] : null, [ 'title' => 'Loyalty Rate', 'value' => round($loyaltyRate, 1).'%', 'description' => 'Persentase pelanggan yang melakukan pembelian lebih dari satu kali.', ], [ 'title' => 'Item per Transaksi', 'value' => round($itemsPerOrder, 1), 'description' => 'Rata-rata jumlah barang yang dibeli dalam satu transaksi.', ], [ 'title' => 'Total Order', 'value' => formatCurrencyNumber($totalOrdersCount), 'description' => 'Total frekuensi transaksi atau pesanan yang masuk selama periode ini.', ], [ 'title' => 'Pendapatan', 'value' => formatCurrencyNumber($totalIncome, 'Rp'), 'description' => 'Total nominal uang yang masuk dari penjualan (Omzet).', ], $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'HPP', 'value' => formatCurrencyNumber($totalCogs, 'Rp'), 'description' => 'Total modal barang atau Harga Pokok Penjualan (HPP).', ] : null, $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Laba Kotor', 'value' => formatCurrencyNumber($grossProfit, 'Rp'), 'description' => 'Pendapatan dikurangi dengan HPP dan Diskon.', ] : null, $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Laba Bersih', 'value' => formatCurrencyNumber($netProfit, 'Rp'), 'description' => 'Laba kotor dikurangi dengan beban operasional dan gaji.', ] : null, [ 'title' => 'Diskon', 'value' => formatCurrencyNumber($totalDiscount, 'Rp'), 'description' => 'Total nilai potongan harga (diskon) yang diberikan kepada pelanggan.', ], [ 'title' => 'Voucher Terpakai', 'value' => formatCurrencyNumber( (clone $orderQuery)->whereNotNull('voucher_id')->count() ), 'description' => 'Jumlah transaksi yang menggunakan voucher diskon selama periode ini.', ], [ 'title' => 'Parfum Terjual', 'value' => formatCurrencyNumber( OrderItem::where('orderable_type', Perfume::class) ->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate)) ->sum('quantity') ).' ml', 'description' => 'Total volume cairan parfum yang terjual dalam satuan mililiter (ml).', ], [ 'title' => 'Produk Terjual', 'value' => formatCurrencyNumber( OrderItem::where('orderable_type', Product::class) ->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate)) ->sum('quantity') ).' pcs', 'description' => 'Total jumlah unit produk yang terjual dalam satuan pcs.', ], [ 'title' => 'Botol Terjual', 'value' => formatCurrencyNumber( OrderItem::where('orderable_type', Bottle::class) ->whereHas('order', fn ($q) => $q->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds))->when($dateQuery || ($startDate && $endDate), $filterDate)) ->sum('quantity') ).' pcs', 'description' => 'Total jumlah unit botol yang terjual dalam satuan pcs.', ], ]); $expenses = array_filter([ [ 'title' => 'Beban Outlet', 'value' => formatCurrencyNumber( Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->sum('amount'), 'Rp' ), 'description' => 'Total biaya operasional atau pengeluaran outlet yang tercatat selama periode ini.', ], $user->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Belanja Barang', 'value' => formatCurrencyNumber( Purchase::when($dateQuery || ($startDate && $endDate), $filterDate) ->sum('total'), 'Rp' ), 'description' => 'Total pengeluaran untuk pembelian stok barang atau inventaris selama periode ini.', ] : null, [ 'title' => 'Penggajian', 'value' => formatCurrencyNumber( Payroll::when(! empty($outletIds), fn ($q) => $q->whereHas('user.outlets', fn ($q2) => $q2->whereIn('outlets.id', $outletIds))) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->paid() ->sum('total_salary'), 'Rp' ), 'description' => 'Total pengeluaran untuk gaji karyawan.', ], ]); $topPerfumes = OrderItem::where('orderable_type', Perfume::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); }) ->with('orderable') ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'), DB::raw('SUM(unit_price * quantity) as total_sales_value')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sales_value') ->take(5) ->get() ->map(fn ($item) => [ 'name' => $item->orderable?->name ?? 'Unknown', 'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'), 'total_sold' => formatCurrencyNumber($item->total_sold), 'total_sales' => formatCurrencyNumber($item->total_sales_value, 'Rp'), ]) ->toArray(); $topProducts = OrderItem::where('orderable_type', Product::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); }) ->with('orderable') ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'), DB::raw('SUM(unit_price * quantity) as total_sales_value')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sales_value') ->take(5) ->get() ->map(fn ($item) => [ 'name' => $item->orderable?->name ?? 'Unknown', 'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'), 'total_sold' => formatCurrencyNumber($item->total_sold), 'total_sales' => formatCurrencyNumber($item->total_sales_value, 'Rp'), ]) ->toArray(); $topBottles = OrderItem::where('orderable_type', Bottle::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); }) ->with('orderable') ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold'), DB::raw('SUM(unit_price * quantity) as total_sales_value')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sales_value') ->take(5) ->get() ->map(fn ($item) => [ 'name' => $item->orderable?->name ?? 'Unknown', 'sale_price' => formatCurrencyNumber($item->orderable?->sale_price ?? 0, 'Rp'), 'total_sold' => formatCurrencyNumber($item->total_sold), 'total_sales' => formatCurrencyNumber($item->total_sales_value, 'Rp'), ]) ->toArray(); // --- Chart Data Logic --- // 1. Revenue & Profit Trend $trendDates = collect(range(29, 0))->map(fn ($i) => now()->subDays($i)->format('Y-m-d')); $ordersTrend = Order::query() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->where('created_at', '>=', now()->subDays(30)->startOfDay()) ->select(DB::raw('DATE(created_at) as date'), DB::raw('SUM(total) as revenue'), DB::raw('SUM(total) - SUM(cogs) - SUM(discount) - SUM(COALESCE(voucher_discount,0)) as gross_profit'), DB::raw('count(*) as count')) ->groupBy('date') ->get() ->keyBy('date'); $expensesTrend = Expense::query() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->where('created_at', '>=', now()->subDays(30)->startOfDay()) ->select(DB::raw('DATE(created_at) as date'), DB::raw('SUM(amount) as total_expense')) ->groupBy('date') ->get() ->keyBy('date'); $this->revenueTrendChart = [ 'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(), 'revenue' => $trendDates->map(fn ($d) => (int) ($ordersTrend->get($d)?->revenue ?? 0))->toArray(), 'profit' => $trendDates->map(fn ($d) => (int) (($ordersTrend->get($d)?->gross_profit ?? 0) - ($expensesTrend->get($d)?->total_expense ?? 0)))->toArray(), ]; // 2. busiest hour $hourFunc = DB::getDriverName() === 'sqlite' ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)'; $hourlySales = (clone $orderQuery) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count')) ->groupBy('hour') ->orderBy('hour') ->get() ->keyBy('hour'); $this->hourlySalesChart = [ 'labels' => collect(range(0, 23))->map(fn ($h) => str_pad($h, 2, '0', STR_PAD_LEFT).':00')->toArray(), 'data' => collect(range(0, 23))->map(fn ($h) => $hourlySales->get($h)?->count ?? 0)->toArray(), ]; // 3. Product Category Distribution $perfumeSales = OrderItem::where('orderable_type', Perfume::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); })->sum(DB::raw('unit_price * quantity')); $productSales = OrderItem::where('orderable_type', Product::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); })->sum(DB::raw('unit_price * quantity')); $bottleSales = OrderItem::where('orderable_type', Bottle::class) ->whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); })->sum(DB::raw('unit_price * quantity')); $this->categoryDistributionChart = [ 'labels' => ['Parfum', 'Produk', 'Botol'], 'data' => [(int) $perfumeSales, (int) $productSales, (int) $bottleSales], ]; // 4. Payment Method $paymentMethods = Payment::whereHas('order', function ($q) use ($outletIds, $dateQuery, $startDate, $endDate, $filterDate) { $q->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate); }) ->select('method', DB::raw('SUM(amount) as total')) ->groupBy('method') ->get(); $this->paymentMethodChart = [ 'labels' => $paymentMethods->map(fn ($pm) => $pm->method?->label() ?? 'Unknown')->toArray(), 'data' => $paymentMethods->pluck('total')->toArray(), ]; // 5. Outlet Performance $outletPerformance = Order::query() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when($dateQuery || ($startDate && $endDate), $filterDate) ->select('outlet_id', DB::raw('SUM(total) as revenue')) ->groupBy('outlet_id') ->with('outlet:id,name') ->get(); $this->outletPerformanceChart = [ 'labels' => $outletPerformance->map(fn ($op) => $op->outlet?->name ?? 'Outlet #'.$op->outlet_id)->toArray(), 'data' => $outletPerformance->pluck('revenue')->toArray(), ]; // 6. Member Growth (Area Chart) - Cumulative $memberGrowth = User::whereHas('customer') ->select(DB::raw('DATE(created_at) as date'), DB::raw('count(*) as count')) ->groupBy('date') ->orderBy('date') ->get(); $cumulativeCount = 0; $memberChartData = $memberGrowth->map(function ($item) use (&$cumulativeCount) { $cumulativeCount += $item->count; return [ 'date' => Carbon::parse($item->date)->format('d M Y'), 'count' => $cumulativeCount, ]; }); $this->memberGrowthChart = [ 'labels' => $memberChartData->pluck('date')->toArray(), 'data' => $memberChartData->pluck('count')->toArray(), ]; // 7. Day of Week Sales $dayOfWeekFunc = DB::getDriverName() === 'sqlite' ? "(CAST(strftime('%w', created_at) AS INTEGER) + 1)" : 'DAYOFWEEK(created_at)'; $dayOfWeekSales = (clone $orderQuery) ->select(DB::raw("$dayOfWeekFunc as day"), DB::raw('SUM(total) as revenue')) ->groupBy('day') ->get() ->keyBy('day'); $days = [ 1 => 'Minggu', 2 => 'Senin', 3 => 'Selasa', 4 => 'Rabu', 5 => 'Kamis', 6 => 'Jumat', 7 => 'Sabtu', ]; $this->dayOfWeekSalesChart = [ 'labels' => array_values($days), 'data' => collect(range(1, 7))->map(fn ($d) => (int) ($dayOfWeekSales->get($d)?->revenue ?? 0))->toArray(), ]; // 8. Customer Type (Member vs Guest) $memberOrders = (clone $orderQuery)->whereNotNull('customer_id')->count(); $guestOrders = (clone $orderQuery)->whereNull('customer_id')->count(); $this->customerTypeChart = [ 'labels' => ['Member', 'Guest (Umum)'], 'data' => [$memberOrders, $guestOrders], ]; // 9. Transaction Trend (Daily Count) $this->transactionTrendChart = [ 'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(), 'data' => $trendDates->map(fn ($d) => (int) ($ordersTrend->get($d)?->count ?? 0))->toArray(), ]; // 10. AOV Trend (Daily Revenue / Daily Count) $this->aovTrendChart = [ 'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(), 'data' => $trendDates->map(function ($d) use ($ordersTrend) { $rev = $ordersTrend->get($d)?->revenue ?? 0; $count = $ordersTrend->get($d)?->count ?? 0; return $count > 0 ? (int) ($rev / $count) : 0; })->toArray(), ]; // 11. Expense Breakdown (Operational vs Payroll) $this->expenseBreakdownChart = [ 'labels' => ['Beban Operasional', 'Gaji (Payroll)'], 'data' => [(int) $totalExpense, (int) $totalPayroll], ]; // 12. Revenue vs Total Cost (COGS + Expense + Payroll) $this->revenueVsCostChart = [ 'labels' => ['Pendapatan', 'Total Biaya (HPP + Beban + Gaji)'], 'data' => [(int) $totalIncome, (int) ($totalCogs + $totalExpense + $totalPayroll)], ]; // 13. Customer Retention (New vs Returning Transactions) $this->customerRetentionChart = [ 'labels' => ['Pelanggan Setia (Repeat)', 'Pelanggan Baru/Sekali'], 'data' => [$repeatCustomers, max(0, $totalCustomers - $repeatCustomers)], ]; // 14. Voucher Usage Trend $vouchersTrend = Order::query() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->where('created_at', '>=', now()->subDays(30)->startOfDay()) ->whereNotNull('voucher_id') ->select(DB::raw('DATE(created_at) as date'), DB::raw('count(*) as count')) ->groupBy('date') ->get() ->keyBy('date'); $this->voucherUsageTrendChart = [ 'labels' => $trendDates->map(fn ($d) => Carbon::parse($d)->format('d M'))->toArray(), 'data' => $trendDates->map(fn ($d) => (int) ($vouchersTrend->get($d)?->count ?? 0))->toArray(), ]; $this->countingData = $countingData; $this->orders = array_values(array_filter($orders)); $this->expenses = array_values(array_filter($expenses)); $this->topPerfumes = $topPerfumes; $this->topProducts = $topProducts; $this->topBottles = $topBottles; return view('livewire.studio.dashboard.analysis', [ 'pageTitle' => 'Analisa', ]); } }