feat: Refactor dashboard data to public properties, update media handling, and fix analysis period logic.
This commit is contained in:
parent
acfa2977b3
commit
a9759f92c1
@ -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,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 () {
|
||||||
|
|||||||
@ -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 () {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user