Feature: Creating Cart Views, rules for abandonment
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Cart\Filament\Resources;
|
||||
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Lunar\Admin\Filament\Resources\CustomerResource;
|
||||
use Lunar\Models\Cart;
|
||||
use Modules\Core\Cart\Filament\Resources\CartResource\Pages;
|
||||
|
||||
/**
|
||||
* Read-only — a cart is managed entirely through the storefront (add/update/remove
|
||||
* line, checkout), never hand-edited by staff. Scoped to carts with a known
|
||||
* `user_id`/`customer_id` only: an anonymous guest's session cart carries no
|
||||
* identity a staff member could act on (no name, no email, nothing to follow up
|
||||
* with), so listing every such row would be noise, not a real admin capability —
|
||||
* see docs/cart.md for the reasoning (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 $navigationIcon = 'heroicon-o-shopping-cart';
|
||||
|
||||
protected static ?string $navigationGroup = 'Sales';
|
||||
|
||||
protected static ?string $modelLabel = 'Cart';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Carts';
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getEloquentQuery()
|
||||
->where(fn (Builder $query) => $query->whereNotNull('user_id')->orWhereNotNull('customer_id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Count only, not a fetch — no rows are loaded. Scoped to genuinely abandoned
|
||||
* carts specifically (mirrors ListCarts::getTabs()'s "Abandoned" query, not
|
||||
* "Ongoing"), since that's the number a staff member glancing at the sidebar
|
||||
* actually wants: 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* `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 now()->sub(config('core.cart.abandoned_after', '1 hour'));
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')
|
||||
->label('Cart')
|
||||
->sortable(),
|
||||
Tables\Columns\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),
|
||||
Tables\Columns\TextColumn::make('user.email')
|
||||
->label('User')
|
||||
->placeholder('—')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('lines_count')
|
||||
->label('Lines')
|
||||
->counts('lines')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('lines_sum_quantity')
|
||||
->label('Items')
|
||||
->sum('lines', 'quantity')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('currency.code')
|
||||
->label('Currency'),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->label('Last activity')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\ViewAction::make(),
|
||||
])
|
||||
->defaultSort('updated_at', 'desc');
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListCarts::route('/'),
|
||||
'view' => Pages\ViewCart::route('/{record}'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user