status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php
@@
 <?php

 namespace App\Livewire\Admin;

+use App\Enums\FinancialStatus;
+use App\Models\Cart;
+use App\Models\Checkout;
+use App\Models\Order;
+use App\Models\OrderLine;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\CarbonPeriod;
+use Livewire\Attributes\Computed;
+use Livewire\Attributes\Locked;
 use Livewire\Component;
+use Throwable;

 class Dashboard extends Component
 {
-    public function render()
+    #[Locked]
+    public int $storeId;
+
+    public string $storeCurrency = 'EUR';
+
+    public string $dateRange = 'last_30_days';
+
+    public ?string $customStartDate = null;
+
+    public ?string $customEndDate = null;
+
+    public int $totalSales = 0;
+
+    public int $ordersCount = 0;
+
+    public int $averageOrderValue = 0;
+
+    public int $visitorsCount = 0;
+
+    public float $salesChange = 0.0;
+
+    public float $ordersChange = 0.0;
+
+    public float $aovChange = 0.0;
+
+    public float $visitorsChange = 0.0;
+
+    /**
+     * @var list<array{date: string, label: string, count: int}>
+     */
+    public array $ordersChartData = [];
+
+    public int $maxOrdersChartCount = 1;
+
+    /**
+     * @var list<array{title: string, units_sold: int, revenue: int}>
+     */
+    public array $topProducts = [];
+
+    /**
+     * @var array{visits: int, add_to_cart: int, checkout_started: int, checkout_completed: int}
+     */
+    public array $funnelData = [
+        'visits' => 0,
+        'add_to_cart' => 0,
+        'checkout_started' => 0,
+        'checkout_completed' => 0,
+    ];
+
+    public function mount(): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeId = $store->getKey();
+        $this->storeCurrency = $store->default_currency;
+
+        $this->loadKpis();
+    }
+
+    public function updatedDateRange(): void
+    {
+        $this->loadKpis();
+    }
+
+    public function updatedCustomStartDate(): void
+    {
+        if ($this->dateRange === 'custom') {
+            $this->loadKpis();
+        }
+    }
+
+    public function updatedCustomEndDate(): void
+    {
+        if ($this->dateRange === 'custom') {
+            $this->loadKpis();
+        }
+    }
+
+    public function loadKpis(): void
+    {
+        [$start, $end] = $this->currentRange();
+        [$previousStart, $previousEnd] = $this->previousRange($start, $end);
+
+        $this->ordersCount = $this->ordersBetween($start, $end)->count();
+        $previousOrdersCount = $this->ordersBetween($previousStart, $previousEnd)->count();
+
+        $this->totalSales = (int) $this->revenueOrdersBetween($start, $end)->sum('total_amount');
+        $previousTotalSales = (int) $this->revenueOrdersBetween($previousStart, $previousEnd)->sum('total_amount');
+
+        $revenueOrdersCount = $this->revenueOrdersBetween($start, $end)->count();
+        $previousRevenueOrdersCount = $this->revenueOrdersBetween($previousStart, $previousEnd)->count();
+
+        $this->averageOrderValue = $revenueOrdersCount > 0 ? intdiv($this->totalSales, $revenueOrdersCount) : 0;
+        $previousAverageOrderValue = $previousRevenueOrdersCount > 0 ? intdiv($previousTotalSales, $previousRevenueOrdersCount) : 0;
+
+        $this->visitorsCount = 0;
+        $this->salesChange = $this->percentChange($this->totalSales, $previousTotalSales);
+        $this->ordersChange = $this->percentChange($this->ordersCount, $previousOrdersCount);
+        $this->aovChange = $this->percentChange($this->averageOrderValue, $previousAverageOrderValue);
+        $this->visitorsChange = 0.0;
+
+        $this->loadChart();
+        $this->loadTopProducts();
+        $this->loadFunnel();
+    }
+
+    public function loadChart(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $counts = $this->ordersBetween($start, $end)
+            ->selectRaw('date(placed_at) as order_date, count(*) as aggregate')
+            ->groupBy('order_date')
+            ->pluck('aggregate', 'order_date');
+
+        $this->ordersChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
+            ->map(fn (Carbon $date): array => [
+                'date' => $date->toDateString(),
+                'label' => $date->format('M j'),
+                'count' => (int) ($counts[$date->toDateString()] ?? 0),
+            ])
+            ->values()
+            ->all();
+
+        $this->maxOrdersChartCount = max(1, max(array_column($this->ordersChartData, 'count') ?: [0]));
+    }
+
+    public function loadTopProducts(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $this->topProducts = OrderLine::query()
+            ->selectRaw('order_lines.title_snapshot as title, sum(order_lines.quantity) as units_sold, sum(order_lines.total_amount) as revenue')
+            ->join('orders', 'orders.id', '=', 'order_lines.order_id')
+            ->where('orders.store_id', $this->storeId)
+            ->whereBetween('orders.placed_at', [$start, $end])
+            ->whereIn('orders.financial_status', $this->revenueStatuses())
+            ->groupBy('order_lines.product_id', 'order_lines.title_snapshot')
+            ->orderByDesc('revenue')
+            ->limit(5)
+            ->get()
+            ->map(fn (OrderLine $line): array => [
+                'title' => (string) $line->title,
+                'units_sold' => (int) $line->units_sold,
+                'revenue' => (int) $line->revenue,
+            ])
+            ->all();
+    }
+
+    public function loadFunnel(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $this->funnelData = [
+            'visits' => 0,
+            'add_to_cart' => Cart::withoutGlobalScopes()
+                ->where('store_id', $this->storeId)
+                ->whereBetween('created_at', [$start, $end])
+                ->count(),
+            'checkout_started' => Checkout::withoutGlobalScopes()
+                ->where('store_id', $this->storeId)
+                ->whereBetween('created_at', [$start, $end])
+                ->count(),
+            'checkout_completed' => $this->ordersCount,
+        ];
+    }
+
+    #[Computed]
+    public function formattedTotalSales(): string
+    {
+        return Money::format($this->totalSales, $this->storeCurrency);
+    }
+
+    #[Computed]
+    public function formattedAov(): string
+    {
+        return Money::format($this->averageOrderValue, $this->storeCurrency);
+    }
+
+    public function render(): mixed
     {
-        return view('livewire.admin.dashboard');
+        return view('livewire.admin.dashboard')->layout('layouts.app', [
+            'title' => __('Dashboard'),
+        ]);
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}
+     */
+    private function currentRange(): array
+    {
+        return match ($this->dateRange) {
+            'today' => [now()->startOfDay(), now()->endOfDay()],
+            'last_7_days' => [now()->subDays(6)->startOfDay(), now()->endOfDay()],
+            'custom' => $this->customRange() ?? [now()->subDays(29)->startOfDay(), now()->endOfDay()],
+            default => [now()->subDays(29)->startOfDay(), now()->endOfDay()],
+        };
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}
+     */
+    private function previousRange(Carbon $start, Carbon $end): array
+    {
+        $days = max(1, $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1);
+        $previousEnd = $start->copy()->subSecond();
+
+        return [$start->copy()->subDays($days)->startOfDay(), $previousEnd];
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}|null
+     */
+    private function customRange(): ?array
+    {
+        if (! $this->customStartDate || ! $this->customEndDate) {
+            return null;
+        }
+
+        try {
+            $start = Carbon::parse($this->customStartDate)->startOfDay();
+            $end = Carbon::parse($this->customEndDate)->endOfDay();
+        } catch (Throwable) {
+            return null;
+        }
+
+        if ($end->lessThan($start)) {
+            return [$end->startOfDay(), $start->endOfDay()];
+        }
+
+        return [$start, $end];
+    }
+
+    /**
+     * @return Builder<Order>
+     */
+    private function ordersBetween(Carbon $start, Carbon $end): Builder
+    {
+        return Order::withoutGlobalScopes()
+            ->where('store_id', $this->storeId)
+            ->whereBetween('placed_at', [$start, $end]);
+    }
+
+    /**
+     * @return Builder<Order>
+     */
+    private function revenueOrdersBetween(Carbon $start, Carbon $end): Builder
+    {
+        return $this->ordersBetween($start, $end)
+            ->whereIn('financial_status', $this->revenueStatuses());
+    }
+
+    /**
+     * @return list<string>
+     */
+    private function revenueStatuses(): array
+    {
+        return [
+            FinancialStatus::Paid->value,
+            FinancialStatus::PartiallyRefunded->value,
+        ];
+    }
+
+    private function percentChange(int $current, int $previous): float
+    {
+        if ($previous === 0) {
+            return $current > 0 ? 100.0 : 0.0;
+        }
+
+        return round((($current - $previous) / $previous) * 100, 1);
     }
 }
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php

type: update

unified_diff:
@@ -4,3 +4,16 @@

+use App\Enums\FinancialStatus;
+use App\Models\Cart;
+use App\Models\Checkout;
+use App\Models\Order;
+use App\Models\OrderLine;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\CarbonPeriod;
+use Livewire\Attributes\Computed;
+use Livewire\Attributes\Locked;
 use Livewire\Component;
+use Throwable;

@@ -8,5 +21,274 @@
 {
-    public function render()
+    #[Locked]
+    public int $storeId;
+
+    public string $storeCurrency = 'EUR';
+
+    public string $dateRange = 'last_30_days';
+
+    public ?string $customStartDate = null;
+
+    public ?string $customEndDate = null;
+
+    public int $totalSales = 0;
+
+    public int $ordersCount = 0;
+
+    public int $averageOrderValue = 0;
+
+    public int $visitorsCount = 0;
+
+    public float $salesChange = 0.0;
+
+    public float $ordersChange = 0.0;
+
+    public float $aovChange = 0.0;
+
+    public float $visitorsChange = 0.0;
+
+    /**
+     * @var list<array{date: string, label: string, count: int}>
+     */
+    public array $ordersChartData = [];
+
+    public int $maxOrdersChartCount = 1;
+
+    /**
+     * @var list<array{title: string, units_sold: int, revenue: int}>
+     */
+    public array $topProducts = [];
+
+    /**
+     * @var array{visits: int, add_to_cart: int, checkout_started: int, checkout_completed: int}
+     */
+    public array $funnelData = [
+        'visits' => 0,
+        'add_to_cart' => 0,
+        'checkout_started' => 0,
+        'checkout_completed' => 0,
+    ];
+
+    public function mount(): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeId = $store->getKey();
+        $this->storeCurrency = $store->default_currency;
+
+        $this->loadKpis();
+    }
+
+    public function updatedDateRange(): void
+    {
+        $this->loadKpis();
+    }
+
+    public function updatedCustomStartDate(): void
+    {
+        if ($this->dateRange === 'custom') {
+            $this->loadKpis();
+        }
+    }
+
+    public function updatedCustomEndDate(): void
+    {
+        if ($this->dateRange === 'custom') {
+            $this->loadKpis();
+        }
+    }
+
+    public function loadKpis(): void
+    {
+        [$start, $end] = $this->currentRange();
+        [$previousStart, $previousEnd] = $this->previousRange($start, $end);
+
+        $this->ordersCount = $this->ordersBetween($start, $end)->count();
+        $previousOrdersCount = $this->ordersBetween($previousStart, $previousEnd)->count();
+
+        $this->totalSales = (int) $this->revenueOrdersBetween($start, $end)->sum('total_amount');
+        $previousTotalSales = (int) $this->revenueOrdersBetween($previousStart, $previousEnd)->sum('total_amount');
+
+        $revenueOrdersCount = $this->revenueOrdersBetween($start, $end)->count();
+        $previousRevenueOrdersCount = $this->revenueOrdersBetween($previousStart, $previousEnd)->count();
+
+        $this->averageOrderValue = $revenueOrdersCount > 0 ? intdiv($this->totalSales, $revenueOrdersCount) : 0;
+        $previousAverageOrderValue = $previousRevenueOrdersCount > 0 ? intdiv($previousTotalSales, $previousRevenueOrdersCount) : 0;
+
+        $this->visitorsCount = 0;
+        $this->salesChange = $this->percentChange($this->totalSales, $previousTotalSales);
+        $this->ordersChange = $this->percentChange($this->ordersCount, $previousOrdersCount);
+        $this->aovChange = $this->percentChange($this->averageOrderValue, $previousAverageOrderValue);
+        $this->visitorsChange = 0.0;
+
+        $this->loadChart();
+        $this->loadTopProducts();
+        $this->loadFunnel();
+    }
+
+    public function loadChart(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $counts = $this->ordersBetween($start, $end)
+            ->selectRaw('date(placed_at) as order_date, count(*) as aggregate')
+            ->groupBy('order_date')
+            ->pluck('aggregate', 'order_date');
+
+        $this->ordersChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
+            ->map(fn (Carbon $date): array => [
+                'date' => $date->toDateString(),
+                'label' => $date->format('M j'),
+                'count' => (int) ($counts[$date->toDateString()] ?? 0),
+            ])
+            ->values()
+            ->all();
+
+        $this->maxOrdersChartCount = max(1, max(array_column($this->ordersChartData, 'count') ?: [0]));
+    }
+
+    public function loadTopProducts(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $this->topProducts = OrderLine::query()
+            ->selectRaw('order_lines.title_snapshot as title, sum(order_lines.quantity) as units_sold, sum(order_lines.total_amount) as revenue')
+            ->join('orders', 'orders.id', '=', 'order_lines.order_id')
+            ->where('orders.store_id', $this->storeId)
+            ->whereBetween('orders.placed_at', [$start, $end])
+            ->whereIn('orders.financial_status', $this->revenueStatuses())
+            ->groupBy('order_lines.product_id', 'order_lines.title_snapshot')
+            ->orderByDesc('revenue')
+            ->limit(5)
+            ->get()
+            ->map(fn (OrderLine $line): array => [
+                'title' => (string) $line->title,
+                'units_sold' => (int) $line->units_sold,
+                'revenue' => (int) $line->revenue,
+            ])
+            ->all();
+    }
+
+    public function loadFunnel(): void
+    {
+        [$start, $end] = $this->currentRange();
+
+        $this->funnelData = [
+            'visits' => 0,
+            'add_to_cart' => Cart::withoutGlobalScopes()
+                ->where('store_id', $this->storeId)
+                ->whereBetween('created_at', [$start, $end])
+                ->count(),
+            'checkout_started' => Checkout::withoutGlobalScopes()
+                ->where('store_id', $this->storeId)
+                ->whereBetween('created_at', [$start, $end])
+                ->count(),
+            'checkout_completed' => $this->ordersCount,
+        ];
+    }
+
+    #[Computed]
+    public function formattedTotalSales(): string
+    {
+        return Money::format($this->totalSales, $this->storeCurrency);
+    }
+
+    #[Computed]
+    public function formattedAov(): string
+    {
+        return Money::format($this->averageOrderValue, $this->storeCurrency);
+    }
+
+    public function render(): mixed
+    {
+        return view('livewire.admin.dashboard')->layout('layouts.app', [
+            'title' => __('Dashboard'),
+        ]);
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}
+     */
+    private function currentRange(): array
     {
-        return view('livewire.admin.dashboard');
+        return match ($this->dateRange) {
+            'today' => [now()->startOfDay(), now()->endOfDay()],
+            'last_7_days' => [now()->subDays(6)->startOfDay(), now()->endOfDay()],
+            'custom' => $this->customRange() ?? [now()->subDays(29)->startOfDay(), now()->endOfDay()],
+            default => [now()->subDays(29)->startOfDay(), now()->endOfDay()],
+        };
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}
+     */
+    private function previousRange(Carbon $start, Carbon $end): array
+    {
+        $days = max(1, $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1);
+        $previousEnd = $start->copy()->subSecond();
+
+        return [$start->copy()->subDays($days)->startOfDay(), $previousEnd];
+    }
+
+    /**
+     * @return array{0: Carbon, 1: Carbon}|null
+     */
+    private function customRange(): ?array
+    {
+        if (! $this->customStartDate || ! $this->customEndDate) {
+            return null;
+        }
+
+        try {
+            $start = Carbon::parse($this->customStartDate)->startOfDay();
+            $end = Carbon::parse($this->customEndDate)->endOfDay();
+        } catch (Throwable) {
+            return null;
+        }
+
+        if ($end->lessThan($start)) {
+            return [$end->startOfDay(), $start->endOfDay()];
+        }
+
+        return [$start, $end];
+    }
+
+    /**
+     * @return Builder<Order>
+     */
+    private function ordersBetween(Carbon $start, Carbon $end): Builder
+    {
+        return Order::withoutGlobalScopes()
+            ->where('store_id', $this->storeId)
+            ->whereBetween('placed_at', [$start, $end]);
+    }
+
+    /**
+     * @return Builder<Order>
+     */
+    private function revenueOrdersBetween(Carbon $start, Carbon $end): Builder
+    {
+        return $this->ordersBetween($start, $end)
+            ->whereIn('financial_status', $this->revenueStatuses());
+    }
+
+    /**
+     * @return list<string>
+     */
+    private function revenueStatuses(): array
+    {
+        return [
+            FinancialStatus::Paid->value,
+            FinancialStatus::PartiallyRefunded->value,
+        ];
+    }
+
+    private function percentChange(int $current, int $previous): float
+    {
+        if ($previous === 0) {
+            return $current > 0 ? 100.0 : 0.0;
+        }
+
+        return round((($current - $previous) / $previous) * 100, 1);
     }

status: completed


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/dashboard.blade.php
@@
-

-    {{-- Breathing in, I calm body and mind. Breathing out, I smile. - Thich Nhat Hanh --}}
-

+

+

+

+            <flux:heading size="xl">Dashboard</flux:heading>
+            <flux:text class="mt-1">Sales, orders, and product movement for the selected store.</flux:text>
+

+
+

+            <flux:select wire:model.live="dateRange" aria-label="Date range">
+                <flux:select.option value="today">Today</flux:select.option>
+                <flux:select.option value="last_7_days">Last 7 days</flux:select.option>
+                <flux:select.option value="last_30_days">Last 30 days</flux:select.option>
+                <flux:select.option value="custom">Custom</flux:select.option>
+            </flux:select>
+
+            <flux:input type="date" wire:model.live="customStartDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range start" />
+            <flux:input type="date" wire:model.live="customEndDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range end" />
+

+

+
+

+        @foreach ([
+            ['label' => 'Total sales', 'value' => $this->formattedTotalSales, 'change' => $salesChange, 'icon' => 'banknotes'],
+            ['label' => 'Orders', 'value' => number_format($ordersCount), 'change' => $ordersChange, 'icon' => 'shopping-bag'],
+            ['label' => 'Average order', 'value' => $this->formattedAov, 'change' => $aovChange, 'icon' => 'receipt-percent'],
+            ['label' => 'Visitors', 'value' => number_format($visitorsCount), 'change' => $visitorsChange, 'icon' => 'users'],
+        ] as $metric)
+            @php
+                $change = (float) $metric['change'];
+                $changeColor = $change > 0 ? 'text-emerald-600 dark:text-emerald-400' : ($change < 0 ? 'text-rose-600 dark:text-rose-400' : 'text-zinc-500 dark:text-zinc-400');
+            @endphp
+
+

+

+

+
{{ $metric['label'] }}

+
{{ $metric['value'] }}

+

+
+

+                        <flux:icon :name="$metric['icon']" class="size-5" />
+

+

+
+

+                    <flux:icon :name="$change >= 0 ? 'arrow-trending-up' : 'arrow-trending-down'" class="size-4" />
+                    {{ $change > 0 ? '+' : '' }}{{ number_format($change, 1) }}%
+

+

+        @endforeach
+

+
+

+

+

+

+                    <flux:heading size="lg">Orders over time</flux:heading>
+                    <flux:text class="mt-1">Daily order count</flux:text>
+

+

+
+

+                @foreach ($ordersChartData as $point)
+                    @php
+                        $height = $point['count'] === 0 ? 2 : max(8, (int) round(($point['count'] / $maxOrdersChartCount) * 100));
+                    @endphp
+
+

+

+                        @if ($loop->first || $loop->last || $loop->iteration % 7 === 0)
+
{{ $point['label'] }}

+                        @endif
+

+                @endforeach
+

+

+
+

+            <flux:heading size="lg">Top products</flux:heading>
+            <flux:text class="mt-1">Ranked by order-line revenue</flux:text>
+
+

+                @forelse ($topProducts as $product)
+

+

+
{{ $product['title'] }}

+
{{ number_format($product['units_sold']) }} sold

+

+
+

+                            {{ \App\Support\Money::format($product['revenue'], $storeCurrency) }}
+

+

+                @empty
+

+                        <flux:icon name="cube" class="mx-auto size-8 text-zinc-400" />
+                        <flux:text class="mt-3">No product sales in this range.</flux:text>
+

+                @endforelse
+

+

+

+
+

+

+

+                <flux:heading size="lg">Conversion funnel</flux:heading>
+                <flux:text class="mt-1">Current range activity</flux:text>
+

+

+
+        @php
+            $maxFunnelValue = max(1, max($funnelData));
+            $funnelSteps = [
+                'visits' => 'Visits',
+                'add_to_cart' => 'Carts',
+                'checkout_started' => 'Checkouts',
+                'checkout_completed' => 'Orders',
+            ];
+        @endphp
+
+

+            @foreach ($funnelSteps as $key => $label)
+                @php
+                    $value = (int) $funnelData[$key];
+                    $width = $value === 0 ? 2 : max(8, (int) round(($value / $maxFunnelValue) * 100));
+                @endphp
+
+

+

+
{{ $label }}

+
{{ number_format($value) }}

+

+
+

+

+

+

+            @endforeach
+

+

+

*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/dashboard.blade.php

unified_diff:
@@ -1,3 +1,142 @@
-

-    {{-- Breathing in, I calm body and mind. Breathing out, I smile. - Thich Nhat Hanh --}}
-

+

+

+

+            <flux:heading size="xl">Dashboard</flux:heading>
+            <flux:text class="mt-1">Sales, orders, and product movement for the selected store.</flux:text>
+

+
+

+            <flux:select wire:model.live="dateRange" aria-label="Date range">
+                <flux:select.option value="today">Today</flux:select.option>
+                <flux:select.option value="last_7_days">Last 7 days</flux:select.option>
+                <flux:select.option value="last_30_days">Last 30 days</flux:select.option>
+                <flux:select.option value="custom">Custom</flux:select.option>
+            </flux:select>
+
+            <flux:input type="date" wire:model.live="customStartDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range start" />
+            <flux:input type="date" wire:model.live="customEndDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range end" />
+

+

+
+

+        @foreach ([
+            ['label' => 'Total sales', 'value' => $this->formattedTotalSales, 'change' => $salesChange, 'icon' => 'banknotes'],
+            ['label' => 'Orders', 'value' => number_format($ordersCount), 'change' => $ordersChange, 'icon' => 'shopping-bag'],
+            ['label' => 'Average order', 'value' => $this->formattedAov, 'change' => $aovChange, 'icon' => 'receipt-percent'],
+            ['label' => 'Visitors', 'value' => number_format($visitorsCount), 'change' => $visitorsChange, 'icon' => 'users'],
+        ] as $metric)
+            @php
+                $change = (float) $metric['change'];
+                $changeColor = $change > 0 ? 'text-emerald-600 dark:text-emerald-400' : ($change < 0 ? 'text-rose-600 dark:text-rose-400' : 'text-zinc-500 dark:text-zinc-400');
+            @endphp
+
+

+

+

+
{{ $metric['label'] }}

+
{{ $metric['value'] }}

+

+
+

+                        <flux:icon :name="$metric['icon']" class="size-5" />
+

+

+
+

+                    <flux:icon :name="$change >= 0 ? 'arrow-trending-up' : 'arrow-trending-down'" class="size-4" />
+                    {{ $change > 0 ? '+' : '' }}{{ number_format($change, 1) }}%
+

+

+        @endforeach
+

+
+

+

+

+

+                    <flux:heading size="lg">Orders over time</flux:heading>
+                    <flux:text class="mt-1">Daily order count</flux:text>
+

+

+
+

+                @foreach ($ordersChartData as $point)
+                    @php
+                        $height = $point['count'] === 0 ? 2 : max(8, (int) round(($point['count'] / $maxOrdersChartCount) * 100));
+                    @endphp
+
+

+

+                        @if ($loop->first || $loop->last || $loop->iteration % 7 === 0)
+
{{ $point['label'] }}

+                        @endif
+

+                @endforeach
+

+

+
+

+            <flux:heading size="lg">Top products</flux:heading>
+            <flux:text class="mt-1">Ranked by order-line revenue</flux:text>
+
+

+                @forelse ($topProducts as $product)
+

+

+
{{ $product['title'] }}

+
{{ number_format($product['units_sold']) }} sold

+

+
+

+                            {{ \App\Support\Money::format($product['revenue'], $storeCurrency) }}
+

+

+                @empty
+

+                        <flux:icon name="cube" class="mx-auto size-8 text-zinc-400" />
+                        <flux:text class="mt-3">No product sales in this range.</flux:text>
+

+                @endforelse
+

+

+

+
+

+

+

+                <flux:heading size="lg">Conversion funnel</flux:heading>
+                <flux:text class="mt-1">Current range activity</flux:text>
+

+

+
+        @php
+            $maxFunnelValue = max(1, max($funnelData));
+            $funnelSteps = [
+                'visits' => 'Visits',
+                'add_to_cart' => 'Carts',
+                'checkout_started' => 'Checkouts',
+                'checkout_completed' => 'Orders',
+            ];
+        @endphp
+
+

+            @foreach ($funnelSteps as $key => $label)
+                @php
+                    $value = (int) $funnelData[$key];
+                    $width = $value === 0 ? 2 : max(8, (int) round(($value / $maxFunnelValue) * 100));
+                @endphp
+
+

+

+
{{ $label }}

+
{{ number_format($value) }}

+

+
+

+

+

+

+            @endforeach
+

+

+


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/dashboard.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/routes/web.php
@@
 use App\Livewire\Admin\Auth\Login as AdminLogin;
 use App\Livewire\Admin\Collections\Form as AdminCollectionForm;
 use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
+use App\Livewire\Admin\Dashboard as AdminDashboard;
 use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
 use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
 use App\Livewire\Admin\Orders\Show as AdminOrderShow;
@@
 Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
-    Route::view('/', 'dashboard')->name('dashboard');
+    Route::livewire('/', AdminDashboard::class)->name('dashboard');
     Route::livewire('products', AdminProductsIndex::class)->name('products.index');
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/routes/web.php

unified_diff:
@@ -5,2 +5,3 @@
 use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
+use App\Livewire\Admin\Dashboard as AdminDashboard;
 use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
@@ -52,3 +53,3 @@
 Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
-    Route::view('/', 'dashboard')->name('dashboard');
+    Route::livewire('/', AdminDashboard::class)->name('dashboard');
     Route::livewire('products', AdminProductsIndex::class)->name('products.index');


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/routes/web.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


cmd: sed -n '1,260p' app/Http/Middleware/ResolveStore.php


command: sed -n '1,260p' app/Http/Middleware/ResolveStore.php

ResolveStore.php

type: read

aggregated_output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}


output:
Chunk ID: feaf8f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 538
Output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}


cmd: rg "travelTo|setTestNow|Carbon::setTestNow" tests -n


command: rg "travelTo|setTestNow|Carbon::setTestNow" tests -n

type: search

cmd: rg 'travelTo|setTestNow|Carbon::setTestNow' tests -n

query: travelTo|setTestNow|Carbon::setTestNow

path: tests

status: failed


output:
Chunk ID: 1c4b1f
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 0
Output:


message: I’m adding focused dashboard tests now: one HTTP route test for auth and store scoping, and one Livewire test for date-range recalculation.

phase: commentary


type: message

role: assistant

text: I’m adding focused dashboard tests now: one HTTP route test for auth and store scoping, and one Livewire test for date-range recalculation.


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php
@@
 <?php

-test('example', function () {
-    $response = $this->get('/');
+use App\Livewire\Admin\Dashboard as AdminDashboard;
+use App\Models\Order;
+use App\Models\OrderLine;
+use App\Models\Product;
+use App\Models\ProductVariant;
+use App\Models\Store;
+use App\Models\User;
+use Database\Seeders\DatabaseSeeder;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Livewire\Livewire;

-    $response->assertStatus(200);
+uses(RefreshDatabase::class);
+
+beforeEach(function (): void {
+    $this->withoutVite();
+    $this->seed(DatabaseSeeder::class);
+});
+
+function adminDashboardStore(): Store
+{
+    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
+    app()->instance('current_store', $store);
+
+    return $store;
+}
+
+function adminDashboardUser(): User
+{
+    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
+}
+
+function adminDashboardOrder(Store $store, string $title, int $quantity, int $unitPrice, array $orderAttributes = []): Order
+{
+    $product = Product::factory()
+        ->withDefaultVariant($unitPrice)
+        ->create([
+            'store_id' => $store->getKey(),
+            'title' => $title,
+        ]);
+    $variant = ProductVariant::withoutGlobalScopes()
+        ->where('product_id', $product->getKey())
+        ->firstOrFail();
+    $total = $quantity * $unitPrice;
+    $order = Order::factory()->paid()->create(array_merge([
+        'store_id' => $store->getKey(),
+        'subtotal_amount' => $total,
+        'shipping_amount' => 0,
+        'tax_amount' => 0,
+        'total_amount' => $total,
+        'placed_at' => now(),
+    ], $orderAttributes));
+
+    OrderLine::factory()->create([
+        'order_id' => $order->getKey(),
+        'product_id' => $product->getKey(),
+        'variant_id' => $variant->getKey(),
+        'title_snapshot' => $title,
+        'sku_snapshot' => 'DASH-'.str($title)->slug()->upper(),
+        'quantity' => $quantity,
+        'unit_price_amount' => $unitPrice,
+        'total_amount' => $total,
+    ]);
+
+    return $order;
+}
+
+test('admin dashboard route requires authentication and renders store scoped metrics', function (): void {
+    $store = adminDashboardStore();
+    adminDashboardOrder($store, 'Dashboard Jacket', 2, 2500);
+    adminDashboardOrder($store, 'Dashboard Cap', 1, 2000);
+
+    $otherStore = Store::factory()->create();
+    adminDashboardOrder($otherStore, 'Other Store Product', 1, 9900);
+
+    $this->get('/admin')->assertRedirect('/admin/login');
+
+    $this->actingAs(adminDashboardUser())
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin')
+        ->assertSuccessful()
+        ->assertSee('Dashboard')
+        ->assertSee('70.00 EUR')
+        ->assertSee('Dashboard Jacket')
+        ->assertDontSee('Other Store Product');
+});
+
+test('admin dashboard recalculates kpis when date range changes', function (): void {
+    $store = adminDashboardStore();
+    $user = adminDashboardUser();
+
+    adminDashboardOrder($store, 'Today Product', 1, 6000);
+    adminDashboardOrder($store, 'Older Product', 1, 4000, [
+        'placed_at' => now()->subDays(15),
+    ]);
+
+    Livewire::actingAs($user)
+        ->test(AdminDashboard::class)
+        ->assertSet('ordersCount', 2)
+        ->assertSet('totalSales', 10000)
+        ->set('dateRange', 'today')
+        ->assertSet('ordersCount', 1)
+        ->assertSet('totalSales', 6000)
+        ->set('dateRange', 'custom')
+        ->set('customStartDate', now()->subDays(20)->toDateString())
+        ->set('customEndDate', now()->subDays(10)->toDateString())
+        ->assertSet('ordersCount', 1)
+        ->assertSet('totalSales', 4000);
 });
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php

unified_diff:
@@ -2,6 +2,109 @@

-test('example', function () {
-    $response = $this->get('/');
+use App\Livewire\Admin\Dashboard as AdminDashboard;
+use App\Models\Order;
+use App\Models\OrderLine;
+use App\Models\Product;
+use App\Models\ProductVariant;
+use App\Models\Store;
+use App\Models\User;
+use Database\Seeders\DatabaseSeeder;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Livewire\Livewire;
+
+uses(RefreshDatabase::class);
+
+beforeEach(function (): void {
+    $this->withoutVite();
+    $this->seed(DatabaseSeeder::class);
+});
+
+function adminDashboardStore(): Store
+{
+    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
+    app()->instance('current_store', $store);
+
+    return $store;
+}

-    $response->assertStatus(200);
+function adminDashboardUser(): User
+{
+    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
+}
+
+function adminDashboardOrder(Store $store, string $title, int $quantity, int $unitPrice, array $orderAttributes = []): Order
+{
+    $product = Product::factory()
+        ->withDefaultVariant($unitPrice)
+        ->create([
+            'store_id' => $store->getKey(),
+            'title' => $title,
+        ]);
+    $variant = ProductVariant::withoutGlobalScopes()
+        ->where('product_id', $product->getKey())
+        ->firstOrFail();
+    $total = $quantity * $unitPrice;
+    $order = Order::factory()->paid()->create(array_merge([
+        'store_id' => $store->getKey(),
+        'subtotal_amount' => $total,
+        'shipping_amount' => 0,
+        'tax_amount' => 0,
+        'total_amount' => $total,
+        'placed_at' => now(),
+    ], $orderAttributes));
+
+    OrderLine::factory()->create([
+        'order_id' => $order->getKey(),
+        'product_id' => $product->getKey(),
+        'variant_id' => $variant->getKey(),
+        'title_snapshot' => $title,
+        'sku_snapshot' => 'DASH-'.str($title)->slug()->upper(),
+        'quantity' => $quantity,
+        'unit_price_amount' => $unitPrice,
+        'total_amount' => $total,
+    ]);
+
+    return $order;
+}
+
+test('admin dashboard route requires authentication and renders store scoped metrics', function (): void {
+    $store = adminDashboardStore();
+    adminDashboardOrder($store, 'Dashboard Jacket', 2, 2500);
+    adminDashboardOrder($store, 'Dashboard Cap', 1, 2000);
+
+    $otherStore = Store::factory()->create();
+    adminDashboardOrder($otherStore, 'Other Store Product', 1, 9900);
+
+    $this->get('/admin')->assertRedirect('/admin/login');
+
+    $this->actingAs(adminDashboardUser())
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin')
+        ->assertSuccessful()
+        ->assertSee('Dashboard')
+        ->assertSee('70.00 EUR')
+        ->assertSee('Dashboard Jacket')
+        ->assertDontSee('Other Store Product');
+});
+
+test('admin dashboard recalculates kpis when date range changes', function (): void {
+    $store = adminDashboardStore();
+    $user = adminDashboardUser();
+
+    adminDashboardOrder($store, 'Today Product', 1, 6000);
+    adminDashboardOrder($store, 'Older Product', 1, 4000, [
+        'placed_at' => now()->subDays(15),
+    ]);
+
+    Livewire::actingAs($user)
+        ->test(AdminDashboard::class)
+        ->assertSet('ordersCount', 2)
+        ->assertSet('totalSales', 10000)
+        ->set('dateRange', 'today')
+        ->assertSet('ordersCount', 1)
+        ->assertSet('totalSales', 6000)
+        ->set('dateRange', 'custom')
+        ->set('customStartDate', now()->subDays(20)->toDateString())
+        ->set('customEndDate', now()->subDays(10)->toDateString())
+        ->assertSet('ordersCount', 1)
+        ->assertSet('totalSales', 4000);
 });


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


message: The dashboard implementation and tests are in place. I’m formatting the PHP changes, then I’ll run the focused admin dashboard test to catch any Livewire or query issues.


text: The dashboard implementation and tests are in place. I’m formatting the PHP changes, then I’ll run the focused admin dashboard test to catch any Livewire or query issues.


output:
Chunk ID: fa418a
Wall time: 0.3409 seconds
Process exited with code 0
Original token count: 5
Output:
{"result":"pass"}


command:
/bin/zsh
-lc
vendor/bin/pint --dirty --format agent

type: unknown

cmd: vendor/bin/pint --dirty --format agent

result: pass


cmd: php artisan test --compact tests/Feature/Admin/DashboardTest.php


output:
Chunk ID: dfe8b5
Wall time: 1.0011 seconds
Process running with session ID 29235
Original token count: 0
Output:


command: php artisan test --compact tests/Feature/Admin/DashboardTest.php

aggregated_output:
  ⨯⨯
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboard route require…
  Expected response status code [>=200, <300] but received 500.
Failed asserting that false is true.

The following exception occurred during the last request:

TypeError: App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105 and defined in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php:226
Stack trace:
#0 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(105): App\Livewire\Admin\Dashboard->previousRange(Object(Carbon\CarbonImmutable), Object(Carbon\CarbonImmutable))
#1 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(80): App\Livewire\Admin\Dashboard->loadKpis()
#2 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\Livewire\Admin\Dashboard->mount()
#3 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#4 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#5 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#6 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Wrapped.php(23): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array)
#7 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(158): Livewire\Wrapped->__call('mount', Array)
#8 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(35): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->callHook('mount', Array)
#9 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHook.php(19): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->mount(Array, false, Array)
#10 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHookRegistry.php(45): Livewire\ComponentHook->callMount(Array, false, Array)
#11 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/EventBus.php(87): Livewire\ComponentHookRegistry::{closure:Livewire\ComponentHookRegistry::boot():39}(Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#12 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/helpers.php(98): Livewire\EventBus->trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#13 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php(76): Livewire\trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#14 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/LivewireManager.php(102): Livewire\Mechanisms\HandleComponents\HandleComponents->mount('admin.dashboard', Array, NULL, Array)
#15 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(19): Livewire\LivewireManager->mount('admin.dashboard', Array)
#16 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/SupportPageComponents.php(118): Livewire\Component->{closure:Livewire\Features\SupportPageComponents\HandlesPageComponents::__invoke():14}()
#17 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(14): Livewire\Features\SupportPageComponents\SupportPageComponents::interceptTheRenderOfTheComponentAndRetreiveTheLayoutConfiguration(Object(Closure))
#18 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Livewire\Component->__invoke()
#19 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#20 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#21 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#22 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Contai…12891 chars truncated…dor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\Foundation\Testing\TestCase->call('GET', '/admin', Array, Array, Array, Array)
#82 /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php(81): Illuminate\Foundation\Testing\TestCase->get('/admin')
#83 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\Tests\Feature\Admin\DashboardTest->{closure:/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php:69}()
#84 [internal function]: P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Factories\TestCaseMethodFactory::getClosure():158}()
#85 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)
#86 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Concerns\Testable::__callClosure():429}()
#87 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\Support\ExceptionTrace::ensure(Object(Closure))
#88 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\Tests\Feature\Admin\DashboardTest->__callClosure(Object(Closure), Array)
#89 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(17): P\Tests\Feature\Admin\DashboardTest->__runTest(Object(Closure))
#90 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\Tests\Feature\Admin\DashboardTest->__pest_evaluable_admin_dashboard_route_requires_authentication_and_renders_store_scoped_metrics()
#91 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\Framework\TestCase->runTest()
#92 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\Framework\TestCase->runBare()
#93 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\Framework\TestRunner->run(Object(P\Tests\Feature\Admin\DashboardTest))
#94 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestCase->run()
#95 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\Framework\TestSuite->run()
#96 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))
#97 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\TextUI\Application->run(Array)
#98 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\Kernel->handle(Array, Array)
#99 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()
#100 {main}

----------------------------------------------------------------------------------

App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105

  at tests/Feature/Admin/DashboardTest.php:82
     78▕
     79▕     $this->actingAs(adminDashboardUser())
     80▕         ->withSession(['current_store_id' => $store->getKey()])
     81▕         ->get('/admin')
  ➜  82▕         ->assertSuccessful()
     83▕         ->assertSee('Dashboard')
     84▕         ->assertSee('70.00 EUR')
     85▕         ->assertSee('Dashboard Jacket')
     86▕         ->assertDontSee('Other Store Product');

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboar…  ViewException
  App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105 (View: /Users/fabianwesner/Herd/shop/storage/framework/views/a168b0ebcff266df6b662fc4b6d06e88.blade.php)

  at app/Livewire/Admin/Dashboard.php:226
    222▕
    223▕     /**
    224▕      * @return array{0: Carbon, 1: Carbon}
    225▕      */
  ➜ 226▕     private function previousRange(Carbon $start, Carbon $end): array
    227▕     {
    228▕         $days = max(1, $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1);
    229▕         $previousEnd = $start->copy()->subSecond();
    230▕

  1   app/Livewire/Admin/Dashboard.php:226
  2   app/Livewire/Admin/Dashboard.php:105

  Tests:    2 failed (3 assertions)
  Duration: 1.01s


output:
Chunk ID: 18ce86
Wall time: 0.0000 seconds
Process exited with code 2
Original token count: 5723
Output:

  ⨯⨯
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboard route require…
  Expected response status code [>=200, <300] but received 500.
Failed asserting that false is true.

The following exception occurred during the last request:

TypeError: App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105 and defined in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php:226
Stack trace:
#0 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(105): App\Livewire\Admin\Dashboard->previousRange(Object(Carbon\CarbonImmutable), Object(Carbon\CarbonImmutable))
#1 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(80): App\Livewire\Admin\Dashboard->loadKpis()
#2 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\Livewire\Admin\Dashboard->mount()
#3 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#4 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#5 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#6 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Wrapped.php(23): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array)
#7 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(158): Livewire\Wrapped->__call('mount', Array)
#8 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(35): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->callHook('mount', Array)
#9 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHook.php(19): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->mount(Array, false, Array)
#10 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHookRegistry.php(45): Livewire\ComponentHook->callMount(Array, false, Array)
#11 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/EventBus.php(87): Livewire\ComponentHookRegistry::{closure:Livewire\ComponentHookRegistry::boot():39}(Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#12 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/helpers.php(98): Livewire\EventBus->trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#13 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php(76): Livewire\trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#14 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/LivewireManager.php(102): Livewire\Mechanisms\HandleComponents\HandleComponents->mount('admin.dashboard', Array, NULL, Array)
#15 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(19): Livewire\LivewireManager->mount('admin.dashboard', Array)
#16 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/SupportPageComponents.php(118): Livewire\Component->{closure:Livewire\Features\SupportPageComponents\HandlesPageComponents::__invoke():14}()
#17 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(14): Livewire\Features\SupportPageComponents\SupportPageComponents::interceptTheRenderOfTheComponentAndRetreiveTheLayoutConfiguration(Object(Closure))
#18 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Livewire\Component->__invoke()
#19 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#20 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#21 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#22 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#23 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Mechanisms/HandleRouting/LivewirePageController.php(15): Illuminate\Container\Container->call(Array)
#24 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): Livewire\Mechanisms\HandleRouting\LivewirePageController->__invoke()
#25 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Route.php(265): Illuminate\Routing\ControllerDispatcher->dispatch(Object(Illuminate\Routing\Route), Object(Livewire\Mechanisms\HandleRouting\LivewirePageController), '__invoke')
#26 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Route.php(211): Illuminate\Routing\Route->runController()
#27 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Router.php(822): Illuminate\Routing\Route->run()
#28 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Routing\Router->{closure:Illuminate\Routing\Router::runRouteWithinStack():821}(Object(Illuminate\Http\Request))
#29 /Users/fabianwesner/Herd/shop/app/Http/Middleware/CheckStoreRole.php(30): Illuminate\Pipeline\Pipeline->{closure:Illuminate\Pipeline\Pipeline::prepareDestination():178}(Object(Illuminate\Http\Request))
#30 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\Http\Middleware\CheckStoreRole->handle(Object(Illuminate\Http\Request), Object(Closure))
#31 /Users/fabianwesner/Herd/shop/app/Http/Middleware/ResolveStore.php(38): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#32 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\Http\Middleware\ResolveStore->handle(Object(Illuminate\Http\Request), Object(Closure))
#33 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php(41): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#34 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Auth\Middleware\EnsureEmailIsVerified->handle(Object(Illuminate\Http\Request), Object(Closure))
#35 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#36 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Routing\Middleware\SubstituteBindings->handle(Object(Illuminate\Http\Request), Object(Closure))
#37 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(63): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#38 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Auth\Middleware\Authenticate->handle(Object(Illuminate\Http\Request), Object(Closure))
#39 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(87): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#40 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
#41 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(48): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#42 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\View\Middleware\ShareErrorsFromSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#43 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(120): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#44 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(63): Illuminate\Session\Middleware\StartSession->handleStatefulRequest(Object(Illuminate\Http\Request), Object(Illuminate\Session\EncryptedStore), Object(Closure))
#45 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Session\Middleware\StartSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#46 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(36): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#47 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle(Object(Illuminate\Http\Request), Object(Closure))
#48 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(74): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#49 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Cookie\Middleware\EncryptCookies->handle(Object(Illuminate\Http\Request), Object(Closure))
#50 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#51 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Router.php(821): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#52 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\Routing\Router->runRouteWithinStack(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))
#53 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\Routing\Router->runRoute(Object(Illuminate\Http\Request), Object(Illuminate\Routing\Route))
#54 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#55 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#56 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Kernel->{closure:Illuminate\Foundation\Http\Kernel::dispatchToRouter():197}(Object(Illuminate\Http\Request))
#57 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportDisablingBackButtonCache/DisableBackButtonCacheMiddleware.php(19): Illuminate\Pipeline\Pipeline->{closure:Illuminate\Pipeline\Pipeline::prepareDestination():178}(Object(Illuminate\Http\Request))
#58 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Livewire\Features\SupportDisablingBackButtonCache\DisableBackButtonCacheMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#59 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#60 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#61 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull->handle(Object(Illuminate\Http\Request), Object(Closure))
#62 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#63 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#64 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Foundation\Http\Middleware\TrimStrings->handle(Object(Illuminate\Http\Request), Object(Closure))
#65 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#66 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Http\Middleware\ValidatePostSize->handle(Object(Illuminate\Http\Request), Object(Closure))
#67 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#68 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance->handle(Object(Illuminate\Http\Request), Object(Closure))
#69 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(61): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#70 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Http\Middleware\HandleCors->handle(Object(Illuminate\Http\Request), Object(Closure))
#71 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(58): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#72 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Http\Middleware\TrustProxies->handle(Object(Illuminate\Http\Request), Object(Closure))
#73 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#74 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks->handle(Object(Illuminate\Http\Request), Object(Closure))
#75 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePathEncoding.php(26): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#76 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\Http\Middleware\ValidatePathEncoding->handle(Object(Illuminate\Http\Request), Object(Closure))
#77 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#78 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#79 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#80 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(607): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#81 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\Foundation\Testing\TestCase->call('GET', '/admin', Array, Array, Array, Array)
#82 /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php(81): Illuminate\Foundation\Testing\TestCase->get('/admin')
#83 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\Tests\Feature\Admin\DashboardTest->{closure:/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php:69}()
#84 [internal function]: P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Factories\TestCaseMethodFactory::getClosure():158}()
#85 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)
#86 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Concerns\Testable::__callClosure():429}()
#87 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\Support\ExceptionTrace::ensure(Object(Closure))
#88 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\Tests\Feature\Admin\DashboardTest->__callClosure(Object(Closure), Array)
#89 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(17): P\Tests\Feature\Admin\DashboardTest->__runTest(Object(Closure))
#90 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\Tests\Feature\Admin\DashboardTest->__pest_evaluable_admin_dashboard_route_requires_authentication_and_renders_store_scoped_metrics()
#91 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\Framework\TestCase->runTest()
#92 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\Framework\TestCase->runBare()
#93 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\Framework\TestRunner->run(Object(P\Tests\Feature\Admin\DashboardTest))
#94 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestCase->run()
#95 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\Framework\TestSuite->run()
#96 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))
#97 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\TextUI\Application->run(Array)
#98 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\Kernel->handle(Array, Array)
#99 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()
#100 {main}

----------------------------------------------------------------------------------

App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105

  at tests/Feature/Admin/DashboardTest.php:82
     78▕
     79▕     $this->actingAs(adminDashboardUser())
     80▕         ->withSession(['current_store_id' => $store->getKey()])
     81▕         ->get('/admin')
  ➜  82▕         ->assertSuccessful()
     83▕         ->assertSee('Dashboard')
     84▕         ->assertSee('70.00 EUR')
     85▕         ->assertSee('Dashboard Jacket')
     86▕         ->assertDontSee('Other Store Product');

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboar…  ViewException
  App\Livewire\Admin\Dashboard::previousRange(): Argument #1 ($start) must be of type Illuminate\Support\Carbon, Carbon\CarbonImmutable given, called in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php on line 105 (View: /Users/fabianwesner/Herd/shop/storage/framework/views/a168b0ebcff266df6b662fc4b6d06e88.blade.php)

  at app/Livewire/Admin/Dashboard.php:226
    222▕
    223▕     /**
    224▕      * @return array{0: Carbon, 1: Carbon}
    225▕      */
  ➜ 226▕     private function previousRange(Carbon $start, Carbon $end): array
    227▕     {
    228▕         $days = max(1, $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1);
    229▕         $previousEnd = $start->copy()->subSecond();
    230▕

  1   app/Livewire/Admin/Dashboard.php:226
  2   app/Livewire/Admin/Dashboard.php:105

  Tests:    2 failed (3 assertions)
  Duration: 1.01s


message: The focused test found the app is returning immutable Carbon instances in this context, while my dashboard typed a few helpers to mutable `Illuminate\Support\Carbon`. I’m loosening those helper types to Carbon’s interface and rerunning.


text: The focused test found the app is returning immutable Carbon instances in this context, while my dashboard typed a few helpers to mutable `Illuminate\Support\Carbon`. I’m loosening those helper types to Carbon’s interface and rerunning.


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php
@@
 use App\Models\OrderLine;
 use App\Models\Store;
 use App\Support\Money;
+use Carbon\CarbonInterface;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Carbon;
 use Illuminate\Support\CarbonPeriod;
@@
-            ->map(fn (Carbon $date): array => [
+            ->map(fn (CarbonInterface $date): array => [
@@
-     * @return array{0: Carbon, 1: Carbon}
+     * @return array{0: CarbonInterface, 1: CarbonInterface}
      */
     private function currentRange(): array
@@
-     * @return array{0: Carbon, 1: Carbon}
+     * @return array{0: CarbonInterface, 1: CarbonInterface}
      */
-    private function previousRange(Carbon $start, Carbon $end): array
+    private function previousRange(CarbonInterface $start, CarbonInterface $end): array
@@
-     * @return array{0: Carbon, 1: Carbon}|null
+     * @return array{0: CarbonInterface, 1: CarbonInterface}|null
      */
@@
-    private function ordersBetween(Carbon $start, Carbon $end): Builder
+    private function ordersBetween(CarbonInterface $start, CarbonInterface $end): Builder
@@
-    private function revenueOrdersBetween(Carbon $start, Carbon $end): Builder
+    private function revenueOrdersBetween(CarbonInterface $start, CarbonInterface $end): Builder
*** End Patch


unified_diff:
@@ -11,2 +11,3 @@
 use App\Support\Money;
+use Carbon\CarbonInterface;
 use Illuminate\Database\Eloquent\Builder;
@@ -139,3 +140,3 @@
         $this->ordersChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
-            ->map(fn (Carbon $date): array => [
+            ->map(fn (CarbonInterface $date): array => [
                 'date' => $date->toDateString(),
@@ -210,3 +211,3 @@
     /**
-     * @return array{0: Carbon, 1: Carbon}
+     * @return array{0: CarbonInterface, 1: CarbonInterface}
      */
@@ -223,5 +224,5 @@
     /**
-     * @return array{0: Carbon, 1: Carbon}
+     * @return array{0: CarbonInterface, 1: CarbonInterface}
      */
-    private function previousRange(Carbon $start, Carbon $end): array
+    private function previousRange(CarbonInterface $start, CarbonInterface $end): array
     {
@@ -234,3 +235,3 @@
     /**
-     * @return array{0: Carbon, 1: Carbon}|null
+     * @return array{0: CarbonInterface, 1: CarbonInterface}|null
      */
@@ -259,3 +260,3 @@
      */
-    private function ordersBetween(Carbon $start, Carbon $end): Builder
+    private function ordersBetween(CarbonInterface $start, CarbonInterface $end): Builder
     {
@@ -269,3 +270,3 @@
      */
-    private function revenueOrdersBetween(Carbon $start, Carbon $end): Builder
+    private function revenueOrdersBetween(CarbonInterface $start, CarbonInterface $end): Builder
     {


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


cmd: vendor/bin/pint --dirty --format agent && php artisan test --compact tests/Feature/Admin/DashboardTest.php


output:
Chunk ID: b15175
Wall time: 1.0018 seconds
Process running with session ID 9332
Original token count: 5
Output:
{"result":"pass"}


command: vendor/bin/pint --dirty --format agent && php artisan test --compact tests/Feature/Admin/DashboardTest.php

aggregated_output:
{"result":"pass"}
  ⨯⨯
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboard route require…
  Expected response status code [>=200, <300] but received 500.
Failed asserting that false is true.

The following exception occurred during the last request:

Error: Class "Illuminate\Support\CarbonPeriod" not found in /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php:140
Stack trace:
#0 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(126): App\Livewire\Admin\Dashboard->loadChart()
#1 /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Dashboard.php(81): App\Livewire\Admin\Dashboard->loadKpis()
#2 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\Livewire\Admin\Dashboard->mount()
#3 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#4 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#5 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#6 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Wrapped.php(23): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array)
#7 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(158): Livewire\Wrapped->__call('mount', Array)
#8 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php(35): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->callHook('mount', Array)
#9 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHook.php(19): Livewire\Features\SupportLifecycleHooks\SupportLifecycleHooks->mount(Array, false, Array)
#10 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/ComponentHookRegistry.php(45): Livewire\ComponentHook->callMount(Array, false, Array)
#11 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/EventBus.php(87): Livewire\ComponentHookRegistry::{closure:Livewire\ComponentHookRegistry::boot():39}(Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#12 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/helpers.php(98): Livewire\EventBus->trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#13 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php(76): Livewire\trigger('mount', Object(App\Livewire\Admin\Dashboard), Array, NULL, false, Array)
#14 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/LivewireManager.php(102): Livewire\Mechanisms\HandleComponents\HandleComponents->mount('admin.dashboard', Array, NULL, Array)
#15 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(19): Livewire\LivewireManager->mount('admin.dashboard', Array)
#16 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/SupportPageComponents.php(118): Livewire\Component->{closure:Livewire\Features\SupportPageComponents\HandlesPageComponents::__invoke():14}()
#17 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Features/SupportPageComponents/HandlesPageComponents.php(14): Livewire\Features\SupportPageComponents\SupportPageComponents::interceptTheRenderOfTheComponentAndRetreiveTheLayoutConfiguration(Object(Closure))
#18 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Livewire\Component->__invoke()
#19 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\Container\BoundMethod::{closure:Illuminate\Container\BoundMethod::call():35}()
#20 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#21 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#22 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#23 /Users/fabianwesner/Herd/shop/vendor/livewire/livewire/src/Mechanisms/HandleRouting/LivewirePageController.php(15): Illum…12534 chars truncated…7): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#81 /Users/fabianwesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\Foundation\Testing\TestCase->call('GET', '/admin', Array, Array, Array, Array)
#82 /Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php(81): Illuminate\Foundation\Testing\TestCase->get('/admin')
#83 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\Tests\Feature\Admin\DashboardTest->{closure:/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DashboardTest.php:69}()
#84 [internal function]: P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Factories\TestCaseMethodFactory::getClosure():158}()
#85 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)
#86 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\Tests\Feature\Admin\DashboardTest->{closure:Pest\Concerns\Testable::__callClosure():429}()
#87 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\Support\ExceptionTrace::ensure(Object(Closure))
#88 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\Tests\Feature\Admin\DashboardTest->__callClosure(Object(Closure), Array)
#89 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(17): P\Tests\Feature\Admin\DashboardTest->__runTest(Object(Closure))
#90 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\Tests\Feature\Admin\DashboardTest->__pest_evaluable_admin_dashboard_route_requires_authentication_and_renders_store_scoped_metrics()
#91 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\Framework\TestCase->runTest()
#92 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\Framework\TestCase->runBare()
#93 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\Framework\TestRunner->run(Object(P\Tests\Feature\Admin\DashboardTest))
#94 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestCase->run()
#95 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\Framework\TestSuite->run()
#96 /Users/fabianwesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))
#97 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\TextUI\Application->run(Array)
#98 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\Kernel->handle(Array, Array)
#99 /Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/fabianwesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()
#100 {main}

----------------------------------------------------------------------------------

Class "Illuminate\Support\CarbonPeriod" not found

  at tests/Feature/Admin/DashboardTest.php:82
     78▕
     79▕     $this->actingAs(adminDashboardUser())
     80▕         ->withSession(['current_store_id' => $store->getKey()])
     81▕         ->get('/admin')
  ➜  82▕         ->assertSuccessful()
     83▕         ->assertSee('Dashboard')
     84▕         ->assertSee('70.00 EUR')
     85▕         ->assertSee('Dashboard Jacket')
     86▕         ->assertDontSee('Other Store Product');

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\DashboardTest > admin dashboar…  ViewException
  Class "Illuminate\Support\CarbonPeriod" not found (View: /Users/fabianwesner/Herd/shop/storage/framework/views/a168b0ebcff266df6b662fc4b6d06e88.blade.php)

  at app/Livewire/Admin/Dashboard.php:140
    136▕             ->selectRaw('date(placed_at) as order_date, count(*) as aggregate')
    137▕             ->groupBy('order_date')
    138▕             ->pluck('aggregate', 'order_date');
    139▕
  ➜ 140▕         $this->ordersChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
    141▕             ->map(fn (CarbonInterface $date): array => [
    142▕                 'date' => $date->toDateString(),
    143▕                 'label' => $date->format('M j'),
    144▕                 'count' => (int) ($counts[$date->toDateString()] ?? 0),

  1   app/Livewire/Admin/Dashboard.php:140
  2   app/Livewire/Admin/Dashboard.php:126

  Tests:    2 failed (3 assertions)
  Duration: 0.98s