diff --git a/app/Livewire/OnlineCalendar.php b/app/Livewire/OnlineCalendar.php
index d4bc9f407..97388b9e8 100644
--- a/app/Livewire/OnlineCalendar.php
+++ b/app/Livewire/OnlineCalendar.php
@@ -5,6 +5,7 @@
use App\Country;
use App\Event;
use Carbon\Carbon;
+use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -12,12 +13,16 @@ class OnlineCalendar extends Component
{
use WithPagination;
+ // The pagination links are ordinary hrefs, so every page change is a fresh request.
+ // Both filters have to travel in the URL or the new page comes back unfiltered.
+ #[Url(as: 'language')]
public $selectedLanguage = '';
public $selectedYear;
public $selectedMonth;
+ #[Url(as: 'month')]
public $selectedDate = 'all';
public $months = [];
@@ -26,8 +31,6 @@ public function mount()
{
$this->selectedYear = Carbon::now()->year;
$this->selectedMonth = Carbon::now()->month;
- $this->selectedDate = 'all';
- $this->selectedLanguage = '';
$this->months = $this->baseQuery()
->orderBy('start_date')
@@ -114,13 +117,25 @@ public function render()
return view('livewire.online-calendar', [
'countryNames' => $this->getCountryNamesFromEvents($events),
'languages' => $languages,
- 'filteredEvents' => $filteredEvents->paginate(24),
+ 'filteredEvents' => $filteredEvents->paginate(24)->appends($this->activeFilters()),
'totalUpcoming' => $totalUpcoming,
'visibleCount' => $filteredEvents->count(),
'monthLabel' => $monthLabel,
]);
}
+ /**
+ * The active filters under the names they use in the URL, so that the page links
+ * rebuilt by the paginator carry them over.
+ */
+ private function activeFilters(): array
+ {
+ return array_filter([
+ 'language' => $this->selectedLanguage,
+ 'month' => $this->selectedDate === 'all' ? null : $this->selectedDate,
+ ]);
+ }
+
private function baseQuery()
{
// The start date alone decides what is upcoming. Gating on the end date instead
diff --git a/resources/views/livewire/online-calendar.blade.php b/resources/views/livewire/online-calendar.blade.php
index c9dfe1557..3fa7ee79b 100644
--- a/resources/views/livewire/online-calendar.blade.php
+++ b/resources/views/livewire/online-calendar.blade.php
@@ -12,9 +12,9 @@
wire:model.live="selectedDate"
class="w-full appearance-none rounded-full border border-slate-200 bg-white py-3 pl-12 pr-10 text-slate-500 font-semibold focus:outline-none focus:ring-2 focus:ring-[#1C4DA1]"
>
-
+
@foreach($months as $month)
-
+
@endforeach
@@ -29,7 +29,7 @@ class="w-full appearance-none rounded-full border border-slate-200 bg-white py-3
class="w-full appearance-none rounded-full border border-slate-200 bg-white py-3 px-4 text-slate-500 font-semibold focus:outline-none focus:ring-2 focus:ring-[#1C4DA1]"
>
@foreach($languages as $language)
-
+
@endforeach
diff --git a/tests/Feature/OnlineActivitiesFilterPaginationTest.php b/tests/Feature/OnlineActivitiesFilterPaginationTest.php
new file mode 100644
index 000000000..7fa5ee9a2
--- /dev/null
+++ b/tests/Feature/OnlineActivitiesFilterPaginationTest.php
@@ -0,0 +1,151 @@
+create([
+ 'start_date' => Carbon::now()->addWeek(),
+ 'end_date' => Carbon::now()->addWeek()->addDay(),
+ 'status' => 'APPROVED',
+ 'activity_type' => 'open-online',
+ 'highlighted_status' => 'NONE',
+ 'language' => $languages,
+ 'title' => $title,
+ ]);
+ }
+
+ /**
+ * Reported by the ambassadors: pick a language, walk to page two and the list is
+ * back to every language, because the page link is an ordinary browser navigation
+ * and the chosen filter never reached the URL.
+ */
+ #[Test]
+ public function the_language_filter_survives_a_jump_to_the_second_page(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ // 25 English activities push the English-only list onto a second page.
+ for ($i = 1; $i <= 25; $i++) {
+ $this->makeActivity('English Activity Number '.$i, ['en']);
+ }
+
+ $german = $this->makeActivity('German Only Activity', ['de']);
+
+ $this->get('/online-activities?language=en&page=2')
+ ->assertStatus(200)
+ ->assertDontSee($german->title);
+ }
+
+ #[Test]
+ public function the_month_filter_survives_a_jump_to_the_second_page(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ $nextMonth = Carbon::now()->addMonthNoOverflow()->startOfMonth();
+
+ for ($i = 1; $i <= 25; $i++) {
+ Event::factory()->create([
+ 'start_date' => $nextMonth->copy()->addDays(2),
+ 'end_date' => $nextMonth->copy()->addDays(3),
+ 'status' => 'APPROVED',
+ 'activity_type' => 'open-online',
+ 'highlighted_status' => 'NONE',
+ 'language' => ['en'],
+ 'title' => 'Next Month Activity Number '.$i,
+ ]);
+ }
+
+ $thisWeek = $this->makeActivity('Happening This Week Activity', ['en']);
+
+ $month = $nextMonth->month.'/'.$nextMonth->year;
+
+ $this->get('/online-activities?month='.urlencode($month).'&page=2')
+ ->assertStatus(200)
+ ->assertSee('for '.$nextMonth->format('F Y'))
+ ->assertDontSee($thisWeek->title);
+ }
+
+ #[Test]
+ public function pagination_links_carry_the_active_filters(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ for ($i = 1; $i <= 25; $i++) {
+ $this->makeActivity('English Activity Number '.$i, ['en']);
+ }
+
+ $html = $this->get('/online-activities?language=en')->getContent();
+
+ $this->assertMatchesRegularExpression(
+ '/href="[^"]*page=2[^"]*language=en|href="[^"]*language=en[^"]*page=2/',
+ $html,
+ 'The link to page two must keep the chosen language.'
+ );
+ }
+
+ /**
+ * The order the ambassadors hit it in: pick the language from the dropdown, which is
+ * a Livewire round trip, then click page two, which is a browser navigation.
+ */
+ #[Test]
+ public function choosing_a_language_then_paginating_keeps_the_language(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ for ($i = 1; $i <= 25; $i++) {
+ $this->makeActivity('English Activity Number '.$i, ['en']);
+ }
+
+ $german = $this->makeActivity('German Only Activity', ['de']);
+
+ $html = Livewire::test(OnlineCalendar::class)
+ ->set('selectedLanguage', 'en')
+ ->html();
+
+ preg_match('/href="([^"]*page=2[^"]*)"/', $html, $matches);
+
+ $this->assertNotEmpty($matches, 'A link to page two should be rendered.');
+
+ $this->get($matches[1])
+ ->assertStatus(200)
+ ->assertDontSee($german->title);
+ }
+
+ #[Test]
+ public function the_dropdowns_show_the_filter_that_is_in_force(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ $this->makeActivity('English Activity', ['en']);
+ $this->makeActivity('German Activity', ['de']);
+
+ $this->get('/online-activities?language=en')
+ ->assertStatus(200)
+ ->assertSee('', false);
+ }
+
+ #[Test]
+ public function the_filters_are_read_from_the_query_string(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+
+ $this->makeActivity('English Activity', ['en']);
+
+ Livewire::withQueryParams(['language' => 'en'])
+ ->test(OnlineCalendar::class)
+ ->assertSet('selectedLanguage', 'en');
+ }
+}