From 4a13297cb7bb5c4268bf954f9e3497e759ceb65e Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 22 Jun 2026 10:43:33 +0700 Subject: [PATCH] feat: enhance dashboard with marketplace summary and additional product statistics for improved analytics --- .../Controllers/Admin/DashboardController.php | 38 +- resources/js/pages/admin/Dashboard.vue | 1096 ++++++++--------- 2 files changed, 532 insertions(+), 602 deletions(-) diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index 28e5649..aa9c186 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -50,6 +50,7 @@ public function index(): Response 'cuttingByStatus' => $this->getCuttingByStatus(), 'orderStats' => $this->getOrderStats(), 'revenueSummary' => $this->getRevenueSummary(), + 'marketplaceSummary' => $this->getMarketplaceSummary(), 'cashAccounts' => $this->getCashAccounts(), 'monthlyCashFlow' => $this->getMonthlyCashFlow(), 'monthlyExpenses' => $this->getMonthlyExpenses($startOfMonth, $endOfMonth), @@ -89,7 +90,7 @@ private function getRawMaterialStock(): array private function getProductStock(): array { $data = ProductVariant::query() - ->selectRaw('SUM(stock) as total_stock, SUM(reject_stock) as total_reject') + ->selectRaw('SUM(stock) as total_stock, SUM(reject_stock) as total_reject, COUNT(*) as total_variants') ->first(); $totalStock = (int) ($data->total_stock ?? 0); @@ -100,10 +101,16 @@ private function getProductStock(): array ->selectRaw('SUM(cutting_results.warehouse_stock * cuttings.cost_per_unit) as total_value') ->value('total_value'); + $totalProducts = \App\Models\Product::query()->count(); + $totalCategories = \App\Models\Category::query()->count(); + return [ 'total_stock' => $totalStock, 'total_reject' => (int) ($data->total_reject ?? 0), 'total_value' => (int) ($totalValue ?? 0), + 'total_variants' => (int) ($data->total_variants ?? 0), + 'total_products' => $totalProducts, + 'total_categories' => $totalCategories, ]; } @@ -306,15 +313,44 @@ private function getRevenueSummary(): array ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(shipping_cost) as total_shipping, COUNT(*) as total_orders, AVG(total_amount) as avg_order') ->first(); + $totalMarketplaceFees = Order::query() + ->where('status', OrderStatus::COMPLETED) + ->whereNotNull('marketplace_settings_snapshot') + ->get() + ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); + return [ 'total_revenue' => (int) ($data->total_revenue ?? 0), 'total_discount' => (int) ($data->total_discount ?? 0), + 'total_marketplace_fees' => $totalMarketplaceFees, + 'total_potongan' => (int) ($data->total_discount ?? 0) + $totalMarketplaceFees, 'total_shipping' => (int) ($data->total_shipping ?? 0), 'total_orders' => (int) ($data->total_orders ?? 0), 'avg_order' => (int) ($data->avg_order ?? 0), ]; } + private function getMarketplaceSummary(): array + { + $marketplaceOrders = Order::query() + ->where('status', OrderStatus::COMPLETED) + ->whereIn('channel', [OrderChannel::SHOPEE, OrderChannel::TIKTOK]) + ->whereNotNull('marketplace_settings_snapshot') + ->get(); + + $totalRevenue = $marketplaceOrders->sum('total_amount'); + $totalFees = $marketplaceOrders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); + $totalNet = $marketplaceOrders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['net_amount'] ?? 0)); + $totalOrders = $marketplaceOrders->count(); + + return [ + 'total_revenue' => (int) $totalRevenue, + 'total_fees' => (int) $totalFees, + 'total_net' => (int) $totalNet, + 'total_orders' => (int) $totalOrders, + ]; + } + private function getCashAccounts(): array { return CashAccount::query() diff --git a/resources/js/pages/admin/Dashboard.vue b/resources/js/pages/admin/Dashboard.vue index 159f790..37f760b 100644 --- a/resources/js/pages/admin/Dashboard.vue +++ b/resources/js/pages/admin/Dashboard.vue @@ -18,11 +18,11 @@ import { UserX, Users, } from '@lucide/vue'; +import { VisGroupedBar, VisXYContainer, VisDonut } from '@unovis/vue'; import { computed, onMounted, onUnmounted, ref } from 'vue'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { ChartContainer } from '@/components/ui/chart'; -import { VisAxis, VisGroupedBar, VisXYContainer, VisDonut, VisStackedBar } from '@unovis/vue'; import type { ChartConfig } from '@/components/ui/chart'; import AdminLayout from '@/layouts/AdminLayout.vue'; @@ -36,6 +36,9 @@ interface DashboardProps { total_stock: number; total_reject: number; total_value: number; + total_variants: number; + total_products: number; + total_categories: number; }; lowStockProducts: Array<{ name: string; @@ -104,10 +107,18 @@ interface DashboardProps { revenueSummary: { total_revenue: number; total_discount: number; + total_marketplace_fees: number; + total_potongan: number; total_shipping: number; total_orders: number; avg_order: number; }; + marketplaceSummary: { + total_revenue: number; + total_fees: number; + total_net: number; + total_orders: number; + }; cashAccounts: Array<{ name: string; balance: number; @@ -254,6 +265,7 @@ const channelChartConfig = computed(() => { color: channelColors[index % channelColors.length], }; }); + return config; }); @@ -266,6 +278,7 @@ const paymentChartConfig = computed(() => { color: paymentColors[index % paymentColors.length], }; }); + return config; }); @@ -278,6 +291,7 @@ const statusChartConfig = computed(() => { color: statusColors[index % statusColors.length], }; }); + return config; }); @@ -290,36 +304,13 @@ const marketingChartConfig = computed(() => { color: marketingColors[index % marketingColors.length], }; }); + return config; }); -const cuttingStatusColors = ['#facc15', 'var(--chart-1)', 'var(--chart-2)', 'var(--chart-4)']; -const cuttingStatusChartConfig = computed(() => { - const config: ChartConfig = {}; - props.cuttingByStatus.forEach((item, index) => { - config[item.status] = { - label: item.label, - color: cuttingStatusColors[index % cuttingStatusColors.length], - }; - }); - return config; -}); - -const cashFlowChartConfig = { - deposits: { - label: 'Pemasukan', - color: 'var(--chart-2)', - }, - withdrawals: { - label: 'Pengeluaran', - color: 'var(--chart-4)', - }, -} satisfies ChartConfig; - type SupplierData = { name: string; amount: number }; type CustomerData = { name: string; amount: number }; type ProductData = { name: string; qty: number }; -type CashFlowData = { label: string; deposits: number; withdrawals: number }; const supplierBarData = computed(() => { return props.topSuppliers.map((s) => ({ @@ -348,6 +339,7 @@ const totalCashBalance = computed(() => {