Phase 7 dashboard widgets, Gitea CI, and branded landing page (T7.1)
- BookingsTodayWidget, RecentBookingsTableWidget (Booking module) and RevenueChartWidget, PaymentFailureRateWidget (Payment module) dashboard widgets, auto-registered via each plugin's existing discoverWidgets(). - .gitea/workflows/tests.yml: Postgres-backed Pest run on push/PR. - Landing page (resources/views/welcome.blade.php) now shows the Famous Linnyone4 EV logo with a single admin login link, and the Filament admin panel uses the same logo as its brand logo.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Filament\Widgets;
|
||||
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Modules\Booking\Enums\BookingStatus;
|
||||
use Modules\Booking\Models\Booking;
|
||||
|
||||
/**
|
||||
* Dispatch-facing snapshot of today's trips — "today" means travel_date, not
|
||||
* created_at, since this is what staff care about when assigning
|
||||
* drivers/vehicles (domain.md §5a), not how many bookings were made today.
|
||||
*/
|
||||
class BookingsTodayWidget extends BaseWidget
|
||||
{
|
||||
protected function getStats(): array
|
||||
{
|
||||
$today = Booking::query()->whereDate('travel_date', today());
|
||||
|
||||
$confirmedToday = (clone $today)->where('status', BookingStatus::Confirmed)->count();
|
||||
$pendingToday = (clone $today)->where('status', BookingStatus::PendingPayment)->count();
|
||||
|
||||
return [
|
||||
Stat::make('Trips Today', (clone $today)->count())
|
||||
->description('Bookings scheduled for today')
|
||||
->color('primary'),
|
||||
Stat::make('Confirmed', $confirmedToday)
|
||||
->description('Paid & ready for driver assignment')
|
||||
->color('success'),
|
||||
Stat::make('Awaiting Payment', $pendingToday)
|
||||
->description('Still pending_payment')
|
||||
->color($pendingToday > 0 ? 'warning' : 'gray'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Filament\Widgets;
|
||||
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
use Modules\Booking\Enums\BookingStatus;
|
||||
use Modules\Booking\Models\Booking;
|
||||
|
||||
class RecentBookingsTableWidget extends BaseWidget
|
||||
{
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
protected int|string|array $columnSpan = 'full';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading('Recent Bookings')
|
||||
->query(
|
||||
Booking::query()
|
||||
->with(['route.fromDestination', 'route.toDestination'])
|
||||
->latest('created_at')
|
||||
->limit(10),
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('booking_ref')
|
||||
->label('Ref'),
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn (BookingStatus $state) => match ($state) {
|
||||
BookingStatus::PendingPayment => 'warning',
|
||||
BookingStatus::Confirmed => 'success',
|
||||
BookingStatus::Cancelled => 'gray',
|
||||
BookingStatus::Expired => 'danger',
|
||||
}),
|
||||
TextColumn::make('route.fromDestination.name')
|
||||
->label('From'),
|
||||
TextColumn::make('route.toDestination.name')
|
||||
->label('To'),
|
||||
TextColumn::make('travel_date')
|
||||
->date(),
|
||||
TextColumn::make('price')
|
||||
->numeric(2),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->since(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Booking\Enums\BookingStatus;
|
||||
use Modules\Booking\Filament\Widgets\BookingsTodayWidget;
|
||||
use Modules\Booking\Models\Booking;
|
||||
|
||||
test('it counts todays trips by status, ignoring other days', function () {
|
||||
$this->actingAs(User::factory()->create());
|
||||
|
||||
Booking::factory()->create(['travel_date' => today(), 'status' => BookingStatus::Confirmed]);
|
||||
Booking::factory()->create(['travel_date' => today(), 'status' => BookingStatus::PendingPayment]);
|
||||
Booking::factory()->create(['travel_date' => today()->addDay(), 'status' => BookingStatus::Confirmed]);
|
||||
|
||||
Livewire::test(BookingsTodayWidget::class)
|
||||
->assertOk()
|
||||
->assertSee('Trips Today')
|
||||
->assertSee('2')
|
||||
->assertSee('Confirmed')
|
||||
->assertSee('Awaiting Payment');
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Booking\Filament\Widgets\RecentBookingsTableWidget;
|
||||
use Modules\Booking\Models\Booking;
|
||||
|
||||
test('it lists the most recently created bookings', function () {
|
||||
$this->actingAs(User::factory()->create());
|
||||
|
||||
$older = Booking::factory()->create(['created_at' => now()->subDay()]);
|
||||
$newer = Booking::factory()->create(['created_at' => now()]);
|
||||
|
||||
Livewire::test(RecentBookingsTableWidget::class)
|
||||
->assertOk()
|
||||
->assertCanSeeTableRecords([$newer, $older]);
|
||||
});
|
||||
Reference in New Issue
Block a user