outlets = auth()->user()->outlets()->get()->pluck('name', 'id')->toArray(); $this->selectedOutletIds = auth()->user()->outlets()->pluck('outlets.id')->toArray(); } public function render(): View { $outletIds = $this->selectedOutletIds; $todayOrder = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->count(); $yesterdayOrder = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->count(); $todayIncome = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->sum('total'); $yesterdayIncome = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->sum('total'); $todayCogs = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->sum('cogs'); $yesterdayCogs = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->sum('cogs'); $todayDiscount = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->sum('discount'); $yesterdayDiscount = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->sum('discount'); $todayVoucher = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->whereNotNull('voucher_id') ->count(); $yesterdayVoucher = Order::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->whereNotNull('voucher_id') ->count(); $todayExpense = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->today() ->sum('amount'); $yesterdayExpense = Expense::when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->yesterday() ->sum('amount'); $todayPurchase = Purchase::query()->today()->sum('total'); $yesterdayPurchase = Purchase::query()->yesterday()->sum('total'); $todayGrossProfit = $todayIncome - $todayCogs - $todayDiscount; $yesterdayGrossProfit = $yesterdayIncome - $yesterdayCogs - $yesterdayDiscount; $todayNetProfit = $todayGrossProfit - $todayExpense; $yesterdayNetProfit = $yesterdayGrossProfit - $yesterdayExpense; $todaySoldPerfume = OrderItem::where('orderable_type', Perfume::class) ->whereHas( 'order', fn ($q) => $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)) ) ->sum('quantity'); $yesterdaySoldPerfume = OrderItem::where('orderable_type', Perfume::class) ->whereHas( 'order', fn ($q) => $q->yesterday()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)) ) ->sum('quantity'); $todayTopPerfume = OrderItem::where('orderable_type', Perfume::class) ->whereHas( 'order', fn ($q) => $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)) ) ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sold') ->with('orderable') ->first(); $yesterdayTopPerfume = OrderItem::where('orderable_type', Perfume::class) ->whereHas( 'order', fn ($q) => $q->yesterday()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)) ) ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sold') ->with('orderable') ->first(); // New Card: Members Joined $todayNewMembers = User::whereHas('customer')->whereDate('created_at', now())->count(); $yesterdayNewMembers = User::whereHas('customer')->whereDate('created_at', now()->yesterday())->count(); // New Card: AOV $todayAov = $todayOrder > 0 ? $todayIncome / $todayOrder : 0; $yesterdayAov = $yesterdayOrder > 0 ? $yesterdayIncome / $yesterdayOrder : 0; $todayProfitMargin = $todayIncome > 0 ? ($todayNetProfit / $todayIncome) * 100 : 0; $yesterdayProfitMargin = $yesterdayIncome > 0 ? ($yesterdayNetProfit / $yesterdayIncome) * 100 : 0; // --- Chart Data Logic --- // 1. Hourly Sales Comparison (Today vs Yesterday) $hourFunc = DB::getDriverName() === 'sqlite' ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)'; $todayHourly = Order::whereDate('created_at', now()) ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count')) ->groupBy('hour') ->get()->keyBy('hour'); $yesterdayHourly = Order::whereDate('created_at', now()->yesterday()) ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count')) ->groupBy('hour') ->get()->keyBy('hour'); $this->hourlyComparisonChart = [ 'labels' => collect(range(0, 23))->map(fn ($h) => str_pad($h, 2, '0', STR_PAD_LEFT).':00')->toArray(), 'today' => collect(range(0, 23))->map(fn ($h) => $todayHourly->get($h)?->count ?? 0)->toArray(), 'yesterday' => collect(range(0, 23))->map(fn ($h) => $yesterdayHourly->get($h)?->count ?? 0)->toArray(), ]; // 2. Today's Distribution (Category) $categorySales = OrderItem::whereHas('order', function ($q) use ($outletIds) { $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)); }) ->select('orderable_type', DB::raw('SUM(unit_price * quantity) as total')) ->groupBy('orderable_type') ->get()->keyBy('orderable_type'); $this->todayCategoryChart = [ 'labels' => ['Parfum', 'Produk', 'Botol'], 'data' => [ (int) ($categorySales->get(Perfume::class)?->total ?? 0), (int) ($categorySales->get(Product::class)?->total ?? 0), (int) ($categorySales->get(Bottle::class)?->total ?? 0), ], ]; // 3. Today's Payment Methods $paymentMethods = Payment::whereHas('order', function ($q) use ($outletIds) { $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)); }) ->select('method', DB::raw('SUM(amount) as total')) ->groupBy('method') ->get(); $this->todayPaymentChart = [ 'labels' => $paymentMethods->map(fn ($pm) => $pm->method?->label() ?? 'Unknown')->toArray(), 'data' => $paymentMethods->pluck('total')->toArray(), ]; // 4. Today's Customer Type $todayMemberOrders = Order::query() ->today() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->whereNotNull('customer_id')->count(); $todayGuestOrders = Order::query() ->today() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->whereNull('customer_id')->count(); $this->todayCustomerTypeChart = [ 'labels' => ['Member', 'Guest (Umum)'], 'data' => [$todayMemberOrders, $todayGuestOrders], ]; // 5. Today's Top 5 Selling Items $todayTopItems = OrderItem::whereHas('order', function ($q) use ($outletIds) { $q->today()->when(! empty($outletIds), fn ($q2) => $q2->whereIn('outlet_id', $outletIds)); }) ->with('orderable') ->select('orderable_id', 'orderable_type', DB::raw('SUM(quantity) as total_sold')) ->groupBy('orderable_id', 'orderable_type') ->orderByDesc('total_sold') ->take(5) ->get(); $this->todayTopSellingChart = [ 'labels' => $todayTopItems->map(fn ($item) => $item->orderable?->name ?? 'Unknown')->toArray(), 'data' => $todayTopItems->pluck('total_sold')->map(fn ($val) => (int) $val)->toArray(), ]; // 6. Today's Top 5 Customers $todayTopCustomers = Order::query() ->today() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->whereNotNull('customer_id') ->select('customer_id', DB::raw('SUM(total) as total_spent')) ->groupBy('customer_id') ->orderByDesc('total_spent') ->take(5) ->with('customer') ->get(); $this->todayTopCustomersChart = [ 'labels' => $todayTopCustomers->map(fn ($o) => $o->customer?->user?->employee?->full_name ?? $o->customer?->user?->name ?? 'Customer #'.$o->customer_id)->toArray(), 'data' => $todayTopCustomers->pluck('total_spent')->map(fn ($val) => (int) $val)->toArray(), ]; // 7. Today's Discount Distribution (With Discount vs Regular) $withDiscount = Order::query() ->today() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->where('discount', '>', 0)->count(); $noDiscount = Order::query() ->today() ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->where('discount', '<=', 0)->count(); $this->todayDiscountChart = [ 'labels' => ['Pakai Diskon', 'Harga Normal'], 'data' => [$withDiscount, $noDiscount], ]; $this->stats = array_values(array_filter([ auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Average Order Value (AOV)', 'value' => formatCurrencyNumber($todayAov, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayAov, 'Rp'), 'trend' => $yesterdayAov > 0 ? round((($todayAov - $yesterdayAov) / $yesterdayAov) * 100, 1).'%' : '∞%', 'trendUp' => $todayAov > $yesterdayAov, ] : null, auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Profit Margin', 'value' => round($todayProfitMargin, 1).'%', 'previous' => round($yesterdayProfitMargin, 1).'%', 'trend' => $yesterdayProfitMargin > 0 ? round($todayProfitMargin - $yesterdayProfitMargin, 1).'%' : '∞%', 'trendUp' => $todayProfitMargin > $yesterdayProfitMargin, ] : null, [ 'title' => 'Total Order', 'value' => $todayOrder, 'previous' => $yesterdayOrder, 'trend' => $yesterdayOrder > 0 ? round((($todayOrder - $yesterdayOrder) / $yesterdayOrder) * 100, 1).'%' : '∞%', 'trendUp' => $todayOrder > $yesterdayOrder, ], [ 'title' => 'Pendapatan', 'value' => formatCurrencyNumber($todayIncome, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayIncome, 'Rp'), 'trend' => $yesterdayIncome > 0 ? round((($todayIncome - $yesterdayIncome) / $yesterdayIncome) * 100, 1).'%' : '∞%', 'trendUp' => $todayIncome > $yesterdayIncome, 'formatted' => formatCurrencyNumber($todayIncome, 'Rp'), ], auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'HPP', 'value' => formatCurrencyNumber($todayCogs, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayCogs, 'Rp'), 'trend' => $yesterdayCogs > 0 ? round((($todayCogs - $yesterdayCogs) / $yesterdayCogs) * 100, 1).'%' : '∞%', 'trendUp' => $todayCogs > $yesterdayCogs, 'formatted' => formatCurrencyNumber($todayCogs, 'Rp'), ] : null, [ 'title' => 'Member Baru', 'value' => $todayNewMembers.' Orang', 'previous' => $yesterdayNewMembers.' Orang', 'trend' => $yesterdayNewMembers > 0 ? round((($todayNewMembers - $yesterdayNewMembers) / $yesterdayNewMembers) * 100, 1).'%' : '∞%', 'trendUp' => $todayNewMembers > $yesterdayNewMembers, ], [ 'title' => 'Diskon', 'value' => formatCurrencyNumber($todayDiscount, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayDiscount, 'Rp'), 'trend' => $yesterdayDiscount > 0 ? round((($todayDiscount - $yesterdayDiscount) / $yesterdayDiscount) * 100, 1).'%' : '∞%', 'trendUp' => $todayDiscount > $yesterdayDiscount, 'formatted' => formatCurrencyNumber($todayDiscount, 'Rp'), ], [ 'title' => 'Voucher Terpakai', 'value' => $todayVoucher, 'previous' => $yesterdayVoucher, 'trend' => $yesterdayVoucher > 0 ? round((($todayVoucher - $yesterdayVoucher) / $yesterdayVoucher) * 100, 1).'%' : '∞%', 'trendUp' => $todayVoucher > $yesterdayVoucher, ], [ 'title' => 'Beban Toko', 'value' => formatCurrencyNumber($todayExpense, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayExpense, 'Rp'), 'trend' => $yesterdayExpense > 0 ? round((($todayExpense - $yesterdayExpense) / $yesterdayExpense) * 100, 1).'%' : '∞%', 'trendUp' => $todayExpense > $yesterdayExpense, ], auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Belanja Barang', 'value' => formatCurrencyNumber($todayPurchase, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayPurchase, 'Rp'), 'trend' => $yesterdayPurchase > 0 ? round((($todayPurchase - $yesterdayPurchase) / $yesterdayPurchase) * 100, 1).'%' : '∞%', 'trendUp' => $todayPurchase > $yesterdayPurchase, ] : null, auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Laba Kotor', 'value' => formatCurrencyNumber($todayGrossProfit, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayGrossProfit, 'Rp'), 'trend' => $yesterdayGrossProfit > 0 ? round((($todayGrossProfit - $yesterdayGrossProfit) / $yesterdayGrossProfit) * 100, 1).'%' : '∞%', 'trendUp' => $todayGrossProfit > $yesterdayGrossProfit, ] : null, auth()->user()->hasRole(['Developer', 'Owner']) ? [ 'title' => 'Laba Bersih', 'value' => formatCurrencyNumber($todayNetProfit, 'Rp'), 'previous' => formatCurrencyNumber($yesterdayNetProfit, 'Rp'), 'trend' => $yesterdayNetProfit > 0 ? round((($todayNetProfit - $yesterdayNetProfit) / $yesterdayNetProfit) * 100, 1).'%' : '∞%', 'trendUp' => $todayNetProfit > $yesterdayNetProfit, ] : null, [ 'title' => 'Parfum Terjual', 'value' => $todaySoldPerfume.' ml', 'previous' => $yesterdaySoldPerfume.' ml', 'trend' => $yesterdaySoldPerfume > 0 ? round((($todaySoldPerfume - $yesterdaySoldPerfume) / $yesterdaySoldPerfume) * 100, 1).'%' : '∞%', 'trendUp' => $todaySoldPerfume > $yesterdaySoldPerfume, ], [ 'title' => 'Top Parfum', 'value' => optional($todayTopPerfume?->orderable)->name.' ('.$todayTopPerfume?->total_sold.'ml)' ?? '-', 'previous' => optional($yesterdayTopPerfume?->orderable)->name.'('.$yesterdayTopPerfume?->total_sold.'ml)' ?? '-', 'trend' => ($yesterdayTopPerfume && $yesterdayTopPerfume->total_sold > 0) ? round((($todayTopPerfume?->total_sold - $yesterdayTopPerfume?->total_sold) / $yesterdayTopPerfume?->total_sold) * 100, 1).'%' : '∞%', 'trendUp' => ($todayTopPerfume?->total_sold ?? 0) > ($yesterdayTopPerfume?->total_sold ?? 0), ], ])); return view('livewire.studio.dashboard.overview'); } }