diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php
index af37118e..01f770ee 100644
--- a/app/Http/Controllers/ProjectController.php
+++ b/app/Http/Controllers/ProjectController.php
@@ -12,16 +12,7 @@ public function show($id)
return view('project', [
'project' => $project,
- 'boards' => $project->boards()
- ->visible()
- ->with(['items' => function ($query) {
- return $query
- ->orderBy('pinned', 'desc')
- ->visibleForCurrentUser()
- ->popular() // TODO: This needs to be fixed to respect the sorting setting from the board itself (sort_items_by)
- ->withCount('votes');
- }])
- ->get(),
+ 'boards' => $project->boards()->visible()->get(),
]);
}
}
diff --git a/app/Livewire/Project/BoardColumn.php b/app/Livewire/Project/BoardColumn.php
new file mode 100644
index 00000000..a323a649
--- /dev/null
+++ b/app/Livewire/Project/BoardColumn.php
@@ -0,0 +1,55 @@
+ '$refresh',
+ ];
+
+ public function mount(): void
+ {
+ $this->sortBy = 'created_at';
+ }
+
+ public function setSortBy(string $sort): void
+ {
+ if (! in_array($sort, ['created_at', 'total_votes', 'last_commented'])) {
+ return;
+ }
+
+ $this->sortBy = $sort;
+ }
+
+ public function render()
+ {
+ $query = $this->board->items()
+ ->visibleForCurrentUser()
+ ->when($this->search, fn ($q) => $q->where('title', 'like', '%' . $this->search . '%'));
+
+ if ($this->sortBy === 'last_commented') {
+ $query->withMax('comments', 'created_at')
+ ->orderBy('pinned', 'desc')
+ ->orderByDesc('comments_max_created_at');
+ } else {
+ $query->orderBy('pinned', 'desc')
+ ->orderByDesc($this->sortBy);
+ }
+
+ $items = $query->get();
+
+ return view('livewire.project.board-column', [
+ 'items' => $items,
+ ]);
+ }
+}
diff --git a/lang/en/general.php b/lang/en/general.php
index 239e27ec..f222d603 100644
--- a/lang/en/general.php
+++ b/lang/en/general.php
@@ -30,4 +30,10 @@
'close' => 'Close',
'save' => 'Save',
+
+ 'search-items' => 'Search items...',
+ 'sort-by' => 'Sort by',
+ 'sort-newest' => 'Newest',
+ 'sort-most-voted' => 'Most voted',
+ 'sort-last-commented' => 'Last commented',
];
diff --git a/lang/es/general.php b/lang/es/general.php
index 63fd5082..8c86da15 100644
--- a/lang/es/general.php
+++ b/lang/es/general.php
@@ -9,4 +9,10 @@
'close' => 'Cerrar',
'save' => 'Guardar',
+
+ 'search-items' => 'Buscar elementos...',
+ 'sort-by' => 'Ordenar por',
+ 'sort-newest' => 'Más recientes',
+ 'sort-most-voted' => 'Más votados',
+ 'sort-last-commented' => 'Último comentado',
];
diff --git a/lang/hu/general.php b/lang/hu/general.php
index 845541e7..d6209da8 100644
--- a/lang/hu/general.php
+++ b/lang/hu/general.php
@@ -31,5 +31,11 @@
'close' => 'Bezárás',
'save' => 'Mentés',
+ 'search-items' => 'Elemek keresése...',
+ 'sort-by' => 'Rendezés',
+ 'sort-newest' => 'Legújabb',
+ 'sort-most-voted' => 'Legtöbb szavazat',
+ 'sort-last-commented' => 'Utoljára hozzászólt',
+
'public-user' => 'Nyilvános felhasználó',
];
diff --git a/lang/it/general.php b/lang/it/general.php
index c7d5b25f..c2517ca0 100644
--- a/lang/it/general.php
+++ b/lang/it/general.php
@@ -10,5 +10,11 @@
'close' => 'Chiudi',
'save' => 'Salva',
+ 'search-items' => 'Cerca elementi...',
+ 'sort-by' => 'Ordina per',
+ 'sort-newest' => 'Più recenti',
+ 'sort-most-voted' => 'Più votati',
+ 'sort-last-commented' => 'Ultimo commento',
+
'public-user' => 'Utente pubblico',
];
diff --git a/lang/nl/general.php b/lang/nl/general.php
index c9868151..aa6bb38c 100644
--- a/lang/nl/general.php
+++ b/lang/nl/general.php
@@ -30,4 +30,10 @@
'close' => 'Sluiten',
'save' => 'Opslaan',
+
+ 'search-items' => 'Zoek items...',
+ 'sort-by' => 'Sorteren op',
+ 'sort-newest' => 'Nieuwste',
+ 'sort-most-voted' => 'Meeste stemmen',
+ 'sort-last-commented' => 'Laatst becommentarieerd',
];
diff --git a/resources/views/livewire/project/board-column.blade.php b/resources/views/livewire/project/board-column.blade.php
new file mode 100644
index 00000000..81c36309
--- /dev/null
+++ b/resources/views/livewire/project/board-column.blade.php
@@ -0,0 +1,128 @@
+
+
+
+
+
+ {{ $board->title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/views/project.blade.php b/resources/views/project.blade.php
index 71f1dad6..58ebb720 100644
--- a/resources/views/project.blade.php
+++ b/resources/views/project.blade.php
@@ -18,67 +18,7 @@
])
>
@forelse($boards as $board)
-
+
@empty
has(
- Board::factory(5)
- ->has(Item::factory(10))
+ Board::factory()
+ ->has(Item::factory(3))
)
->create();
- get(route('projects.show', $project))->assertSeeInOrder(Item::all()->pluck('title')->toArray());
+ $response = get(route('projects.show', $project));
+
+ Item::all()->each(fn (Item $item) => $response->assertSeeText($item->title));
});
it('includes votes for items', function () {
@@ -71,14 +73,14 @@
get(route('projects.show', $project))->assertSeeInOrder(['item 2' , 'item 1']);
});
-test('items are sorted by vote count', function () {
+test('items are sorted by newest by default', function () {
$project = Project::factory()
->has(
Board::factory()
->has(
Item::factory(2)->state(new Sequence(
- ['title' => 'item 1', 'total_votes' => 1],
- ['title' => 'item 2', 'total_votes' => 10]
+ ['title' => 'item 1', 'created_at' => now()->subDay()],
+ ['title' => 'item 2', 'created_at' => now()]
))
)
)->create();
diff --git a/tests/Feature/Livewire/Project/BoardColumnTest.php b/tests/Feature/Livewire/Project/BoardColumnTest.php
new file mode 100644
index 00000000..b37caa22
--- /dev/null
+++ b/tests/Feature/Livewire/Project/BoardColumnTest.php
@@ -0,0 +1,174 @@
+has(Board::factory())->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->assertStatus(200);
+});
+
+it('displays items', function () {
+ $project = Project::factory()
+ ->has(Board::factory()->has(Item::factory(3)))
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->assertSee($board->items->pluck('title')->toArray());
+});
+
+it('defaults to created_at sort', function () {
+ $project = Project::factory()
+ ->has(Board::factory()->state(['sort_items_by' => 'popular']))
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->assertSet('sortBy', 'created_at');
+});
+
+it('sorts by newest when setSortBy is called with created_at', function () {
+ $project = Project::factory()
+ ->has(
+ Board::factory()->has(
+ Item::factory(2)->state(new Sequence(
+ ['title' => 'older item', 'created_at' => now()->subDay()],
+ ['title' => 'newer item', 'created_at' => now()],
+ ))
+ )
+ )
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->call('setSortBy', 'created_at')
+ ->assertSeeInOrder(['newer item', 'older item']);
+});
+
+it('sorts by most voted when setSortBy is called with total_votes', function () {
+ $project = Project::factory()
+ ->has(
+ Board::factory()->has(
+ Item::factory(2)->state(new Sequence(
+ ['title' => 'low votes', 'total_votes' => 1],
+ ['title' => 'high votes', 'total_votes' => 10],
+ ))
+ )
+ )
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->call('setSortBy', 'total_votes')
+ ->assertSeeInOrder(['high votes', 'low votes']);
+});
+
+it('sorts by last commented', function () {
+ $project = Project::factory()
+ ->has(Board::factory()->has(Item::factory(2)->state(new Sequence(
+ ['title' => 'old comment item'],
+ ['title' => 'new comment item'],
+ ))))
+ ->create();
+ $board = $project->boards->first();
+ $items = $board->items;
+
+ Comment::factory()->create([
+ 'item_id' => $items[0]->id,
+ 'created_at' => now()->subDay(),
+ ]);
+ Comment::factory()->create([
+ 'item_id' => $items[1]->id,
+ 'created_at' => now(),
+ ]);
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->call('setSortBy', 'last_commented')
+ ->assertSeeInOrder(['new comment item', 'old comment item']);
+});
+
+it('keeps pinned items at the top regardless of sort', function () {
+ $project = Project::factory()
+ ->has(
+ Board::factory()->has(
+ Item::factory(2)->state(new Sequence(
+ ['title' => 'unpinned high votes', 'pinned' => false, 'total_votes' => 100],
+ ['title' => 'pinned low votes', 'pinned' => true, 'total_votes' => 1],
+ ))
+ )
+ )
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->call('setSortBy', 'total_votes')
+ ->assertSeeInOrder(['pinned low votes', 'unpinned high votes']);
+});
+
+it('shows empty state when board has no items', function () {
+ $project = Project::factory()->has(Board::factory())->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->assertSee(trans('items.no-items'));
+});
+
+it('filters items by search query', function () {
+ $project = Project::factory()
+ ->has(
+ Board::factory()->has(
+ Item::factory(2)->state(new Sequence(
+ ['title' => 'Add dark mode support'],
+ ['title' => 'Fix login bug'],
+ ))
+ )
+ )
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->set('search', 'dark mode')
+ ->assertSeeText('Add dark mode support')
+ ->assertDontSeeText('Fix login bug');
+});
+
+it('shows all items when search is cleared', function () {
+ $project = Project::factory()
+ ->has(
+ Board::factory()->has(
+ Item::factory(2)->state(new Sequence(
+ ['title' => 'Add dark mode support'],
+ ['title' => 'Fix login bug'],
+ ))
+ )
+ )
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->set('search', 'dark mode')
+ ->assertDontSeeText('Fix login bug')
+ ->set('search', '')
+ ->assertSeeText('Add dark mode support')
+ ->assertSeeText('Fix login bug');
+});
+
+it('ignores invalid sort values', function () {
+ $project = Project::factory()
+ ->has(Board::factory())
+ ->create();
+ $board = $project->boards->first();
+
+ Livewire::test(BoardColumn::class, ['project' => $project, 'board' => $board])
+ ->call('setSortBy', 'invalid_sort')
+ ->assertSet('sortBy', 'created_at');
+});