Skip to content

Commit 89d47d7

Browse files
committed
Filter by tags
1 parent 663b8c6 commit 89d47d7

7 files changed

Lines changed: 249 additions & 186 deletions

File tree

app/Http/Controllers/ComputerScienceResourceController.php

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,20 @@ function __construct(CommentService $commentService, UpvoteService $upvoteServic
3131
public function index(Request $request)
3232
{
3333
$query = ComputerScienceResource::query();
34-
34+
3535
// Eager load relations
3636
$query->with(['tags', 'votes', 'upvoteSummary', 'reviewSummary', 'commentsCountRelationship']);
37-
37+
3838
// Fulltext search on name
3939
if ($name = $request->query('name')) {
4040
$query->whereFullText('name', $name);
4141
}
42-
42+
4343
// Fulltext search on description
4444
if ($description = $request->query('description')) {
4545
$query->whereFullText('description', $description);
4646
}
47-
47+
4848
// Filter by platforms (array)
4949
if ($platforms = $request->query('platforms')) {
5050
$query->where(function ($q) use ($platforms) {
@@ -53,30 +53,42 @@ public function index(Request $request)
5353
}
5454
});
5555
}
56-
56+
5757
// Filter by difficulty (array)
5858
if ($difficulty = $request->query('difficulty')) {
5959
$query->whereIn('difficulty', (array) $difficulty);
6060
}
61-
61+
6262
// Filter by pricing (array)
6363
if ($pricing = $request->query('pricing')) {
6464
$query->whereIn('pricing', (array) $pricing);
6565
}
66-
67-
// Optional: Filter by tags (across any tag type)
68-
if ($tags = $request->query('tags')) {
69-
$query->withAnyTags((array) $tags);
66+
67+
// Filter by topic tags
68+
if ($topics = $request->query('topics')) {
69+
$query->withAnyTags((array) $topics, 'topics');
70+
}
71+
72+
// Filter by programming languages
73+
if ($programmingLanguages = $request->query('programming_languages')) {
74+
$query->withAnyTags((array) $programmingLanguages, 'programming_languages');
75+
}
76+
77+
// Filter by general tags
78+
if ($generalTags = $request->query('general_tags')) {
79+
$query->withAnyTags((array) $generalTags, 'general_tags');
7080
}
71-
81+
82+
83+
7284
// Paginate and return
7385
$resources = $query->paginate(10)->appends($request->query());
74-
86+
7587
return Inertia::render('Resources/Index', [
7688
'resources' => $resources,
7789
]);
7890
}
79-
91+
8092
/**
8193
* Show the form for creating a new resource.
8294
*/

app/Models/ComputerScienceResource.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function reviewSummary(): HasOne
3939
{
4040
return $this->hasOne(ResourceReviewSummary::class);
4141
}
42-
42+
4343
/**
4444
* Get all the reviews.
4545
*/
@@ -55,7 +55,7 @@ public function edits(): HasMany
5555

5656
/**
5757
* Attribute to get and set platforms as an array
58-
*
58+
*
5959
* @return Attribute
6060
*/
6161
protected function platforms(): Attribute
Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
11
<script setup>
2+
import { ref, watch } from "vue";
23
import { Tag } from "primevue";
34
import { Icon } from "@iconify/vue";
4-
import { ref } from "vue";
55
import AutoComplete from "primevue/autocomplete";
6-
import { defineModel, defineProps } from "vue";
6+
import { defineModel } from "vue";
77
import axios from "axios";
88
9-
const props = defineProps({
10-
initial: {
11-
type: Array,
12-
required: true,
13-
},
14-
});
15-
16-
const model = defineModel()
9+
const model = defineModel(); // v-model from parent
1710
18-
const selectedTags = ref(new Set(props.initial));
11+
const selectedTags = ref([]);
1912
const searchValue = ref("");
2013
const tagResult = ref([]);
2114
const tagCount = ref({});
2215
const emptySearchMessage = ref("");
2316
17+
// Sync initial model value to internal state
18+
watch(
19+
() => model.value,
20+
(newVal) => {
21+
if (Array.isArray(newVal)) {
22+
selectedTags.value = [...newVal];
23+
}
24+
},
25+
{ immediate: true }
26+
);
27+
2428
const addTag = (tag) => {
25-
if (tag && !selectedTags.value.has(tag)) {
26-
selectedTags.value.add(tag);
29+
if (tag && !selectedTags.value.includes(tag)) {
30+
selectedTags.value.push(tag);
2731
searchValue.value = "";
28-
model.value = Array.from(selectedTags.value);
32+
model.value = [...selectedTags.value];
2933
}
3034
};
3135
3236
const removeTag = (tag) => {
3337
if (tag) {
34-
selectedTags.value.delete(tag);
35-
model.value = Array.from(selectedTags.value);
38+
selectedTags.value = selectedTags.value.filter((t) => t !== tag);
39+
model.value = [...selectedTags.value];
3640
}
3741
};
3842
@@ -70,38 +74,36 @@ const filterSuggestions = () => {
7074
</script>
7175

7276
<template>
73-
<div class="flex col-auto gap-3 flex-col justify-center items-center">
74-
<!-- Search bar to add tags -->
75-
<AutoComplete
76-
v-model="searchValue"
77-
:suggestions="tagResult"
78-
:empty-search-message="emptySearchMessage"
79-
@complete="filterSuggestions"
80-
@item-select="handleSelect"
81-
@keydown="handleKeydown"
82-
placeholder="Type to add tags"
83-
completeOnFocus
84-
>
85-
<template #option="slotProps">
86-
<div class="flex items-center justify-between w-full">
87-
<span>{{ slotProps.option }}</span>
88-
<span
89-
v-if="tagCount[slotProps.option] !== undefined"
90-
class="py-0.5 px-1 rounded-lg bg-gray-100 text-sm text-gray-700"
91-
>
92-
{{ tagCount[slotProps.option] }}
93-
</span>
94-
</div>
95-
</template>
96-
</AutoComplete>
97-
<!-- List of tags -->
98-
<div class="mt-2">
99-
<Tag v-for="tag in selectedTags" :key="tag" class="mr-2 mb-2">
100-
<button @click="() => removeTag(tag)" class="mr-1">
101-
<Icon icon="mdi:remove-bold" />
102-
</button>
103-
<span class="text-base">{{ tag }}</span>
104-
</Tag>
105-
</div>
77+
<!-- Search bar to add tags -->
78+
<AutoComplete
79+
v-model="searchValue"
80+
:suggestions="tagResult"
81+
:empty-search-message="emptySearchMessage"
82+
@complete="filterSuggestions"
83+
@item-select="handleSelect"
84+
@keydown="handleKeydown"
85+
placeholder="Type to add tags"
86+
completeOnFocus
87+
>
88+
<template #option="slotProps">
89+
<div class="flex items-center justify-between w-full">
90+
<span>{{ slotProps.option }}</span>
91+
<span
92+
v-if="tagCount[slotProps.option] !== undefined"
93+
class="py-0.5 px-1 rounded-lg bg-gray-100 text-sm text-gray-700"
94+
>
95+
{{ tagCount[slotProps.option] }}
96+
</span>
97+
</div>
98+
</template>
99+
</AutoComplete>
100+
<!-- List of tags -->
101+
<div class="mt-2">
102+
<Tag v-for="tag in selectedTags" :key="tag" class="mr-2 mb-2">
103+
<button @click="() => removeTag(tag)" class="mr-1">
104+
<Icon icon="mdi:remove-bold" />
105+
</button>
106+
<span class="text-base">{{ tag }}</span>
107+
</Tag>
106108
</div>
107109
</template>

0 commit comments

Comments
 (0)