126 lines
4.8 KiB
PHP
126 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace Modules\Core\Cart\Filament\Resources;
|
|
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Actions\ViewAction;
|
|
use Modules\Core\Cart\Filament\Resources\CartResource\Pages\ListCarts;
|
|
use Modules\Core\Cart\Filament\Resources\CartResource\Pages\ViewCart;
|
|
use Filament\Resources\Resource;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Support\Carbon;
|
|
use Lunar\Admin\Filament\Resources\CustomerResource;
|
|
use Lunar\Models\Cart;
|
|
use Modules\Core\Cart\Filament\Resources\CartResource\Pages;
|
|
use Modules\Core\Cart\Services\CartLifecycleService;
|
|
|
|
/**
|
|
* Read-only — a cart is managed entirely through the storefront (add/update/remove
|
|
* line, checkout), never hand-edited by staff. Lists every cart, guest carts
|
|
* included — see docs/cart.md ("Scope: every cart, identified or not"). An
|
|
* anonymous cart's Customer/User columns just render "—" (see table() below)
|
|
* rather than the row being hidden outright: most real traffic never reaches
|
|
* an identified user/customer, and "how many carts are ongoing/abandoned
|
|
* right now" is a real reporting need regardless of identity — excluding
|
|
* anonymous carts would silently undercount it. Lunar itself ships no cart
|
|
* admin view at all to follow a precedent from.
|
|
*/
|
|
class CartResource extends Resource
|
|
{
|
|
protected static ?string $model = Cart::class;
|
|
|
|
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-shopping-cart';
|
|
|
|
protected static string | \UnitEnum | null $navigationGroup = 'Sales';
|
|
|
|
protected static ?string $modelLabel = 'Cart';
|
|
|
|
protected static ?string $pluralModelLabel = 'Carts';
|
|
|
|
/**
|
|
* Count only, not a fetch — no rows are loaded. Combines BOTH abandoned
|
|
* states (`active()` already covers "no order at all" and "draft order,
|
|
* never placed" together — see ListCarts::getTabs()'s "Abandoned Cart" /
|
|
* "Abandoned Checkout" tabs for where they're split apart), not "Ongoing"
|
|
* — the badge is meant to answer "how many carts might need following up
|
|
* on," not the total including ones someone is actively shopping in right
|
|
* now.
|
|
*/
|
|
public static function getNavigationBadge(): ?string
|
|
{
|
|
return (string) static::getEloquentQuery()->active()->where('updated_at', '<=', static::abandonedCutoff())->count();
|
|
}
|
|
|
|
public static function lifecycle(): CartLifecycleService
|
|
{
|
|
return app(CartLifecycleService::class);
|
|
}
|
|
|
|
/**
|
|
* `Cart::scopeActive()` (not-yet-converted-to-an-order carts) mixes two very
|
|
* different things together: a cart someone is actively shopping in right now,
|
|
* and one that's genuinely been left behind. Lunar tracks no time-based
|
|
* staleness signal of its own — `Cart::updated_at` plus a configurable
|
|
* threshold (`config('core.cart.abandoned_after')`, default 1 hour) is what
|
|
* this resource uses to tell them apart. A cart with no recent activity is
|
|
* "Abandoned"; anything more recent is "Ongoing".
|
|
*/
|
|
public static function abandonedCutoff(): Carbon
|
|
{
|
|
return static::lifecycle()->abandonedCutoff();
|
|
}
|
|
|
|
public static function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->columns([
|
|
TextColumn::make('id')
|
|
->label('Cart')
|
|
->sortable(),
|
|
TextColumn::make('customer.full_name')
|
|
->label('Customer')
|
|
->placeholder('—')
|
|
->searchable()
|
|
->url(fn (Cart $record) => $record->customer_id !== null
|
|
? CustomerResource::getUrl('view', ['record' => $record->customer_id])
|
|
: null),
|
|
TextColumn::make('user.email')
|
|
->label('User')
|
|
->placeholder('—')
|
|
->searchable(),
|
|
TextColumn::make('lines_count')
|
|
->label('Lines')
|
|
->counts('lines')
|
|
->sortable(),
|
|
TextColumn::make('lines_sum_quantity')
|
|
->label('Items')
|
|
->sum('lines', 'quantity')
|
|
->sortable(),
|
|
TextColumn::make('currency.code')
|
|
->label('Currency'),
|
|
TextColumn::make('updated_at')
|
|
->label('Last activity')
|
|
->dateTime()
|
|
->sortable(),
|
|
])
|
|
->recordActions([
|
|
ViewAction::make(),
|
|
])
|
|
->defaultSort('updated_at', 'desc');
|
|
}
|
|
|
|
public static function getPages(): array
|
|
{
|
|
return [
|
|
'index' => ListCarts::route('/'),
|
|
'view' => ViewCart::route('/{record}'),
|
|
];
|
|
}
|
|
|
|
public static function canCreate(): bool
|
|
{
|
|
return false;
|
|
}
|
|
}
|