|null */ public ?array $filters = []; public static function canAccess(): bool { return auth()->user()?->can('view_reports') ?? false; } public function mount(): void { $this->filtersForm->fill(); } public function filtersForm(Schema $schema): Schema { return $schema ->components([ DatePicker::make('travel_date_from') ->label('Travel date from') ->live(), DatePicker::make('travel_date_to') ->label('Travel date to') ->afterOrEqual('travel_date_from') ->live(), Select::make('status') ->label('Status') ->options(BookingStatus::class) ->native(false) ->placeholder('All statuses') ->live(), Select::make('ev_route_id') ->label('Route') ->options(fn () => EvRoute::with(['fromDestination', 'toDestination'])->get() ->mapWithKeys(fn (EvRoute $route) => [$route->id => $route->name])) ->searchable() ->placeholder('All routes') ->live(), Select::make('created_by_channel') ->label('Channel') ->options(BookingChannel::class) ->native(false) ->placeholder('All channels') ->live(), ]) ->columns(3) ->statePath('filters'); } public function table(Table $table): Table { return $table ->query(fn (): Builder => $this->reportQuery()) ->columns([ TextColumn::make('booking_ref') ->label('Ref') ->sortable(), TextColumn::make('travel_date') ->date() ->sortable(), TextColumn::make('route.name') ->label('Route'), TextColumn::make('created_by_channel') ->badge(), TextColumn::make('status') ->badge(), TextColumn::make('passenger_name') ->label('Passenger'), TextColumn::make('passenger_count') ->label('Pax') ->state(fn (Booking $record) => $record->vehicleOptions->sum('passenger_count')) ->summarize(Summarizer::make() ->label('Total') ->using(fn (QueryBuilder $query) => Booking::query() ->with('vehicleOptions') ->whereIn('id', (clone $query)->pluck('id')) ->get() ->sum(fn (Booking $b) => $b->vehicleOptions->sum('passenger_count')))), TextColumn::make('price') ->numeric(2) ->sortable() ->summarize(Sum::make()->label('Total')), TextColumn::make('payment_status') ->label('Payment') ->state(fn (Booking $record) => $this->bestPayment($record)?->status?->value ?? '—'), TextColumn::make('payment_amount') ->label('Paid') ->state(fn (Booking $record) => $this->bestPayment($record)?->amount) ->summarize(Summarizer::make() ->label('Total') ->using(fn (QueryBuilder $query) => Booking::query() ->with('payments') ->whereIn('id', (clone $query)->pluck('id')) ->get() ->sum(fn (Booking $b) => $this->bestPayment($b)?->amount ?? 0))), TextColumn::make('driver_name') ->label('Driver') ->placeholder('—'), ]) ->defaultSort('travel_date', 'desc') ->paginated([25, 50, 100]); } public function reportQuery(): Builder { $data = $this->filters ?? []; return Booking::query() ->with(['route.fromDestination', 'route.toDestination', 'payments', 'vehicleOptions']) ->when($data['travel_date_from'] ?? null, fn (Builder $q, $d) => $q->whereDate('travel_date', '>=', $d)) ->when($data['travel_date_to'] ?? null, fn (Builder $q, $d) => $q->whereDate('travel_date', '<=', $d)) ->when($data['status'] ?? null, fn (Builder $q, $s) => $q->where('status', $s)) ->when($data['ev_route_id'] ?? null, fn (Builder $q, $id) => $q->where('ev_route_id', $id)) ->when($data['created_by_channel'] ?? null, fn (Builder $q, $c) => $q->where('created_by_channel', $c)) // A unique tie-breaker after travel_date — required for FromQuery's // chunked export to paginate deterministically (see its docblock); // the table's own defaultSort() applies on top of this for display. ->orderBy('travel_date', 'desc') ->orderBy('id'); } protected function bestPayment(Booking $record): ?Payment { return $record->payments->sortByDesc( fn (Payment $p) => $p->status === PaymentStatus::Completed ? 1 : 0 )->first(); } protected function getHeaderActions(): array { return [ Action::make('exportCsv') ->label('Export CSV') ->icon(Heroicon::OutlinedArrowDownTray) ->action(fn () => Excel::download( new BookingsRevenueExport($this->reportQuery()), 'bookings-revenue-'.now()->format('Y-m-d').'.csv', ExcelFormat::CSV, )), Action::make('exportXlsx') ->label('Export Excel') ->icon(Heroicon::OutlinedArrowDownTray) ->action(fn () => Excel::download( new BookingsRevenueExport($this->reportQuery()), 'bookings-revenue-'.now()->format('Y-m-d').'.xlsx', ExcelFormat::XLSX, )), ]; } }