Skip to content

Commit 036975b

Browse files
committed
Applying the sorting filters. Now I need tests.
1 parent e970191 commit 036975b

6 files changed

Lines changed: 154 additions & 90 deletions

File tree

app/Http/Controllers/ComputerScienceResourceController.php

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use App\Services\CommentService;
1111
use App\Services\ResourceReviewService;
1212
use App\Services\SortingManagers\GeneralVotesSortingManager;
13+
use App\Services\SortingManagers\ResourceSortingManager;
1314
use Illuminate\Http\Request;
1415
use Illuminate\Support\Facades\Auth;
1516
use Illuminate\Support\Facades\Log;
@@ -22,12 +23,18 @@ class ComputerScienceResourceController extends Controller
2223
protected $commentService;
2324
protected $generalVotesSortingManager;
2425
protected $reviewService;
25-
26-
function __construct(CommentService $commentService, GeneralVotesSortingManager $generalVotesSortingManager, ResourceReviewService $reviewService)
27-
{
26+
protected $resourceSortingManager;
27+
28+
function __construct(
29+
CommentService $commentService,
30+
GeneralVotesSortingManager $generalVotesSortingManager,
31+
ResourceReviewService $reviewService,
32+
ResourceSortingManager $resourceSortingManager,
33+
) {
2834
$this->commentService = $commentService;
2935
$this->generalVotesSortingManager = $generalVotesSortingManager;
3036
$this->reviewService = $reviewService;
37+
$this->resourceSortingManager = $resourceSortingManager;
3138
}
3239

3340
/**
@@ -40,49 +47,52 @@ public function index(Request $request)
4047
// Eager load relations
4148
$query->with(['tags', 'votes', 'upvoteSummary', 'reviewSummary', 'commentsCountRelationship']);
4249

43-
$validator = Validator::make([
44-
'name' => $request->query('name'),
45-
'description' => $request->query('description'),
46-
'platforms' => $request->query('platforms'),
47-
'difficulty' => $request->query('difficulty'),
48-
'pricing' => $request->query('pricing'),
49-
'topics' => $request->query('topics'),
50-
'programming_languages' => $request->query('programming_languages'),
51-
'general_tags' => $request->query('general_tags'),
52-
53-
'community_rating' => $request->query('community_rating'),
54-
'teaching_clarity' => $request->query('teaching_clarity'),
55-
'engagement' => $request->query('engagement'),
56-
'practicality' => $request->query('practicality'),
57-
'user_friendliness' => $request->query('user_friendliness'),
58-
'updates' => $request->query('updates'),
59-
],
60-
[
61-
'name' => ['nullable', 'string', 'max:100'],
62-
'name' => ['nullable', 'string', 'max:1000'],
63-
'platforms' => ['nullable', 'array', 'min:1'],
64-
'platforms.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.platforms'))],
65-
'difficulty' => ['nullable', 'string', Rule::in(config('computerScienceResource.difficulties'))],
66-
'pricing' => ['nullable', 'string', Rule::in(config('computerScienceResource.pricings'))],
67-
68-
'topic_tags' => ['nullable', 'array', 'min:3'],
69-
'topic_tags.*' => ['required', 'distinct', 'string', 'max:50'],
70-
71-
'general_tags' => ['nullable', 'array'],
72-
'general_tags.*' => ['required', 'distinct', 'string', 'max:50'],
73-
'programming_language_tags' => ['nullable', 'array'],
74-
'programming_language_tags.*' => ['required', 'distinct', 'string', 'max:50'],
75-
76-
'community_rating' => ['nullable', 'integer', 'between:1,4'],
77-
'teaching_clarity' => ['nullable', 'integer', 'between:1,4'],
78-
'engagement' => ['nullable', 'integer', 'between:1,4'],
79-
'practicality' => ['nullable', 'integer', 'between:1,4'],
80-
'user_friendliness' => ['nullable', 'integer', 'between:1,4'],
81-
'updates' => ['nullable', 'integer', 'between:1,4'],
82-
]);
83-
84-
if (!$validator->validate())
85-
{
50+
$validator = Validator::make(
51+
[
52+
'name' => $request->query('name'),
53+
'description' => $request->query('description'),
54+
'platforms' => $request->query('platforms'),
55+
'difficulty' => $request->query('difficulty'),
56+
'pricing' => $request->query('pricing'),
57+
'topics' => $request->query('topics'),
58+
'programming_languages' => $request->query('programming_languages'),
59+
'general_tags' => $request->query('general_tags'),
60+
61+
'community_rating' => $request->query('community_rating'),
62+
'teaching_clarity' => $request->query('teaching_clarity'),
63+
'engagement' => $request->query('engagement'),
64+
'practicality' => $request->query('practicality'),
65+
'user_friendliness' => $request->query('user_friendliness'),
66+
'updates' => $request->query('updates'),
67+
],
68+
[
69+
'name' => ['nullable', 'string', 'max:100'],
70+
'name' => ['nullable', 'string', 'max:1000'],
71+
'platforms' => ['nullable', 'array', 'min:1'],
72+
'platforms.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.platforms'))],
73+
'difficulty' => ['nullable', 'string', Rule::in(config('computerScienceResource.difficulties'))],
74+
'pricing' => ['nullable', 'string', Rule::in(config('computerScienceResource.pricings'))],
75+
76+
'topic_tags' => ['nullable', 'array', 'min:3'],
77+
'topic_tags.*' => ['required', 'distinct', 'string', 'max:50'],
78+
79+
'general_tags' => ['nullable', 'array'],
80+
'general_tags.*' => ['required', 'distinct', 'string', 'max:50'],
81+
'programming_language_tags' => ['nullable', 'array'],
82+
'programming_language_tags.*' => ['required', 'distinct', 'string', 'max:50'],
83+
84+
'community_rating' => ['nullable', 'integer', 'between:1,4'],
85+
'teaching_clarity' => ['nullable', 'integer', 'between:1,4'],
86+
'engagement' => ['nullable', 'integer', 'between:1,4'],
87+
'practicality' => ['nullable', 'integer', 'between:1,4'],
88+
'user_friendliness' => ['nullable', 'integer', 'between:1,4'],
89+
'updates' => ['nullable', 'integer', 'between:1,4'],
90+
91+
// TODO: Add more validation for the dates
92+
]
93+
);
94+
95+
if (!$validator->validate()) {
8696
// TODO: actually show the error, need to flash instead
8797
return back()->with('error', 'Invalid query parameters data');
8898
}
@@ -141,7 +151,7 @@ public function index(Request $request)
141151
'user_friendliness',
142152
'updates',
143153
'overall_rating',
144-
];
154+
];
145155

146156
foreach ($ratingFilters as $field) {
147157
if ($rating = $request->query($field)) {
@@ -168,10 +178,8 @@ public function index(Request $request)
168178
}
169179

170180
/// Handle Sorting
171-
172-
// Sort by votes
173-
174-
// Sort by reviews
181+
$sortBy = $request->query('sort_by', 'top');
182+
$query = $this->resourceSortingManager->applySort($query, $sortBy);
175183

176184
// Paginate and return
177185
$resources = $query->paginate(10)->appends($request->query());

app/Services/SortingManagers/GeneralVotesSortingManager.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77

88
class GeneralVotesSortingManager extends SortingManager
99
{
10-
protected array $strategies = [DateSortingStrategy::class, VoteSortingStrategy::class];
10+
protected array $strategies = [
11+
DateSortingStrategy::class,
12+
VoteSortingStrategy::class,
13+
];
1114
}

app/Services/SortingManagers/ResourceSortingManager.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
namespace App\Services\SortingManagers;
44

55
use App\SortingStrategies\DateSortingStrategy;
6+
use App\SortingStrategies\ResourceReviewsSortingStrategy;
67
use App\SortingStrategies\VoteSortingStrategy;
78

8-
class GeneralVotesSortingManager extends SortingManager
9+
class ResourceSortingManager extends SortingManager
910
{
10-
protected array $strategies = [DateSortingStrategy::class, VoteSortingStrategy::class];
11+
protected array $strategies = [
12+
DateSortingStrategy::class,
13+
VoteSortingStrategy::class,
14+
ResourceReviewsSortingStrategy::class,
15+
];
1116
}

app/SortingStrategies/ResourceReviewsSortingStrategy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static function apply(Builder $query, string $sortBy): Builder
2222
$reviewTable = $instance->getReviewSummaryTable();
2323

2424
return $query->orderByRaw(
25-
"{$reviewTable}.{$sortBy} / NULLIF({$reviewTable}.review_count, 0) DESC"
25+
"{$reviewTable}.{$sortBy} / NULLIF({$reviewTable}.review_count, 1) DESC"
2626
);
2727
}
2828
}

resources/js/Components/Resources/FilterBar.vue

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
import { ref, onMounted } from "vue";
33
import { Icon } from "@iconify/vue";
44
import { router } from "@inertiajs/vue3";
5-
import { platforms, pricings, difficulties } from "@/Helpers/labels";
5+
import {
6+
platforms,
7+
pricings,
8+
difficulties,
9+
resourceSortingLabels,
10+
} from "@/Helpers/labels";
611
import InputText from "primevue/inputtext";
712
import MultiSelect from "primevue/multiselect";
813
import Button from "primevue/button";
914
import Rating from "primevue/rating";
10-
import Calendar from 'primevue/calendar';
15+
import Calendar from "primevue/calendar";
1116
1217
import TagSelector from "@/Components/Form/TagSelector.vue";
13-
import SortUpvotesByDropdown from "../Comments/SortUpvotesByDropdown.vue";
1418
1519
// text filters
1620
const name = ref("");
@@ -33,6 +37,9 @@ const selectedPracticality = ref(null);
3337
const selectedUserFriendliness = ref(null);
3438
const selectedUpdates = ref(null);
3539
40+
// sort_by options
41+
const selectedSorting = ref("top");
42+
3643
// date filters
3744
const createdFrom = ref(null);
3845
const createdTo = ref(null);
@@ -71,6 +78,9 @@ onMounted(() => {
7178
refVar.value = v ? parseInt(v, 10) : null;
7279
}
7380
81+
// initialize sort_by
82+
selectedSorting.value = urlParams.get("sort_by") || "top";
83+
7484
createdFrom.value = urlParams.get("created_from")
7585
? new Date(urlParams.get("created_from") + "T00:00:00")
7686
: null;
@@ -95,11 +105,12 @@ onMounted(() => {
95105
createdFrom.value !== null ||
96106
createdTo.value !== null ||
97107
updatedFrom.value !== null ||
98-
updatedTo.value !== null
108+
updatedTo.value !== null ||
109+
selectedSorting.value !== "top"
99110
);
100111
}
101112
102-
advancedOpen.value = isAnyAdvancedFilterSet()
113+
advancedOpen.value = isAnyAdvancedFilterSet();
103114
});
104115
105116
function extractIndexedArray(urlParams, base) {
@@ -112,6 +123,10 @@ function extractIndexedArray(urlParams, base) {
112123
return result;
113124
}
114125
126+
function selectSorting(option) {
127+
selectedSorting.value = option;
128+
}
129+
115130
function search() {
116131
router.visit(
117132
route("resources.index", {
@@ -142,7 +157,6 @@ function search() {
142157
practicality: selectedPracticality.value || undefined,
143158
user_friendliness: selectedUserFriendliness.value || undefined,
144159
updates: selectedUpdates.value || undefined,
145-
146160
created_from:
147161
createdFrom.value?.toISOString().slice(0, 10) || undefined,
148162
created_to:
@@ -151,6 +165,7 @@ function search() {
151165
updatedFrom.value?.toISOString().slice(0, 10) || undefined,
152166
updated_to:
153167
updatedTo.value?.toISOString().slice(0, 10) || undefined,
168+
sort_by: selectedSorting.value || undefined,
154169
}),
155170
{ preserveScroll: true }
156171
);
@@ -183,6 +198,9 @@ function resetFilters() {
183198
createdTo.value = null;
184199
updatedFrom.value = null;
185200
updatedTo.value = null;
201+
202+
// Sorting
203+
selectedSorting.value = "top";
186204
}
187205
</script>
188206

@@ -286,10 +304,8 @@ function resetFilters() {
286304
</div>
287305
</div>
288306
<!-- Advanced Filters Section -->
289-
<div
290-
v-if="advancedOpen"
291-
class="flex flex-wrap gap-4 mb-4"
292-
>
307+
<div v-if="advancedOpen" class="flex flex-wrap gap-4 mb-4">
308+
<!-- rating/date filters ... -->
293309
<div>
294310
<label class="block text-sm font-medium mb-1"
295311
>Min. Community</label
@@ -364,15 +380,14 @@ function resetFilters() {
364380
>
365381
<Calendar
366382
v-model="createdFrom"
383+
:max-date="createdTo"
367384
showIcon
368385
dateFormat="yy-mm-dd"
369386
class="w-full"
370387
/>
371388
</div>
372389
<div class="flex-1 min-w-[200px]">
373-
<label class="block text-sm font-medium mb-1"
374-
>Created To</label
375-
>
390+
<label class="block text-sm font-medium mb-1">Created To</label>
376391
<Calendar
377392
v-model="createdTo"
378393
:min-date="createdFrom"
@@ -389,15 +404,14 @@ function resetFilters() {
389404
>
390405
<Calendar
391406
v-model="updatedFrom"
407+
:max-date="updatedTo"
392408
showIcon
393409
dateFormat="yy-mm-dd"
394410
class="w-full"
395411
/>
396412
</div>
397413
<div class="flex-1 min-w-[200px]">
398-
<label class="block text-sm font-medium mb-1"
399-
>Updated To</label
400-
>
414+
<label class="block text-sm font-medium mb-1">Updated To</label>
401415
<Calendar
402416
v-model="updatedTo"
403417
:min-date="updatedFrom"
@@ -407,15 +421,30 @@ function resetFilters() {
407421
/>
408422
</div>
409423

410-
<section>
411-
<h1>Sorting</h1>
412-
<SortUpvotesByDropdown></SortUpvotesByDropdown>
413-
Something else on reviews
414-
</section>
424+
<!-- Sorting Buttons -->
425+
<div class="w-full">
426+
<h2 class="text-sm font-medium mb-2">Sorting</h2>
427+
<div class="flex flex-wrap gap-2">
428+
<button
429+
v-for="opt in resourceSortingLabels"
430+
:key="opt.value"
431+
type="button"
432+
@click="selectSorting(opt.value)"
433+
:class="[
434+
'px-3 py-1 rounded-full text-sm font-medium focus:outline-none',
435+
selectedSorting === opt.value
436+
? 'bg-blue-600 text-white'
437+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300',
438+
]"
439+
>
440+
{{ opt.label }}
441+
</button>
442+
</div>
443+
</div>
415444
</div>
416445

417446
<div class="flex items-end flex-wrap gap-4 w-full">
418-
<!-- Advanced Filters Toggle (now on the left) -->
447+
<!-- Advanced Filters Toggle -->
419448
<div>
420449
<button
421450
type="button"
@@ -424,9 +453,7 @@ function resetFilters() {
424453
>
425454
<Icon
426455
:icon="
427-
advancedOpen
428-
? 'mdi:chevron-up'
429-
: 'mdi:chevron-down'
456+
advancedOpen ? 'mdi:chevron-up' : 'mdi:chevron-down'
430457
"
431458
class="w-4 h-4 transition-transform duration-200"
432459
/>
@@ -438,7 +465,7 @@ function resetFilters() {
438465
</button>
439466
</div>
440467

441-
<!-- Reset & Filter Buttons (now on the right) -->
468+
<!-- Reset & Filter Buttons -->
442469
<div class="flex gap-4 ml-auto">
443470
<Button
444471
label="Reset"

0 commit comments

Comments
 (0)