feat: Refactor dashboard data to public properties, update media handling, and fix analysis period logic.

This commit is contained in:
Yoga Pangestu 2026-01-03 15:00:46 +07:00
parent acfa2977b3
commit a9759f92c1
6 changed files with 49 additions and 29 deletions

View File

@ -69,6 +69,18 @@ class Analysis extends Component
public array $voucherUsageTrendChart = []; 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 public function mount(): void
{ {
$this->outlets = auth()->user()->outlets()->get()->pluck('name', 'id')->toArray(); $this->outlets = auth()->user()->outlets()->get()->pluck('name', 'id')->toArray();
@ -83,7 +95,7 @@ public function render(): View
$range = $this->range; $range = $this->range;
$dateQuery = match ($period) { $dateQuery = match ($period) {
'7' => now()->subDays(1), '7' => now()->subDays(7),
'30' => now()->subDays(30), '30' => now()->subDays(30),
'90' => now()->subDays(90), '90' => now()->subDays(90),
'365' => now()->subDays(365), '365' => now()->subDays(365),
@ -397,8 +409,9 @@ public function render(): View
]; ];
// 2. busiest hour // 2. busiest hour
$hourFunc = DB::getDriverName() === 'sqlite' ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)';
$hourlySales = (clone $orderQuery) $hourlySales = (clone $orderQuery)
->select(DB::raw('HOUR(created_at) as hour'), DB::raw('count(*) as count')) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count'))
->groupBy('hour') ->groupBy('hour')
->orderBy('hour') ->orderBy('hour')
->get() ->get()
@ -484,8 +497,9 @@ public function render(): View
]; ];
// 7. Day of Week Sales // 7. Day of Week Sales
$dayOfWeekFunc = DB::getDriverName() === 'sqlite' ? "(CAST(strftime('%w', created_at) AS INTEGER) + 1)" : 'DAYOFWEEK(created_at)';
$dayOfWeekSales = (clone $orderQuery) $dayOfWeekSales = (clone $orderQuery)
->select(DB::raw('DAYOFWEEK(created_at) as day'), DB::raw('SUM(total) as revenue')) ->select(DB::raw("$dayOfWeekFunc as day"), DB::raw('SUM(total) as revenue'))
->groupBy('day') ->groupBy('day')
->get() ->get()
->keyBy('day'); ->keyBy('day');
@ -564,14 +578,15 @@ public function render(): View
'data' => $trendDates->map(fn ($d) => (int) ($vouchersTrend->get($d)?->count ?? 0))->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', [ return view('livewire.studio.dashboard.analysis', [
'pageTitle' => 'Analisa', 'pageTitle' => 'Analisa',
'countingData' => $countingData,
'orders' => $orders,
'expenses' => $expenses,
'topPerfumes' => $topPerfumes,
'topProducts' => $topProducts,
'topBottles' => $topBottles,
]); ]);
} }
} }

View File

@ -40,6 +40,8 @@ class Overview extends Component
public array $todayDiscountChart = []; public array $todayDiscountChart = [];
public array $stats = [];
public function mount(): void public function mount(): void
{ {
$this->outlets = auth()->user()->outlets()->get()->pluck('name', 'id')->toArray(); $this->outlets = auth()->user()->outlets()->get()->pluck('name', 'id')->toArray();
@ -171,15 +173,17 @@ public function render(): View
// --- Chart Data Logic --- // --- Chart Data Logic ---
// 1. Hourly Sales Comparison (Today vs Yesterday) // 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()) $todayHourly = Order::whereDate('created_at', now())
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
->select(DB::raw('HOUR(created_at) as hour'), DB::raw('count(*) as count')) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count'))
->groupBy('hour') ->groupBy('hour')
->get()->keyBy('hour'); ->get()->keyBy('hour');
$yesterdayHourly = Order::whereDate('created_at', now()->yesterday()) $yesterdayHourly = Order::whereDate('created_at', now()->yesterday())
->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds)) ->when(! empty($outletIds), fn ($q) => $q->whereIn('outlet_id', $outletIds))
->select(DB::raw('HOUR(created_at) as hour'), DB::raw('count(*) as count')) ->select(DB::raw("$hourFunc as hour"), DB::raw('count(*) as count'))
->groupBy('hour') ->groupBy('hour')
->get()->keyBy('hour'); ->get()->keyBy('hour');
@ -282,7 +286,7 @@ public function render(): View
'data' => [$withDiscount, $noDiscount], 'data' => [$withDiscount, $noDiscount],
]; ];
$stats = array_filter([ $this->stats = array_values(array_filter([
[ [
'title' => 'Average Order Value (AOV)', 'title' => 'Average Order Value (AOV)',
'value' => formatCurrencyNumber($todayAov, 'Rp'), 'value' => formatCurrencyNumber($todayAov, 'Rp'),
@ -418,10 +422,8 @@ public function render(): View
: '∞%', : '∞%',
'trendUp' => ($todayTopPerfume?->total_sold ?? 0) > ($yesterdayTopPerfume?->total_sold ?? 0), 'trendUp' => ($todayTopPerfume?->total_sold ?? 0) > ($yesterdayTopPerfume?->total_sold ?? 0),
], ],
]); ]));
return view('livewire.studio.dashboard.overview', [ return view('livewire.studio.dashboard.overview');
'stats' => $stats,
]);
} }
} }

View File

@ -16,12 +16,11 @@
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
use Livewire\WithFileUploads;
#[Title('Pengeluaran')] #[Title('Pengeluaran')]
class Expense extends Component class Expense extends Component
{ {
use WithAuthorization, WithCloseModal, WithConfirmation, WithFileUploads, WithSubscribeNotification, WithToast, WithUpdatedData; use WithAuthorization, WithCloseModal, WithConfirmation, WithSubscribeNotification, WithToast, WithUpdatedData;
public ExpenseForm $form; public ExpenseForm $form;

View File

@ -39,10 +39,14 @@ protected function syncMedia(array $newMedia, $model, string $collectionName): v
protected function uploadMedia(array $mediaArray, $model, string $collectionName): void protected function uploadMedia(array $mediaArray, $model, string $collectionName): void
{ {
foreach ($mediaArray as $file) { foreach ($mediaArray as $file) {
if (isset($file['path'])) { if (is_array($file) && isset($file['path'])) {
$model->addMedia($file['path']) $model->addMedia($file['path'])
->preservingOriginal() ->preservingOriginal()
->toMediaCollection($collectionName); ->toMediaCollection($collectionName);
} elseif ($file instanceof \Illuminate\Http\UploadedFile) {
$model->addMedia($file->getRealPath())
->preservingOriginal()
->toMediaCollection($collectionName);
} }
} }
} }

View File

@ -66,10 +66,10 @@
Livewire::actingAs($this->user) Livewire::actingAs($this->user)
->test(Analysis::class) ->test(Analysis::class)
// All period (default) // All period (default)
->assertSet('orders.1.value', 'Rp150.000') ->assertSet('orders.5.value', 'Rp150.000')
// Last 7 days (only shows today's order) // Last 7 days (only shows today's order)
->set('period', '7') ->set('period', '7')
->assertSet('orders.1.value', 'Rp50.000'); ->assertSet('orders.5.value', 'Rp50.000');
}); });
it('filters data by custom date range', function () { it('filters data by custom date range', function () {
@ -89,7 +89,7 @@
->test(Analysis::class) ->test(Analysis::class)
->set('range.start', '2024-01-01') ->set('range.start', '2024-01-01')
->set('range.end', '2024-01-31') ->set('range.end', '2024-01-31')
->assertSet('orders.1.value', 'Rp200.000'); ->assertSet('orders.5.value', 'Rp200.000');
}); });
it('shows top performers correctly with aggregation', function () { it('shows top performers correctly with aggregation', function () {

View File

@ -48,10 +48,10 @@
Livewire::actingAs($this->user) Livewire::actingAs($this->user)
->test(Overview::class) ->test(Overview::class)
->assertSet('stats.0.value', 2) // Total Order Today ->assertSet('stats.2.value', 2) // Total Order Today
->assertSet('stats.0.previous', 1) // Total Order Yesterday ->assertSet('stats.2.previous', 1) // Total Order Yesterday
->assertSet('stats.1.value', 'Rp200.000') // Pendapatan Today (2 * 100k) ->assertSet('stats.3.value', 'Rp200.000') // Pendapatan Today (2 * 100k)
->assertSet('stats.1.previous', 'Rp80.000'); // Pendapatan Yesterday (1 * 80k) ->assertSet('stats.3.previous', 'Rp80.000'); // Pendapatan Yesterday (1 * 80k)
}); });
it('filters stats by selected outlets', function () { it('filters stats by selected outlets', function () {
@ -66,13 +66,13 @@
Livewire::actingAs($this->user) Livewire::actingAs($this->user)
->test(Overview::class) ->test(Overview::class)
// No filter (shows both) // No filter (shows both)
->assertSet('stats.1.value', 'Rp150.000') ->assertSet('stats.3.value', 'Rp150.000')
// Filter by main outlet only // Filter by main outlet only
->set('selectedOutletIds', [$this->outlet->id]) ->set('selectedOutletIds', [$this->outlet->id])
->assertSet('stats.1.value', 'Rp100.000') ->assertSet('stats.3.value', 'Rp100.000')
// Filter by another outlet only // Filter by another outlet only
->set('selectedOutletIds', [$anotherOutlet->id]) ->set('selectedOutletIds', [$anotherOutlet->id])
->assertSet('stats.1.value', 'Rp50.000'); ->assertSet('stats.3.value', 'Rp50.000');
}); });
it('shows top perfume correctly', function () { it('shows top perfume correctly', function () {